Coder Social home page Coder Social logo

build-linux's Introduction

Build yourself a Linux

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 a lot. Too much time has been 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.

The Linux Kernel

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. There are multiple versions to choose from, choosing one is usually a tradeoff between stability and wanting newer features. If you look at the Releases tab, you can see how long each version will be supported and keeps receiving updates. So you can usually just apply the update or use the updated version without changing anything else or having something break.

So just pick a version and download the tar.xz file and extract it with tar -xf linux-version.tar.xz. To build the kernel we obviously need a compiler and some build tools. Installing build-essential on Ubuntu (or base-devel on Arch Linux) will almost give you everything you need. You'll also need to install bc for some reason.

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) 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 separate kernel module. This is a part of the kernel that will be put in a separate file and can 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. 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. When it's done, it should tell you where your finished kernel is placed. This is usually arch/x86/boot/bzImage in the linux source directory for Intel computers.

Other useful/interesting ways to configure the kernel are:

  • make localmodconfig will look at the modules that are currently loaded in the running kernel and change the config so that only those are enabled as module. Useful for when you only want to build the things you need without having to figure out what that is. So you can install something like Ubuntu on the machine first, copy the config to your build 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 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 for fat32 support at the time.

  • make localyesconfigis the same as above but everything gets compiled in the kernel instead as a kernel module.

  • make allmodconfig generates a new config where all options are enabled and as much as possible as module.

  • make allyesconfigis same as above but with everything compiled in the kernel.

  • make randconfig generates a random config...

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 are commonly referred to as the 'coreutils'. Busybox has that and a lot more, like utilities from util-linux so we can do stuff like mount and even a complete init system. Basically, it contains most tools you expect to be present on a Linux system, except they are a slightly simplified version of the regular ones.

You can get the source from busybox.net. They also provide prebuilt binaries which will do just fine for most use-cases. But just 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 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 we do this, let's look into musl first.

The C Standard Library

The C standard library is more important to the operating system than you might think. It provides some useful functions and an interface to the kernel. But it also handles DNS requests and provides a dynamic linker. We don't really have to pay attention to any of this, we can just statically link the one we are using right now which is probably 'glibc'. This means the following part is optional. But I thought this would make it more interesting and it also makes us able to build smaller binaries.

That's because we are going to use musl, which is a lightweight libc implementation. You can get it by installing musl-tools on Ubuntu or simply musl on Arch Linux. Now we can link binaries to musl instead of glibc by using musl-gcc instead of gcc.

Before we can build busybox with musl, we need sanitized kernel headers for use with musl. You get that from this github repo. And set CONFIG_EXTRA_CFLAGS in your busybox config to CONFIG_EXTRA_CFLAGS="-I/path/to/kernel-headers/x86_64/include" to use them. Obviously change /path/to to the location where you put the headers repo, which can be relative from within the busybox source directory.

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 aware that even though there is a libc standard, musl is not always a drop-in replacement for glibc if the application you're compiling uses glibc specific things.

Building the Disk Image

Installing an OS on a file instead of a real disk complicates things but this makes development and testing easier.

So let's start by allocating a new file of size 100M by doing fallocate -l100M image(some distros 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 disk with fdisk image. It automatically creates an 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' until you're done. Finally press 'w' exit and to write the changes to the image.

$ fdisk image

Welcome to fdisk (util-linux 2.29.2).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.

Device does not contain a recognized partition table.
Created a new DOS disklabel with disk identifier 0x319d111f.

Command (m for help): n
Partition type
   p   primary (0 primary, 0 extended, 4 free)
   e   extended (container for logical partitions)
Select (default p):

Using default response p.
Partition number (1-4, default 1):
First sector (2048-204799, default 2048):
Last sector, +sectors or +size{K,M,G,T,P} (2048-204799, default 204799):

Created a new partition 1 of type 'Linux' and of size 99 MiB.

Command (m for help): w
The partition table has been altered.
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 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:

$ losetup -P -f --show image
/dev/loop0

The loop device probably ends with a 0 but it could be different in your case. The -P makes sure the partition also gets a loop device, /dev/loop0p1 in my case. Let's make a filesystem on it.

$ mkfs.ext4 /dev/loop0p1

If you want to use something other than ext4, be sure to enable it when configuring your kernel. Now that we have done that, we can mount it and start putting everything in place.

$ mkdir image_root
$ mount /dev/loop0p1 image_root
$ 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 is actually standardized and applications often assume this is the way you're doing it, but you can often do what you want. You can find more info here.

$ mkdir -p {dev,etc,home,lib}
$ mkdir -p {mnt,opt,proc,srv,sys}
$ mkdir -p var/{lib,lock,log,run,spool}
$ install -d -m 0750 root
$ install -d -m 1777 tmp
$ mkdir -p usr/{include,lib,share,src}

We'll copy our binaries over.

$ cp /path/to/busybox usr/bin/busybox
$ cp /path/to/bzImage boot/bzImage

You can call every busybox utility by supplying the utility as an argument, like so: busybox ls --help. But busybox also detects by what name it is called and then executes that utility. So you can put symlinks for each utility and busybox can figure out which utility you want by the symlink's name.

for util in $(./usr/bin/busybox --list-full); do
  ln -s /usr/bin/busybox $util
done

These symlinks might be incorrect from outside the system because of the absolute path, but they work just fine from within the booted system.

Lastly, we'll copy some files from ../filesystem to the image that will be of some use to us later.

$ cp ../filesystem/{passwd,shadow,group,issue,profile,locale.sh,hosts,fstab} etc
$ install -Dm755 ../filesystem/simple.script usr/share/udhcpc/default.script
# optional
$ 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 I use for belgian azerty.

The Boot Loader

The next step is to install the bootloader - the program that loads our kernel in memory and starts it. For this we use GRUB, one of the most widely used bootloaders. It has a ton of features but we are going to keep it very simple. Installing it is very simple, we just do this:

grub-install --modules=part_msdos \ 
             --target=i386-pc \
             --boot-directory="$PWD/boot" \
             /dev/loop0

Ubuntu users might need to install grub-pc-bin first if they are on an EFI system.

The --target=i386-pc tells grub to use the simple msdos MBR bootloader. This is often the default, but this can vary from machine to machine so you better specify it here. The --boot-directory options tells grub to install the grub files in /boot inside the image instead of the /boot of your current system. --modules=part_msdos is a workaround for a bug in Ubuntu's grub. When you use losetup -P, grub doesn't detect the root device correctly and doesn't think it needs to support msdos partition tables and won't be able to find the root partition.

Now we just have to configure grub and then our system should be able to boot. This basically means telling grub how to load the kernel. This config is located at boot/grub/grub.cfg (some distro's use /boot/grub2). This file needs to be created first, but before we do that, we need to figure something out first. If you look at /proc/cmdline on your own machine you might see something like this:

$ cat /proc/cmdline
BOOT_IMAGE=/boot/vmlinuz-4.4.0-71-generic root=UUID=83066fa6-cf94-4de3-9803-ace841e5066c ro

These are the arguments passed to your kernel when it's booted. The 'root' 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 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 support it because it depends on the implementation of the filesystem. 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' depends on the order the bios will load the disk and this can change when you add a new disk, or for a variety of other reasons. 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 somewhat recent addition to the kernel for msdos partition tables (it's actually a GPT thing). We'll find the id like this:

$ fdisk -l ../image | grep "Disk identifier"
Disk identifier: 0x4f4abda5

Then we drop the 0x and append the partition number as two digit hexidecimal. An 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:

linux /boot/bzImage quiet init=/bin/sh root=PARTUUID=4f4abda5-01
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.

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 running.

So now we should be able to boot the system. You can umount the image, exit root and start a VM to test it out. The simplest way of doing this is using QEMU. The Ubuntu package is qemu-kvm, and just qemu on Arch Linux.

$ cd ../
$ umount image_root
$ exit # we shouldn't need root anymore
$ qemu-system-x86_64 -enable-kvm image

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 you.

$ qemu-system-x86_64 -enable-kvm \
                     -kernel bzImage \
                     -append "quiet init=/bin/sh root=/dev/sda1" \
                     image

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.

PID 1: /sbin/init

The first process started by the kernel (now /bin/sh) has process id 1. This 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 will become the parent process.

This implies that PID 1 has a special role to fill in our operating system. Namely that of starting everything, keeping everything running, and shutting everything down because it's the first and last process to live.

This also makes this init process very suitable to start and manage services as is the case with the very common sysvinit and the more modern systemd. But this isn't strictly necessary and some other process can carry the burden of service supervision, which is the case with the runit-like init that is included with busybox.

Unless you passed the rw kernel parameter the root filesystem is mounted as read-only. So before we can make changes to our running system we have to remount it as read-write first. Before we can do any mounting at all we have to mount the proc pseudo filesystem that serves as an interface to kernel.

$ mount -t proc proc /proc
$ mount / -o remount,rw

busybox provides only two ways of editing files: vi and ed. If you are not confortable using either of those you could always shutdown the VM, mount the image again, and use your favorite text editor on your host machine.

If you don't use a qwerty keyboard, you might have noticed that the VM uses a qwerty layout as this is the default. You might want to change it to azerty with loadkmap < /usr/share/keymaps/be-latin1.bmap. You can dump the layout you 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). 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.

#!/bin/sh
# /etc/init.d/startup

# mount the special pseudo filesytems /proc and /sys
mount -t proc proc /proc -o nosuid,noexec,nodev
mount -t sysfs sys /sys -o nosuid,noexec,nodev
# /dev isn't required if we boot without initramfs because the kernel
# will have done this for us but it doesn't hurt
mount -t devtmpfs dev /dev -o mode=0755,nosuid
mkdir -p /dev/pts /dev/shm
# /dev/pts contains pseudo-terminals, gid 5 should be the
# tty user group
mount -t devpts devpts /dev/pts -o mode=0620,gid=5,nosuid,noexec
# /run contains runtime files like pid files and domain sockets
# they don't need to be stored on the disk, we'll store them in RAM
mount -t tmpfs run /run -o mode=0755,nosuid,nodev
mount -t tmpfs shm /dev/shm -o mode=1777,nosuid,nodev
# the nosuid,noexec,nodev options are for security reasons and are not
# strictly necessary, you can read about them in the 'mount'
# man page

# the kernel does not read /etc/hostname on it's own
# you need to write it in /proc/sys/kernel/hostname to set it
# don't forget to create this file if you want to give your system a name
if [[ -f /etc/hostname ]]; then
  cat /etc/hostname > /proc/sys/kernel/hostname
fi

# mdev is a mini-udev implementation that
# populates /dev with devices by scanning /sys
# see the util-linux/mdev.c file in the busybox source
# for more information
mdev -s
echo /sbin/mdev > /proc/sys/kernel/hotplug

# the "localhost" loopback network interface is
# down at boot, we have to set it 'up' or we won't be able to
# make local network connections
ip link set up dev lo

# you could add the following to change the keyboard layout at boot
loadkmap < /usr/share/keymaps/be-latin1.bmap

# mounts all filesystems in /etc/fstab
mount -a
# make the root writable if this hasn't been done already
mount -o remount,rw /
# end of /etc/init.d/startup

The next file is the init configuration /etc/inittab. The syntax of this file is very similar to that of sysvinit's inittab but has several differences. For more information you can look at the examples/inittab file in the busybox source.

# /etc/inittab
::sysinit:/bin/echo STARTING SYSTEM
::sysinit:/etc/init.d/startup
tty1::respawn:/sbin/getty 38400 tty1
tty2::respawn:/sbin/getty 38400 tty2
tty3::respawn:/sbin/getty 38400 tty3
::ctrlaltdel:/bin/umount -a -r
::shutdown:/bin/echo SHUTTING DOWN
::shutdown:/bin/umount -a -r
# end of /etc/inittab

The sysinit entry is the first command init will execute. We'll put our startup script here. You can specify multiple entries of this kind and they 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 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. If no tty is specified it will figure out what the console is. 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 it configures certain things it otherwise assumes already to be configured. E.g. it sources /etc/profile.

We can now start our system with init. You can remove the init=/bin/sh entry in /boot/grub/grub.cfg because it defaults to /sbin/init. If you reboot the system you should see a login screen. But if you run reboot, you'll notice it won't do anything. This happens because normally reboot tells the running init to reboot. You know - the init that isn't running right now. So we have two options, we could run reboot -f which skips the init, or we could do this:

$ exec init

Because the shell we are currently using is PID 1 and you could just replace the shell process with init and our system should be properly booted now presenting you a login prompt.

The root password should be empty so it should only ask for a username.

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 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 a script. This run script usually starts the daemon and doesn't exit until the daemon does. If run exits runit will think the service itself has stopped and if it wasn't supposed to stop, runit will try to restart it. So be careful with forking daemons. Starting the service is done with runsv. This is the process that actually monitors the service and restarts it if necessary. Usually you won't run it manually but doing so is useful for testing services.

The first service we are going to create is a logging service that collects messages from other processes and stores them in files.

$ mkdir -p /etc/init.d/syslogd
$ vi /etc/init.d/syslogd/run
$ cat /etc/init.d/syslog/run
#!/bin/sh
exec syslogd -n
$ chmod +x /etc/init.d/syslog/run
$ runsv /etc/init.d/syslogd & # asynchronous
$ sv status /etc/init.d/syslogd
run: /etc/init.d/syslogd: (pid 991) 1170s

It's that simple, but we have to make sure syslogd doesn't fork or else runsv will keep trying to start it even though it is already running. That's what the -n option is for. The sv command can be used to control the service.

To make sure that our new service is started at boot we could create a new inittab entry for it but this isn't very flexible. A better solution is to use runsvdir. This runs runsv for every service in a directory. So running runsvdir /etc/init.d would do the trick but this way we can't disable services at boot. To solve this issue we'll create a separate directory and symlink the enabled services in there.

$ mkdir -p /etc/rc.d
$ ln -s /etc/init.d/syslogd /etc/rc.d
$ runsvdir /etc/rc.d & # asynchronous

If we add ::respawn:/usr/bin/runsvdir /etc/rc.d to /etc/inittab all the services symlinked in /etc/rc.d will be started at boot. Enabling and disabling a service now consists of creating and removing a symlink in /etc/rc.d. Note that runsvdir monitors this directory and starts the service when the symlink appears and not just at boot.

Syslog

syslogd implements the well known syslog protocol for logging. This means that it creates a UNIX domain socket at /dev/log for daemons to connect and send their logs to. Usually it puts all of the collected logs in /var/log/messages unless told otherwise. You can specify filters in /etc/syslog.conf to put certain logs in different files.

$ vi /etc/syslog.conf
$ cat /etc/syslog.conf
kern.* /var/log/kernel.log
$ sv down /etc/init.d/syslogd # restart
$ sv up /etc/init.d/syslogd

This will put everything the kernel has to say in a separate log file /var/log/kernel.log. But syslogd doesn't read the kernel logs like rsyslog does. We need a different service for that.

$ mkdir -p /etc/init.d/klogd
$ vi /etc/init.d/klogd/run
$ cat /etc/init.d/klogd/run
#!/bin/sh
sv up /etc/init.d/syslogd || exit 1
exec klogd -n
$ chmod +x /etc/init.d/klogd/run
$ ln -s /etc/init.d/klogd /etc/rc.d

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

The very last thing we will do is provide our system with a network connection.

$ mkdir -p /etc/init.d/udhcpc
$ vi /etc/init.d/udhcpc/run
$ cat /etc/init.d/udhcpc/run
#!/bin/sh
exec udhcpc -f -S
$ chmod +x /etc/init.d/udhcpc/run
$ ln -s /etc/init.d/udhcpc /etc/rc.d

Now 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 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. Thanks for reading. I hope you learned something useful. I certainly did while making this.

build-linux's People

Contributors

agreenbhm avatar gutoandreollo avatar iaddict avatar michielderhaeg avatar naereen avatar stevesbrain avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

build-linux's Issues

What do i need for this?

What do i need?
I read the readme and it has stuff like bin/sh and sbin/init, i could not find anything on sbin/init so i just made my own and that didn't work, messing with the grub.cfg init parameter didn't help either
Requested init /sbin/init failed (error -13)

error: linux/kd.h: No such file or directory when building on non-arch platforms

I received the following:

console-tools/kbd_mode.c:33:22: error: linux/kd.h: No such file or directory
console-tools/kbd_mode.c: In function ‘kbd_mode_main’:
console-tools/kbd_mode.c:56: error: ‘KDGKBMODE’ undeclared (first use in this function)
console-tools/kbd_mode.c:56: error: (Each undeclared identifier is reported only once
console-tools/kbd_mode.c:56: error: for each function it appears in.)
console-tools/kbd_mode.c:57: error: ‘K_RAW’ undeclared (first use in this function)
console-tools/kbd_mode.c:59: error: ‘K_XLATE’ undeclared (first use in this function)
console-tools/kbd_mode.c:61: error: ‘K_MEDIUMRAW’ undeclared (first use in this function)
console-tools/kbd_mode.c:63: error: ‘K_UNICODE’ undeclared (first use in this function)
console-tools/kbd_mode.c:70: error: ‘KDSKBMODE’ undeclared (first use in this function)
make[2]: *** [console-tools/kbd_mode.o] Error 1
make[1]: *** [console-tools] Error 2

Per this github thread, you need to init the submodule.

#13

cd build-linux
git submodule update --init
make

Posting here in the event this helps anyone else. This appears to happen on non-arch versions of Linux (at least, centos and ubuntu).

Busybox issue

While running make all (specifically, make busybox) I ran into this issue:

console-tools/kbd_mode.c:33:22: fatal error: linux/kd.h: No such file or directory
 #include <linux/kd.h>
                      ^
compilation terminated.
scripts/Makefile.build:197: recipe for target 'console-tools/kbd_mode.o' failed
make[2]: *** [console-tools/kbd_mode.o] Error 1
Makefile:742: recipe for target 'console-tools' failed
make[1]: *** [console-tools] Error 2
make[1]: Leaving directory '/home/vnz/work/build-linux/busybox-1.26.2'
Makefile:26: recipe for target 'busybox' failed
make: *** [busybox] Error 2
[1]    27046 exit 2     make busybox

Any idea on how should this be resolved?

bzImage?

Hello, I am very new to building Linux. Firstly thank you for this nice article.
So I tried to follow your explanation but I was suck here:

We'll copy our binaries over.

$ cp /path/to/busybox usr/bin/busybox
$ cp /path/to/bzImage boot/bzImage

bzImage appeared all of the sudden, without being mentioned until now. Newcomers to Linux like me may have no idea what does bzImage means and where to find the bzImage (although when I figured it out by looking through your Makefile).

BusyBox provides a --install option to symlink itself

The busybox binary can be configured to accept a --install option to create links to itself for all the various commands in /bin/, /sbin, /usr/bin, and /usr/sbin. To have busybox install itself to these directories, using symlinks (assuming BusyBox is configued with --install), chroot into the installation image, then:

/path/to/busybox --install -s

Will create symlinks (without -s it will create hardlinks) for all configured applets.

In my opinion, this is better then iterating over --list output with a script.

I got a slight problem while building on debian aarch64 (arm64)

The error is this:

/usr/bin/ld: scripts/dtc/dtc-parser.tab.o:(.bss+0x8): multiple definition of `yylloc'; scripts/dtc/dtc-lexer.lex.o:(.bss+0x38): first defined here
collect2: error: ld returned 1 exit status
make[3]: *** [scripts/Makefile.host:100: scripts/dtc/dtc] Error 1
make[2]: *** [scripts/Makefile.build:403: scripts/dtc] Error 2
make[2]: *** Waiting for unfinished jobs....
HOSTLD scripts/mod/modpost
make[1]: *** [Makefile:556: scripts] Error 2
make[1]: Leaving directory '/home/debian/belgrade/build-linux/linux-4.4.52'
make: *** [Makefile:16: bzImage] Error 2

QEMU Boot Loop & Freezes

Hello,

So I have followed all of the steps on the README file, and I successfully compiled the kernel. But, when I tried to boot the kernel on QEMU, it will just show:

Decompressing Linux, Parsing ELFOr something similar to that.

I also tried using the command that is provided in the README, and it just say

Booting from ROM and froze.

I am using Debian 10, the latest one.

Is there anyway to fix this? Thank you!

fs.tar missing?

gen-image.sh refers to fs.tar, am assuming its the filesystem folder?

How would I go about rebuilding?

The title says it all. How would I rebuild?

I tried doing make again but I get an error saying:

make -C filesystem . make[1]: Entering directory '/home/samhep0803/build-linux/filesystem' . Makefile:1: *** missing separator. Stop. . make[1]: Leaving directory '/home/samhep0803/build-linux/filesystem' . Makefile:32: recipe for target 'fs.tar' failed . make: *** [fs.tar] Error 2

I figured it was because I already built but surely I don't have to delete my whole folder and clone this repo again.

Hopefully, you could help.

Thanks, Sam.

Kernel Panic on real hardware. (not a dupe)

I keep getting the same error as with the other issue, but the only difference is that I'm trying it on real hardware.

To be specific a PC with windows installed on it. However, I'm having trouble getting it to even boot as I'm getting a kernel panic. Also, I'm using the Makefile provided.

To be honest, I have no clue what to do.

Thanks, Sam.

Problem booting from qemu

$ sudo qemu-system-x86_64 -enable-kvm image

WARNING: Image format was not specified for 'image' and probing guessed raw.
         Automatically detecting the format is dangerous for raw images, write operations on block 0 will be restricted.
         Specify the 'raw' format explicitly to remove the restrictions.
Could not initialize SDL(No available video device) - exiting

Kernel Panic

Hi
Congratulations for the tuto.
I get the message druring the boot.
kernel panic - not syncing vfs unable to mount root fs on unknown-block (0 0)

I tried change grub.cfg to root=/dev/sda{1,2,3,4} and did not work.

My disk identifier is 0x1593f630

$ cat image-root/boot/grub/grub.cfg
linux /boot/bzImage quiet init=/bin/sh root=PARTUUID=1593f630-01
boot

I use Debian 8.

tks

build busybox with -static flag

I had issues running busybox as the first process. Due to a cryptic (to me) kernel error, it took me a while to realize that busybox was compiled dynamically.
Adding CONFIG_EXTRA_CFLAGS="-static" to the busybox .config builds a static binary and allows the kernel to run it.

Rethinking boot process and initramfs

Here are my thoughts and experiments about the subject:

Initramfs hides a lot and brings unnecesary complexity eventhough
it is unneeded in 99% of the cases. So starting with an extracted
initramfs I've ended up with the attached init on /boot.

Machine boots from /boot straight and pivots to /. No initramfs
employed. Contents of /boot is always accessible and editable.
I can provide more details if there is further interest.

/boot only has the following (About 6MB in total):

  • syslinux
  • Linux kernel: Minimal one with necessary modules built-in.
    (Just enough to mount and pivot to /. devtmpfs included and auto mounted)
  • Busbox
  • Fonts and keymaps
    (Needed for proper luks password entry on non-US setups)
  • init script. See below.

Suggested common disk layout:
/dev/sda1 -> /boot - 128M btrfs created with -M mixed mode
/dev/sda2 -> / compressed btrfs on luks. (or f2fs for flash/SSD)

#!/bin/ash
setfont  /boot/lat5-16.psfu.gz  -C /dev/console &
loadkmap < /boot/trq.bmap &

# backgrounding with '&' is for utilizing parallel execution and
# cutting some boot seconds. You can also use it elsewhere.
# But NOT below! These lines should execute sequentially.
# Use '&' wisely.

mount  -t proc  proc  /proc  -o nosuid,noatime,nodev,noexec
# add crypt_setup lines here.
mount  /dev/sda2  /mnt
cd  /mnt
mount  --move  /dev  ./dev
mount  --move  /proc  ./proc
pivot_root   .  boot
exec chroot  .  /bin/init

How to add more software to my system? How to boot on hardware?

The tutorial is really nice and kept quite simple (I don't know if it is too simple for beginners starting from Ubuntu). It definitely is a good guide for people who already know a thing or two about Linux (I might be able to use it, however I run Gentoo on my Desktop ;P), so thank you a lot for putting your time into writing it.

There is, however, one thing which I am missing: How do I boot my system and then install new software? You are missing that part completely. You should add a little guide detailing how to add gcc or Clang and git to the system, so that source code can be fetched and compiled.

Also a little guide on how to prepare that system for installation on hardware would be nice ;)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.