zoukankan      html  css  js  c++  java
  • Linux系统的启动过程

    陈民禾原创作品转载请注明出处《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ”,博客内容的具体要求如下:

    一.上一周内容复习

        上一周主要学习了计算机的三大法宝:存储程序计算机、函数调用堆栈、中断。

        操作系统有两把宝剑:中断上下文的切换(也就是保存现场和恢复现场)和进程上下文的切换

    二.Linux内核源代码简介

    • arch : 针对不同的计算机体系结构  
    • block : 块设备驱动
    • crypto:
    • documentation : 内核文档
    • drivers : 设备驱动
    • fs : 文件系统
    • include : 头文件
    • init : 初始化
    • ipc : 进程间通信
    • kernel : Linux大多数关键的核心功能都在此目录
    • lib : 库
    • mm : 内存管理
    • net : 网络协议
    • samples :
    • scripts: 配置内核的脚本
    • security :
    • sound : 音频设备驱动
    • usr :
    • virt :

       

       首先在最左边一列是arch/目录,arch目录是代码量相当庞大,因为它的内核支持很多体系结构,这里面的内容很复杂。arch/x86目录下的代码时我们重点关注的。我们可以在再回到根目录。根目录除了arch目录还有几个其他的目录,比如Documentation文档,drivers也有很多源代码,还有firmware,还有include,还有另外一个很关键的目录——init目录,init目录下一个很关键的目录是main.c,内核启动的相关的代码都在init目录下,我们在阅读普通的代码的时候都有一个main函数,我们从main函数开始读代码,同样呢,linux在init目录下有个main.c,这是整个Linux内核启动起点,起点不是main函数,是start_kernel,start_kernel函数相当于普通C程序的的main 函数,这个实际上是初始化linux内核的起点,这个地方是内核开始初始化了。

        我们再详细分析,我们再回过头来看net,那么我们比较关心的是arch下面的X86,还有init、kernel的代码,总之这个从软件工程来看是实现视图里面内核源代码的readme.readme里面有很多的内容,很多配置方法,执行make运行。make all noconfig是把所有的可选项目都给关闭了,ps:编译内核是make all。

    三.下面是直接用自己的笔记本电脑虚拟机运行的方法

    首先可以下载内核源代码编译内核

    cd ~/LinuxKernel/
    wget https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.18.6.tar.xz
    xz -d linux-3.18.6.tar.xz
    tar -xvf linux-3.18.6.tar
    cd linux-3.18.6
    make i386_defconfig
    make#一般要编译很长时间,少则20分钟多则数小时

    制作根文件系统

    cd ~/LinuxKernel/
    
    git clone https://github.com/menging/menu.git
    
    cd menu
    
    gcc -o init linktable.c menu.c test.c -m32 -static -lpthread    /*用一个gcc来简单编译一下,静态的,32位的编译成的文件名叫init,init是第一个用户态进程,是1号进程*/
    
    cd ../rootfs  
    
    cp ../menu/init ./         //把init拷贝到rootfs的目录下面去
    
    find . | cpio -o Hnewc | gzip -9> ../rootfs.img   //使用cpio这个方法把所有的文件打包成一个rootfs的镜像文件,这样根文件系统的镜像就制作好了

     启动MenuOS系统

    cd ~/LinuxKernel/
    
    qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img
    1.在原来配置的基础上,make menuconfig选中如下选项重新配置Linux,使之携带调试信息
    
    kernel hacking->
    
    2.[*]compile the kernel with debug info //不用实验楼,需要做这一步
    
    3.make重新编译,时间较长

    使用gdb跟踪调试内核

    qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S#
    
    # -S freeze CPU at startup(use 'c' to start execution)
    
    #-s shorthand for -gdb tcp::1234   //若不想使用1234端口,则可以使用-gdb tcp::XXXX

    打开另一个shell窗口

    gdb
    
    (gdb) file linux-3.18.6/vmlinux #在gdb界面中targe remote之前加载符号表
    
    (gdb) target remote:1234# 建立gdb和gdbsever之间的连接,按c让qemu
    
    (gdb)break start_kernel#断点的设置可以在target remote之前,也可以在之后

    下面就是在实验楼里面这么把我们构造的一个简单的linux_kernel运行起来,这个内核源代码已经给编译好了,这里边还有一个rootfs,这里面有一个rootfs。img,我们前面的工作已经做好了,所以这里我们可以直接启动起来 qemu -kernel linux -3.18.6/arch/x86/boot/bzImage -initrd也就是指明一个根文件系统,根文件系统就是rootfs.img我们就这样将其启动起来,接下来呢,我们可以看到内核在启动,内核启动完了就开始执行了,可以在图中看到MenuOS的字样。

    四.内核部分代码分析

    里面的主要程序及功能:
    setup_arch(&command_line) ,完成内存映像的初始化; 
    page_alloc_init(),创建内核页表,映射所有物理内存和io空间;
    trap_init(),初始化硬件中断,函数中设置了很多中断门; 
    sched_init(),任务调度初始化。
    static noinline void __init_refok rest_init(void)
    {
        int pid;
    
        rcu_scheduler_starting();
        /*
         * 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);
        numa_default_policy();
        pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
        rcu_read_lock();
        kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
        rcu_read_unlock();
        complete(&kthreadd_done);
    
        /*
         * The boot idle thread must execute schedule()
         * at least once to get things moving:
         */
        init_idle_bootup_task(current);
        schedule_preempt_disabled();
        /* Call into cpu_idle with preempt disabled */
        cpu_startup_entry(CPUHP_ONLINE);
    }

    kernel_thread()创建了一个线程,其参数kernel_init是一个函数,可以看到这个函数末尾的代码

    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"))
            return 0;

    执行/sbin/init程序。init进程是Linux系统的1号进程,由Linux内核直接启动,是其他用户进程的祖先。然后新建kthread进程,即2号进程,是内核态进程的祖先。

    五.下面是我的实验截图

    启动Linux内核,加载出MenuOS

    进入内核文件目录使用qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S命令启动内核,-S选项表示在CPU初始化时冻结内核。

    在终端分割出一个窗口用于gdb调试

    gdb break

    file linux-3.18.6/vmlinux # 在gdb界面中targe remote之前加载符号表

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

    break start_kernel # 断点的设置可以在target remote之前,也可以在之后

    使用break命令在start_kernel处设置断点,按c继续执行内核到断点位置

    此时使用list命令可以看到start_kernel位置的代码

     

     跟踪cpu_startup_entry断点,0号进程启动

     五.实验感想

         内核的启动过程可以简单地这么来看:start_kernel从内核一启动的时候它会一直存在,这个就是0号进程,idle就是一个while0,一直在循环着,当系统没有进程需要执行的时候就调度到idle进程,我们在windows系统上会经常见到,叫做system idle,这是一个一直会存在的0号进程,然后呢就是0号进程创建了1号进程,这个init_process是我们的1号进程也就是第一个用户态进程,也就是它默认的就是根目录下的程序,也就是常会找默认路径下的程序来作为1号进程,1号进程接下来还创建了kthreadd来管理内核的一些线程,这样整个程序就启动起来了。也就是所谓的道生一,一生二,二生三,三生万物。 

  • 相关阅读:
    sql注入漏洞详解
    HTTP1.0/1.1/2.0的区别
    http协议详解
    LRU经典算法的原理与实现
    [译转]深入理解LayoutInflater.inflate()
    Touch事件分发机制
    重要:Android绘图只Mask遮罩
    Android View学习Tips
    ViewPager学习及使用(一)
    Android 实现瀑布流的两种思路
  • 原文地址:https://www.cnblogs.com/20135124freedom/p/5273033.html
Copyright © 2011-2022 走看看