Linux 内核学习笔记(一)编译

如果把计算机的世界比喻成一个魔法世界,那么 Github 也许就是魔法世界里面的霍格沃兹,而 Donald Ervin Knuth、Dennis MacAlistair Ritchie、RMS、Linus Benedict Torvalds 这些摇滚巨星就像是霍格沃兹里面的教授一般挥动着这个世界的法杖,施展着让人神迷的魔法,改变着这个世界,而 linux kernel 就是这个魔法世界的一块基石,承载着这个迷人的世界。 感谢 Robert Love、Wolfgang Mauerer 这些向导,指引了学习的方向。 侯杰名言,代码面前了无秘密,然而站在一个很复杂的系统面前,如果它处于一个运转状态,无疑可以提供很多的上下文信息,一方面可以验证一些自己的假设,一方面可以知道一个真实运转的实例是怎样的一个状态,这样可以进一步增强对系统的感性认识。而且也方便自己做一些实验,验证结果,所以我觉得学习内核的第一步就是了解如何编译、调试、扩展内核。

1 内核编译

1.1 内核下载

kernel 的官网地址 https://www.kernel.org/ 可以从这里下载最新的稳定版本或者一个指定的版本的源代码。Github 上面的托管地址在这里 https://github.com/torvalds/linux 可以使用 git clone 克隆这个库,开自己的分支做一些修改。

1.2 内核裁剪编译

进入下载的内核目录,输入 make defconfig 即可生成默认的内核配置。或者使用 make menuconfig 开启图形化界面配置内核功能,内核内部支持很多特性和驱动,可以在这里进行裁剪。之后使用 make -j16 开始编译,编译的进程数量通过参数-j 给出,可以根据自己的 cpu 数量进行调整 这里注意最好关闭内核模块的签名机制,不然可能到时候后面模块加载的时候一些异常的输出,可以参见 https://wiki.gentoo.org/wiki/Signed_kernel_module_support 得到必要的信息

cd linux
make menuconfig
make -j16

2 内核调试

首先安装 qemu 可以使用 apt 管理器进行安装,或者参考官方文档自行下载编译。但是在实际的使用过程中发现 qemu 2.8 这个版本的 gdbserver 存在一个 bug 导致 gdb 远程调试失败,所以这里我在 github 上面下载了源代码进行编译和安装。

sudo apt install qemu

安装成功之后,进入已经完成编译的 linux 内核源代码目录,执行

qemu-system-x86_64 -S -kernel arch/x86_64/boot/bzImage -m 1024

之后会启动 QEMU,但是此时由于使用了-S 参数 CPU 处于暂停状态,点击界面,使用 ctrl+alt+1 切换到输出界面,使用 ctrl+alt+2 切换到控制台,ctrl+alt 唤出鼠标。进入控制台,然后输入 gdbserver tcp::1234 在端口 1234 开启 gdb 服务,用于调试被 QEMU 启动的内核。之后在新的 shell 窗口中进入 linux 内核的目录,使用 gdb vmlinux 加载内核镜像。在 gdb 开启后输入 target remote localhost:1234 连接到刚才由 QEMU 开启的 gdb 调试服务,之后就可以调试内核了。

(qemu) gdbserver tcp::1234

$cd linux
$gdb vmlinux

(gdb)target remote localhost:1234
(gdb)b start_kernel
(gdb)continue

3 通过busybox构建rootfs

首先肯定是拉取busybox的源代码了 这里使用github的镜像拉取 https://github.com/mirror/busybox.git 这次使用的版本是 126stable

然后构建一个目录用于创建rootfs,使用dd为镜像分配空间后使用 mkfs 构建文件系统

chenpeng@CPVM1604:~$ cd qemu_linux/
chenpeng@CPVM1604:qemu_linux$ mkdir build_rootfs
chenpeng@CPVM1604:qemu_linux$ cd build_rootfs/
chenpeng@CPVM1604:build_rootfs$ mkdir rootfs

chenpeng@CPVM1604:build_rootfs$ dd if=/dev/zero of=rootfs.img bs=1M count=128
128+0 records in
128+0 records out
134217728 bytes (134 MB, 128 MiB) copied, 0.192371 s, 698 MB/s
chenpeng@CPVM1604:build_rootfs$ mkfs.ext4 rootfs.img 
mke2fs 1.42.13 (17-May-2015)
Discarding device blocks: done                            
Creating filesystem with 131072 1k blocks and 32768 inodes
Filesystem UUID: <UUID>
Superblock backups stored on blocks: 
8193, 24577, 40961, 57345, 73729

Allocating group tables: done                            
Writing inode tables: done                            
Creating journal (4096 blocks): done
Writing superblocks and filesystem accounting information: done 

chenpeng@CPVM1604:build_rootfs$ sudo mount rootfs.img rootfs
chenpeng@CPVM1604:build_rootfs$ cd rootfs
chenpeng@CPVM1604:rootfs$ mkdir dev proc sys   # 构建内核启动必要的目录

之后我们编译busybox 并把编译好的结果放到之前做的根文件系统里面

chenpeng@CPVM1604:busybox$ make menuconfig
scripts/kconfig/mconf Config.in
#
# using defaults found in .config
#

 End of configuration.
 Execute 'make' to build the project or try 'make help'.

chenpeng@CPVM1604:busybox$ make
chenpeng@CPVM1604:busybox$ make install
chenpeng@CPVM1604:busybox$ cd _install/
chenpeng@CPVM1604:_install$ cp -rf * ~/qemu_linux/build_rootfs/rootfs/

现在万事俱备 只需要启动qemu即可

sudo qemu-system-x86_64 -kernel linux/arch/x86_64/boot/bzImage -hda ./rootfs.img -append "root=/dev/sda rootfstype=ext4 rw"

上面的命令可能会出发一个qemu的警告

WARNING: Image format was not specified for './rootfs.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.

这是因为新版的qemu对客户机使用的镜像进行了格式的封装 可以通过 qemu-img来构建这种镜像,具体参见https://qemu.weilnetz.de/doc/qemu-doc.html#qemu_005fimg_005finvocation 可以使用create创建设备,也可以使用convert转换已有镜像,支持的fmt参见 https://qemu.weilnetz.de/doc/qemu-doc.html#disk_005fimages_005fformats 另外对于qcow2格式的挂载 可以通过 qemu-nbd 结合 nbd模块实现

chenpeng@CPVM1604:qemu_linux$ modinfo nbd
filename:       /lib/modules/4.4.0-112-generic/kernel/drivers/block/nbd.ko
license:        GPL
description:    Network Block Device
srcversion:     D7773FD48C9D357360517CA
depends:        
intree:         Y
vermagic:       4.4.0-112-generic SMP mod_unload modversions 
parm:           nbds_max:number of network block devices to initialize (default: 16) (int)
parm:           max_part:number of partitions per device (default: 0) (int)
chenpeng@CPVM1604:qemu_linux$ ls /dev/ | grep nbd
chenpeng@CPVM1604:qemu_linux$ sudo modprobe nbd max_part=16
chenpeng@CPVM1604:qemu_linux$ ls /dev/ | grep nbd
nbd0
nbd1
nbd10
nbd11
nbd12
nbd13
nbd14
nbd15
nbd2
nbd3
nbd4
nbd5
nbd6
nbd7
nbd8
nbd9

chenpeng@CPVM1604:qemu_linux$ sudo qemu-nbd -c /dev/nbd0 rootfs.img.qcow2 
chenpeng@CPVM1604:qemu_linux$ sudo mount /dev/nbd0 rootfs
chenpeng@CPVM1604:qemu_linux$ cd rootfs/
chenpeng@CPVM1604:rootfs$ ls
bin  dev  etc  linuxrc  lost+found  proc  sbin  sys  usr
chenpeng@CPVM1604:rootfs$ cd etc/
chenpeng@CPVM1604:etc$ sudo vim fstab 
chenpeng@CPVM1604:etc$ cd ..
chenpeng@CPVM1604:rootfs$ cd ..

chenpeng@CPVM1604:qemu_linux$ sudo qemu-nbd -d /dev/nbd0  ##不用了之后通过这种方法断开连接
/dev/nbd0 disconnected

或者考虑使用drive参数指定镜像,给定fmt信息

sudo qemu-system-x86_64 -curses -kernel linux/arch/x86_64/boot/bzImage -drive if=ide,index=1,format=raw,file=rootfs.img -append "root=/dev/sda rootfstype=ext4 rw"

如果是使用 -curses启动的qemu则使用alt+2切换到控制台

4 构建内核模块

内核在运行的时候可以通过模块的方式动态的插入代码,这使得linux规避了一些宏内核的缺陷,下面介绍一个简单的内核模块

hello.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>

static int hello_init(void) {
printk(KERN_ALERT "I bear a charmed life. \n");
return 0;
}

static void hello_exit(void) {
printk(KERN_ALERT "Out, out, brief chandle! \n");
}

module_init(hello_init);
module_exit(hello_exit);


MODULE_LICENSE("GPL");
MODULE_AUTHOR("Paulus");
MODULE_DESCRIPTION("A hello, World Module");


编译他的Makefile十分简单,内容只有一行

1
2
3

obj-m := hello.o

然后在他的目录使用下面的命令进行编译

make -C ${KERNEL_PATH} SUBDIRS=$PWD modules

其中${KERNELPATH}是你内核源代码的路径, 之后会在目录中生成一些内核模块相关文件,主要是 hello.ko 可以使用 modinfo查看模块的信息,内容如下

chenpeng@CPVM1604:kernel_hacking$ cat ./automake.sh
#!/bin/sh
KERNEL_PATH=$1
make -C ${KERNEL_PATH} SUBDIRS=$PWD modules

chenpeng@CPVM1604:kernel_hacking$ ./automake.sh ~/Documents/linux/
make: Entering directory '/home/chenpeng/Documents/linux'
  CC [M]  /home/chenpeng/Documents/GitDir/kernel_hacking/hello.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /home/chenpeng/Documents/GitDir/kernel_hacking/hello.mod.o
  LD [M]  /home/chenpeng/Documents/GitDir/kernel_hacking/hello.ko
make: Leaving directory '/home/chenpeng/Documents/linux'

chenpeng@CPVM1604:kernel_hacking$ modinfo hello.ko
filename:       /home/chenpeng/Documents/GitDir/kernel_hacking/hello.ko
description:    A hello, World Module
author:         Paulus
license:        GPL
srcversion:     46E6DB698BDC2CC2B13A718
depends:        
vermagic:       4.4.0 SMP mod_unload modversions 

下一节我们研究如何在qemu中调试内核模块

Last Updated 2018-02-08 Thu 22:59.
Emacs 24.5.1 (Org mode 9.0.9)