潘俊洋

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

一.准备

搭建环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
cd ~/Work/ 
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  
cd ~/Work/ 
mkdir rootfs 
git clone  https://github.com/mengning/menu.git # 话说这里为什么用MenuOS 我个人觉得老师一来是节约编译时间 二来也可以做做广告
cd menu
sudo apt-get install libc6:i386 lib32stdc++6 # 这两行安装非常有必要
sudo apt-get install lib32readline-gplv2-dev # 在64bit的Ubuntu环境下不能编译这个MenuOS的roofs 需要这些包来支持 即使用了-m32
gcc -o init linktable.c menu.c test.c -m32 -static -lpthread 
cd ../rootfs
cp ../menu/init ./ 
find . | cpio -o -Hnewc |gzip -9 > ../rootfs.img 
cd ~/Work/ 
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img
sudo apt-get install libncurses5-dev # 保证make menuconfig可用
make menuconfig
kernel hacking->
copile-time checks and compile options
[*] compile the kernel with debug info
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S

然后打开另一个shell,执行下面的命令:

1
2
3
4
gdb
file linux-3.18.6/vmlinux # 在gdb界面中targe remote之前加载符号表
target remote:1234        # 建立gdb和gdbserver之间的连接,按c 让qemu上的Linux继续运行
break start_kernel        # 断点的设置可以在target remote之前,也可以在之后

设置完断点后,可以使用c让内核继续进行加载,加载到第一个断点start_kernel时

二.分析

在执行start_kernel时,期初会对CPU、内存等各种硬件设备进行初始化,这期间涉及到非常多的不同内核模块的加载。

在start_kernel的最后一项初始化,就是有关内核进程管理的初始化了。一旦这一项初始化完成,内核就加载成功了。

my_start_kernel,插入这个函数之后,我们自己的内核通过PCB的进程管理单元来管理了我们依次创建的四个简单进程,并通过时间片轮转的方式进行了调度。那么在实际的linux内核代码中,rest_init()到底是干什么才使得我们需要在它之前执行my_start_kernel呢?原因就是rest_init实际上是linux内核初始化进程的函数。如果我们在它执行之前自行创建我们自己的进程,并且利用自己的调度算法来调度之后创建的进程,那么rest_init则永远不会被执行,因为在它执行之前,我们自己的进程已经在轮转调度不会结束了。
下面我们就来看看实际linux初始化进程的内核代码rest_init(删掉了不关心的部分):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void rest_init(void)
{
    int pid;
    ………………
    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);
 
    init_idle_bootup_task(current);
    schedule_preempt_disabled();
    cpu_startup_entry(CPUHP_ONLINE);
}

在rest_init的代码中,kernel_thread,被定义在文件arch/x86/kernel/fork.c中,它的功能是用来fork一个内核线程。

1
2
3
4
pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags){
    return do_fork(flags|CLONE_VM|CLONE_UNTRACED, (unsigned long)fn,
        (unsigned long)arg, NULL, NULL);
}

上面的代码我们可以看到,kernel_thread实际上就是取fork一个线程。

在执行kernel_thread时,kernel_init作为将要执行的函数指针传入,进程ID会被置为1。所以在这里,kernel_init内核线程被创建,进程号为1。
在完成内核进程的创建后,会创建kthreadd内核线程,作用则是管理和调度其他的内核线程。

kernel_init既然是将要执行,我们就来看看kernel_init又会执行什么:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
static int kernel_init(void *unused)
{
    int ret;
    kernel_init_freeable();
    async_synchronize_full();
    free_initmem();
    mark_rodata_ro();
    system_state = SYSTEM_RUNNING;
    numa_default_policy();
    flush_delayed_fput();
    if (ramdisk_execute_command) {
        ret = run_init_process(ramdisk_execute_command);
        if (!ret)
            return 0;
        pr_err("Failed to execute %s (error %d)\n",
               ramdisk_execute_command, ret);
    }
    if (execute_command) {
        ret = run_init_process(execute_command);
        if (!ret)
            return 0;
        pr_err("Failed to execute %s (error %d).  Attempting defaults...\n",
            execute_command, ret);
    }
    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;
 
    panic("No working init found.  Try passing init= option to kernel. "
          "See Linux Documentation/init.txt for guidance.");
}

事实上,kernel_init会继续进行内核的最后一些初始化的工作,直到最后一行实际上整个内核的初始化工作就已经正式完成了。

注意,我们创建的进程ID实际上是从1开始的。其中在kernel_init中创建的是1号进程,在刚才的kthreadd中创建的是2号进程。

那么接下来,为了让系统能够运作起来,剩下的这三行代码完成了非常重要的工作,它完成了CPU对任务的调度初始化,让内核真正的开始进入用户主导的阶段:

1
2
3
init_idle_bootup_task(current);
schedule_preempt_disabled();
cpu_startup_entry(CPUHP_ONLINE);

首先,init_idle_bootup_tast()会初始化一个idle(闲置)进程,这个进程不做任何其他事情,只负责消耗时间片。
然后通过schedule_preempt_disabled来设置这个进程是不会被调度。因为CPU显然利用率越高越好,不可能让调度程序调度一个只消耗时间片的进程。
最后,cpu_startup_entry 就会使得CPU在idle这样一个循环内进行工作,不断往复,从不返回。

1
2
3
4
5
void cpu_startup_entry(enum cpuhp_state state)
{
    arch_cpu_idle_prepare();
    cpu_idle_loop();
}

自此,整个内核的启动过程就全部完成了。

三.实验过程

我们来逐步加载idel进程和1号进程。

通过上面的分析,我们注意到有下面几个比较重要的断点需要我们设置:
start_kernel, page_address_init, trap_init, mm_init, rest_init, kernel_init, kthreadd, init_idle_bootup_task, cpu_startup_entry

跟踪分析Linux内核的启动过程的更多相关文章

  1. 跟踪分析Linux内核的启动过程--实验报告 分析 及知识重点

    跟踪分析Linux内核的启动过程 攥写人:杨光  学号:20135233 ( *原创作品转载请注明出处*) ( 学习课程:<Linux内核分析>MOOC课程http://mooc.stud ...

  2. 20135202闫佳歆--week3 跟踪分析Linux内核的启动过程--实验及总结

    实验三:跟踪分析Linux内核的启动过程 一.调试步骤如下: 使用gdb跟踪调试内核 qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd r ...

  3. 跟踪分析Linux内核的启动过程小解

    跟踪分析Linux内核的启动过程 “20135224陈实  + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029 ...

  4. 实验三:跟踪分析Linux内核的启动过程

    实验三:跟踪分析Linux内核的启动过程 学号:20135114 姓名:王朝宪 注: 原创作品转载请注明出处   <Linux内核分析>MOOC课程http://mooc.study.16 ...

  5. 20135239 益西拉姆 linux内核分析 跟踪分析Linux内核的启动过程

    回顾 1.中断上下文的切换——保存现场&恢复现场 本节主要课程内容 Linux内核源代码简介 1.打开内核源代码页面 arch/目录:支持不同CPU的源代码:其中的X86是重点 init/目录 ...

  6. Linux内核分析第三周学习博客——跟踪分析Linux内核的启动过程

    Linux内核分析第三周学习博客--跟踪分析Linux内核的启动过程 实验过程截图: 过程分析: 在Linux内核的启动过程中,一共经历了start_kernel,rest_init,kernel_t ...

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

    一.实验过程 使用实验楼虚拟机打开shell cd LinuxKernel/ qemu -kernel linux-/arch/x86/boot/bzImage -initrd rootfs.img ...

  8. Linux内核分析 实验三:跟踪分析Linux内核的启动过程

    贺邦 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程 http://mooc.study.163.com/course/USTC-1000029000 一. 实验过程 ...

  9. Linux内核分析实验三----跟踪分析Linux内核的启动过程

    一.Linux内核源代码介绍 1.根目录 arch/x86目录下的代码是我们重点关注的,arch中包括支持不同CPU的源代码. init目录下包含内核启动相关的代码,如main.c(start_ker ...

随机推荐

  1. C# 连接Oracle时报错的问题

    “/”应用程序中的服务器错误. 试图加载格式不正确的程序. (异常来自 HRESULT:0x8007000B) 说明: 执行当前 Web 请求期间,出现未经处理的异常.请检查堆栈跟踪信息,以了解有关该 ...

  2. 报数的golang实现

    报数序列是一个整数序列,按照其中的整数的顺序进行报数,得到下一个数.其前五项如下: . . . . . 1 被读作 "one 1" ("一个一") , 即 11 ...

  3. Java中BufferedReader到底是一个什么类?

    1.java.io.BufferedReader 和 java.io.BufferedWriter 类各拥有8192字符的缓冲区.当BufferedReader在读取文本文件时,会先尽量从文件中读入字 ...

  4. 转://执行impdp时出现ORA-39154错误的解决案例

    问题描述如下导出过程正常:expdp \"/ as sysdba\" tables=user_a.t directory=mydir dumpfile=t.dmp logfile= ...

  5. 【html5】如何让Canvas标签自适应设备

    javascript方法: var oC=document.querySelectorAll('canvas')[0];oC.width=document.documentElement.client ...

  6. go标准库的学习-mime/multipart

    参考:https://studygolang.com/pkgdoc 导入方式: import "mime/multipart" multipart实现了MIME的multipart ...

  7. leetcode 338. Counting Bits,剑指offer二进制中1的个数

    leetcode是求当前所有数的二进制中1的个数,剑指offer上是求某一个数二进制中1的个数 https://www.cnblogs.com/grandyang/p/5294255.html 第三种 ...

  8. MATLAB的两种移位运算

    MATLAB的两种移位运算: 1)circshift矩阵移位 circshift:循环移位数组 语法:B = circshift(A,shiftize) 说明: B  = circshift(A,sh ...

  9. webpack2 项目构建一

    最近工作忙,学习被暂停了,还是网上多看看资料,多学习学习一下,看到一些好的资料,自己想整理一下,这不,webpack2项目构建都还没有弄懂,webpack3就已经发布了,说实话周末现在真不想看书和研究 ...

  10. Linux x64 -- 内核程序(驱动程序)读取任意进程数据实现

    四级页表结构 现在的64位Linux系统中,并没有使用全部的64位地址空间,而是使用其低48位,高16位并没有使用. 其中 39至47这9位用于索引PGD(page global directory) ...