Source code for the kernel development workshops at the ETU
The program of workshops can be found in the following files:
All work is done in the docker container. The image is very simple, it just installs necessary utilities to build kernel and make rootfs.
Everything can be done in rootless mode.
Makefile is the entrypoint for almost all commands in this
project. The Makefile is a bit overcomplicate ;). The Makefile is assumed
to be used inside the container. For host use, change the BUILD_DIR
and WORK_DIR variables. You can also configure versions and other parameters
using the corresponding variables
The most important targets are:
kernel-configuredownloads, unzips and configures linux kernel. Config can be specified withKERNEL_CFGvariablekernel-build[re]builds linux kernelkernel-compile-commandscreatescompile-commands.jsonforclangdramfs-busyboxbuilds initrd ramfs based onbusyboxanddropbearrootfs-alpinebuilds ext4 rootfs based onalpine. The installed packages can be configured using theROOTFS_ALPINE_PACKAGESvariablemodule-*[re]builds out-of-tree kernel modules, copys them into the VM and inserts themqemu-runruns qemu with specified rootfsqemu-killkills qemu processqemu-sshconnects to VM with ssh
It could be just another Makefile, but it is justfile
for the just.
It is the out-of-container entrypoint to execute commands in the project.
It's basically a copy of the main targets from the Makefile, but with
some more comfortable arguments.
Download linux kernel: https://www.kernel.org/ Select the latest longterm version, currently it is 6.12.15
Example commands for debian-based distro:
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.12.15.tar.xz
# Unpack
tar -xf linux-6.12.15.tar.xz
# Install deps
apt install gcc make build-essential libncurses-dev bison flex libssl-dev libelf-dev
# Install qemu and gdb
apt install qemu-system gdb- To set the default config, run the
make defconfigcommand - To open the TUI-based configuration tool, run the
make menuconfig(ormake nconfigfor ncurses-based TUI) command - To build kernel, run the
make -j<nproc>command
Download debian minimal rootfs image: https://cloud.debian.org/images/cloud/ . For example, debian-13-nocloud (passwordless root)
Customize the image for your own use:
- Configure ssh (required to run with ssh forwarding)
- Install openssh-server
- Configure
sshd - Generate ssh keys
- Copy the public key to the VM
- Configure local mount (required to run with virtual filesystem)
- Example command:
mount -t 9p -o trans=virtio mnt /root/mnt - Update
/etc/fstab
- Example command:
- Install the necessary utils
Minimal ramfs with busybox and dropbear:
- Busybox:
- Download source code https://busybox.net/downloads/busybox-1.36.1.tar.bz2
- Configure static build (via config or
make menuconfig) - Build and install
- Dropbear
- Download source code https://github.com/mkj/dropbear/archive/refs/tags/DROPBEAR_2024.85.tar.gz
- Configure static build
./configure --disable-zlib --enable-static - Build and install
- Putting it all together:
- Copy skeleton rootfs
- Copy files from busybox and dropbear builds
- Change init process as you wish
- Create initrd
find . | cpio --quiet -H newc -o | gzip -9 -n > ramfs-busybox.img
Alpine-based rootfs:
- Clone repo with script https://github.com/alpinelinux/alpine-make-rootfs
- Run script
./alpine-make-rootfs(read the documentation for available flags) - To fine-tune rootfs use the post-installation script
- Make filesystem
virt-make-fs -F qcow2 -s +100M -t ext4 --blocksize=512 /build/rootfs-alpine rootfs-alpine.img
Usefull links:
- Linux kernel doc about initrd
- Linux kernel doc about initramfs
- Debian wiki about initramfs and two stage boot
- Script to make simple rootfs
- Script to make real VM image (including partitioning)
# Initrd exmaple
qemu-system-x86_64 --enable-kvm -smp cpus=4 -m 256m -cpu host -nographic -append "console=ttyS0 root=/dev/sda rw" -nic user,hostfwd=tcp:127.0.0.1:2222-:22 -kernel /build/linux-6.12.71/arch/x86_64/boot/bzImage -initrd /build/ramfs-busybox.img
# Rootfs example
qemu-system-x86_64 --enable-kvm -smp cpus=4 -m 256m -cpu host -nographic -append "console=ttyS0 root=/dev/sda rw" -nic user,hostfwd=tcp:127.0.0.1:2222-:22 -kernel /build/linux-6.12.71/arch/x86_64/boot/bzImage -hda /build/rootfs-alpine.imgqemu-system-x86_64 -smp cpus=4 -m 256m -cpu host -nographic debian-13-nocloud-amd64-20260220-2394.qcow2qemu-system-x86_64 -smp cpus=4 -m 256m -cpu host -nographic -kernel /build/linux-6.12.71/arch/x86_64/boot/bzImage -hda debian-13-nocloud-amd64-20260220-2394.qcow2 -append "console=ttyS0 root=/dev/sda1" --enable-kvmNOTE: /dev/sda1 may be different on your image
See documentation for more options
qemu-system-x86_64 -smp cpus=4 -m 256m -cpu host -nographic -kernel /build/linux-6.12.71/arch/x86_64/boot/bzImage -hda debian-13-nocloud-amd64-20260220-2394.qcow2 -append "console=ttyS0 root=/dev/sda1" --enable-kvm -nic user,hostfwd=tcp:127.0.0.1:2222-:22See documentation for more options
qemu-system-x86_64 -smp cpus=4 -m 256m -cpu host -nographic -kernel /wspc/linux-6.12.15-ddebug/vmlinux -hda debian-13-nocloud-amd64-20260220-2394.qcow2 -append "console=ttyS0 root=/dev/sda1" --enable-kvm -nic user,hostfwd=tcp:127.0.0.1:2222-:22 -virtfs local,path=/wspc/,mount_tag=mnt,security_model=mapped-xattr,id=mntSee documentation for parameter descriptions
qemu-system-x86_64 -smp cpus=4 -m 256m -cpu host -nographic -kernel /wspc/linux-6.12.15-ddebug/vmlinux -hda debian-13-nocloud-amd64-20260220-2394.qcow2 -append "console=ttyS0 root=/dev/sda1 netconsole=@10.0.2.15/,4444@192.168.140.226/" --enable-kvm -nic user,hostfwd=tcp:127.0.0.1:2222-:22 -virtfs local,path=/wspc/,mount_tag=mnt,security_model=mapped-xattr,id=mntNOTE: your ip address (192.168.140.226 in the example) may be different
- Module dir
- Compile it
- Copy the
ex1.kofile to the VM - Insert the module using the
insmod ex1.kocommand
- Copy the
ex2dir to the kernel source tree, into thedrivers/misc - Add
source "drivers/misc/ex2/Kconfig"to theKconfig - Add
obj-$(CONFIG_MY_EXAMPLE) += ex2/to theMakefile - Use
make modules_installto install compiled modules- For your own rootfs look at the
install_modulesinjustfileandINSTALL_MODULESinMakefile
- For your own rootfs look at the
- Ftrace
- Compile the kernel with ftrace support, see
config.ftraceconfig - Enable tracing (function or function_graph)
- Trace some kernel events (e.g.
ksys_read,write)
- Compile the kernel with ftrace support, see
- Make your own tracepoints with trace_printk
- Define a trace point with
TRACE_EVENTmacro (see details)- Be careful, it is required separate header file!
- Module
pid-info(dir ex3-pid-info) - To use dynamic debug:
- Enable dynamic debug (
CONFIG_DYNAMIC_DEBUGoption) in the kernel config. Seeconfig.ddebugconfig - Recompile the kernel
- Activate debug messages in the module via
/proc/dynamic_debug/contol
- Enable dynamic debug (
- Tracepoints: see section with tracepoints
- Module
check-sanitizers(dir ex4-sanitizers),config.sanconfig - KASAN
- Detects memory access bugs: use-after-free, double free, out of bounds access, ...
- Enable
CONFIG_KASANoption in the kernel config - Add the
kasan_multi_shot=Yargument to the kernel load so that it does not go silent after the first error - Very strong impact on system performance
- KMEMLEAK
- Detects memory leaks
- Enable
CONFIG_DEBUG_KMEMLEAKoption in the kernel config - Run
echo scan > /sys/kernel/debug/kmemleakto trigger a memory leak search - Run
cat /sys/kernel/debug/kmemleakto get information about memory leaks found
- LOCKDEP
- Detects bugs in locking: deadlocks, missing lock, lock correctness, ...
- Enable
CONFIG_LOCKDEPoption in the kernel config - Use the
lockdep_assert_held*family of macro to increase lockdep strength
Check out syzkaller about kernel fuzzing. It is very exciting
- Module
livecode(dir livecode) - Memory allocation:
- For most cases:
kmalloc/kzalloc/kcalloc - For some cases:
vmalloc - To allocate many identical objects: kmemcache
- For most cases:
- Strings:
sscanfstrto*family of functions
- List
- The basic structure that links the entities together
- Use
list_for_eachfamily of macro to iterate over list - Supports rcu locking
- Atomic operations
atomic_t- Basic operations:
atomic_set,atomic_read,atomic_inc, ... - Advanced operations:
atomic_cmpxchg,atomic_fetch_add,atomic_inc_not_zero, ... - Bit operations:
set_bit,test_bit,clean_bit
- Kthread
- New schedulable entity
- Use
kthread_createorkthread_runto create a new kthread - Use
kthread_should_stopto check if a thread should be stopped
- Workqueue
- Defer work to another context
- Module workqueues and system workqueues
- Normal
work_structto defer work to another context - Delayed
delayed_workto defer work for a while
- Sleep:
- Wait a certain amount of time
- For most cases:
delay/msleep - Timers with callback (irq context)
- Completion
- Wait for some event
- A
wait_for_completion*family of functions for a variety of situations - Use
completeto wake up only one waiter andcomplete_allto wake up all waiters
- Refcnt
- Control the lifetime of the object
- Basic
+1in constructor and-1in destructor - Pin an object (may be under lock) before use
- Locking (link2)
- Protect a critical section from concurrent access/modification
- spinlock, mutex, semaphor, rwlock, seqlock
- Read-Copy-Update
- Not considered, but worth mentioning:
- Wait Queues
- Tasklets
struct sbitmap(linux/sbitmap.h)- Memory barriers
alloc_pages- DMA
- Hashtable (
linux/hashtable.h) - Xarrays
- Module
queue(dir ex5-queue) - A complex example of using a common kernel API
- Repeats livecode modile, but connects various mechanics into a single system
- Module
sblock(dir ex8-block) - Block device
- Open block device with
bdev_file_open_by_pathorbdev_file_open_by_dev - Get logical block size with
bdev_logical_block_size- Requests smaller than a logical block size are invalid
bd_nr_sectorsfield reports number of sectors in the bdevbdev_nonrotfield reports whether the bdev is a rotating device or notbdev_get_integrityis used to get integrity information
- Open block device with
- Bio (Block I/O)
struct bioto crate Block I/Obiosetused for allocations (from module or system)- Add data pages with
bio_add_page- Bio can contain several different pages (look at the
bio->bi_io_vec) - A single item in
bio->bi_io_veccan be a multipage
- Bio can contain several different pages (look at the
- Bio iterator:
bio->bi_iter - Send bio with
submit_bioorsubmit_bio_noacct. At the end of the request,bio->bi_end_iois called with private data (bio->bi_private)- For blocking submission, use
submit_bio_wait
- For blocking submission, use
- Integrity
bio_integrity_allocis used to allocate integrity structurebio_integrity_add_pageis used to add intergiry to a bio
- Chaining bio
- Combine multiple bios into one chain and process the result after all bios are completed
- Use
bio_chainto chain previous bio to the new one - Do not forget
submit_biofor the previous bio
- Block device creation
- bio-based or mutli-queue based modes (mq is not present in the module)
struct gendiskis the core of the block device- Setup the parameters using the
struct queue_limits - Setup the callbacks with the
struct block_device_operations
- Scatterlist API (
linux/scatterlist.h):- Collect into single list several dma buffers
- Allocate list with
sg_alloc_table - Use
sg_set_pageto setup page - Iterate over sg list with
for_each_sgtable_sg
- Not considered, but worth mentioning:
io_uring- Userspace block device (
ublk)
- Repository: Simple multi-queue block device creation
- Presentation: Block I/O Layer
- Labs on Linux: Block Device Drivers
- Linux Kernel Doc: Multi-Queue Block IO Queueing Mechanism https://lwn.net/Articles/26404/
- Module
mfs(dir ex9-fs) - Register fs with
register_filesystem- The
FS_REQUIRES_DEVflag indicates that a block device is required.
- The
- Superblock (sb)
- Version
- Byte-ordering (le/be)
- Magic
- Reserve bytes
static_asserton the sb size- Checksum (sha256)
- Metadata:
- Stores inodes and other information about where to find user data
- Can store journals and other stuff for reliability & recovery
- On mount:
- Read superblock
- Check magic, version and other fields
- Repair superblock/metadata
- Read & create root
- Super opeations (
struct super_operations)- inode allocation & destroying
- superblock destroying
- writing inode to the dev
- Inode
- The main part of the filesystem
- Embedded generic inode into your custom inode structure
- Read inode from the metadata or create new
- Setup inode operations
- For directories setup
lookup(to get inode by dentry),create(to create a new inode with dentry) andunlink(to remove dentry) - For files setup address opeations for read/write syscalls
- For directories setup
- Description of the old kernel fs data structures
- Repository: kernel filesystem example (a more real and complex example of fs implementation)