Ask HN: How to learn proper systems programming?
I am a software engineer for the past 10 years, and did frontend and backend development. I am learning Rust at the moment and have the following books:
- "The Linux Programming Interface"
- "Systems Programming with Linux"
- "Adavnaced UNIX Programming"
What I struggle with: How to get exposure to projects to learn for a future job? I had a Rust job for around half a year, where people build web servers and came from a C and C++ background. Half of the stuff they wrote I didn't understand (flushing, opening another channel just for logs so we don't fill up the other ones etc. etc.).
Now I wonder how I can get access to this type of information, how to properly learn it?
96 comments
[ 3.2 ms ] story [ 163 ms ] threadYou've found some pretty good resources already by the looks of it though.
[1] https://www.manning.com/books/rust-in-action
Author here - please feel free to ask any questions :)
Start with a book or a few texts/tutorials on the subject and begin building. Along the way you'll find the questions and choices involved. Find the answers from the internet or books. This is when I'd recommend some open source projects (not earlier) to see how they solved the specific problems. If you just go into an open-source project you won't have an understanding of the problem, just the answers, so it won't help you nearly as much.
I don't think this is system programming rather network programming.
You should try embedded or try to write drivers
Better yet write a mini OS your own that interface with hardware
Edit: Wow, I'm getting down voted. I guess political correctness
The OP asked about System Programming not System software.
Driver is an interface and you can write application software on top of it.
System programming obviously involves writing system software.
This is probably why you are getting downvoted. Squabling about semantics / gatekeeping what counts as systems-programming isn't useful. It probably doesn't answer OPs question, and it is rather rude to assume that what OP meant by "systems programming" is wrong just because it is different from what you mean by "systems programming".
edit: maybe it was your dead comment that caused the downvotes.
There are a lot of neat, and not-too-intimidating things that will give you a good appreciation of the techniques used in systems programming you don't see in other areas. Eg reading technical documentation (datasheets, Reference Manuals etc), reading and writing registers, familiarizing yourself with memory layout, and communications protocols. Writing a HAL to see how you can abstract over register writes to make high-level code.
In other words, I agree, and don't think this post's parent was gatekeeping; it's the definition I had assumed as well.
- Release It! (https://pragprog.com/titles/mnee2/release-it-second-edition/)
- Designing Data-Intensive Applications (https://dataintensive.net/)
I would suggest finding an open source project of interest and taking a deep dive into its code and documentation to understand how it works and why it was built that way.
Which reminds me, this should help with that: The Architecture of Open Source Applications (http://www.aosabook.org/en/index.html)
Modern Operating Systems by Tanenbaum is a good theory book - this will probably answer your questions about flushing etc
for down and dirty:
Advanced Programming in the UNIX Environment
TCP/IP Illustrated, Volume 1 (2 and 3)
- One system in isolation - Operating Systems: Three Easy Pieces. Covers persistence, virtualisation and concurrency. This book is available for free at https://pages.cs.wisc.edu/~remzi/OSTEP/
- Multiple systems, and how data flows through them - Designing Data Intensive Applications. Covers the low level details of how databases persist data to disk and how multiple nodes coordinate with each other. If you’ve heard of the “CAP theorem”, this is the source to learn it from. Worth every penny.
More on why these two books are worth reading at https://teachyourselfcs.com
- One system in isolation
- Multiple systems, and how data flows through them
Which would be really good names for a two part series.
- https://www.educative.io/courses/operating-systems-virtualiz...
Which is worth checking as an introductory course.
Although the language you use to learn system & network programming doesn't matter much, it is better if you use C or C++ to practise and learn. This is because the kernel itself is written in C and exposes system calls that can be used directly from a C/C++ program. That said, "The Linux Programming Interface"(I am personally reading it) is a really good book. It talks a lot about how one should go about using system calls to get things done by the kernel. Make sure to read a little every day and try out the examples by writing C/C++ programs.
I recently realized that TLPI doesn't talk much about why are things the way they are(a very good example would be virtual memory and related stuff). You should refer some theoretical book for this. I suggest you go with "Operating systems" by Deitel & Choffnes.
Read man pages and practise using the libc/kernel APIs. For example, if you want to know about flushing, read 'man 3 fflush'. This might be needed when you want to flush all the input/output data that has been buffered by the C library before you can get fresh input from stdin. For example, if prompts are buffered, you definitely don't want to "scanf" before you have flushed the buffers. If you want to learn network programming, read chapters related to socket and refer 'man 2 socket'.
You will eventually get to a point where you will be able to connect all the dots(APIs) and be able to figure out what exactly you will need to get some problem solved.
Finally, don't learn for a future job. Learn for yourself. This will help you in the long run.
Since you're looking at rust, https://www.youtube.com/user/gamozolabs/videos could be a good fit.
Most existing systems programming is in C/C++. Rust is new and there isn't much battle hardened code out there.
From there, you'll know where to go next based on what you've learned so far.
My recommended reading list is:
[1] Operating Systems: Three Easy Pieces https://pages.cs.wisc.edu/~remzi/OSTEP
[2] Intel Software Developer Manuals (especially volume 1 and 3A) https://software.intel.com/content/www/us/en/develop/article...
[3] OSDev wiki https://wiki.osdev.org
It is easy to get started. Here's a system call function for x86_64 Linux:
https://github.com/matheusmoreira/liblinux/blob/master/sourc...
With this single function, it is possible to do anything. You can rewrite the entire Linux user space with this.
The Linux kernel itself has a nolibc.h file that they use for their own freestanding tools:
https://github.com/torvalds/linux/blob/master/tools/include/...
It's even better than what I came up with. Permissively licensed. Supports lots of architectures. There's process startup code so you don't even need GCC's startfiles in order to have a normal main function. The only missing feature is the auxiliary vector. You can just include it and start using Linux immediately.
You can absolutely do this with Rust as well. Rust supports programming with no standard library. If I remember correctly, the inline assembly macros are still an unstable feature. It's likely to change in the future though.
1. https://pdos.csail.mit.edu/6.S081/2020/schedule.html (check video links and do all the lab assignments)
2. https://www.youtube.com/watch?v=dEWplhfg4Dg&list=PLf3ZkSCyj1... (based on old MIT 6.828)
3. Networks Review (self study)
Somebody mentioned Intel manuals in the comment (those are really helpful)
These courses helped alot. I would suggest to take some firmware and device driver development course as well. My conclusion is that, these are tough skills and the learning process can be accelerated if you can find some entry level position in a small company which do this kind of work.
The only thing I missed was support for when I really ran into a wall, but perhaps I just don't know the right irc channel.
If not, what is a channel?
This will teach you what Rust is saving you from and help you understand how it does it.
[1]: https://www.valgrind.org/
I think it's worth mentioning that the majority of topics involved in this type of webserver work wouldn't necessarily be covered by a systems programming book. There's obviously overlap between all of systems programming, networking, and concurrent/distributed systems, but if you plan to focus on web servers, I'd pick up texts on the other topics as well.
It's something I was terrified of doing.
But once I got in there and started poking around, I realized it was just ordinary plain-vanilla C code. Not C++. Just C code.
With my local copy, I started to hack pg_dump to do something special that we wanted at the time. Even after 30 years of coding, I'm not that especially good of a programmer. But I ended up getting our own special version of pg_dump that did what we wanted at the time and it went into production dumping hundreds of gigs of data every day!
But what I'm not, is afraid. I'm not afraid to try anything.
And that's what it takes to do deep, systems level programming.
Don't be afraid.
Those bits are just bits. And it's just code... and most of it was not written by wizards. Just ordinary people like you and me. Don't be afraid man.
Clone the repo and setup a workable build environment and start tinkering and compiling and running to see what happens.
You would be totally shocked to find out what you can actually achieve.
Those books will only go so far.
The rabbit hole of reading code you go down of reading code when you do that is, I believe, the best real introduction to systems programming. Once you've been doing that for a while, you internalize the idioms. I'd recommend large projects because you're less likely to just end up copying some person or couple of people's quirks, and learning to read large codebases is a major skill in itself. The process of submitting patches often turns into ad hoc mentoring, where you'll be told what you didn't get right this go around. After you've done that for a while, you realize the same things apply to other large systems projects, and you end up exposed to a number of different styles of systems stuff.
Code is easy and doesn't scare me. I'm scared about the platform though: code runs on platforms (POSIX, mostly), which are incredibly dated, ill-designed and full of terrible corner cases[1]. There's so many ways to shoot yourself in the foot I'm afraid to do anything sensitive on my own.
«It is not UNIX’s job to stop you from shooting your foot. If you so choose to do so, then it is UNIX’s job to deliver Mr. Bullet to Mr Foot in the most efficient way it knows.»
[1]: Dan Lu's writing on files are a good illustration of that:
- http://danluu.com/file-consistency/
- https://danluu.com/deconstruct-files/
- https://danluu.com/filesystem-errors/
That said, at some point knowing the POSIX APIs does become necessary, but that's usually relevant a bit later when you start becoming interested in modifying the toolkit of large projects.
If you're wanting to start a project on your own rather than jump into an existing project, in a nutshell, if you don't have hard real-time requirements, if you're using C, use glib, if you're using C++, use Qt. There are other libs that are useful for real-time usage (basically things that don't ever have hidden memory allocation), but I'm not familiar enough with that space to recommend one.
My own definition of “system programming” is exactly “developing on top of the system” vs “developing in the comfort of a helper library” (and its limits). I consider myself a decent application programmer, but I'm not a system programmer (at least not yet ;).
The OP was talking about Postgres, if you don't know how the pitfalls of write(2), or don't know how to use mmap(2), you're going to have trouble making a database on your own.
Here are a bunch of things I would broadly consider systems programming:
- Kernel and driver development
- Low level library development (pretty much anything involving bit wrangling)
- Platform abstraction libraries
- Database development (not usage)
- Message queuing systems
- Daemon / server development
Perhaps the recurring pattern there, and differentiated from application programming is that most of those are tools for other applications, rather than applications themselves. I don't think that all library / daemon development is systems programming, but a whole lot of it is.
I'm coming from a background of having done 5 of the 6 of those groups (though there could obviously be more listed there). For most significant projects, as noted, there will be internal APIs that wrap system APIs (or in the case of the kernel, where the system APIs are totally irrelevant, except for the parts that implement the system calls). Generally someone first jumping into those projects isn't going to immediately start hacking on the internal libraries.
As it were, I have actually written a database from scratch [1]. That database uses Qt and Qt-like APIs internally where possible. There are places in there where you need to know the intricacies of mmap, fsync and similar, but they're compartmentalized to a couple of classes. I'd still call code that isn't in those couple of classes "systems programming".
[1] https://blog.directededge.com/2009/02/27/on-building-a-stupi...
The implementation behind this file is one of them:
https://doxygen.postgresql.org/fd_8h.html
That is the actual file API that you'd use inside of Postgres. While the API there isn't going to win any beauty pageants, it's significantly more sane than raw POSIX.
For comparison, this is the interface to mmaped files within my company's internal database:
https://gist.github.com/scotchi/83609f5eb9b98ac3b4b4476ed621...
There's a grand total of 1 call to mmap in all of our source. Most of the time for writing code using mapped files here, all you'd see is the interface above.
I sent in a patch to an experienced C programmer a few years ago and in his code review he said about my #ifdef flags - "Typically HAVE_X are about the environment and USE_X are optional features within that environment."
Listening to Linux kernel developers online, they say a lot of people start kernel development with one small patch, or attempting to port some new device or chip to work with Linux. Once their patch gets merged, they sometimes continue to send in patches. The developers say that good contributors tend to not last long as independent contributors, and tend to get pulled into companies who need such programmers.
And there are open jobs for people with such skills ( https://us-redhat.icims.com/jobs/84882/senior-software-engin... )
It could be as simple as a `sleep` in the right circumstances. Maybe your resulting patch is a boring handful like:
but you need to read and develop understanding of the code to define the conditions in terms of the program's state. And you also need the same understanding to know where exactly in the code to introduce this snippet. Not to mention compiling it; even with Makefiles, lower-level languages could still be tricky to build.How this can be useful: Long time ago, we were observing odd behavior from Redis under extremely high load. Of course it did not replicate consistently and when it did, it lasted too short for us to properly observe and make hypotheses. So I had the brilliant idea of installing a custom Redis binary in our test environment that tweaked the odds so that the behavior happened almost consistently, no need to thrash the server. I had to read through Redis source code to make it happen (and added a hell lot of logs too). Plus in the end, it's not quite compiling Linux but, boy, I did compile Redis from scratch!
Anything that's been done by a lot of people, has been done well-enough by some stupid asshole.
I am a stupid asshole.
Therefore, I can probably figure out how to do [thing] well-enough, if it's been done by a lot of people.
It's worked out OK so far.
I’m not saying you have to be afraid, just that you sometimes have to tread a bit slower and do a lot of testing as you go until you learn the internals of whatever system you are working on
(Aside: this is what coupling does — you should fear coupling!)
I can relate to this as well. I will share couple of things just to add to this.
I have ~8YoE now. When I was at my first job, I did not have any formal CS degree, I had completed bachelors in commerce and was struggling with masters in computer applications (had year drops). My first job was in a company started by ~7-8 ex-veritas folks, all of them being hardcore system developers. I had dreams of being the same like them some day (yet to happen). ~1 year in this job and I shared my aspirations to become a system developer - I was given a task, implement persistent ram mechanism, something that will persist data in the RAM even after soft reboot, without dumping data on hard drive, using Linux kernel. "what to do" (trick/technique) was told by them, how to do it was left for me. It took me 4 days (2 weekends) to complete this. Over first weekend I learnt how to get Linux source code, add custom syscall, compile kernel etc. On second weekend I actually got to go through the code, find places to add patches, test etc.
I was also afraid before starting and my then boss had said few things like .. "because you think folks sitting in the west are something special, they are not ..", "whole thing is man made. If one man can do it, so can you". It was a matter of going through the code and understand. Do things repeatedly without giving up. Spend long hours, take notes. Once you have the context and that code is running in your head - you get what to do!
I also did the something similar few years after this. I was learning Go and wanted to do something better. I got into delve codebase (debugger for Go) and I patched it to work for cgo binaries. Its a small patch but for that I had to learn delve's architecture + what is dwarf standard and stuff. Context was pretty huge compared to what I needed to patch it. Same story though, I was little afraid thinking - omg! it's a freaking debugger, how am I going to understand all this to make changes. But in fact they are just the same constructs.
Go ahead and jump into some project you use, it helps understanding codebase faster. Read Read Read. Give yourself time to learn the codebase. You will definitely be able to contribute "properly" for that project. Don't be afraid :)
https://www.atariarchives.org
https://archive.org/details/pcinternsystempr0000tisc
Granted they are probably too old, but the concepts of what is actually systems programming is there, you can then get hold of an Arduino or Rasperry PI like device and do your own little OS or bare metal game,
https://www.cl.cam.ac.uk/projects/raspberrypi/tutorials/os/o...
http://www.science.smith.edu/dftwiki/index.php/Tutorial:_Ass...
Or maybe trying your hand at compilers with https://www.nand2tetris.org or given the similarities of Rust with ML, maybe dive into Tiger Book (https://www.cs.princeton.edu/~appel/modern/ml).
Or maybe
https://www.manning.com/books/rust-in-action
https://www.apress.com/gp/book/9781484258590 (Rust for IoT)
(And by the way, thanks OP for asking this question! I’ve also been wanting to learn systems programming, but I haven’t gotten around to asking yet. And all the suggested resources look fascinating… there goes my university vacation!)
https://www.amazon.com/Windows-Internals-Part-architecture-m...
https://www.amazon.com/Windows-Internals-Part-Developer-Refe...
These aren't about programming per se, but if you want to do systems programming it helps to have a detailed understanding of the system. :)
After that, specific reading probably depends on the exact task you want to perform, but MS has good documentation and tutorials in many areas. Writing drivers, for example:
https://docs.microsoft.com/en-us/windows-hardware/drivers/ge...
You can access them via assembler, C, etc.
I do not have the expertise to work on either field but this is something in the plan. The good part is that most malwares are targeting Windows so you get a lot of samples.
Also, Windows is an excellent way to learn systems programming because the documentation and tooling is so good and things hardly ever change.
There is a wealth of documentation on MSDN for writing device drivers and such. And great tools for remote debugging so you can set up a VM in hyper-v and step through the code from the host system.
Do make sure if you're analyzing malware you do it in a VM on a machine you don't care about having to wipe, and isolate it from the rest of your network.
I've tried to learn C probably 4 times now and I just don't like it. But then I came across LuaJIT ffi which, very easily allows you to use whatever shared library and call whatever syscalls directly and that was a game changer!
After that I decided to test ziglang, which a big part of it's design decisions is interoperability with C, and I'm in love with it! It really feels like anything I would need C for I can do in zig.
If Rust if your jam find a way to call the linux syscalls directly from Rust, not just using a cargo library, but actually importing the appropriate headers and successfully doing an epoll or something.
It will feel like suddenly the man (3) pages all make sense and are extremlly useful!
Good luck!
Also, make the case that many things that you learn while doing frontend programming like asynchronous programming are skills that will port over just fine.