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:
97
README.md
97
README.md
@@ -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.
|
||||
|
Reference in New Issue
Block a user