
Writing my own init with Go - CSDude
http://www.mustafaak.in/2016/02/08/writing-my-own-init-with-go.html
======
jabagonuts
> 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.

~~~
agumonkey
it should be called acquiring information, not 'breaking' things :D

~~~
kough
"Actively exploring modes of failure"

~~~
slantview
Or maybe hacking ;)

------
chubot
NOTE: 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...](http://www.mustafaak.in/2016/02/09/forking-process-in-myinit-
go.html)

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
Signal 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. :)

~~~
chubot
So the way Python does it is that it doesn't call back into the interpreter
loop from the signal handler. That would be a really bad idea as you note,
because the interpreter can call malloc and do all sorts of other stuff.

It queues your Python signal handling function, returns from the signal
handler, and then runs it on the main loop at the next tick.

As far as I know this works fine. I wrote the same program in Python and it
seems to work just fine, and have more functionality than the Go version. I
guess one problem is that if you do a long blocking syscall on a tick in the
main loop, your signal handler can be delayed indefinitely. There might be
some hacks around this in the Python interpreter, but I don't recall offhand.

~~~
pcwalton
Yeah, that's basically having the runtime advance to the next GC safe point
before running the handler. It's not direct control over signal handlers, but
it's fairly low overhead. I guess Go could do this too if it wanted to. I
don't think it works for fork though.

~~~
chubot
Yeah the problem with Go and "async processes" doesn't have to do with GC --
it has to do with M:N threading. I'm glad Rust abandoned M:N threading.

Though it is definitely a killer feature of Go to write in a blocking style
rather than in a state machine style... but I feel that there might have been
a way to do it without baking it so hard into the runtime (?). Maybe like
coroutines in Python, which run on a single thread and are optional.

M:N threading causes a lot of other complications too, like calling from C to
Go, etc.

------
falcolas
The 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
Wow, this is great, thanks!

------
_yy
Nice 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
Not 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
Is there even any concurrency in this case? Wouldn't e.g. OCaml be a better
choice?

~~~
commentzorro
What 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
Even 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
Init'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
This 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)

------
teh_klev
Part two is now up on his website:

[http://www.mustafaak.in/2016/02/09/forking-process-in-
myinit...](http://www.mustafaak.in/2016/02/09/forking-process-in-myinit-
go.html)

------
tkinom
Writing 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
Yeah, 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.

------
vog
@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
Thank 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.

~~~
pcwalton
Garbage 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
Well 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.

------
joshwget
Although 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)

------
zettahash
Using 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
Good 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.

------
SixSigma
> 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
So 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.

~~~
SixSigma
Try reading what I said.

I said "Linux isn't perfect, like you think it is"

~~~
betenoire
You are being a dick, offering no reasons other than the opinions of people
you presumable agree with.

~~~
SixSigma
If you could just tell my why it's perfect then, give me something to work
with.

~~~
betenoire
I can try... you scorned the commenter for not reading your comment literally,
and inferring things instead. When I read your comment literally, I see you
quoting people and saying it's not perfect.

Truly an empty comment, as it adds nothing except makes you look smarter than
someone else. You don't offer anything insightful, you are trying to be
insightful by proximity. So anyway, you reinforced your complex when you said
"Try reading what I wrote". So I called you a dick.

Now take your own advice and "try to read what _I_ wrote". I only called you a
dick. Why are you trying to make me defend a position I never took about
perfection?

~~~
SixSigma
> I only called you a dick.

oh we're going down that route. Well if it's literalism we're wasting time
with then try reading _your_ own comment

> You are being a dick, offering no reasons other than the opinions of people
> you presumable agree with.

That isn't just "calling me a dick".

but lets stop here

------
daveloyall
@CSDude, I recommend putting a link to part 2 on the part 1 page.

~~~
CSDude
Done! Thanks.

------
sotaan
If you improve the perf/functionalities of init using Go, I will give this
language a go right after. It seems promising tho.

~~~
sotaan
Is that possible to recode init in any other language using your technique?
For instance can I try it in Ruby?

~~~
tronje
Since 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.

~~~
masklinn
> 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)

------
mmarx
The kernel already supports acquiring a DHCP lease via the “ip=dhcp” boot
option. Why does this need to be a feature of init?

~~~
pjmlp
Because init is common to several UNIXes and ip=dhcp is a Linux specific
feature?

~~~
mmarx
> 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).

