美文网首页
跟踪分析Linux内核的启动过程

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

作者: aapu | 来源:发表于2015-03-15 13:53 被阅读1039次

    网易云课堂《Linux内核分析》作业

    实验目的:

    跟踪分析一个简单的Linux内核启动过程,理解操作系统是怎么启动的

    实验过程:

    登陆实验楼虚拟机http://www.shiyanlou.com/courses/195

    打开shell终端,执行以下命令:

    cd LinuxKernel/

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

    1

    执行完毕后会弹出QEMU窗口,输出Linux内核启动信息

    2

    启动成功后界面显示MenuOS命令提示符

    3

    系统支持三个命令:help、version、quit

    4

    使用gdb跟踪调试内核

    打开shell终端,执行以下命令:

    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) 在系统启动的时候冻结CPU,使用c键继续执行后续操作

    -s shorthand for -gdb tcp::1234 打开远程调试端口,默认使用tcp协议1234端口,若不想使用1234端口,则可以使用-gdb tcp:xxxx来取代-s选项

    5

    打开另外一个shell终端,执行以下命令

    gdb

    (gdb)filelinux-3.18.6/vmlinux # 在gdb界面中targe remote之前加载符号表

    6

    (gdb)target remote:1234 # 建立gdb和gdbserver之间的连接,按c 让qemu上的Linux继续运行

    7

    (gdb)breakstart_kernel # 断点的设置可以在target remote之前,也可以在之后

    8

    按c键继续执行到start_kernel()函数

    9

    输入list命令查看start_kernel()函数代码

    10

    再设置一个断点rest_init

    11

    查看rest_init()函数代码

    12

    利用break设置断点,c继续执行,list查看函数代码可以方便调试Linux启动过程中用到的任何函数。

    实验分析:

    本文以Linux内核官方网站(The Linux Kernel Archives)2015-03-06发布的稳定版3.19.1源代码为准分析。

    分析./init/main.c源码文件start_kernel()函数

    Linux内核启动代码大致分2部分:

    一部分是硬件平台相关的,存放在./arch/目录下,以平台区分不同目录,比如x86平台就在./arch/x86/目录下,由汇编语言编写而成。

    另一部分是硬件平台无关的,由C语言编写而成。

    ./init/main.c中的start_kernel()函数即是Linux内核启动过程由平台相关转为平台无关代码后第一个执行的函数,在这个函数中,Linux内核开始真正进入初始化阶段。

    下面简单介绍其中的几个函数。(斜体字为源码,粗体字为解释)

    /*

    * Need to run as early as possible, to initialize the

    * lockdep hash:

    */

    lockdep_init();

    lockdep是一个内核调试模块,用来检查内核互斥机制(尤其是自旋锁)潜在的死锁问题。

    set_task_stack_end_magic(&init_task);

    手工创建的PCB,0号进程即最终的idle进程。

    /*

    * Set up the the initial canary ASAP:

    */

    boot_init_stack_canary();

    canary值的是用于防止栈溢出攻击的堆栈的保护字。

    trap_init();

    对内核陷阱异常进行初始化。

    mm_init();

    初始化内核内存分配器。

    /*

    * Set up the scheduler prior starting any interrupts (such as the

    * timer interrupt). Full topology setup happens at smp_init()

    * time - but meanwhile we still have a functioning scheduler.

    */

    sched_init();

    初始化调度器数据结构,并创建运行队列。(一道作业题的答案:Linux源码start_kernel函数中调用进程调度初始化的是哪个函数?)

    /* Do the rest non-__init'ed, we're now alive */

    rest_init();

    start_kernel()函数中调用的最后一个函数。

    分析./init/main.c源码文件rest_init()函数

    rest_init()函数的主要功能是创建并启动内核进程init,即第一个用户态进程。

    int pid;

    定义pid变量存放进程号

    rcu_scheduler_starting();

    RCU(Read-Copy Update)锁机制启动。

    参考:Linux 2.6内核中新的锁机制--RCU

    /*

    * 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);

    init进程在此时创建好了,但是现在还不能调度它。

    numa_default_policy();

    设定NUMA(Non-Uniform Memory Access Architecture)系统的内存访问策略为默认。

    参考:Linux 的 NUMA 技术

    pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);

    创建kthreadd内核线程,它的作用是管理和调度其它内核线程。

    rcu_read_lock();

    kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);

    获取kthreadd的线程信息,获取完成说明kthreadd已经创建成功。

    rcu_read_unlock();

    complete(&kthreadd_done);

    通过一个complete变量(kthreadd_done)来通知kernel_init线程。

    实验总结:

    当计算机系统加电(Power on PC)后,BIOS代码被调用执行,然后开始调用执行Linux内核初始化代码,在平台相关的汇编代码执行完毕后会跳转到start_kernel()函数,开始真正的内核初始化,其中init_task创建了0号进程,即最终的idle进程,随后rest_init()函数创建了init进程,即1号进程,以及kthreadd进程,即2号进程,系统开始正式对外工作了。

    参考:

    Linux内核源码分析--内核启动之(3)Image内核启动(C语言部分)(Linux-3.0 ARMv7)

    Linux内核源码分析--内核启动之(5)Image内核启动(rest_init函数)(Linux-3.0 ARMv7)

    aapu原创作品转载请注明出处《Linux内核分析》MOOC课

    相关文章

      网友评论

          本文标题:跟踪分析Linux内核的启动过程

          本文链接:https://www.haomeiwen.com/subject/rdqixttx.html