{"id":16760765,"url":"https://github.com/michielderhaeg/build-linux","last_synced_at":"2025-05-14T14:07:21.831Z","repository":{"id":44392520,"uuid":"89632732","full_name":"MichielDerhaeg/build-linux","owner":"MichielDerhaeg","description":"A short tutorial about building Linux based operating systems.","archived":false,"fork":false,"pushed_at":"2024-06-03T16:41:09.000Z","size":225,"stargazers_count":5160,"open_issues_count":7,"forks_count":336,"subscribers_count":185,"default_branch":"master","last_synced_at":"2025-04-11T06:13:06.699Z","etag":null,"topics":["busybox","kernel","tutorial"],"latest_commit_sha":null,"homepage":"","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/MichielDerhaeg.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-04-27T19:24:39.000Z","updated_at":"2025-04-10T06:10:44.000Z","dependencies_parsed_at":"2024-10-27T23:56:48.668Z","dependency_job_id":null,"html_url":"https://github.com/MichielDerhaeg/build-linux","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MichielDerhaeg%2Fbuild-linux","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MichielDerhaeg%2Fbuild-linux/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MichielDerhaeg%2Fbuild-linux/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MichielDerhaeg%2Fbuild-linux/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/MichielDerhaeg","download_url":"https://codeload.github.com/MichielDerhaeg/build-linux/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254159186,"owners_count":22024558,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["busybox","kernel","tutorial"],"created_at":"2024-10-13T04:24:30.209Z","updated_at":"2025-05-14T14:07:21.790Z","avatar_url":"https://github.com/MichielDerhaeg.png","language":"Shell","readme":"Build yourself a Linux\n======================\n\nIntroduction\n------------\n\n*This started out as a personal project to build a very small Linux based\noperating system that has very few moving parts but is still very complete and\nuseful. Along the way of figuring out how to get the damn thing to boot and\nmaking it do something useful I learned quite a lot. Too much time has been\nspent reading very old and hard to find documentation. Or when there was none,\nthe source code of how other people were doing it. So I thought, why not share\nwhat I have learned.*\n\n[This git repo](https://github.com/MichielDerhaeg/build-linux) contains a\nMakefile and scripts that automate everything that will be explained in this\ndocument. But it doesn't necessarily do everything in the same order as it's\nexplained. You can also use that as reference if you'd like.\n\nThe Linux Kernel\n----------------\n\nThe kernel is the core component of our operating system. It manages the\nprocesses and talks to the hardware on our behalf. You can retrieve a copy of\nthe source code easily from [kernel.org](https://www.kernel.org/). There are\nmultiple versions to choose from, choosing one is usually a tradeoff between\nstability and wanting newer features. If you look at the\n[Releases](https://www.kernel.org/category/releases.html) tab, you can see how\nlong each version will be supported and keeps receiving updates. So you can\nusually just apply the update or use the updated version without changing\nanything else or having something break.\n\nSo just pick a version and download the tar.xz file and extract it with ``tar\n-xf linux-version.tar.xz``. To build the kernel we obviously need a compiler and\nsome build tools. Installing ``build-essential`` on Ubuntu (or ``base-devel`` on\nArch Linux) will almost give you everything you need. You'll also need to\ninstall ``bc`` for some reason.\n\nThe next step is configuring your build, inside the untarred directory you do\n``make defconfig``. This will generate a default config for your current cpu\narchitecture and put it in ``.config``. You can edit it directly with a text\neditor but it's much better to do it with an interface by doing ``make nconfig``\n(this needs ``libncurses5-dev`` on Ubuntu) because it also deals with\ndependencies of enabled features. Here you can enable/disable features\nand device drivers with the spacebar. ``*`` means that it will be compiled in\nyour kernel image. ``M`` means it will be compiled inside a separate kernel\nmodule. This is a part of the kernel that will be put in a separate file and can\nbe loaded in or out dynamically in the kernel when they are required. The default\nconfig will do just fine for basic stuff like running in a virtual machine. But\nin our case, we don't really want to deal with kernel modules so we'll just do\nthis: ``sed \"s/=m/=y/\" -i .config``. And we're done, so we can simply do ``make`` to\nbuild our kernel. Don't forget to add ``-jN`` with `N` the number of cores\nbecause this might take a while. When it's done, it should tell you where your\nfinished kernel is placed. This is usually ``arch/x86/boot/bzImage`` in the\nlinux source directory for Intel computers.\n\nOther useful/interesting ways to configure the kernel are:\n\n  * ``make localmodconfig`` will look at the modules that are currently\n      loaded in the running kernel and change the config so that only those are\n      enabled as module. Useful for when you only want to build the things you\n      need without having to figure out what that is. So you can install\n      something like Ubuntu on the machine first, copy the config to your build\n      machine, usually located in /boot, Arch Linux has it in gzipped at\n      /proc/config.gz). Do ``lsmod \u003e /tmp/lsmodfile``, transfer this file to you\n      build machine and run ``LSMOD=lsmodfile make localmodconfig`` there\n      after you created ``.config``. And you end up with a kernel that is\n      perfectly tailored to your machine. But this has a huge disadvantage, your\n      kernel only supports what you were using at the time. If you insert a\n      usb drive it might not work because you weren't using the kernel module\n      for fat32 support at the time.\n\n  * ``make localyesconfig``is the same as above but everything gets compiled in\n      the kernel instead as a kernel module.\n\n  * ``make allmodconfig`` generates a new config where all options are enabled\n      and as much as possible as module.\n\n  * ``make allyesconfig``is same as above but with everything compiled in the\n      kernel.\n\n  * ``make randconfig`` generates a random config...\n\nYou can check out ``make help`` for more info.\n\nBusybox Userspace\n-----------------\n\nAll these tools you know and love like ``ls``, ``echo``, ``cat`` ``mv``, and\n``rm`` and so on are commonly referred to as the 'coreutils'. Busybox has that\nand a lot more, like utilities from ``util-linux`` so we can do stuff like\n``mount`` and even a complete init system. Basically, it contains most tools\nyou expect to be present on a Linux system, except they are a slightly\nsimplified version of the regular ones.\n\nYou can get the source from [busybox.net](https://busybox.net/). They also\nprovide prebuilt binaries which will do just fine for most use-cases. But just\nto be sure we will build our own version.\n\nConfiguring busybox is very similar to configuring the kernel. It also uses a\n``.config`` file and you can do ``make defconfig`` to generate one and ``make\nmenuconfig`` to configure it with a GUI. But we are going to use the one I\nprovided (which I stole from Arch Linux). You can find the config in the git\nrepo with the name ``bb-config``. Like the ``defconfig`` version, this has most\nutilities enabled but with a few differences like statically linking all\nlibraries.  Building busybox is again done by simply doing ``make``, but before\nwe do this, let's look into ``musl`` first.\n\nThe C Standard Library\n----------------------\n\nThe C standard library is more important to the operating system than you might\nthink. It provides some useful functions and an interface to the kernel. But it\nalso handles DNS requests and provides a dynamic linker. We don't really have to\npay attention to any of this, we can just statically link the one we are using\nright now which is probably 'glibc'. This means the following part is optional.\nBut I thought this would make it more interesting and it also makes us able to\nbuild smaller binaries.\n\nThat's because we are going to use [musl](https://www.musl-libc.org/), which is\na lightweight libc implementation. You can get it by installing ``musl-tools``\non Ubuntu or simply ``musl`` on Arch Linux. Now we can link binaries to musl\ninstead of glibc by using ``musl-gcc`` instead of ``gcc``.\n\nBefore we can build busybox with musl, we need sanitized kernel headers for use\nwith musl. You get that from [this github\nrepo](https://github.com/sabotage-linux/kernel-headers). And set\n``CONFIG_EXTRA_CFLAGS`` in your busybox config to\n``CONFIG_EXTRA_CFLAGS=\"-I/path/to/kernel-headers/x86_64/include\"`` to use them.\nObviously change ``/path/to`` to the location where you put the headers repo,\nwhich can be relative from within the busybox source directory.\n\nIf you run ``make CC=musl-gcc`` now, the busybox executable will be\nsignificantly smaller because we are statically linking a much smaller libc.\n\nBe aware that even though there is a libc standard, musl is not always a\ndrop-in replacement for glibc if the application you're compiling uses glibc\nspecific things.\n\nBuilding the Disk Image\n-----------------------\n\nInstalling an OS on a file instead of a real disk complicates things but this\nmakes development and testing easier.\n\nSo let's start by allocating a new file of size 100M by doing ``fallocate -l100M\nimage``(some distros don't have ``fallocate`` so you can do ``dd if=/dev/zero\nof=image bs=1M count=100`` instead). And then we format it like we would format\na disk with ``fdisk image``. It automatically creates an MBR partition table for\nus and we'll create just one partition filling the whole image by pressing 'n' and\nafterwards just use the default options for everything and keep spamming 'enter'\nuntil you're done. Finally press 'w' exit and to write the changes to the\nimage.\n```bash\n$ fdisk image\n\nWelcome to fdisk (util-linux 2.29.2).\nChanges will remain in memory only, until you decide to write them.\nBe careful before using the write command.\n\nDevice does not contain a recognized partition table.\nCreated a new DOS disklabel with disk identifier 0x319d111f.\n\nCommand (m for help): n\nPartition type\n   p   primary (0 primary, 0 extended, 4 free)\n   e   extended (container for logical partitions)\nSelect (default p):\n\nUsing default response p.\nPartition number (1-4, default 1):\nFirst sector (2048-204799, default 2048):\nLast sector, +sectors or +size{K,M,G,T,P} (2048-204799, default 204799):\n\nCreated a new partition 1 of type 'Linux' and of size 99 MiB.\n\nCommand (m for help): w\nThe partition table has been altered.\nSyncing disks.\n```\n\nIn order to interact with our new partition we'll create a loop device for our\nimage. Loop devices are block devices (like actual disks) that in our case\npoint to a file instead of real hardware. For this we need root so sudo up\nwith ``sudo su`` or however you prefer to gain root privileges and afterwards\nrun:\n```bash\n$ losetup -P -f --show image\n/dev/loop0\n```\nThe loop device probably ends with a 0 but it could be different in your case.\nThe ``-P`` makes sure the partition also gets a loop device, ``/dev/loop0p1`` in\nmy case. Let's make a filesystem on it.\n```bash\n$ mkfs.ext4 /dev/loop0p1\n```\nIf you want to use something other than ext4, be sure to enable it when\nconfiguring your kernel. Now that we have done that, we can mount it and start\nputting everything in place.\n```bash\n$ mkdir image_root\n$ mount /dev/loop0p1 image_root\n$ cd image_root # it's assumed you do the following commands from this location\n$ mkdir -p usr/{sbin,bin} bin sbin boot\n```\nAnd while we're at it, we can create the rest of the file system hierarchy. This\nis actually standardized and applications often assume this is the way you're\ndoing it, but you can often do what you want. You can find more info\n[here](http://www.pathname.com/fhs/).\n```bash\n$ mkdir -p {dev,etc,home,lib}\n$ mkdir -p {mnt,opt,proc,srv,sys}\n$ mkdir -p var/{lib,lock,log,run,spool}\n$ install -d -m 0750 root\n$ install -d -m 1777 tmp\n$ mkdir -p usr/{include,lib,share,src}\n```\nWe'll copy our binaries over.\n```bash\n$ cp /path/to/busybox usr/bin/busybox\n$ cp /path/to/bzImage boot/bzImage\n```\nYou can call every busybox utility by supplying the utility as an argument, like\nso: ``busybox ls --help``. But busybox also detects by what name it is called\nand then executes that utility. So you can put symlinks for each utility and\nbusybox can figure out which utility you want by the symlink's name.\n\n```bash\nfor util in $(./usr/bin/busybox --list-full); do\n  ln -s /usr/bin/busybox $util\ndone\n```\nThese symlinks might be incorrect from outside the system because of the\nabsolute path, but they work just fine from within the booted system.\n\nLastly, we'll copy some files from ``../filesystem`` to the image that will be\nof some use to us later.\n```bash\n$ cp ../filesystem/{passwd,shadow,group,issue,profile,locale.sh,hosts,fstab} etc\n$ install -Dm755 ../filesystem/simple.script usr/share/udhcpc/default.script\n# optional\n$ install -Dm644 ../filesystem/be-latin1.bmap usr/share/keymaps/be-latin1.bmap\n```\nThese are the basic configuration files for a UNIX system. The .script file is\nrequired for running a dhcp client, which we'll get to later. The keymap file is\na binary keymap file I use for belgian azerty.\n\nThe Boot Loader\n---------------\n\nThe next step is to install the bootloader - the program that loads our kernel in\nmemory and starts it. For this we use GRUB, one of the most widely used\nbootloaders. It has a ton of features but we are going to keep it very simple.\nInstalling it is very simple, we just do this:\n```bash\ngrub-install --modules=part_msdos \\ \n             --target=i386-pc \\\n             --boot-directory=\"$PWD/boot\" \\\n             /dev/loop0\n```\nUbuntu users might need to install ``grub-pc-bin`` first if they are on an EFI\nsystem.\n\nThe ``--target=i386-pc`` tells grub to use the simple msdos MBR bootloader. This\nis often the default, but this can vary from machine to machine so you better\nspecify it here. The ``--boot-directory`` options tells grub to install the grub\nfiles in /boot inside the image instead of the /boot of your current system.\n``--modules=part_msdos`` is a workaround for a bug in Ubuntu's grub. When you\nuse ``losetup -P``, grub doesn't detect the root device correctly and doesn't\nthink it needs to support msdos partition tables and won't be able to find the\nroot partition.\n\nNow we just have to configure grub and then our system should be able to boot.\nThis basically means telling grub how to load the kernel. This config is located\nat ``boot/grub/grub.cfg`` (some distro's use ``/boot/grub2``). This file needs\nto be created first, but before we do that, we need to figure something out\nfirst. If you look at ``/proc/cmdline`` on your own machine you might see\nsomething like this:\n```bash\n$ cat /proc/cmdline\nBOOT_IMAGE=/boot/vmlinuz-4.4.0-71-generic root=UUID=83066fa6-cf94-4de3-9803-ace841e5066c ro\n```\nThese are the arguments passed to your kernel when it's booted. The 'root'\noption tells our kernel which device holds the root filesystem that needs to be\nmounted at '/'. The kernel needs to know this or it won't be able to boot. There\nare different ways of identifying your root filesystem. Using a UUID is a\ngood way because it is a unique identifier for the filesystem generated when you\ndo ``mkfs``. The issue with using this is that the kernel doesn't really\nsupport it because it depends on the implementation of the filesystem. This\nworks on your system because it uses an initramfs, but we can't use it now. We\ncould do ``root=/dev/sda1``, this will probably work but it has some other problems.\nThe 'a' in 'sda' depends on the order the bios will load the disk and this\ncan change when you add a new disk, or for a variety of other reasons.\nOr when you use a different type of interface/disk it can be something entirely\ndifferent. So we need something more robust. I suggest we use the PARTUUID. It's\na unique id for the partition (and not the filesystem like UUID) and this is a\nsomewhat recent addition to the kernel for msdos partition tables (it's actually\na GPT thing). We'll find the id like this:\n```bash\n$ fdisk -l ../image | grep \"Disk identifier\"\nDisk identifier: 0x4f4abda5\n```\nThen we drop the 0x and append the partition number as two digit hexidecimal. An\nMBR only has 4 partitions max so that it's hexidecimal or decimal doesn't really\nmatter, but that's what the standard says. So the grub.cfg should look like this:\n```\nlinux /boot/bzImage quiet init=/bin/sh root=PARTUUID=4f4abda5-01\nboot\n```\nThe ``defconfig`` kernel is actually a debug build so it's very verbose, so to\nmake it shut up you can add the ``quiet`` option. This stops it from being\nprinted to the console. You can still read it with the ``dmesg`` utility.\n\n``init`` specifies the first process that will be started when the kernel is\nbooted. For now we just start a shell, we'll configure a real init while it's\nrunning.\n\nSo now we should be able to boot the system. You can umount the image, exit root\nand start a VM to test it out. The simplest way of doing this is using QEMU.\nThe Ubuntu package is ``qemu-kvm``, and just ``qemu`` on Arch Linux.\n```bash\n$ cd ../\n$ umount image_root\n$ exit # we shouldn't need root anymore\n$ qemu-system-x86_64 -enable-kvm image\n```\nAnd if everything went right you should now be dropped in a shell in our\nhomemade operating system.\n\n**Side note:** When using QEMU, you don't actually need a bootloader. You can\ntell QEMU to load the kernel for you.\n```bash\n$ qemu-system-x86_64 -enable-kvm \\\n                     -kernel bzImage \\\n                     -append \"quiet init=/bin/sh root=/dev/sda1\" \\\n                     image\n\n```\nWhere ``bzImage`` points to the kernel you built on your system, not the image.\nand ``-append`` specifies the kernel arguments (don't forget the quotes). This\ncould be useful when you would like to try different kernel parameters without\nchanging ``grub.cfg`` every time.\n\nPID 1: /sbin/init\n---------------\n\nThe first process started by the kernel (now ``/bin/sh``) has process id 1. This\nis not just a number and has some special implications for this process. The\nmost important thing to note is that when this process ends, you'll end up with\na kernel panic. PID 1 can never ever die or exit during the entire runtime of\nyour system. A second and less important consequence of being PID 1 is when\nanother process 'reparents', e.g. when a process forks to the background, PID 1\nwill become the parent process.\n\nThis implies that PID 1 has a special role to fill in our operating system.\nNamely that of starting everything, keeping everything running, and shutting\neverything down because it's the first and last process to live.\n\nThis also makes this ``init`` process very suitable to start and manage services\nas is the case with the very common ``sysvinit`` and the more modern\n``systemd``. But this isn't strictly necessary and some other process can carry\nthe burden of service supervision, which is the case with the\n[runit](http://smarden.org/runit/)-like ``init`` that is included with\n``busybox``.\n\nUnless you passed the ``rw`` kernel parameter the root filesystem is mounted as\nread-only. So before we can make changes to our running system we have to\nremount it as read-write first. Before we can do any mounting at all we have\nto mount the ``proc`` pseudo filesystem that serves as an interface to kernel.\n```bash\n$ mount -t proc proc /proc\n$ mount / -o remount,rw\n```\n\n``busybox`` provides only two ways of editing files: ``vi`` and ``ed``. If you\nare not confortable using either of those you could always shutdown the VM,\nmount the image again, and use your favorite text editor on your host machine.\n\nIf you don't use a qwerty keyboard, you might have noticed that the VM uses a\nqwerty layout as this is the default. You might want to change it to azerty with\n``loadkmap \u003c /usr/share/keymaps/be-latin1.bmap``. You can dump the layout you\nare using on your host machine with ``busybox dumpkmap \u003e keymap.bmap`` in a\nvirtual console (not in X) and put this on your image instead.\n\nFirst, we'll create a script that handles the initialisation of the system\nitself (like mounting filesystems and configuring devices, etc). You could call it\n``startup`` and put it in the ``/etc/init.d`` directory (create this first).\nDon't forget to ``chmod +x`` this file when you're done.\n```bash\n#!/bin/sh\n# /etc/init.d/startup\n\n# mount the special pseudo filesytems /proc and /sys\nmount -t proc proc /proc -o nosuid,noexec,nodev\nmount -t sysfs sys /sys -o nosuid,noexec,nodev\n# /dev isn't required if we boot without initramfs because the kernel\n# will have done this for us but it doesn't hurt\nmount -t devtmpfs dev /dev -o mode=0755,nosuid\nmkdir -p /dev/pts /dev/shm\n# /dev/pts contains pseudo-terminals, gid 5 should be the\n# tty user group\nmount -t devpts devpts /dev/pts -o mode=0620,gid=5,nosuid,noexec\n# /run contains runtime files like pid files and domain sockets\n# they don't need to be stored on the disk, we'll store them in RAM\nmount -t tmpfs run /run -o mode=0755,nosuid,nodev\nmount -t tmpfs shm /dev/shm -o mode=1777,nosuid,nodev\n# the nosuid,noexec,nodev options are for security reasons and are not\n# strictly necessary, you can read about them in the 'mount'\n# man page\n\n# the kernel does not read /etc/hostname on it's own\n# you need to write it in /proc/sys/kernel/hostname to set it\n# don't forget to create this file if you want to give your system a name\nif [[ -f /etc/hostname ]]; then\n  cat /etc/hostname \u003e /proc/sys/kernel/hostname\nfi\n\n# mdev is a mini-udev implementation that\n# populates /dev with devices by scanning /sys\n# see the util-linux/mdev.c file in the busybox source\n# for more information\nmdev -s\necho /sbin/mdev \u003e /proc/sys/kernel/hotplug\n\n# the \"localhost\" loopback network interface is\n# down at boot, we have to set it 'up' or we won't be able to\n# make local network connections\nip link set up dev lo\n\n# you could add the following to change the keyboard layout at boot\nloadkmap \u003c /usr/share/keymaps/be-latin1.bmap\n\n# mounts all filesystems in /etc/fstab\nmount -a\n# make the root writable if this hasn't been done already\nmount -o remount,rw /\n# end of /etc/init.d/startup\n```\n\nThe next file is the init configuration ``/etc/inittab``. The syntax of this\nfile is very similar to that of ``sysvinit``'s ``inittab`` but has several\ndifferences. For more information you can look at the ``examples/inittab`` file\nin the busybox source.\n```inittab\n# /etc/inittab\n::sysinit:/bin/echo STARTING SYSTEM\n::sysinit:/etc/init.d/startup\ntty1::respawn:/sbin/getty 38400 tty1\ntty2::respawn:/sbin/getty 38400 tty2\ntty3::respawn:/sbin/getty 38400 tty3\n::ctrlaltdel:/bin/umount -a -r\n::shutdown:/bin/echo SHUTTING DOWN\n::shutdown:/bin/umount -a -r\n# end of /etc/inittab\n```\nThe ``sysinit`` entry is the first command ``init`` will execute. We'll put our\n``startup`` script here. You can specify multiple entries of this kind and they\nwill be executed sequentially. The same goes for the ``shutdown`` entry, which\nwill obviously be executed at shutdown. The ``respawn`` entries will be executed\nafter ``sysinit`` and will be restarted when they exit. We'll put some\n``getty``'s on the specified tty's. These will ask for your username and execute\n``/bin/login`` which will ask for your password and starts a shell for you when\nit's correct. If you don't care for user login and passwords, you could instead\nof the ``getty``'s do ``::askfirst:-/bin/sh``. ``askfirst`` does the same as\n``respawn`` but asks you to press enter first. If no tty is specified it will\nfigure out what the console is. The ``-`` infront of ``-/bin/sh`` means that\nthe shell is started as a login shell. ``/bin/login`` usually does this for us\nbut we have to specify it here. Starting the shell as a login shell means that\nit configures certain things it otherwise assumes already to be configured. E.g.\nit sources ``/etc/profile``.\n\nWe can now start our system with ``init``. You can remove the ``init=/bin/sh``\nentry in ``/boot/grub/grub.cfg`` because it defaults to ``/sbin/init``. If\nyou reboot the system you should see a login screen. But if you run ``reboot``,\nyou'll notice it won't do anything. This happens because normally ``reboot``\ntells the running ``init`` to reboot. You know - the ``init`` that isn't running\nright now. So we have two options, we could run ``reboot -f`` which skips the\n``init``, or we could do this:\n```bash\n$ exec init\n```\nBecause the shell we are currently using is PID 1 and you could just replace the\nshell process with ``init`` and our system should be properly booted now\npresenting you a login prompt.\n\nThe root password should be empty so it should only ask for a username.\n\nService Supervision\n-------------------\n\nIn the last part of our OS building adventure we'll look into setting up some\nservices. An important thing to note is that we are using\n[runit](http://smarden.org/runit/) for service supervision, which is quite different\nfrom how the more common ``sysvinit`` does things but it'll give you a\nfeel for which problems it's supposed to solve and how.\n\nA basic service consists of a directory containing a ``run`` executable, usually\na script. This ``run`` script usually starts the daemon and doesn't exit until\nthe daemon does. If ``run`` exits ``runit`` will think the service itself has\nstopped and if it wasn't supposed to stop, ``runit`` will try to restart it. So\nbe careful with forking daemons. Starting the service is done with ``runsv``.\nThis is the process that actually monitors the service and restarts it if\nnecessary. Usually you won't run it manually but doing so is useful for testing\nservices.\n\nThe first service we are going to create is a logging service that collects\nmessages from other processes and stores them in files.\n\n```bash\n$ mkdir -p /etc/init.d/syslogd\n$ vi /etc/init.d/syslogd/run\n$ cat /etc/init.d/syslog/run\n#!/bin/sh\nexec syslogd -n\n$ chmod +x /etc/init.d/syslog/run\n$ runsv /etc/init.d/syslogd \u0026 # asynchronous\n$ sv status /etc/init.d/syslogd\nrun: /etc/init.d/syslogd: (pid 991) 1170s\n```\nIt's that simple, but we have to make sure ``syslogd`` doesn't fork or else\n``runsv`` will keep trying to start it even though it is already running. That's\nwhat the ``-n`` option is for. The ``sv`` command can be used to control the\nservice.\n\nTo make sure that our new service is started at boot we could create a new\n``inittab`` entry for it but this isn't very flexible. A better solution is to\nuse ``runsvdir``. This runs ``runsv`` for every service in a directory. So\nrunning ``runsvdir /etc/init.d`` would do the trick but this way we can't\ndisable services at boot. To solve this issue we'll create a separate directory\nand symlink the enabled services in there.\n```bash\n$ mkdir -p /etc/rc.d\n$ ln -s /etc/init.d/syslogd /etc/rc.d\n$ runsvdir /etc/rc.d \u0026 # asynchronous\n```\nIf we add ``::respawn:/usr/bin/runsvdir /etc/rc.d`` to ``/etc/inittab`` all the\nservices symlinked in ``/etc/rc.d`` will be started at boot. Enabling and\ndisabling a service now consists of creating and removing a symlink in ``/etc/rc.d``.\nNote that ``runsvdir`` monitors this directory and starts the service when the\nsymlink appears and not just at boot.\n\n### Syslog\n\n``syslogd`` implements the well known ``syslog`` protocol for logging. This\nmeans that it creates a UNIX domain socket at ``/dev/log`` for daemons to\nconnect and send their logs to. Usually it puts all of the collected logs in\n``/var/log/messages`` unless told otherwise. You can specify filters in\n``/etc/syslog.conf`` to put certain logs in different files.\n```bash\n$ vi /etc/syslog.conf\n$ cat /etc/syslog.conf\nkern.* /var/log/kernel.log\n$ sv down /etc/init.d/syslogd # restart\n$ sv up /etc/init.d/syslogd\n```\nThis will put everything the kernel has to say in a separate log file\n``/var/log/kernel.log``. But ``syslogd`` doesn't read the kernel logs like\n``rsyslog`` does. We need a different service for that.\n```bash\n$ mkdir -p /etc/init.d/klogd\n$ vi /etc/init.d/klogd/run\n$ cat /etc/init.d/klogd/run\n#!/bin/sh\nsv up /etc/init.d/syslogd || exit 1\nexec klogd -n\n$ chmod +x /etc/init.d/klogd/run\n$ ln -s /etc/init.d/klogd /etc/rc.d\n```\nNow we should see kernel logs appearing in ``/var/log/kernel.log``.\nThe ``sv up /etc/init.d/syslogd || exit 1`` line makes sure ``syslogd`` is\nstarted before ``klogd``. This is how we add dependencies in ``runit``. If\n``syslogd`` hasn't been started yet ``sv`` will fail and ``run`` will exit.\n``runsv`` will attempt to restart ``klogd`` after a while and will only\nsucceed when ``syslogd`` has been started. Believe it or not, this is what the\nrunit documentation says about making dependencies.\n\n### DHCP\n\nThe very last thing we will do is provide our system with a network connection.\n```bash\n$ mkdir -p /etc/init.d/udhcpc\n$ vi /etc/init.d/udhcpc/run\n$ cat /etc/init.d/udhcpc/run\n#!/bin/sh\nexec udhcpc -f -S\n$ chmod +x /etc/init.d/udhcpc/run\n$ ln -s /etc/init.d/udhcpc /etc/rc.d\n```\nNow we're done. Yes - it's that simple. Note that udhcpc just asks for a lease\nfrom the DHCP server and that's it. When it has a lease it executes\n``/usr/share/udhcpc/default.script`` to configure the system. We already copied\nthis script to this location. This script is included with the busybox source.\nThese scripts usually use ``ip``, ``route``, and write to ``/etc/resolv.conf``.\nIf you would like a static ip, you'll have to write a script that does these\nthings.\n\nEpilogue\n--------\n\nThat's it! We're done for now. Thanks for reading. I hope you learned something\nuseful. I certainly did while making this.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmichielderhaeg%2Fbuild-linux","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmichielderhaeg%2Fbuild-linux","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmichielderhaeg%2Fbuild-linux/lists"}