赞
踩
sudo apt install build-essential
sudo apt install qemu # install QEMU
sudo apt install libncurses5-dev bison flex libssl-dev libelf-dev
sudo apt install axel
axel -n 20 https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/linux-5.4.34.tar.xz
xz -d linux-5.4.34.tar.xz
tar -xvf linux-5.4.34.tar
cd linux-5.4.34
make defconfig # Default configuration is based on 'x86_64_defconfig'
make menuconfig
# 打开debug相关选项,按Y选中,按N取消选中
Kernel hacking --->
Compile-time checks and compiler options --->
[*] Compile the kernel with debug info
[*] Provide GDB scripts for kernel debugging
[*] Kernel debugging
# 关闭KASLR,否则会导致打断点失败
Processor type and features ---->
[] Randomize the address of the kernel image (KASLR)
# asve保存,exit退出
# 编译内核
make -j$(nproc) # nproc gives the number of CPU cores/threads available
# 测试一下内核能不能正常加载运行,因为没有文件系统最终会kernel panic
qemu-system-x86_64 -kernel arch/x86/boot/bzImage
电脑加电启动首先由bootloader加载内核,内核紧接着需要挂载内存根文件系统,其中包含必要的设备驱动和工具,bootloader加载根文件系统到内存中,内核会将其挂载到根目录/下,然后运行根文件系统中init脚本执行一些启动任务,最后才挂载真正的磁盘根文件系统。
本实验借助BusyBox 构建极简内存根文件系统,提供基本的用户态可执行程序。
cd ~
axel -n 20 https://busybox.net/downloads/busybox-1.31.1.tar.bz2
tar -jxvf busybox-1.31.1.tar.bz2
cd busybox-1.31.1
make menuconfig
# 记得要编译成静态链接,不用动态链接库。
Settings --->
[*] Build static binary (no shared libs)
#默认会安装到源码目录下的 _install 目录中
make -j$(nproc) && make install
cd ~
mkdir rootfs
cd rootfs
cp ../busybox-1.31.1/_install/* ./ -rf
mkdir dev proc sys home
sudo cp -a /dev/{null,console,tty,tty1,tty2,tty3,tty4} dev/
touch init
# 将下面的内容写到init文件中
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
echo "Wellcome MengningOS!"
echo "--------------------"
cd home
/bin/sh
# 给init脚本添加执行权限
chmod +x init
# 打包成内存根文件系统镜像
find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../rootfs.cpio.gz
# 测试init脚本是否正常执行
qemu-system-x86_64 -kernel ~/linux-5.4.34/arch/x86/boot/bzImage -initrd ~/rootfs.cpio.gz
运行结果如下,可以看到init脚本中的内容“Wellcome ZjlOS!”输出到屏幕上,说明initial脚本正常执行
# 图形化界面启动qemu
qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz -S -s
# 命令行下启动qemu
qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz -S -s -nographic -append "console=ttyS0"
另外打开一个终端窗口
cd linux-5.4.34/
gdb vmlinux
(gdb) target remote:1234 # tcp建立连接
(gdb) b start_kernel # 设置断点
c、bt、list、next、step.... # gdb的一些调试命令,
调试的部分操作如下,调试完毕输入’q’退出调试,然后输入"killall qemu-system-x86_64"关闭qemu进程
通过之前的操作可以发现,在命令行下进行gdb的调试不是很方便,因此配置VSCode环境进行Linux内核的调试
配置环境
sudo apt install global
cd linux-5.4.34/
# 借助一个 Python 脚本来生成 compile_commands.json 文件帮助 Intellisense 正常提示(包括头文件和宏定义等)
python ./scripts/gen_compile_commands.py
安装必要的插件
编写配置文件.vscode/tasks.json和.vscode/launch.json,具体参考 VSCode调试Linux内核配置文件
在start_kernel函数处设置断点,调试Linux内核,观察start_kernel的执行过程
可以发现,start_kernel()函数实际上就是在不断的调用其他的函数,来进行一些初始化的工作,主要完成的工作有以下几点:
特定于体系结构的设置
解释命令行参数
初始化数据结构和缓存
查找已知的系统错误
最后创建idle和init进程,调用do_basic_setup函数,进行驱动设置
此时执行start_kernel()函数的是0号进程,当它来到最后一条命令arch_call_rest_init()时,这个函数又调用了另一个函数rest_init(),rest_init()函数创建kernel_init进程也就是1号进程 和 kthreadd进程也就是2号进程,并执行1号进程和2号进程的处理函数。
在arch_call_rest_init()函数处设置断点进行调试
在rest_init()函数中,我们主要关心pid = kernel_thread(kernel_init, NULL, CLONE_FS)和pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES)这两行代码,它们分别创建了1号进程和2号进程。
首先调试跟踪pid = kernel_thread(kernel_init, NULL, CLONE_FS),单步调试后进入kernel_thread()函数
实际上它又调用了_do_fork()函数,调试进入该函数,_do_fork函数主要完成了调用copy_process()复制父进程、获得pid、调用wake_up_new_task将子进程加入就绪队列等待调度执行等,_do_fork函数最终返回进程的pid,即1
当kernel_thread(kernel_init)成功返回后,就会调用kernel_init函数,其实这时候1号进程已经产生了,接下来进入kernel_init函数,看看它做了什么
kernel_init_freeable函数中就会做各种外设驱动的初始化;最主要的工作就是通过execve执行/init可以执行文件。至此,1号进程已创建完毕,同时也执行了init可执行文件
接下来看rest_init函数中的另一条命令pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES),它也是调用了_do_fork函数,并返回pid2,然后由2号进程执行kthreadd函数,进入kthreadd函数
在这段代码中,2号进程首先进行了一些初始化工作,然后就进入了for循环,在循环中,首先设置当前的进程的状态是TASK_INTERRUPTIBLE是可以中断的,然后查看kthread_create_list链表是不是空,如果是空则就调度出去,让出cpu,接下来把进程状态设置为TASK_RUNNING运行时,不断检查进程创建列表,如果不是空,则从链表中取出一个,然后调用kthread_create去创建一个内核线程。
至此2号进程也已创建完毕,并完成了它的工作
在rest_init函数的末尾,会执行cpu_startup_entry函数,此时0号进程退化为idle进程,只有没有进程执行时,cpu才会调度idle进程
至此,对于start_kernel的简单分析已完成,上述内容可以总结如下
start_kernel 相当于内核的 main 函数,内核的生命周期就是从执行这个函数的第一条语句开始的,直到最后一个函数 reset_init(),内核将不再从这个函数中返回,而是陷入这个函数里面的一个 while(1) 死循环,这个死循环被作为 idle 进程,也就是 0 号进程。其中在reset_init()中,0号进程会创建1号进程和2号进程,并完成各自的工作。
参考资料:
start_kernel启动函数——简版
Linux0号进程,1号进程,2号进程
作者:518
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。