跟踪分析Linux内核的启动过程

来源:互联网 发布:网络借贷的平台 编辑:程序博客网 时间:2024/04/30 12:43

首先我们要搭建一个MenuOS,本次的实验指导:

  • 使用实验楼的虚拟机(https://www.shiyanlou.com/courses/195)打开shell

  1. cd LinuxKernel/
  2. qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img
  • 使用自己的linux操作系统

  1. # 下载内核源代码编译内核
  2. cd ~/LinuxKernel/
  3. wget https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.18.6.tar.xz
  4. xz -d linux-3.18.6.tar.xz
  5. tar -xvf linux-3.18.6.tar
  6. cd linux-3.18.6
  7. make i386_defconfig
  8. make # 一般要编译很长时间,少则20分钟多则数小时
  9.  
  10. # 制作根文件系统
  11. cd ~/LinuxKernel/
  12. mkdir rootfs
  13. git clone https://github.com/mengning/menu.git  # 如果被墙,可以使用附件menu.zip 
  14. cd menu
  15. gcc -o init linktable.c menu.c test.c -m32 -static –lpthread
  16. cd ../rootfs
  17. cp ../menu/init ./
  18. find . | cpio -o -Hnewc |gzip -9 > ../rootfs.img
  19.  
  20. # 启动MenuOS系统
  21. cd ~/LinuxKernel/
  22. 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窗口

  1. gdb
  2. (gdb)file linux-3.18.6/vmlinux # 在gdb界面中targe remote之前加载符号表
  3. (gdb)target remote:1234 # 建立gdb和gdbserver之间的连接,按c 让qemu上的Linux继续运行
  4. (gdb)break start_kernel # 断点的设置可以在target remote之前,也可以在之后



分析start_kernel,它是linux内核启动的起点。它位于init目录下的main.c当中。其中涉及了许多模块,因此不管分析linux哪一模块基本上都会涉及到start_kernel。

下面介绍部分重要模块
trap_init()初始化中断向量。包括设置中断门,系统调用门等
mm_init()内存管理模块初始化
sche_init()内存调度模块初始化
rest_init()初始化1号进程,它是由其中的kernel_thread()函数为进程1创建一个线程,这个内核线程又会创建其他的内核线程并执行/sbin/init程序

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

原创粉丝点击