[HN Gopher] Booting Linux in QEMU and Writing PID 1 in Go to Ill...
       ___________________________________________________________________
        
       Booting Linux in QEMU and Writing PID 1 in Go to Illustrate Kernel
       as Program
        
       Author : birdculture
       Score  : 249 points
       Date   : 2025-12-04 14:25 UTC (7 days ago)
        
 (HTM) web link (serversfor.dev)
 (TXT) w3m dump (serversfor.dev)
        
       | tombert wrote:
       | I love blog posts like this. You're not wrong in saying that the
       | kernel is sort of this magical block box to most engineers
       | (including me). I know how to use systemd and I know how to use
       | bash and I know a few other things, but the kernel has always
       | been "the kernel", and it's something I've never really tried to
       | mess with. But you're right: ulimately the kernel is _just a
       | program_. Yes, it 's a big and important program that works at a
       | lower level than I typically work at, but it's probably not
       | something that is impossible for me to learn some basic stuff
       | around.
       | 
       | I have had a bit of a dream of building a full desktop operating
       | system around seL4 [1], with all drivers in user space and the
       | guts fully verified in Isabelle, but learning about this level of
       | code kind of feels like drinking from a firehose. I would like to
       | port over something like xserver and XFCE and go from there, but
       | I've never made a proper attempt because of how overwhelming it
       | feels.
       | 
       | [1] I know about sculpt and Genode, and while those are
       | interesting, not quite what I want.
        
         | ronsor wrote:
         | You can actually disable most features of the Linux kernel,
         | including multi-user support (everything will run as root). The
         | end result is a stripped down kernel fit for only running your
         | single desired application.
        
           | tosti wrote:
           | gmake tinyconfig all
           | 
           | The result of that probably won't boot your friendly
           | neighbourhood desktop distro.
        
         | TZubiri wrote:
         | It reminds me of when people speak of money as a product. Sure,
         | maybe you are right, but I think more of it as something in
         | relation to products/programs than as a product/program itself.
         | 
         | The fact that it's also a product/program is some brainfucky
         | exercise that might either be an interesting hobby thought
         | experiment OR it might be a very relevant nuance that will be
         | useful to the top 0.1% of professionals who need a 99.9%
         | accuracy, like the difference between classical and
         | relativistic mechanics.
         | 
         | I mean, sure you are right that kernels are programs and that
         | money is a product, and that gravity is not a force. But I am a
         | mere mortal and I will stick to my incorrect and incomplete
         | mental model at a small expense of accuracy to the great
         | advantage of being comprehensible.
        
         | bitwize wrote:
         | Try working on NetBSD or OpenBSD. You can learn kernel hacking
         | by literally reading the man pages. Changing, rebuilding,and
         | booting your own custom kernel is tremendously exciting.
        
         | bicolao wrote:
         | > But you're right: ulimately the kernel is just a program.
         | 
         | Play a bit with user mode linux [1] the kernel becomes
         | literally a linux program, that I believe you can even debug
         | with gdb (hazy memory as I tried uml last time maybe a decade
         | ago)
         | 
         | In theory you can also attach gdb to qemu running linux, but
         | that's more complicated.
         | 
         | [1] https://en.wikipedia.org/wiki/User-mode_Linux
        
           | ktpsns wrote:
           | And User Mode Linux was the basic technology for dirt cheap
           | (not so) virtual machines at some VPS providers 15yrs ago.
           | This had some disadvantages, for instance you could not load
           | custom kernel modules in the VM (such as for VPN), actually
           | you could not modify the kernel at all.
        
       | peddling-brink wrote:
       | Ahh, this was really cool. I'm not sure I understand the kernel
       | much better, but init and the concept of an operating system make
       | a lot more sense.
       | 
       | I'd love a similarly styled part two that dives into making a
       | slightly useful distro from "scratch" in go.
        
       | geonineties wrote:
       | I would say something a little different. The kernel is a
       | _library_ that has an init routine you can provide the function
       | for. Or put another way, without the kernel your go program would
       | have to have drivers statically compiled into it. This was the
       | world of DOS, btw.
        
         | charcircuit wrote:
         | More importantly, a kernel is a platform. Conceptually it isn't
         | that much different than other platforms such as Chrome or
         | Roblox. They all have to care about the lifecycle of content,
         | expose input events to content, allow content to render things,
         | make sure bad things don't happen when running poorly
         | programmed or malicous content, etc.
        
           | zsoltkacsandi wrote:
           | > More importantly, a kernel is a platform.
           | 
           | Completely agree with this framing. We will get there by the
           | end of the series.
        
             | tosti wrote:
             | Yeah no. An operating system kernel doesn't just act as a
             | host for userland processes, it interacts with hardware.
             | Hardware behaves in weird and unexpected ways, can be quite
             | hard to debug, can fail, etc.
             | 
             | This is why Linux is excellent. Users of other operating
             | systems often remind people to update their device drivers.
             | A non-technical Linux responds asking what the heck device
             | drivers are. To the casual user, device drivers become
             | invisible because they work exactly as intended.
        
         | sedatk wrote:
         | I agree with your point, but I must correct you on DOS: it had
         | device drivers too. :) That's how we used to access mouse
         | input, CD drives, network, extended memory, etc. Yes, it sucked
         | on the graphics and sound; every app basically had to
         | reimplement its own graphics and audio layer from scratch, but
         | the rest was quite abstracted away.
        
           | 1313ed01 wrote:
           | There were generic VESA SVGA drivers towards the end of the
           | MS-DOS era.
           | 
           | Sound blaster(16) also came close to being standard enough
           | that games could just support that.
           | 
           | Extrapolating I think MS-DOS was on a nice trajectory to
           | having complete enough (and reasonably simple and non-
           | bloated!) APIs for everything important, when it was killed
           | off. Late MS-DOS 32-bit games were usually trivial to install
           | and run.
        
       | pastage wrote:
       | This is a really clean write up, but it is absolutely a happy
       | path. I do feel the kernel is too big to be called a program. It
       | is almost everything you want from comp sci class, router,
       | scheduler, queue, memory manager. There are some interesting
       | things that you have to handle if you do not run and OS and init
       | on hardware e.g. handle signals, how do you shutdown, reap child
       | process. I believe you are always better off with an init process
       | and an OS.
        
         | zsoltkacsandi wrote:
         | > I do feel the kernel is too big to be called a program.
         | 
         | I kind of agree, but the kernel as a program serves a
         | pedagogical framing here.
         | 
         | The goal of the post is to make it more tangible for
         | developers, they write programs that are files on the disk, and
         | you can interact with them. That's where the analogy came from.
        
         | markhahn wrote:
         | yes, it's misleading clickbait.
         | 
         | the author's apparent epiphany is realizing that _init_ is just
         | a program. the kernel is, of course, software as well, but it
         | does injustice to both  "program" and "kernel" to lump them
         | together.
        
       | fxbois wrote:
       | Thank you for this quite perfect blog post (short, interesting,
       | well written). One subject I would be interested in is what are
       | all the parameters a kernel accepts
        
         | pouulet wrote:
         | Something like this?
         | 
         | https://docs.kernel.org/admin-guide/kernel-parameters.html
        
       | zoobab wrote:
       | Is there a patch for systemd so that you can start it without
       | PID1 monopoly?
        
       | drnick1 wrote:
       | It's a bit unnatural to use Go when C is the "native language" of
       | Linux and pretty much every operating system.
        
         | cpach wrote:
         | I mean what you run is still machine code anyway, right?
        
         | ktpsns wrote:
         | Talos Linux [1], "the Kubernetes Operating System", is written
         | in Go. That means it _exactly_ works as the little demo here,
         | where the Kernel hands over to a statically compiled Go code as
         | init script.
         | 
         | Talos is really an interesting linux distribution because it
         | has no classical user _space_ , i.e. there is no such thing as
         | a $PATH including /bin, /usr/bin, etc. The shell is instead a
         | network API, following the kubernetes configuration-as-code
         | paradigm. The linux host (node) is supposed to run
         | containerized applications. If you really want to, you can use
         | a special container to get access to the actual user space from
         | the node.
         | 
         | [1] https://www.talos.dev/ [2]
         | https://github.com/siderolabs/talos/releases/tag/v1.11.5
        
           | preisschild wrote:
           | I also use Talos, but I wonder if just using systemd for the
           | init process wouldn't have been easier. You can interface
           | with systemd in go quite easily anyways...
        
             | cpach wrote:
             | s6 (perhaps with s6-rc) is another interesting option. One
             | could say it's less opinionated than systemd. Or perhaps
             | it's more correct to say it has another set of opinions.
        
           | tayo42 wrote:
           | Off-topic i guess. Are there like large scale success stories
           | using this os?
        
             | ktpsns wrote:
             | Yes. I know at least one big cloud provider (actually the
             | biggest) in Germany who uses Talos for their managed k8s.
        
         | zsoltkacsandi wrote:
         | The goal was to strip away most of the complexities (including
         | C), to make the topic more approachable for a broader audience.
         | 
         | Go seemed a perfect fit, it is easy to pick up the syntax and
         | see what is going on, but you can still be close to the OS.
        
         | themafia wrote:
         | Go can speak C. It's fine.
        
       | mrbluecoat wrote:
       | > If you ever wondered what this name means: vmlinuz: vm for
       | virtual memory, linux, and z indicating compression
       | 
       | Thank you. I have always wondered that.
        
         | Tor3 wrote:
         | In the early days when the kernel was small (I used to build
         | kernels and copy them to floppy disks, and boot Linux from
         | there) the kernel was called 'vmlinux', and when compression
         | was added after the kernel started to get bigger it became
         | 'vmlinuz'. It was still possible to boot from 'vmlinux', and it
         | may be possible today as well, for all I know.
        
       | alexellisuk wrote:
       | Interesting starter post.. I took this one step further a few
       | years ago to make the init mount various other /proc /sys etc
       | filesystems and boot up with Firecracker - using a container
       | image as a rootfs.. GitHub
       | https://github.com/alexellis/firecracker-init-lab Blog post:
       | https://actuated.com/blog/firecracker-container-lab
        
       | CSDude wrote:
       | I had a similar experiment ~10yr ago, see relevant discussion
       | https://news.ycombinator.com/item?id=11064694
       | 
       | And updated domain:
       | https://mustafaakin.dev/posts/2016-02-08-writing-my-own-init...
        
       | westurner wrote:
       | Systemd service unit and systemd-nspawn support could be written
       | in Go, too;
       | 
       | From https://news.ycombinator.com/item?id=41270425 re: "MiniBox,
       | ultra small busybox without uncommon options":
       | 
       | > _There 's a pypi:SystemdUnitParser._
       | 
       | > _docker-systemctl-replacement > systemctl3.py parses and
       | schedules processes defined in systemd unit files:
       | https://github.com/gdraheim/docker-systemctl-replacement/blo... _
       | 
       | From a container2wasm issue about linux-wasm the other day:
       | https://github.com/container2wasm/container2wasm/issues/550#... :
       | 
       | > [ uutils/uucore, uutils/coreutils, uutils/procps, uutils/util-
       | linux, findutils, diffutils, toybox (C), rustybox, ]
        
       | WesolyKubeczek wrote:
       | Can anyone explain why CGO_ENABLED needs to be set to 1 here?
        
         | zsoltkacsandi wrote:
         | In the post it is set to 0. `CGO_ENABLED=0 go build -o init .`
         | 
         | The only reason is because I like to be explicit, and I could
         | not know what was set before in the user's environment.
        
       | zsoltkacsandi wrote:
       | Author here. It was a bit emotional seeing this on the front
       | page.
       | 
       | My goal with this post and the whole (work in progress) series is
       | to fill the gap between "here are the commands to do X" and "if
       | you want to contribute to the kernel, you need to learn this"
       | style books and tutorials.
       | 
       | I want something in between, for developers who just want a solid
       | mental model of how Linux fits together.
       | 
       | The rough progression I have in mind is:
       | 
       | 1. the Linux kernel as "just a program"
       | 
       | 2. system calls as the kernel's API
       | 
       | 3. files as resources manipulated through system calls, forming a
       | consistent API
       | 
       | 4. the filesystem hierarchy as a namespace system, not a direct
       | map of disk layout
       | 
       | 5. user/group IDs and permissions as the access control mechanism
       | for resources (files)
       | 
       | 6. processes, where all of the above comes together
       | 
       | I deliberately chose Go for the examples instead of C because I
       | want this to be approachable to a broader audience of developers,
       | while still being close enough to the OS to show what's really
       | going on.
       | 
       | As a developer, this kind of understanding has been incredibly
       | useful for me for writing better software, debugging complex
       | issues with tools like strace and lsof, or the proc fs. I would
       | like to help others to gain the same knowledge.
        
         | potato-peeler wrote:
         | Can you also consider adapting Linux from scratch as a part of
         | this series? Or Maybe after this series, you can expand what is
         | learnt to build a minimal Linux distribution. I suppose that
         | might give a good understanding on how to apply this knowledge
         | and a have a foundation on the internals of the os itself.
        
           | zsoltkacsandi wrote:
           | I want to keep this series focused, but LFS-style content is
           | definitely something I'm considering for later, I think it's
           | a good idea.
           | 
           | That said, this series will also give you practical,
           | applicable knowledge as we progress.
        
         | preisschild wrote:
         | Another "interesting" related thing I found is that pid 1
         | signals are handled differently in the kernel. Basically,
         | SIGTERM is ignored and you need to explicitly handle it in your
         | program. Took me quite a while before I found out why my
         | program in a container didn't quit gracefully...
         | 
         | https://raby.sh/sigterm-and-pid-1-why-does-a-container-linge...
        
         | pollux_423 wrote:
         | Really cool post, clear, easy to follow, just the right length
         | and depth. Lookig forward to read the whole series!
        
         | kunley wrote:
         | Hi! Great article.
         | 
         | I guess also one of the points of using Go was the fact it has
         | own memory management for obtaining memory pages it interacts
         | only with the kernel.
         | 
         | I mean, had you used C, it would be better to compile it
         | statically, otherwise you'd need to put also glibc and ld.so
         | and what else into the initrd, I guess
        
       | gr4vityWall wrote:
       | The writing is really succinct and easy to follow.
       | 
       | One thing that could be improved is that the author could break
       | down some of the commands, and explain what their arguments mean.
       | For example:
       | 
       | > mknod rootfs/dev/console c 5 1
       | 
       | Depending on the reader's background, the args 'c', '5', and '1'
       | can look arbitrary and not mean much. Of course, we can just look
       | those up, and it doesn't make the article worse.
        
         | 0xFEE1DEAD wrote:
         | For anyone curious: "c" just means that it's a character
         | device.
         | 
         | There is also "b" for block device (e.g. a disk, a partition,
         | or even something like a loopback device) and "p" for FIFOs
         | (similar to mkfifo).
         | 
         | The two numbers are just identifiers to specify the device, so
         | in case of `5 1` it means the systems tty console, while `1 8`
         | would mean "blocking random byte device" (mknod dev/random c 1
         | 8)
        
       | jkrejcha wrote:
       | A fun little tidbit, if you don't provide an init to the kernel
       | command line, it'll try to look for them in a few places in this
       | order:
       | 
       | 1. /sbin/init
       | 
       | 2. /etc/init
       | 
       | 3. /bin/init
       | 
       | 4. /bin/sh
       | 
       | It dropping you into a shell is a pretty neat little way to allow
       | recovery if you somehow really borked your init
        
         | wibbily wrote:
         | The kernel even has a special error message for you when it
         | happens:
         | 
         | > Bailing out, you are on your own. Good luck.
         | 
         | https://unix.stackexchange.com/questions/96720
        
           | kmm wrote:
           | That's actually a message from the (Arch) initramfs[1], in
           | case it can't mount the root filesystem or find an init to
           | hand off to.
           | 
           | The kernel has a different error message: "No working init
           | found. Try passing init= option to kernel."[2]
           | 
           | 1: https://github.com/archlinux/mkinitcpio/blob/2dc9e12814aaf
           | cc... 2: https://github.com/torvalds/linux/blob/d358e5254674b
           | 70f34c84...
        
       | akpa1 wrote:
       | I love that it's possible to boot a raw Linux kernel this way; I
       | only learned about it very recently when working on a university
       | project. It makes me want to fiddle around with it more and
       | really understand the nuts and bolts of a modern Linux system and
       | work out what actually is responsible for what and, crucially,
       | when it happens.
        
       | zsofia wrote:
       | Nice demo. It's great to see such a clean, beginner-friendly
       | explanation of kernel vs. init responsibilities.
        
       | LorantToth wrote:
       | Love how simply you explain concepts that are completely foreign
       | to me. Enjoyed it very much!
        
       | Tigike wrote:
       | Wow, what a nice and easily understandable explanation of an
       | overcomplicated topic. This kind of teaching method is so much
       | needed in software development.
        
         | markhahn wrote:
         | I'm curious why you think it's overcomplicated.
         | 
         | That is: this seemed like the first 3 minutes of the first
         | lecture on an freshman OS course, or similar in any book on
         | systems. The complication you refer to - is it just from the
         | clutter of adjacent words (EFI, grub, kmod maybe?)
        
       | maccard wrote:
       | Stupid question, but what does the default init program do? If I
       | have a single application (say a game), can I just set up the
       | file system, statically link my game and bundle it as an iso,
       | rather than say containerising it?
       | 
       | Purely academic.
        
         | Gazoche wrote:
         | In theory yes, though depending on the complexity of your game
         | you may need to bundle a lot of userspace libraries and other
         | programs along with your kernel to make it work. Most graphical
         | applications expect a display server like X11 or Wayland to
         | talk to, at minimum.
        
           | maccard wrote:
           | Yeah, that's the hard part (but also the appeal). How minimal
           | can I go and still have a single-use system. Maybe a holiday
           | project...
        
         | maxboone wrote:
         | Absolutely, and the init system does not even have to set up
         | the filesystem and all. If you boot your machine by adding
         | `init=/bin/bash` to the kernel command line you'll have a
         | fairly functioning system.
         | 
         | Do anything necessary from there to boot your game, and record
         | those steps in a script. When that's done you can just point
         | your init cmdline to that script (doesn't even have to be a
         | binary, a script with the #!/bin/bash shebang should just
         | work).
        
         | markhahn wrote:
         | of course. init is just pid 1. it can be a copy of "Hello,
         | World!" (suitably linked) or whatever.
        
       | maxboone wrote:
       | Another cool way to show that 'the Linux kernel as "just a
       | program"' is that you can also run the kernel as a regular binary
       | without needing QEMU to emulate a full system:
       | 
       | - https://www.kernel.org/doc/html/v5.9/virt/uml/user_mode_linu...
        
       | teraflop wrote:
       | Nice article! One point of clarification:
       | 
       | > When the kernel starts it does not have all of the parts loaded
       | that are needed to access the disks in the computer, so it needs
       | a filesystem loaded into the memory called initramfs (Initial RAM
       | filesystem).
       | 
       | The kernel _might_ not have all the parts needed to mount the
       | filesystem, especially on a modern Linux distro that supports a
       | wide variety of hardware.
       | 
       | Initramfs exists so that parts of the boot logic can be handled
       | in userspace. Part of this includes deciding which device drivers
       | to load as kernel modules, using something like udev.
       | 
       | Another part is deciding _which_ root filesystem to mount. The
       | root FS might be on an LVM volume that needs to be configured
       | with device-mapper, or unlocked with decrypt. Or it might be
       | mounted over a network, which in turn requires IP configuration
       | and authentication. You don 't want the kernel to have those
       | mechanisms hard-coded, so initramfs allows handling them in
       | userspace.
       | 
       | But strictly speaking, you don't _need_ any of that for a minimal
       | system. You can boot without initramfs at all, as long as no
       | special userspace setup is required. i.e., the root FS is a plain
       | old disk partition specified on the kernel command line, and the
       | correct drivers (e.g. for a SCSI /SATA hard drive) are already
       | linked into the kernel.
        
         | tosti wrote:
         | This. Only CPU microcode can't be loaded without an initramfs
         | unless you enable late loading, but that's labeled dangerous
         | because it may cause instability. If needed, you could let the
         | built-in motherboard uefi do the microcode updates instead.
        
         | seanw444 wrote:
         | I've used Linux for quite some time, and had always kinda
         | wondered what purpose initramfs served, since I have to rebuild
         | it so often. Thanks.
        
           | tosti wrote:
           | Linux includes a cpio utility and documentation for building
           | your own initramfs.
        
         | ktpsns wrote:
         | When I used Gentoo, where you typically configure&compile the
         | kernel yourself, I never used initramfs.
         | 
         | This was 20yrs ago. Gentoo was really a great teacher.
        
           | spwa4 wrote:
           | Problem with that was that you'd run literally every module
           | initialization and occasionally there were some that crashed
           | the kernel.
        
       | markhahn wrote:
       | isn't this obvious?
       | 
       | maybe the audience is people who've never heard of init or
       | thought about kernel vs userspace.
        
       | CupricTea wrote:
       | I got close to this realization after learning barely enough
       | U-Boot to launch my own bare metal program for the JH7110. I
       | could never get into Linux From Scratch because it was more
       | focused on getting an _entire_ system working when I really just
       | wanted to see how it spins up to get going.
       | 
       | Then at some point the other week I realized I could technically
       | have a working Linux "system" with nothing more than a kernel and
       | a dirt simple hello world program in /sbin/init.
       | 
       | I haven't had the time or inclination to scratch that itch but
       | it's nice to see this article confirm it.
        
       | bradfitz wrote:
       | Related, I gave a 6 minute lightning talk about writing tests in
       | Go that use the test binary itself as the PID 1 under an emulated
       | Linux in QEMU:
       | 
       | https://docs.google.com/presentation/d/1rAAyOTCsB8GLbMgI0CAb...
       | 
       | https://www.youtube.com/watch?v=69Zy77O-BUM
        
       | pa7ch wrote:
       | Gokrazy is a minimal linux distro that just boots into a go init
       | program. You can run on a raspberry pi or pc. It has a little
       | init system that just takes a path you normally use in `go run`
       | and just runs them and restarts as needed. Its been a joy for me
       | to play around with. Has A/B updates as well.
       | 
       | https://gokrazy.org/
        
       ___________________________________________________________________
       (page generated 2025-12-11 23:01 UTC)