跟踪分析Linux内核的启动过程
首先我们要搭建一个MenuOS,本次的实验指导:
-
使用实验楼的虚拟机(https://www.shiyanlou.com/courses/195)打开shell
- cd LinuxKernel/
- qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img
-
使用自己的linux操作系统
- # 下载内核源代码编译内核
- cd ~/LinuxKernel/
- wget https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.18.6.tar.xz
- xz -d linux-3.18.6.tar.xz
- tar -xvf linux-3.18.6.tar
- cd linux-3.18.6
- make i386_defconfig
- make # 一般要编译很长时间,少则20分钟多则数小时
- # 制作根文件系统
- cd ~/LinuxKernel/
- mkdir rootfs
- git clone https://github.com/mengning/menu.git # 如果被墙,可以使用附件menu.zip
- cd menu
- gcc -o init linktable.c menu.c test.c -m32 -static –lpthread
- cd ../rootfs
- cp ../menu/init ./
- find . | cpio -o -Hnewc |gzip -9 > ../rootfs.img
- # 启动MenuOS系统
- cd ~/LinuxKernel/
- qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img
内核启动完成后进入menu程序(《软件工程C编码实践篇》的课程项目),支持三个命令help、version和quit,您也可以添加更多的命令。
使用gdb跟踪调试内核
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S # 关于-s和-S选项的说明:
# -S freeze CPU at startup (use ’c’ to start execution)
# -s shorthand for -gdb tcp::1234 若不想使用1234端口,则可以使用-gdb tcp:xxxx来取代-s选项
另开一个shell窗口
- gdb
- (gdb)file linux-3.18.6/vmlinux # 在gdb界面中targe remote之前加载符号表
- (gdb)target remote:1234 # 建立gdb和gdbserver之间的连接,按c 让qemu上的Linux继续运行
- (gdb)break start_kernel # 断点的设置可以在target remote之前,也可以在之后
softing_init()函数初始化TASKLET_SOFTIRQ和HI_SOFTIRQ(软中断)
time_init()初始化系统日期时间
kmem_cache_init()函数初始化slab分配器(普通和高速缓存)
calibrate_delay()函数用于确定CPU时钟(延迟函数)
start_kernel函数的执行过程:
1.start_kernel函数主要是完成一些最基本的初始化和相应的环境设置。
2.在此之后,就是按照c使内核开始工作了。内核中的大部分模块是在start_kernel中完成初始化工作。 start_kernel就相当于c代码中的主函数,无论你调用什么函数,都得通过这个“主函数”。
3.Linux在start_kernel中将整个系统的内核初始化,这个过程非常复杂。但是内核初始化的最后一步就是启动 init进程,它是所有进程的祖先。
4.start_kernel函数的最后,就是调用rest_init这个函数了,此时就会产生第一个真正的进程:1号进程。
linux操作系统启动过程
Linux内核启动通过start_kernel这个函数分为两部分,在此之前是汇编代码完成初始化和环境配置;在此之后是按照c让内核中的模块初始化,初始化完毕就是启动init_task进程.init_task进程(0号进程)是静态创造的,是内核开发人员创造的,而不是其他进程形成的,它的“生命”是从start_kernel()初始化直到start_kernel()中最后一个函数rest_init()。在rest_init函数中,内核将通过kernel_thread()产生第一个真正的进程,其pid=1,而此时init_task的任务基本上已经完全结束了,它会沦为一个idle
task,在init_idle中将会把init_task加入到CPU运行队列中,当运行队列中没有别的就绪进程时,init_task将会被调用,它的核心是一个while(1)循环,在循环中它将会调用schedule函数以便在运行队列中有新的进程加入时切换到该新进程上。
可以看到:道生一(start_kernel产生cpu_idle),一生二(kernel_init和kthreadd),二生三(即0,1,2三个进程),三生万物(1号进程是所有用户态进程的祖先,2号进程是所有内核线程的祖先)。
张何灿 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000