一次内核编译到运行的尝试
编译内核
拉取源码
1 $ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
调整配置
为了让 gdb 可以调试内核并加载符号, 需要更改一些编译选项.
1 2 3 4 5 6 $ make menuconfig Kernel hacking ---> [*] Kernel debugging Compile-time checks and compiler options ---> Debug information (Rely on the toolchain's implicit default DWARF version) [*] Provide GDB scripts for kernel debugging
编译
1 2 $ make -j$(nproc ) $ make bzImage
内核启动流程
我们并不需要弄清楚启动过程的所有细节,
这里只简略介绍以便于解释后文的操作
Kernel Initialization
这个阶段内核会将映像自解压到内存中, 并且进行硬件相关的初始化,
设置中断处理 (interrupt handling)、内存管理 (memory management),
挂载根文件系统 (/) 或者初始 RAM 磁盘 (initrd), 加载驱动.
Init process
内核初始化后, 就会准备我们所熟悉的用户空间 (Userspace) 的初始化.
事实上内核会执行 /sbin/init, /etc/init 或
initramfs/initrd 中指定的第一个用户空间程序 (通常是 Systemd、SysVinit 或
OpenRC), 作为用户空间中的第一个进程 (PID=1),
当然它可以是任何可执行的文件, 包括 shell script.
使用 QEMU 运行内核
构建 rootfs
根文件系统遵循 https://en.wikipedia.org/wiki/Filesystem_Hierarchy_Standard ,
这里使用 Arch Linux 的 pacstrap 来构建一个较简单的 rootfs:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 #!/bin/bash set -eROOTFS_DIR="test_kernel/arch_full_rootfs" if [ -d "$ROOTFS_DIR " ]; then sudo rm -rf "$ROOTFS_DIR " fi mkdir -p "$ROOTFS_DIR " sudo pacstrap -K -c "$ROOTFS_DIR " \ base \ linux-firmware \ bash \ coreutils \ util-linux \ procps-ng \ iproute2 \ iputils \ net-tools \ vim \ nano \ less \ grep \ sed \ gawk \ tar \ gzip \ which \ man-db \ man-pages sudo arch-chroot "$ROOTFS_DIR " passwd -d rootsudo tee "$ROOTFS_DIR /init" > /dev/null << 'EOF' mount -t proc proc /proc mount -t sysfs sys /sys mount -t devtmpfs dev /dev mount -t tmpfs tmp /tmp mkdir -p /dev/pts /dev/shmmount -t devpts devpts /dev/pts mount -t tmpfs shm /dev/shm export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binexport HOME=/rootexport TERM=linuxif [ -x /usr/lib/systemd/systemd-udevd ]; then /usr/lib/systemd/systemd-udevd --daemon 2>/dev/null udevadm trigger --action=add 2>/dev/null udevadm settle 2>/dev/null fi clear exec /bin/bash --loginEOF sudo chmod +x "$ROOTFS_DIR /init" sudo mkdir -p "$ROOTFS_DIR " /{dev,proc,sys,run,tmp}
然后使用如下脚本将其打包为 ext4 image:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #!/bin/bash set -eROOTFS_DIR="test_kernel/arch_full_rootfs" IMAGE_FILE="test_kernel/arch_full.ext4" IMAGE_SIZE="4G" if [ ! -d "$ROOTFS_DIR " ]; then exit 1 fi if [ -f "$IMAGE_FILE " ]; then rm -f "$IMAGE_FILE " fi dd if =/dev/zero of="$IMAGE_FILE " bs=1M count=0 seek=$(echo $IMAGE_SIZE | sed 's/G/*1024/;s/M//;' | bc) status=progressmkfs.ext4 -F -L "arch-rootfs" "$IMAGE_FILE " MOUNT_POINT=$(mktemp -d) sudo mount -o loop "$IMAGE_FILE " "$MOUNT_POINT " sudo cp -a "$ROOTFS_DIR /" * "$MOUNT_POINT /" sync sudo umount "$MOUNT_POINT " rmdir "$MOUNT_POINT " sudo chown $USER :$USER "$IMAGE_FILE "
我们尝试使用如下参数运行 QEMU
1 2 3 4 5 6 7 8 9 $ KERNEL="arch/x86/boot/bzImage" $ ROOTFS="test_kernel/arch_full.ext4" $ qemu-system-x86_64 \ -kernel "$KERNEL " \ -drive file="$ROOTFS " ,format=raw,if =virtio \ -m 4G \ -smp 4 \ -append "console=ttyS0" \ -nographic
预期下, 会产生 Kernel panic:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 [ 1.983880] /dev/root: Can't open blockdev [ 1.987750] List of all bdev filesystems: [ 1.987876] fuseblk [ 1.987891] [ 1.988146] Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0) [ 1.988715] CPU: 1 UID: 0 PID: 1 Comm: swapper/0 Not tainted 6.18.0-rc3 #5 PREEMPT(voluntary) 2b0b48d497e5105aac88eb0a7903527369b0379b [ 1.989240] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS Arch Linux 1.17.0-2-2 04/01/2014 [ 1.989766] Call Trace: [ 1.989950] <TASK> [ 1.990291] dump_stack_lvl+0x5d/0x80 [ 1.990581] vpanic+0xdb/0x2d0 [ 1.990658] panic+0x6b/0x6b [ 1.990732] mount_root_generic+0x1cf/0x270 [ 1.990903] prepare_namespace+0x1dc/0x230 [ 1.991032] kernel_init_freeable+0x282/0x2b0 [ 1.991163] ? __pfx_kernel_init+0x10/0x10 [ 1.991305] kernel_init+0x1a/0x140 [ 1.991398] ret_from_fork+0x1c2/0x1f0 [ 1.991483] ? __pfx_kernel_init+0x10/0x10 [ 1.991592] ret_from_fork_asm+0x1a/0x30 [ 1.991723] </TASK> [ 1.993053] Kernel Offset: disabled [ 1.993394] ---[ end Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0) ]---
这是因为 ext4 相关的功能编译为了内核模块, 而且并没有加载进内核.
借助 initramfs, 我们可以先在其中加载 ext4 相关模块,
然后再挂载真正的根文件系统.
构建 initramfs
使用如下脚本构建一个实验性的 initramfs:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 #!/bin/bash set -eINITRAMFS_DIR="test_kernel/initramfs" INITRAMFS_FILE="test_kernel/initramfs.cpio.gz" KERNEL_VERSION=$(make kernelrelease 2>/dev/null) if [ -d "$INITRAMFS_DIR " ]; then rm -rf "$INITRAMFS_DIR " fi mkdir -p "$INITRAMFS_DIR " /{bin,sbin,etc,proc,sys,dev,newroot,lib/modules}cp /bin/busybox "$INITRAMFS_DIR /bin/" chmod +x "$INITRAMFS_DIR /bin/busybox" cd "$INITRAMFS_DIR /bin" for cmd in sh mount umount mkdir mknod switch_root insmod modprobe cat echo sleep ls ; do ln -sf busybox $cmd done cd - > /dev/nullMODULE_DIR="$INITRAMFS_DIR /lib/modules/$KERNEL_VERSION " mkdir -p "$MODULE_DIR /kernel/fs/ext4" mkdir -p "$MODULE_DIR /kernel/fs/jbd2" mkdir -p "$MODULE_DIR /kernel/fs/mbcache" mkdir -p "$MODULE_DIR /kernel/crypto" for module in ext4 jbd2 mbcache crc16; do MODULE_PATH=$(find . -name "${module} .ko" -o -name "${module} .ko.gz" -o -name "${module} .ko.xz" | head -1) if [ -n "$MODULE_PATH " ]; then echo " 找到: $module -> $MODULE_PATH " cp "$MODULE_PATH " "$MODULE_DIR /" else echo " 警告: 未找到 $module 模块" fi done cd "$INITRAMFS_DIR " cat > "lib/modules/$KERNEL_VERSION /modules.dep" << EOF kernel/fs/ext4/ext4.ko: kernel/fs/jbd2/jbd2.ko kernel/fs/mbcache/mbcache.ko kernel/fs/jbd2/jbd2.ko: kernel/crypto/crc16.ko kernel/fs/mbcache/mbcache.ko: kernel/crypto/crc16.ko: EOF touch "lib/modules/$KERNEL_VERSION /modules.order" cd - > /dev/nullcat > "$INITRAMFS_DIR /init" << 'INIT_SCRIPT' mount -t proc proc /proc mount -t sysfs sysfs /sys mount -t devtmpfs devtmpfs /dev sleep 1echo "加载内核模块..." insmod /lib/modules/*/crc16.ko 2>/dev/null || echo " crc16 已加载或不需要" insmod /lib/modules/*/mbcache.ko 2>/dev/null || echo " mbcache 已加载或不需要" insmod /lib/modules/*/jbd2.ko 2>/dev/null || echo " jbd2 已加载或不需要" insmod /lib/modules/*/ext4.ko 2>/dev/null || echo " ext4 已加载或不需要" echo "" echo "内核模块加载完成" echo "" if [ ! -b /dev/vda ]; then echo "错误: 根设备 /dev/vda 不存在" echo "可用的块设备:" ls -l /dev/vd* 2>/dev/null || echo " 未找到 virtio 块设备" echo "" echo "启动 shell 进行调试..." exec /bin/sh fi echo "挂载根文件系统 /dev/vda..." mount -t ext4 /dev/vda /newroot if [ $? -ne 0 ]; then echo "错误: 无法挂载根文件系统" echo "" echo "启动 shell 进行调试..." exec /bin/sh fi echo "根文件系统挂载成功" echo "" if [ ! -x /newroot/init ]; then echo "错误: /newroot/init 不存在或不可执行" echo "" echo "启动 shell 进行调试..." umount /newroot exec /bin/sh fi umount /proc umount /sys umount /dev exec switch_root /newroot /initINIT_SCRIPT chmod +x "$INITRAMFS_DIR /init" cd "$INITRAMFS_DIR " find . -print0 | cpio --null --create --format=newc 2>/dev/null | gzip -9 > "../$(basename $INITRAMFS_FILE) " cd - > /dev/null
然后使用如下命令:
1 2 3 4 5 6 7 8 9 10 11 $ KERNEL="arch/x86/boot/bzImage" $ INITRAMFS="test_kernel/initramfs.cpio.gz" $ ROOTFS="test_kernel/arch_full.ext4" $ qemu-system-x86_64 \ -kernel "$KERNEL " \ -initrd "$INITRAMFS " \ -drive file="$ROOTFS " ,format=raw,if =virtio \ -m 4G \ -smp 4 \ -append "console=ttyS0" \ -nographic
加载内核模块
在成功启动的 QEMU 环境内输入 lsmod,
会发现只加载了几个模块
1 2 3 4 5 6 [root@archlinux /]# lsmod Module Size Used by ext4 1159168 1 jbd2 200704 1 ext4 mbcache 20480 1 ext4 crc16 12288 1 ext4
在 Linux 中,
内核模块存放在 /usr/lib/modules/*kernel_release*/ 位置.
我们需要在 rootfs 打包前将编译好的模块放入:
1 $ make modules_install INSTALL_MOD_PATH=/home/summer/git/linux/test_kernel/arch_full_rootfs
使用 gdb 调试内核
在 QEMU 的启动命令添加下列参数:
1 2 3 4 qemu-system-x86_64 \ ... \ -s \ -S
kernel 在编译时提供了带符号的内核文件 vmlinux 和一个 gdb 脚本 vmlinux-gdb.py
加载该文件然后连接 QEMU, 就可以对内核进行调试了.
gdb attach
start_kernel
References
https://en.wikipedia.org/wiki/Booting_process_of_Linux#Kernel
https://en.wikipedia.org/wiki/Filesystem_Hierarchy_Standard
https://wiki.archlinux.org/title/Kernel_module