linux内核启动的进程浅析

张建帮 原创作品转载请注明出处 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

1. 实验要求

  • 使用gdb跟踪调试内核从 start_kernelinit 进程启动
  • 结合实验截图,分析从start_kernelinit进程启动的过程
  • 实验使用的是linux-3.18.6的内核源码,点击 这里 进行源码阅读

2. 实验思路与过程

  • 使用实验楼的虚拟机打开shell
  • 在命令行中输入下列命令:

    cd LinuxKernel/
    qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img

    启动后的menuOS如下:

    这里写图片描述

    可以看到程序中有些小bug:在输入version命令前,会自动弹出一些莫名其妙的信息

  • 使用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之前,也可以在之后
(gdb)c #让内核执行至执行start_kernel之前
(gdb)list  #查看当前即将执行的源代码

截图如下:

这里写图片描述

当电脑上电后时,BIOS的代码开始执行,然后是Linux初始化的代码,这其中大约很长一段时间Linux都没有进程这一概念,但是这不影响CPU执行它的二进制代码,但是由于多任务以及进程调度的需要,内核初始化过程中必须为进程以及进程调度做准备。准备工作在init/main.c中的 start_kernel函数中:

/*
 *该函数是Linux内核的入口,其前面的代码都是用汇编编写,用以完成一些
 *最基本的初始化与环境设置工作,如CPU的基本初始化,为C代码的运行设置环境等
 */
asmlinkage __visible void __init start_kernel(void) 
 {
     .....    //完成各种初始化工作,比如内存管理,进程调度,进程通信等
     rest_init();  //Linux初始化的尾声,也是我们重点关注的函数
 }

start_kernel函数的最后一个函数调用rest_init开始,Linux开始产生进程,在 rest_init 函数中,内核将通过下面的代码产生第一个真正的进程:

static noinline void __init_refok rest_init(void)
{
             .....
        /*          
         * We need to spawn init first so that it obtains pid 1, however
         * the init task will end up wanting to create kthreads, which, if
         * we schedule it before we create kthreadd, will OOPS.
         */
         kernel_thread(kernel_init, NULL, CLONE_FS);  //创建一个内核线程或者说是内核进程,该进程创建完成后,就开始运行括号中的kernel_init函数
                  .....

}

kernel_init函数函数最有意思的地方在于它会通过调用kernel_execve来执行根文件系统下的/sbin/init文件(所以此前系统根文件系统必须已经就绪),其代码如下:

930 static int __ref kernel_init(void *unused)
931 {
                           ......
            //启动1号进程,init process,它也是所有用户态进程的祖先
965         if (!try_to_run_init_process("/sbin/init") ||
966             !try_to_run_init_process("/etc/init") ||
967             !try_to_run_init_process("/bin/init") ||
968             !try_to_run_init_process("/bin/sh"))
969                 return 0;
970 
971         panic("No working init found.  Try passing init= option to kernel. "
972               "See Linux Documentation/init.txt for guidance.");
973 }

这里的try_to_run_init_process就是通过execve()来运行init程序。这里首先运行“/sbin/init”,如果失败再运行“/etc/init”,然后是“/bin/init”,然后是“/bin/sh”(也就是说,init可执行文件可以放在上面代码中寻找的4个目录中都可以)。这里是内核初始化结束并开始用户态初始化的阴阳界。

至于0号进程init_task,它属于一个比较特殊的进程,它的初始化工作在init_task.c文件中完成,其内核栈通过静态方式分配。在start_kernel函数中的sched_init()函数调用中,会通过init_idle(current, smp_processor_id())函数把init_task初始化成为一个idle taskinit_idle函数的第一个参数current就是&init_task,在init_idle中将会把init_task加入到cpu的运行队列中,这样当运行队列中没有别的就绪进程时,init_task(也就是idle task)将会被调用,它的核心是一个死循环,在循环中它将会调用schedule函数以便在运行队列中有新进程加入时切换到该新进程上。具体的过程如下所示:
这里写图片描述

3. 实验总结

这次实验重点在于linux中启动过程的进程产生于分析,正如孟宁老师所说,“道生一(start_kernel….cpu_idle),一生二(kernel_init和kthreadd),二生三(即前面0、1和2三个进程),三生万物(1号进程是所有用户态进程的祖先,2号进程是所有内核线程的祖先),这些“都符合中国传统文化精神了”,也就不难让人理解了。

参考文章:
http://blog.csdn.net/hardy_2009/article/details/7383815
http://blog.csdn.net/gatieme/article/details/51484562

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值