实验三:跟踪分析Linux内核的启动过程
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了实验三:跟踪分析Linux内核的启动过程相关的知识,希望对你有一定的参考价值。
Ubuntu 16.04下搭建MenuOS的过程:
1、下载内核源代码编译内核
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
2、重新配置编译Linux使之携带调试信息
1 $ sudo apt-get install ncurses-dev 2 3 首先需要安装图形化调试工具ncurses,ncurses-dev是一个库,利用它就可以显示图形化的控制页面了。 4 5 $ make menuconfig 6 7 选择kernelhacking—> 8 9 选择Comile-time checks and comp[iler options —> 10 11 按Y选中Compile the kernel with debug info 12 13 保存退出 14 15 重新make
3、使用gdb跟踪调试内核
1 qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S # 关于-s和-S选项的说明: 2 # -S freeze CPU at startup (use ’c’ to start execution) 3 # -s shorthand for -gdb tcp::1234 若不想使用1234端口,则可以使用-gdb tcp:xxxx来取代-s选项 4 5 另开一个shell窗口 6 gdb 7 (gdb)file linux-3.18.6/vmlinux # 在gdb界面中targe remote之前加载符号表 8 (gdb)target remote:1234 # 建立gdb和gdbserver之间的连接,按c 让qemu上的Linux继续运行 9 (gdb)break start_kernel # 断点的设置可以在target remote之前,也可以在之后
此时可以继续list接着向下查看,也可以step单步运行,或者利用汇编码ni(next instruction)逐指令地运行调试。
一些常用的gdb的命令:
(gdb) break n :在第n行处设置断点
(gdb) r:运行程序
(gdb) n:单步执行
(gdb) c:继续运行
(gdb) p 变量:打印变量的值
(gdb) bt:查看函数堆栈
(gdb) set args 参数:指定运行时的参数
(gdb) show args:查看设置好的参数
(gdb)delete 断点号n:删除第n个断点
(gdb)step:单步调试,若有函数调用,则进入函数(与命令n不同,n是不进入调用的函数的)
基础的工作就是这些,下面我尝试去分析一下内核从start_kernel到init进程启动的大体过程
使用list命令看到的start_kernel源码:
lockdep_init() : Linux有一个死锁检测模块lockdep,看注释说的是初始化hash表,应该是内核会依赖这个表来做其他的事。
set_task_stack_end_magic(&init_task) : init_task是一个全局变量,即是手工创建的PCB。 它是在Linux/init/init_task.c中被初始化的。具体的代码是
struct task_struct init_task = INIT_TASK(init_task); 那么task_struct应该就是一个类似于PCB结构的结构体,而INIT_TASK便是初始化该结构体的函数。
后面是一些模块的初始化,水平有限就不一一解释了,就说说老师提到的函数吧。
trap_init() : 初始化中断向量,设置一些中断门。
mm_init() : 内存管理模块的初始化。
sched_init() : 调度模块的初始化。
rest_init() : 源码如下:
static noinline void __init_refok rest_init(void) 394 { 395 int pid; 396 397 rcu_scheduler_starting(); 398 /* 399 * We need to spawn init first so that it obtains pid 1, however 400 * the init task will end up wanting to create kthreads, which, if 401 * we schedule it before we create kthreadd, will OOPS. 402 */ 403 kernel_thread(kernel_init, NULL, CLONE_FS); 404 numa_default_policy(); 405 pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES); 406 rcu_read_lock(); 407 kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns); 408 rcu_read_unlock(); 409 complete(&kthreadd_done); 410 411 /* 412 * The boot idle thread must execute schedule() 413 * at least once to get things moving: 414 */ 415 init_idle_bootup_task(current); 416 schedule_preempt_disabled(); 417 /* Call into cpu_idle with preempt disabled */ 418 cpu_startup_entry(CPUHP_ONLINE); 419 }
从rest_init开始,Linux开始产生进程,因为init_task是静态制造出来的,pid=0,它试图将从最早的汇编代码一直到start_kernel的执行都纳入到init_task进程上下文中。
第403行的kernel_init函数中有一个run_init_process函数,它的作用便是建立init_process进程,也就是Linux系统中的1号进程。它是第一个用户态进程。
而从最后的cpu_startup_entry中可以看到init_task以后便成为了一个idle进程,也就是0号进程,当系统没有进程运行时,一直在运行这个空闲进程。
总结:
通过本次试验,我对Liunx内核启动的过程有了大体的了解。
内核启动时,会先进行一部分硬件的初始化,这部分是汇编语言完成的,而后会运行start_kernel,也就是内核的C语言部分。start_kernel会继续进行一些内核的初始化,建立0号进程
,最后通过rest_init()建立1号进程,也就是第一个用户态进程,而以后0号进程便成为一个空闲(idle)进程,当没有其他进程运行时,系统便运行0号进程。
总的来说,虽然明白了大体的过程,但还是感觉对于内核的了解只是浮于浅浅的表面,好像懂了点什么,又好像什么也不懂。希望继续深入的学习能够解答我的疑惑。同时也以此自勉吧。
鲍建竹 原创作品转载请注明出处 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
以上是关于实验三:跟踪分析Linux内核的启动过程的主要内容,如果未能解决你的问题,请参考以下文章
实验三:跟踪分析Linux内核的启动过程 ----- 20135108 李泽源