LFS#01 Booting Linux kernel 1.2.13
5/26/2025
Welcome to the first episode of Linux From Scratch.
We will dive into Linux internals using a Linux kernel from 1995 !
This is the “textual” version of a youtube playlist [FR].
By the way I encourage you to check his videos and follow him on X/Bluesky.
Usually, you boot an OS from an USB key, a CD/.iso image for the installation, or directly from a HDD/SSD for your day-to-day usage.
We will create from scratch our boot medium, a simple file.
Disk image creation
$ dd if=/dev/zero of=disk.img bs=1M count=2
Here we create a file called disk.img from the special /dev/zero device which outputs zeros.
bs is for block size, of 1M, and we create 2 blocks, for a total of 2M.
Next we have to create a partition, and a partition table on the disk.
The partition table is written in the MBR (Master Boot Record).
Let’s create a primary partition
$ fdisk disk.img
Welcome to fdisk (util-linux 2.41).
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 (MBR) disklabel with disk identifier 0x95b49196.
Command (m for help): n
Partition type
p primary (0 primary, 0 extended, 4 free)
e extended (container for logical partitions)
Select (default p): p
Partition number (1-4, default 1): 1
First sector (1-4095, default 1): 1
Last sector, +/-sectors or +/-size{K,M,G,T,P} (1-4095, default 4095):
Created a new partition 1 of type 'Linux' and of size 2 MiB.
Command (m for help): a
Selected partition 1
The bootable flag on partition 1 is enabled now.
Command (m for help): w
The partition table has been altered.
What we created here is a new bootable primary partition of the size of the disk.
For now the MBR sector only contains the partition table and some metadata.
Inspecting our disk with xxd
If we inspect the last bytes of the first 512 bytes sector, that’s what we have:
$ xxd -l 512 disk.img | tail -5
000001b0: 0000 0000 0000 0000 9691 b495 0000 8000 ................
000001c0: 0200 8341 0100 0100 0000 ff0f 0000 0000 ...A............
000001d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000001e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000001f0: 0000 0000 0000 0000 0000 0000 0000 55aa ..............U.
At offset 0x01b8 we find the disk signature (4 bytes) 0x9691b595.
At offset 0x01be, we find the 1st partition entry (16 bytes) starting with 0x80…
0x80 means that the partition is active (bootable).
The last 2 bytes: 0x55aa represent the MBR signature.
Now we have to write the bootloader, you probably know grub, maybe lilo, but here we will use syslinux.
It’s the piece of software that is called by the BIOS when you boot your computer.
It’s job is to find the VBR (more later) to continue the boot process.
What we need here is the file mbr.bin.
Next, we copy the boot utility into our disk.
$ dd if=mbr.bin of=disk.img bs=440 count=1 conv=notrunc
440 bytes, placed at the beginning of the disk, notrunc to avoid truncating to 440 bytes.
Let’s examine what we have now.
$ xxd -l 512 disk.img
00000000: 33c0 fa8e d88e d0bc 007c 89e6 0657 8ec0 3........|...W..
00000010: fbfc bf00 06b9 0001 f3a5 ea1f 0600 0052 ...............R
00000020: 52b4 41bb aa55 31c9 30f6 f9cd 1372 1381 R.A..U1.0....r..
00000030: fb55 aa75 0dd1 e973 0966 c706 8d06 b442 .U.u...s.f.....B
00000040: eb15 5ab4 08cd 1383 e13f 510f b6c6 40f7 ..Z......?Q...@.
00000050: e152 5066 31c0 6699 e866 00e8 3501 4d69 .RPf1.f..f..5.Mi
00000060: 7373 696e 6720 6f70 6572 6174 696e 6720 ssing operating
00000070: 7379 7374 656d 2e0d 0a66 6066 31d2 bb00 system...f`f1...
00000080: 7c66 5266 5006 536a 016a 1089 e666 f736 |fRfP.Sj.j...f.6
00000090: f47b c0e4 0688 e188 c592 f636 f87b 88c6 .{.........6.{..
000000a0: 08e1 41b8 0102 8a16 fa7b cd13 8d64 1066 ..A......{...d.f
000000b0: 61c3 e8c4 ffbe be7d bfbe 07b9 2000 f3a5 a......}.... ...
000000c0: c366 6089 e5bb be07 b904 0031 c053 51f6 .f`........1.SQ.
000000d0: 0780 7403 4089 de83 c310 e2f3 4874 5b79 [email protected][y
000000e0: 3959 5b8a 4704 3c0f 7406 247f 3c05 7522 9Y[.G.<.t.$.<.u"
000000f0: 668b 4708 668b 5614 6601 d066 21d2 7503 f.G.f.V.f..f!.u.
00000100: 6689 c2e8 acff 7203 e8b6 ff66 8b46 1ce8 f.....r....f.F..
00000110: a0ff 83c3 10e2 cc66 61c3 e876 004d 756c .......fa..v.Mul
00000120: 7469 706c 6520 6163 7469 7665 2070 6172 tiple active par
00000130: 7469 7469 6f6e 732e 0d0a 668b 4408 6603 titions...f.D.f.
00000140: 461c 6689 4408 e830 ff72 2766 813e 007c F.f.D..0.r'f.>.|
00000150: 5846 5342 7509 6683 c004 e81c ff72 1381 XFSBu.f......r..
00000160: 3efe 7d55 aa0f 85f2 febc fa7b 5a5f 07fa >.}U.......{Z_..
00000170: ffe4 e81e 004f 7065 7261 7469 6e67 2073 .....Operating s
00000180: 7973 7465 6d20 6c6f 6164 2065 7272 6f72 ystem load error
00000190: 2e0d 0a5e acb4 0e8a 3e62 04b3 07cd 103c ...^....>b.....<
000001a0: 0a75 f1cd 18f4 ebfd 0000 0000 0000 0000 .u..............
000001b0: 0000 0000 0000 0000 9691 b495 0000 8000 ................
000001c0: 0200 8341 0100 0100 0000 ff0f 0000 0000 ...A............
000001d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000001e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000001f0: 0000 0000 0000 0000 0000 0000 0000 55aa ..............U.
Now we have written our MBR, with the ASCII representation, you can see messages that syslinux can output when running.
Let’s try to boot
To boot from our disk file, we will use QEMU, a virtualization/emulation tool.
Note that I’m using qemu v10, your actual command line may vary.
$ qemu-system-i386 -nographic -enable-kvm -m 4 -hda disk.img
-enable-kvm: enable hardware acceleration virtualization (VT…)
-m 4: 4MB of memory (yes only 4MB)
-hda: our disk image
HDA was used for IDE disks while SDA was used for SCSI disks, nowadays you will usually find SDA mentionned.
Here’s the output of our command:
WARNING: Image format was not specified for 'disk.img' 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.
SeaBIOS (version Arch Linux 1.16.3-1-1)
iPXE (http://ipxe.org) 00:03.0 C900 PCI2.10 PnP PMM+00393330+002F3330 C900
Booting from Hard Disk...
Missing operating system.
Booting from Floppy...
Boot failed: could not read the boot disk
Booting from DVD/CD...
Boot failed: Could not read from CDROM (code 0003)
Booting from ROM...
iPXE (PCI 00:03.0) starting execution...ok
iPXE initialising devices...ok
iPXE 1.20.1+ (g4bd0) -- Open Source Network Boot Firmware -- http://ipxe.og
Features: DNS HTTP iSCSI TFTP AoE ELF MBOOT PXE bzImage Menu PXEXT
net0: 52:54:00:12:34:56 using 82540em on 0000:00:03.0 (open)
[Link:up, TX:0 TXE:0 RX:0 RXE:0]
Configuring (net0 52:54:00:12:34:56)...... ok
net0: 10.0.2.15/255.255.255.0 gw 10.0.2.2
Nothing to boot: No such file or directory (http://ipxe.org/2d03e13b)
No more network devices
No bootable device.
Syslinux could not find the OS to boot: Missing operating system
And that’s because we did not put any !
We need a filesystem for our system partition
When writing files on a disk, you need a way to organize those files, determine where each part of the file is stored, their name, rights and other metadata.
That’s where filesystems shine.
There are many FS out there but remember, we want to boot a system from 1995 !
We will use mk2efs to create an ext2 filesystem.
Be careful with the options here, as the kernel won’t boot if it detects an invalid revision for example.
$ mke2fs -E revision=0,offset=512 -b 4k disk.img
mke2fs 1.47.2 (1-Jan-2025)
Warning: offset specified without an explicit file system size.
Creating a file system with 512 blocks but this might
not be what you want.
Discarding device blocks: done
Creating filesystem with 512 4k blocks and 256 inodes
Allocating group tables: done
Writing inode tables: done
Writing superblocks and filesystem accounting information: done
revision=0 : we want to use the first release of mke2fs
offset=512 : we don’t want to erase our MBR
b 4k : block size of 4k bytes
Mounting the partition
$ mkdir mnt
$ sudo mount -o loop,offset=512 disk.img mnt
$ ls mnt/
lost+found
loop : special option because we are mounting from a file, so behind the scenes it creates a loop device
offset=512 : remember that our actual partition, with our newly created FS is after the MBR, so at offset 512 !
Bytes 0 to 511 correspond to the first sector = MBR
lost+found : contains inodes which have been corrupted on a crash for example
Setup VBR with extlinux
The MBR job’s is to find and run the VBR (Volume Boot Record), which is a more complex program that our 440 bytes MBR.
To setup the VBR, we will use the extlinux utility, provided with syslinux.
$ sudo extlinux --install mnt
mnt is device /dev/loop0
Warning: unable to obtain device geometry (defaulting to 64 heads, 32 sectors)
(on hard disks, this is usually harmless.)
$ ls mnt/
ldlinux.c32 ldlinux.sys lost+found
We can see 2 new files:
ldlinux.sys: the actual bootloader on the partition ldlinux.c32: the visual part of the bootloader
We can now unmount our system partition
$ sudo umount mnt
Let’s boot again with our installed bootloader
$ qemu-system-i386 -nographic -enable-kvm -m 4 -hda disk.img
WARNING: Image format was not specified for 'disk.img' 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.
SeaBIOS (version Arch Linux 1.16.3-1-1)
iPXE (http://ipxe.org) 00:03.0 C900 PCI2.10 PnP PMM+00393330+002F3330 C900
Booting from Hard Disk...
SYSLINUX 6.04 EDD 6.04-pre3-3-g05ac953c* Copyright (C) 1994-2015 H. Peter
et al
WARNING: No configuration file found
boot:
We have a boot invite, but there is no kernel yet so we cannot do much.
We have to put a kernel on the disk and configure syslinux to use it !
Syslinux config file
This is the content of our syslinux config file:
DEFAULT linux
TIMEOUT 50
PROMPT 1
LABEL linux
KERNEL vmlinuz
APPEND root=/dev/hda1
This means:
- Boot linux as the default target
- Shows the prompt
- Use the file vmlinuz as the kernel
- Setup the root filesystem to /dev/hda1
The next step is to copy this config file with the linux kernel (vmlinuz) on our disk.
To do this, mount again the partition on mnt then simply cp the files.
sudo cp syslinux.cfg vmlinuz mnt/
$ ls mnt
ldlinux.c32 ldlinux.sys lost+found syslinux.cfg vmlinuz
$ file vmlinuz
vmlinuz: DOS/MBR boot sectorS
On interesting thing to note here is the kernel file is actually bootable and seen as a MBR boot sector.
With qemu you could boot directly with the kernel from a floppy disk or with the -kernel option.
Trying to boot our kernel (again)
$ qemu-system-i386 -display curses -enable-kvm -m 4 -hda disk.img
The output should end with something like:
Linux version 1.2.13 (root@bigkitty) (gcc version 2.7.0) #1 Wed Aug 23 01:06:43
CDT 1995
Partition check:
hda: multiple mode turned off
hda: hda1
VFS: Mounted root (ext2 filesystem).
-display curses, is used here because the 1.2.13 kernel does not support serial output, so we would not be able to see what the kernel prints with the -nographic option.
You can also try with -display gtk.
What you see here is the kernel init process, but of course there are missing parts, we cannot login, we don’t have any prompt.
Let’s summarize what we have done so far:
- Create a disk image, a simple file as a base to boot our system
- Create a bootable partition, a partition table in the MBR sector
- Write the MBR program (syslinux)
- Create a filesystem on the partition
- Write the VBR (syslinux)
- Add syslinux config and the kernel image
Boot progress: kernel mounted the root FS
In the next episode, we will try to get further in the boot process and hopefully interact with the system !