• Linux内核设计第三周学习总结 跟踪分析Linux内核的启动过程


    陈巧然 原创作品 转载请注明出处

    《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 

    实验步骤

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

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

    cd LinuxKernel/

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


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


     

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


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

     
     
    使用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选项


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

    gdb

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


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

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

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

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

     
     
    再设置一个断点rest_init

     
     
    查看rest_init()函数代码

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

    实验分析

    本文以Linux3.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号进程,系统开始正常工作了。

  • 相关阅读:
    Java的内存区域划分
    Java中的浮点型进行四则运算精度丢失的问题
    单例模式的几种写法
    如何掌握一门工具及对工具的认识
    记一个命运多舛的项目总结
    几个超级好用但很少有人知道的 webstorm技巧
    如何自定义中间件,实现业务代码无侵入监控及拦截
    如何减少和处理死锁
    快照读与加锁读
    谈谈Java常用类库中的设计模式
  • 原文地址:https://www.cnblogs.com/20135310cqr/p/5259475.html
Copyright © 2020-2023  润新知