1
0
mirror of https://github.com/MichielDerhaeg/build-linux.git synced 2025-09-02 12:42:50 +02:00

various doc fixes

This commit is contained in:
Michiel Derhaeg
2017-04-25 19:43:37 +02:00
parent 19697e0afc
commit 876a106396

View File

@@ -1,25 +1,26 @@
Build yourself a Linux
======================
Abstract
--------
Introduction
------------
*This started out as a personal project to build a very small Linux based
operating system that has very few moving parts but is still very complete and
useful. Along the way of figuring out how to get the damn thing to boot and
making it do something useful I learned quite alot. Too much time has been
spent reading very old and hard to find documentation, or when there was none
spent reading very old and hard to find documentation. Or when there was none,
the source code of how other people were doing it. So I thought, why not share
what I have learned.*
This git repo contains a Makefile and scripts that automate everything that will
be explained in this document. But it doesn't necessarily do everything in the
same order as it's explained. You can also use that as reference if you'd like.
[This git repo](todo) contains a Makefile and scripts that automate everything
that will be explained in this document. But it doesn't necessarily do
everything in the same order as it's explained. You can also use that as
reference if you'd like.
The Linux Kernel
----------------
The core component of our operating system, the kernel, that which manages the
The kernel is the core component of our operating system. It manages the
processes and talks to the hardware on our behalf. You can retrieve a copy of
the source code easily from [kernel.org](https://www.kernel.org/). There are
multiple versions to choose from, choosing one is usually a tradeoff between
@@ -39,14 +40,15 @@ The next step is configuring your build, inside the untarred directory you do
``make defconfig``. This will generate a default config for your current cpu
architecture and put it in ``.config``. You can edit it directly with a text
editor but it's much better to do it with an interface by doing ``make nconfig``
(this needs ``libncurses5-dev`` on Ubuntu). Here you can enable/disable features
(this needs ``libncurses5-dev`` on Ubuntu) because it also deals with
dependencies of enabled features. Here you can enable/disable features
and device drivers with the spacebar. ``*`` means that it will be compiled in
your kernel image. ``M`` means it will be compiled inside a seprate kernel
module. This is a part of the kernel that will be put in a seperate file and can
be loaded in dynamically in the kernel when they are required. The default
be loaded in or out dynamically in the kernel when they are required. The default
config will do just fine for basic stuff like running in a virtual machine. But
in our case, we don't really want to deal with kernel modules so we'll just do
this: ``sed "s/=m/=y/" -i .config``. We're done, so we can simply do ``make`` to
this: ``sed "s/=m/=y/" -i .config``. And we're done, so we can simply do ``make`` to
build our kernel. Don't forget to add ``-jN`` with `N` the number of cores
because this might take a while.
@@ -60,10 +62,10 @@ Other useful/interesting ways to configure the kernel are:
machine, usually located in /boot, Arch Linux has it in gzipped at
/proc/config.gz). Do ``lsmod > /tmp/lsmodfile``, transfer this file to you
build machine and run ``LSMOD=lsmodfile make localmodconfig`` there
starting from the config you copied. And you end up with a kernel that is
perfectly tailored for your machine. But this has a huge disadvantage, your
after you created ``.config``. And you end up with a kernel that is
perfectly tailored to your machine. But this has a huge disadvantage, your
kernel only supports what you were using at the time. If you insert a
usb drive, it might not work because you weren't using the kernel module
usb drive it might not work because you weren't using the kernel module
for fat32 support at the time.
* ``make localyesconfig``is the same as above but everything gets compiled in
@@ -82,8 +84,8 @@ You can check out ``make help`` for more info.
Busybox Userspace
-----------------
All these tools you know and love like ``ls``, ``echo``, ``cat`` ``mv`` and
``rm`` and so on. Which are commenly referred to as the 'coreutils'. It has that
All these tools you know and love like ``ls``, ``echo``, ``cat`` ``mv``, and
``rm`` and so on are commonly referred to as the 'coreutils'. Busybox has that
and alot more, like utilities from ``util-linux`` so we can do stuff like
``mount`` and even a complete init system. Basicly most tools to expect to be
present on a Linux system only are these somewhat simplified.
@@ -95,7 +97,7 @@ to be sure we will build our own version.
Configuring busybox is very similar to configuring the kernel. It also uses a
``.config`` file and you can do ``make defconfig`` to generate one and ``make
menuconfig`` to configure it with a GUI. But we are going to use the one I
provided (which I stole from Arch Linux). You can find the config in this git
provided (which I stole from Arch Linux). You can find the config in the git
repo with the name ``bb-config``. Like the ``defconfig`` version, this has most
utilities enabled but with a few differences like statically linking all
libraries. Building busybox is again done by simply doing ``make``, but before
@@ -125,10 +127,10 @@ repo](https://github.com/sabotage-linux/kernel-headers). And set
Obviously change ``/path/to`` to the location where you put the headers repo,
can be relative from within the busybox source directory.
If you run ``make`` now, the busybox executable will be significantly smaller
because we are statically linking a much smaller libc.
If you run ``make CC=musl-gcc`` now, the busybox executable will be
significantly smaller because we are statically linking a much smaller libc.
Be advised that even though there is a libc standard, musl is not always a
Be aware that even though there is a libc standard, musl is not always a
drop-in replacement from glibc if the application you're compiling uses glibc
specific things.
@@ -136,13 +138,13 @@ Building the Disk Image
-----------------------
Installing a OS on a file instead of a real disk complicates things but this
makes development and testing it easier.
makes development and testing easier.
So let's start by allocating a new file of size 100M by doing ``fallocate -l100M
image``(some distro's don't have ``fallocate`` so you can do ``dd if=/dev/zero
of=image bs=1M count=100`` instead). And then we format it like we would format
a dis with ``fdisk image``. It automatically creates a MBR partition table for
us and we'll create just 1 partition filling the whole by pressing 'n' and
a disk with ``fdisk image``. It automatically creates a MBR partition table for
us and we'll create just one partition filling the whole image by pressing 'n' and
afterwards just use the default options for everything and keep spamming 'enter'
untill you're done. Finally press 'w' exit and to write the changes to the
image.
@@ -176,7 +178,7 @@ Syncing disks.
In order to interact with our new partition we'll create a loop device for our
image. Loop devices are block devices (like actual disks) that in our case
point to a file instread of real hardware. For this we need root so sudo up
point to a file instead of real hardware. For this we need root so sudo up
with ``sudo su`` or however you prefer to gain root privileges and afterwards
run:
```bash
@@ -195,7 +197,7 @@ putting everything in place.
```bash
$ mkdir image_root
$ mount /dev/loop0p1 image_root
$ cd image_root # it's important you do the following commands from this location
$ cd image_root # it's assumed you do the following commands from this location
$ mkdir -p usr/{sbin,bin} bin sbin boot
```
And while we're at it, we can create the rest of the file system hierarchy. This
@@ -238,7 +240,7 @@ $ install -Dm644 ../filesystem/be-latin1.bmap usr/share/keymaps/be-latin1.bmap
```
These are the basic configuration files for a UNIX system. The .script file is
required for running a dhcp client, which we'll get to later. The keymap file is
a binary keymap file for belgian azerty I use.
a binary keymap file I use for belgian azerty.
The Boot Loader
---------------
@@ -277,22 +279,22 @@ option tells our kernel which device holds the root filesystem that needs to be
mounted at '/'. The kernel needs to know this or it won't be able to boot. There
are different ways of identifying your the root filesystem. Using a UUID is a
good way because it is a unique identifier for the filesystem generated when you
do ``mkfs``. The issue with using this, is that the kernel doesn't really
do ``mkfs``. The issue with using this is that the kernel doesn't really
support it because it depends on the implementation of the filesystem. This
works on your system and I'll explain later why. But we can't use it now. We
could do ``root=/dev/sda1``, this will probably work but it has some problems.
The 'a' in 'sda' is dependant on the order the bios will load the disk and this
works on your system because it uses an initramfs. But we can't use it now. We
could do ``root=/dev/sda1``, this will probably work but it has some other problems.
The 'a' in 'sda' is can depend on the order the bios will load the disk and this
can change when you add a new disk or sometimes the order can change randomly.
Or when you use a different type of interface/disk it can be something entirely
different. So we need something more robust. I suggest we use the PARTUUID. It's
a unique id for the partition (and not the filesystem, like UUID) and this is a
a unique id for the partition (and not the filesystem like UUID) and this is a
somewhat recent addition to the kernel for msdos partition tables (it's actually
a GPT thing). We'll find the id like this:
```bash
$ fdisk -l ../image | grep "Disk identifier"
Disk identifier: 0x4f4abda5
```
Then we drop the 0x and append the partition number as two digit hexidecimal. A
Then we drop the 0x and append the partition number as two digit hexidecimal. A
MBR only has 4 partitions max so that it's hexidecimal or decimal doesn't really
matter but that's what the standard says. So the grub.cfg should look like this:
```
@@ -301,7 +303,7 @@ boot
```
The ``defconfig`` kernel is actually a debug build so it's very verbose, so to
make it shut up you can add the ``quiet`` option. This stops it from being
printed to the console, you can still read it with the ``dmesg`` utility.
printed to the console. You can still read it with the ``dmesg`` utility.
``init`` specifies the first process that will be started when the kernel is
booted. For now we just start a shell, we'll configure a real init while it's
@@ -320,7 +322,7 @@ And if everything went right you should now be dropped in a shell in our
homemade operating system.
**Side note:** When using QEMU, you don't actually need a bootloader. You can
tell QEMU to load the kernel for us.
tell QEMU to load the kernel for you.
```bash
$ qemu-system-x86_64 -enable-kvm \
-kernel bzImage \
@@ -328,8 +330,8 @@ $ qemu-system-x86_64 -enable-kvm \
image
```
Where ``bzImage`` points to the kernel we built on your system, not the image.
and ``-append`` specifies the kernel arguments(don't forget the quotes). This
Where ``bzImage`` points to the kernel you built on your system, not the image.
and ``-append`` specifies the kernel arguments (don't forget the quotes). This
could be useful when you would like to try different kernel parameters without
changing ``grub.cfg`` every time.
@@ -341,7 +343,7 @@ is not just a number and has some special implications for this process. The
most important thing to note is that when this process ends, you'll end up with
a kernel panic. PID 1 can never ever die or exit during the entire runtime of
your system. A second and less important consequence of being PID 1 is when
another process 'reparents' e.g. when a process forks to the background PID 1
another process 'reparents', e.g. when a process forks to the background, PID 1
will become the parent process.
This implies that PID 1 has a special role to fill in our operating system.
@@ -375,7 +377,7 @@ are using on your host machine with ``busybox dumpkmap > keymap.bmap`` in a
virtual console (not in X) and put this on your image instead.
First, we'll create a script that handles the initialisation of the system
itself like mounting filesystems and configuring devices, etc. I called it
itself like mounting filesystems and configuring devices, etc. You could call it
``startup`` and put it in the ``/etc/init.d`` directory (create this first).
Don't forget to ``chmod +x`` this file when you're done.
```bash
@@ -451,10 +453,10 @@ will be executed sequentially. The same goes for the ``shutdown`` entry, which
will obviously be executed at shutdown. The ``respawn`` entries will be executed
after ``sysinit`` and will be restarted when they exit. We'll put some
``getty``'s on the specified tty's. These will ask for your username and execute
``/bin/login`` which will ask for your password and stars a shell for you when
``/bin/login`` which will ask for your password and starts a shell for you when
it's correct. If you don't care for user login and passwords, you could instead
of the ``getty``'s do ``::askfirst:-/bin/sh``. ``askfirst`` does the same as
``respawn`` but asks you to press enter first. No tty is specified so it will
``respawn`` but asks you to press enter first. If no tty is specified it will
figure out what the console is. And the ``-`` infront of ``-/bin/sh`` means that
the shell is started as a login shell. ``/bin/login`` usually does this for us
but we have to specify it here. Starting the shell as a login shell means that
@@ -482,8 +484,8 @@ Service Supervision
In the last part of our OS building adventure we'll look into setting up some
services. An important thing to note is that we are using
[runit](http://smarden.org/runit/) for service supervision, which is different
quite from how the more common ``sysvinit`` does things but it'll give you a
[runit](http://smarden.org/runit/) for service supervision, which is quite different
from how the more common ``sysvinit`` does things but it'll give you a
feel for which problems it's supposed to solve and how.
A basic service consists of a directory containing a ``run`` executable, usually
@@ -558,12 +560,13 @@ exec klogd -n
$ chmod +x /etc/init.d/klogd
$ ln -s /etc/init.d/klogd /etc/rc.d
```
And now we should see kernel logs appearing in ``/var/log/kernel.log``.
Now we should see kernel logs appearing in ``/var/log/kernel.log``.
The ``sv up /etc/init.d/syslogd || exit 1`` line makes sure ``syslogd`` is
started before ``klogd``. This is how we add dependencies in ``runit``. If
``syslogd`` hasn't been started yet ``sv`` will fail and ``run`` will exit.
``runsvdir`` will attempt to restart ``klogd`` after a while and will only
succeed when ``syslogd`` has been started.
``runsv`` will attempt to restart ``klogd`` after a while and will only
succeed when ``syslogd`` has been started. Believe it or not, this is what the
runit documentation says about making dependencies.
### DHCP
@@ -581,12 +584,12 @@ And we're done. Yes it's that simple. Note that udhcpc just asks for a lease
from the DHCP server and that's it. When it has a lease it executes
``/usr/share/udhcpc/default.script`` to configure the system. We already copied
this script to this location. This script is included with the busybox source.
These script usually use ``ip``, ``route``, and write to ``/etc/resolv.conf``.
These scripts usually use ``ip``, ``route``, and write to ``/etc/resolv.conf``.
If you would like a static ip, you'll have to write a script that does these
things.
Epilogue
--------
That's it! We're done for now. I hope you learned something useful, I certainly
did while making this.
That's it! We're done for now. Thanks for reading. I hope you learned something
useful, I certainly did while making this.