10 ms·
Writing my own init with Go
- _yy 11y agoNice project! Even if you believe that systemd is too complicated, it's a great example on how to build an asynchronous, modern init system. Upstart uses some highly questionable tricks (ptrace!) to track processes across forks whereas systemd uses cgroups.
- pjmlp 11y agoNot only that, it is a nice example of using Go in a role that many would use C for, so it might convince a few to try their hands to safer programming languages. Next step, bare metal runtime! :)
- lmm 11y agoIs there even any concurrency in this case? Wouldn't e.g. OCaml be a better choice?
- commentzorro 11y agoWhat benefit would adding the overhead of learning OCaml be to this project? It's not like a functional or immutable language would add any value here, would it?!
- lmm 11y agoEven at this 3-line stage, the stateful log.SetOutput construct is ugly and error-prone. So I'd say a functional/immutable language would already be adding value, and that would only increase as the amount of code grows. At a minimum an init is going to involve parsing config and something similar to a state machine, both good use cases for functional languages.
- groovy2shoes 11y agoInit's job is to start the init scripts and then hang around to foster orphan processes. Ideally, it should not be parsing configs, but should spawn another process to do that sort of work. A good PID 1 is very, very minimal.
- lmm 11y agoThis project isn't just a PID1 though, it's a full system. (Well at the moment it's a hello world, but that's the intent)
- jerf 11y agoWhen C is taken as the baseline, there's a very long list of languages that look better for this sort of program. I don't see a huge reason that init has to be C, or even particularly benefits from it. (Specific init implementations may have the advantage of being battle-tested, but that's a characteristic of the program and time, not implementation language per se.)
- davexunit 11y agoUsing high-level languages for init systems is a great idea. In fact, we do this for the Guix System Distribution. Our init system is called GNU Shepherd, and it's written in Guile Scheme. It's great because you write services as Scheme code. For example, I have a number of Ruby web servers that I run for development at work that are nearly identical. Since services are first-class Scheme objects, I wrote a simple function that returns a service object for running a Ruby web server, specialized for a given application. For this workflow, I run Shepherd as an unprivileged user and additionally manage all of my user services like gpg-agent, emacs daemon, offlineimap, etc. No crappy external domain specific language to learn; data is code. Highly recommended. http://www.gnu.org/software/shepherd/ http://www.gnu.org/software/shepherd/
- pjmlp 11y agoPersonally I would use OCaml over Go, as I prefer expressive languages. However given Oberon's influence on Go, I find positive other devs that like Go, do use it for such purposes. OCaml already has MirageOS advertising it for system level coding. :) Edit: Regarding concurrency, even systems programming languages older than C have built in support for concurrency. C and C++ are probably the outsiders in terms of built-in support for concurrency, language or std libs.
- JoachimSchipper 11y agoIf you want to build a system that can survive out-of-memory conditions, you need to implement init so that it doesn't allocate memory at semi-random moments. Both Go and OCaml will fight you on that requirement. (IIRC, Go is designed to allocate memory the moment your stack grows too deep - which is usually a good idea, but not here.) Of course, very few Unices actually survive out-of-memory - Linux runs an OOM killer by default, OpenBSD tends to crash the kernel, etc. Apparently Solaris does have a good story here. (Of course, you'd have to ensure that the system doesn't spend all its time swapping, regardless.)
- rakoo 11y agoErlang would look be a very interesting choice here, seeing how any Erlang system is basically a miniature OS. It even has an init already.
- vezzy-fnord 11y agocgroups are overkill for process tracking, per se. The Linux kernel has the Netlink proc connector for that purpose. Subscribing to cgroupfs entails a much higher cognitive and resource load in having to deal with a hierarchical resource management subsystem altogether. Upstart is much more readable even though it has various idiosyncrasies.
- digi_owl 11y agoI get the impression that the primary reason for systemd using cgroups was "security". This in that it provided the means of namespace isolating daemons. This in turn because if you follow Poetterings project history it starts with him creating Pulseaudio to handle moving between sound devices while in use, then crated Avahi to provide a means of Pulseaudio equipped computers to find each other on a network, and then came Systemd based on what he learned about daemon security while writing Avahi. Sadly i can't shake the feeling that the security in systemd is all too often leaning towards "security theater".
- jabagonuts 11y ago> The reason that I wrote sleep 3 seconds is that I want to see how the kernel reacts when init dies. That was my favorite line in the entire post. I love seeing this kind of fearless experimentation. When I first learned to program, I desperately wanted everything to work and would try very hard not to break things. As I've matured into senior roles, I spend of a lot of effort creating "safe" development and testing environments like the author did here, just so that I can break things and make observations and then throw the environment away when I've learned what I needed to learn. Tools like vagrant and cheap cloud computing have made this easier to do than ever.
- vog 11y ago@CSDude: Thanks for sharing this very interesting project! From a technical point of view, what are the reasons you chose Go over alternatives, such as C, C++, OCaml or Rust? (Just curious, as all those languages have compilers that produce fast, optimized, self-contained binaries.)
- CSDude 11y agoThank you. I found it easier to develop in Go and I use Go in the work environment heavily. But compared to C/C++, I think having a garbage collector is a must for modern init, and I will write all the other utilities in Go as well for better maintainability.
- vog 11y agoDo you also have some comparison to Rust? Rust has a very advanced memory lifecycle management. The promising part is that it may give you almost all of the advantages of a garbage collector while not needing an actual garbage collector in most cases. Like RAII in C++, but much more advanced. I'd be interested whether this actually works well for a nontrivial project such as an init system.
- pcwalton 11y agoGarbage collection isn't a must for a modern init. Memory safety may arguably be, but I see no reasonable argument that garbage collection (a particular strategy for memory management) is.
- CSDude 11y agoWell I would say memory safety, not necessarily garbage collection. It was also a bit overstatement but I would really love memory safety for many core utilities.
- SixSigma 11y ago> Linux is mostly perfect as it is Your critical thinking has not been engaged. Linux is a useful bit of code but "perfect" is not what it is known as everywhere. My side of the room like this little ditty Linux, by amateurs, for amateurs -- Dave Presotto [1] Or i’ve wondered whether Linux sysfs should be called syphilis -- Charles Forsyth [2] Or if you like your detractors with a bit more fame Unix has retarded OS research by 10 years and linux has retarded it by 20. -- Dennis Ritchie as quoted by by Boyd Roberts in 9fans. I won't go on [1] http://research.google.com/pubs/author4927.html http://research.google.com/pubs/author4927.html [2] http://www.terzarima.net/ http://www.terzarima.net/
- PopsiclePete 11y agoSo what does an OS purist like yourself run? BeOS? Plan9? All OS's suck. They're just horrible. But so is everything else. It's all flawed in one way or another. Windows is flawed, OS X is flawed, our tools are flawed, our languages, our editors - everything. If you want perfect, you have to throw away your computer and somehow live inside Knuth's books.
- brockers 11y ago...aaaaaaand already better than SystemD.
- acdha 11y agoPlease don't muddy someone's learning experiment by rehashing a tired flame-war
- deleted 11y ago[deleted]
- sotaan 11y agoIf you improve the perf/functionalities of init using Go, I will give this language a go right after. It seems promising tho.
- sotaan 11y agoIs that possible to recode init in any other language using your technique? For instance can I try it in Ruby?
- tronje 11y agoSince init is the first process that runs, you can't use an interpreted language like ruby (since the interpreter would have to run first, which is impossible if your program itself is init). Maybe there's a Ruby-compiler out there somewhere, but as-is Ruby would be impossible.
- xena 11y agoAll you need is fork(), exec(), and signal handling. You could even write one in PHP.
- justinsaccount 11y agoThis is simply not true. It's not impossible and it works exactly like any program would.
- mveety 11y agoNo it wouldn't. Either hack a quick shim to start the interpreter or rely on the operating system to figure out how to run a script. The #! is there for a reason.
- deleted 11y ago[deleted]
- masklinn 11y ago> Since init is the first process that runs, you can't use an interpreted language like ruby Sure you can, you can even use a shell script: https://wiki.gentoo.org/wiki/Custom_Initramfs#Init https://wiki.gentoo.org/wiki/Custom_Initramfs#Init
- falcolas 11y agoThe author might look at how a minimal (yet fully functional) init works: http://git.suckless.org/sinit/tree/ http://git.suckless.org/sinit/tree/ (look at config.def.h and sinit.c) and build up from there. Given how minimal sinit is, it's a great place to start your functionality from, and see the power (and limitations) of a basic SystemV init system is.
- CSDude 11y agoWow, this is great, thanks!
- teh_klev 11y agoPart two is now up on his website: http://www.mustafaak.in/2016/02/09/forking-process-in-myinit-go.html http://www.mustafaak.in/2016/02/09/forking-process-in-myinit...
- daveloyall 11y ago@CSDude, I recommend putting a link to part 2 on the part 1 page.
- CSDude 11y agoDone! Thanks.
- zettahash 11y agoUsing Go even earlier in the boot stage in an initramfs (as a replacement for tools like dracut) crossed my mind recently. Go's statically linked binaries and fast builds make this particularly appealing to me. With (a lot of) work you could forego busybox entirely.
- loudmax 11y agoGood point about static linking. Though Go's big fat binaries count against it as a busybox replacement in areas where storage space is a premium.
- chubot 11y agoNOTE: You can't write an init without threads in Go, as you can in C. (I tried awhile back) At least you can't with its portable interfaces such as exec.Command() and cmd.Wait(): http://www.mustafaak.in/2016/02/09/forking-process-in-myinit-go.html http://www.mustafaak.in/2016/02/09/forking-process-in-myinit... You end burning a thread per process, which is pretty lame for an init. The wait() call blocks a goroutine AND OS thread, so the go runtime will have to start a new thread for the next wait(). Whereas in C you can do the entire thing from a simple thread (I call it "async processes"). You set SIGCHLD handler and get async exit notifications, and then process those in a single-threaded loop. I'm pretty sure there is a problem doing this in Go (besides resorting to the syscall module)... I think it has to do with the fact that Go turns signals into messages on a channel, but I don't remember right now... EDIT: Now I remember. The problem with using the syscall module is that Go does not export Fork and Exec. It exports ForkExec, because it needs to ensure they run on the same thread. https://golang.org/pkg/syscall/#ForkExec https://golang.org/pkg/syscall/#ForkExec This severely limits the usefulness of your init program, because in Unix there are all sorts of important things that happen in between. It's how you set the child process state. You basically can't do anything with file descriptors or containers at all with this model.
- pcwalton 11y agoSignal handlers are pretty much impossible to write directly in a managed language (at least without heavy contortions from the runtime), because you could get a signal during a non-GC-safe point and then deadlock or corrupt your program. For example, malloc often takes locks internally; if you get a signal during malloc with that lock held and then try to malloc in your signal handler you can deadlock. Managed languages like Go treat allocation as an implementation detail rather than something the programmer has to explicitly invoke, making them basically incompatible with signal handlers. (BTW, this is mostly a problem with signal handlers as a concept, not with Go.) > The problem with using the syscall module is that Go does not export Fork and Exec. It exports ForkExec, because it needs to ensure they run on the same thread. And because you can't safely allocate after a fork in managed languages, because fork drops your other threads, leading to the same problems above. :)
- 11y ago
- tkinom 11y agoWriting init in Go probably have size issue compare to typical C implementation. Last golang helloworld I built was 1.4MB. The /sbin/init in my ubuntu dist is only 194k. Busybox version of init optimized for size/features can be much smaller. Tough to use it in OpenWRT, DDWrt type of embedded system where system might only have a few megabytes of RAM/ROM.
- CSDude 11y agoYeah, you are right. Go is too heavy weight. But the nice thing in Go 1.4+, you can install go std and dynamically link your applications. I will do it in the next blog posts to save some space, but yeah, I would still need some huge lib.
- joshwget 11y agoAlthough very different than a traditional Linux distribution, the init system for RancherOS might be worth taking a look at. It's written entirely in Go as well. https://github.com/rancher/os/tree/master/init https://github.com/rancher/os/tree/master/init
- mmarx 11y agoThe kernel already supports acquiring a DHCP lease via the “ip=dhcp” boot option. Why does this need to be a feature of init?
- pjmlp 11y agoBecause init is common to several UNIXes and ip=dhcp is a Linux specific feature?
- mmarx 11y ago> Because init is common to several UNIXes and ip=dhcp is a Linux specific feature? And yet the author seems to be comfortable using those linux-specific features: “Also, the kernel paremeters must have the rw flag or you have to remount the /dev/sda1 as rw, but changing kernel parameters are much more easier.” (from the follow-up post).
- CSDude 11y agoI did not know we could have ip=dhcp option, but I will need to create a network manager now or later, but for trying out some stuff, I can use that flag. Also, I will not bundle services to init, it will be just forking, signaling and waiting. All the other utitilies will be small independent code exercises for me.