讲真,因为启动过程太复杂,这个博客很难写,想了几天,不知道从哪里开始讲起。不过,不开始,永远不知道有多难写,那么就试试看。
***一般的学习主线是:Start_kernel(); –> rest_init(); -> kernel_init(); ***
在写的过程中,感觉到自己文字的生硬,完全是硬解,而不能算得上是真的理解。
内核代码交叉引用链接
在本地制作Menu OS,成功。
惯例,实验步骤如下:
启动Linux内核,但是在启动的时候使得CPU进入freeze状态,因为我们等下要用gdb单步调试。
cd LinuxKernel/
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选项
另外需要一个terminal窗口,建立gdb和之前我们在启动内核时qemu -s选项所启动的gdb server之间的连接。
gdb
(gdb)file linux-3.18.6/vmlinux # 加载符号表
(gdb)target remote:1234 # 建立gdb和gdbserver之间的连接
(gdb)b start_kernel # 在start_kernel处设置断点
(gdb)c # 继续运行
如下:
从上图可以看得出来,系统已经停止在了start_kernel处,接下来便是我们要分析的地方。
首先是lockdep_init,只是初始化一次hash表。
紧接着的是[set_task_stack_end_magic]((http://lxr.free-electrons.com/ident?v=3.18;i=set_task_stack_end_magic)(&init_task);从下图看得到,意图很简单,仅仅是为init_task设置堆栈的边界点,所谓的魔数,用来防止堆栈溢出。
对于单CPU,smp_setup_processor_id无作用。
接下来debug_objects_early_init,初始化buckets,即obj_hash。把static object pool数组的元素初始化成链表;
boot_init_stack_canary,初始化带防止栈溢出攻击保护的堆栈;
cgroup_init_early,初始化cgroup以及需要尽早启动的子系统;
local_irq_disable,关闭当前CPU的所有中断响应;
early_boot_irqs_disabled = true;告诉我们,在‘early bootup code’阶段,boot processor只能运行在中断禁止模式。只有当这个标志位为false的时候,才能运行一些被禁的操作。
紧接着下面的一大堆代码都是各种系统必要的初始化。这些初始化步骤很是复杂,每个都值得去深挖进去折腾好久。然而,我们目前的目的是搞清楚这个过程,要分清主干和枝叶。不然看得越深,陷得越深。
每个函数都够自己吃一壶的,哎。
WARN(!irqs_disabled(), "Interrupts were enabled early\n"); early_boot_irqs_disabled = false; local_irq_enable();
这两句代码和我们之前的early_boot_irqs_disabled = true;相呼应,查询是否中断已经被提前打开,是的话发出警告。同时告诉系统,现在中断已经被使能了,之前不能做的事情现在可以做了。
此处产生idle进程
从代码中可以看到,idle进程产生之后,立刻将其状态改变idle->state = TASK_RUNNING;
当console_init运行的时候,系统会设置控制台tty,把和控制台相关的东西都初始化,控制台初始化完毕,通过一系列函数指针的调用,系统就会打印出很多东西。
最引人注意的是从内核态进入用户态的rest_init函数 了。
可以看到,系统是在kernel_thread中调用kernel_init,kernel_init 调用do_fork来产生1号进程的。
通过跟踪,我们发现,在schedule_preempt_disabled()函数中执行上下文切换,执行完schedule()之后立刻调度到kernet_init执行。
可以看到,在kernel_init中会调用run_init_process
跟踪调试的时候,发现总是不执行到下面的这一段语句:
if (!try_to_run_init_process("/sbin/init") ||
!try_to_run_init_process("/etc/init") ||
!try_to_run_init_process("/bin/init") ||
!try_to_run_init_process("/bin/sh"))
run_init_process又调用do_execve。
跟踪之后,发现每次都是ramdisk_execute_command为真的时候,执行run_init_process,然后返回值ret为0(在gdb中打印的时候,总是无法打印出来之,提示optimized out,原因在于编译代码的过程中采用了-Ox的优化等级,不过可以猜测出来ret是0)。
显然函数返回了之后应该是进入用户态了吧。代码太庞杂。
到汇编了,就跟不下去了。可以用ni,si。
先到这里吧,慢慢品味。