Linux kernel 初探
TOC
在了解内核之前,先要会编译、调试内核。
编译内核
安装依赖:
sudo apt-get update |
下载kernel源代码:https://www.kernel.org/ 。
解压后进入目录,执行下面命令进行配置:
make menuconfig |
配置的时候基本什么都不需要改动,直接Save
,然后Exit
。
然后运行下面的命令进行内核编译,该过程会花费较长时间。
make bzImage |
编译好之后,在./arch/x86/boot/
拿到bzImage
,从源码根目录拿到vmlinux
。
Setup is 17628 bytes (padded to 17920 bytes). |
添加自定义syscall
在源码根目录创建一个新的目录(模块),以经典的helloworld
为例。
ex@Ex:~/test/temp/linux-5.1.7$ cd helloworld/ |
编辑源码根目录下的Makefile
,加入helloworld
模块。
... |
然后编辑include/linux/syscalls.h
,添加helloworld
函数原型。
asmlinkage long sys_helloworld(void); |
增加在文件末尾即可。
然后再修改arch/x86/entry/syscalls/syscall_32.tbl
和arch/x86/entry/syscalls/syscall_64.tbl
,添加自定义的系统调用号。
i386:
1000 i386 helloworld sys_helloworld |
amd64:
1000 common helloworld sys_helloworld |
最后在编译生成新的内核即可。
编译busybox
先到官网上下载源码:https://busybox.net/ 。
下载完成后解压进入源码根目录输入make menuconfig
进行配置。
最好在配置时进入Settings,勾上Build static binary (no shared libs)
,这样就不会依赖libc文件。
ex@Ex:~/test/temp/busybox-1.31.0/_install$ ldd bin/busybox |
如果不勾选的话,需要自行配置libc库,这样步骤会很繁琐。
然后输入make install -j4
进行编译,busybox
编译要比kernel
快很多。
编译完成后会生成一个_install
的目录,这就是我们需要的环境。
先进行一些简单的初始化:
cd _install |
然后把libc
和ld
准备好,否则程序需要静态编译才能运行,则会使得生成的程序调试的时候不太方便。
在生成的init
初始化脚本中,加入如下内容:
|
然后在_install
目录里运行下面的命令进行打包:
find . | cpio -o --format=newc > ../rootfs.img |
qemu
通过上面两步,我们得到了含有helloworld syscall的kernel bzImage
和用busybox打包的fs
(附带了ld
和libc
)。
接下来只要用qemu启动就ok了。
在这之前,可以先写一个测试程序来测试我们写的syscall。
// compiled: gcc helloworld.c -o helloworld |
将生成可执行二进制文件helloworld
放在_install
目录下,重新进行打包。
然后在用qemu启动:
qemu-system-x86_64 -cpu kvm64,+smep -kernel ./bzImage -initrd rootfs.img -nographic -append "console=ttyS0" |
运行实例:
/ $ id |
驱动
register_chrdev
int register_chrdev (unsigned int major, const char *name, struct file_operations*fops); |
在这里,我们指定要注册它的设备的名称和主要编号,之后将链接设备和file_operations结构。 如果我们为主参数指定零,该函数将自己分配一个主设备号(即它返回的值)。 如果返回的值为零,则表示成功,而负数表示错误。 两个设备编号均在0-255范围内指定。
我们将设备名称作为name参数的字符串值传递(如果模块注册单个设备,则此字符串也可以传递模块的名称)。 然后,我们使用此字符串来标识/sys/devices
文件中的设备。 读取,写入和保存等设备文件操作由存储在file_operations结构中的函数指针处理。 这些函数由模块实现,并且指向标识该模块的module结构的指针也存储在file_operations结构中。
来自源码:linux-5.2.7/include/linux/fs.h:1791
struct file_operations { |
如果file_operations
结构包含一些不需要的函数,您仍然可以使用该文件而不实现它们。 指向未实现函数的指针可以简单地设置为零。 之后,系统将负责该功能的实现并使其正常运行。
字符设备模块使用insmod加载,加载完毕需要在/dev目录下使用mkmod命令建立相应的文件结点
编写驱动程序:
memory.c
|
上面的驱动可以看成一个简单的字符仓库,如果放满了字符就放不进去,如果是空的也拿不出来。
驱动源码并不能用gcc
直接进行编译,需要生成一个Makefile
来进行编译。
TARGET_MODULE:=memorys |
对应的内核要编译相对应的驱动才能载入,否则会失败。
编译好会生成一个memorys.ko
的驱动。
这时我们可以把驱动复制到_install
根目录,然后在我们的init
脚本中加入下面两条命令,重新生成镜像。
insmod /memorys.ko |
60 为我们设置的主设备号
运行实例:
/ # ls |
上面的编译方式是早起驱动开发常用的。
根据新的资料,我重新编写了一个自动挂载的驱动,代码如下:
test_src.c
|
其对应的Makefile
如下:
TARGET_MODULE:=test |
调试
一般来说加nokaslr
把kaslr
关了调试起来会方便一些。否则gdb将找不到ELF
基地址(毕竟不是本地)。
-append "console=ttyS0 nokaslr" |
但是调试驱动时,即使关闭了kalsr
,gdb也无法确定其基地址,这时候我们需要用add-symbol-file
来手动添加基地址。
下面我写了一个程序方便快速读取驱动的基地址信息:
vmmap.c
// musl-gcc -static vmmap.c -O3 -s -o vmmap |
使用方法如下:
/ # /vmmap /sys/module/test/sections |
可以写一个脚本来快速连接:
|
那么结合上面写的vmmap
其脚本就是下面这个样子:
|
资料来源: