前面粗略分析start_kernel函数,此函数中基本上是对内存管理和各子系统的数据结构初始化。在内核初始化函数start_kernel执行到最后,就是调用rest_init函数,这个函数的主要使命就是创建并启动内核线程init。这个函数虽然意思为剩下的初始化,但是这个“剩下”的可是内容颇多,下面详细分析如下:

  1. /*
  2. * 我们必须确定在一个非__init函数或
  3. * 其他根线程(root thread)和初始化线程(init thread)间的竞态。
  4. * (这种竞态可能导致start_kernel在根线程运作到cpu_idle前被free_initmem“收割”。)
  5. *
  6. *
  7. *  gcc-3.4 偶尔会将这个函数作为内联函数, 所以使用了noinline.
  8. */
  9. static __initdata DECLARE_COMPLETION(kthreadd_done);
    1. 定义一个complete变量来告诉init线程:kthreads线程已经创建完成。
    2. 从前似乎不是用complete锁,而是用大内核锁。
  10. static noinline void __init_refok rest_init(void)
  11. {
  12. int pid;
  13. rcu_scheduler_starting();
  14. /*
  15. 我们必须先创建init内核线程,这样它就可以获得pid为1。
  16. 尽管如此init线程将会挂起来等待创建kthreads线程。
  17. 如果我们在创建kthreadd线程前调度它,就将会出现OOPS。
  18. */
  19. kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
    1. 创建kernel_init内核线程,内核的1号进程!!!!!
  20. numa_default_policy();
    1. 设定NUMA系统的内存访问策略为默认
  21. pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
    1. 创建kthreadd内核线程,它的作用是管理和调度其它内核线程。
    2. 它循环运行一个叫做kthreadd的函数,该函数的作用是运行kthread_create_list全局链表中维护的内核线程。
    3. 调用kthread_create创建一个kthread,它会被加入到kthread_create_list 链表中;
    4. 被执行过的kthread会从kthread_create_list链表中删除;
    5. 且kthreadd会不断调用scheduler函数让出CPU。此线程不可关闭。
     
    上面两个线程就是我们平时在Linux系统中用ps命令看到:

    1. tekkaman@Super-MAGI:~/development/analyze/linux-3.0$ ps -A
    2. PID TTY TIME CMD
    3. 1 ? 00:00:00 init
    4. 2 ? 00:00:00 kthreadd
    5. ......

    rcu_read_lock();

  22. kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
  23. rcu_read_unlock();
  24. complete(&kthreadd_done);
  25. /*
  26. * 为让系统运作起来,
  27. * boot idle线程必须至少执行一次schedule():
  28. */
  29. init_idle_bootup_task(current);
  30. preempt_enable_no_resched();
  31. schedule();
  32. preempt_disable();
  33. /*  在抢占禁用时调用cpu_idle */
  34.  cpu_idle();
    1. 此时内核本体进入了idle状态,用循环消耗空闲的CPU时间片,该函数从不返回。在有其他进程需要工作的时候,该函数就会被抢占!这个函数因构架不同而异。
  35. }
     在以上的函数中,内核创建了两个内核线程,一个是内核线程的管理者,另一个是内核初始化线程init,后者是我们分析内核启动需要关注的,这个线程继续做系统的初始化(其中就包含了设备驱动系统):

  1. 下面这个函数就是内核init线程运行的函数,它将完成设备驱动程序的初始化,并调用init_post函数启动用户空间的init进程。
  1. static int __init kernel_init(void * unused)
  2. {
  3. /*
  4. * 等待kthreadd的启动完成.
  5. */
  6. wait_for_completion(&kthreadd_done);
  7. /*
  8. * init可以在任何节点(node)分配到内存页
  9. */
  10. set_mems_allowed(node_states[N_HIGH_MEMORY]);
  11. /*
  12. * init可以在任何CPU上运行.
  13. */
  14. set_cpus_allowed_ptr(current, cpu_all_mask);
    1. 增加当前进程的CPU亲和力,使所有的CPU(如果是SMP)都可以运行本线程。
    2. 线程可以被迁移到被设置掩码的CPU上运行,但如果在位掩码中删除该CPU位,此线程就不会在那个CPU上运行。
  15. cad_pid = task_pid(current);
    1. cad_pid为接收Ctrl-alt-del操作的INT信号的进程ID,此处很明显是设为了init的PID
  16. smp_prepare_cpus(setup_max_cpus);
  17. do_pre_smp_initcalls();
  18. lockup_detector_init();
  19. smp_init();
  20. sched_init_smp();
    1. 以上代码是在SMP系统做准备,激活所有CPU,并开始SMP系统的调度。
  21.     do_basic_setup();
    1. 到此,与构架相关的部分已经初始化完成了,do_basic_setup函数主要是初始化设备驱动,完成其他驱动程序(直接编译进内核的模块)的初始化。内核中大部分的启动数据输出(都是各设备的驱动模块输出)都是这里产生的。
    2. 此函数比较重要,以后会详细分析!
  22. /* 打开根文件系统中的 /dev/console , 此处不可失败 */
  23. if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
  24. printk(KERN_WARNING "Warning: unable to open an initial console.\n");
    1. 这是kernel_init(以后的init进程)打开的第一个文件,它也就成为了标准输入。
    2. 这里需要打开 /dev/console,如果没有这个节点,系统就出错。这个错误信息也是经常碰到的。可能的原因是:
    3. 1、制作文件系统的时候忘记创建/dev/console节点
    4. 2、文件系统挂载问题,挂载上的文件系统不是什么都没有,就是挂错了节点。
  25.     (void) sys_dup(0);
  26.     (void) sys_dup(0);
    1. 复制两次标准输入(0)的文件描述符(它是上面打开的/dev/console,也就是系统控制台):
    2. 一个作为标准输出(1)
    3. 一个作为标准出错(2)
    4. 现在标准输入、标准输出、标准出错都是/dev/console了。
    5. 这个console在内核启动参数中可以配置为某个串口(ttySn、ttyOn等等),也可以是虚拟控制台(tty0)。所以我们就在串口或者显示器上看到了之后的系统登录提示。
  27. /*
  28. * 检查是否有早期用户空间的init程序。如果有,让其执行
  29. *
  30. */
  31. if (!ramdisk_execute_command)
  32. ramdisk_execute_command = "/init";
  33. if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
  34. ramdisk_execute_command = NULL;
  35. prepare_namespace();
  36. }
  37. /*
  38. * Ok, 我们已经完成了启动初始化, and
  39. * 且我们本质上已经在运行。释放初始化用的内存(initmem)段
  40. * 并开始用户空间的程序..
  41. */
  42.     init_post();
  43. return 0;
  44. }

在内核init线程的最后执行了init_post函数,在这个函数中真正启动了用户空间进程init,详解如下:

  1. /* 这是一个非__init函数。强制让它为非内联函数,以防 gcc
  2. * 让它内联到init()中并成为init.text段的一部分。
  3. */
    1. 从此函数名可知,这个函数是运行在用户空间的init程序之前
  4. static noinline int init_post(void)
  5. {
  6. /* 在释放内存前,必须完成所有的异步 __init 代码 */
  7. async_synchronize_full();
  8. free_initmem();
    1. 释放所有init.* 段中的内存。
  9. mark_rodata_ro();
    1. 通过修改页表,保证只读数据段为只读属性。大部分构架为空函数。
  10. system_state = SYSTEM_RUNNING;
    1. 设置系统状态为运行状态
  11. numa_default_policy();
    1. 设定NUMA系统的内存访问策略为默认
  12. current->signal->flags |= SIGNAL_UNKILLABLE;
    1. 设置当前进程(init)为不可以杀进程(忽略致命的信号)
  13. if (ramdisk_execute_command) {
  14. run_init_process(ramdisk_execute_command);
  15. printk(KERN_WARNING "Failed to execute %s\n",
  16. ramdisk_execute_command);
  17. }
    1. 如果ramdisk_execute_command有指定的init程序,就执行它。
  18. /*
  19. * 我们尝试以下的每个函数,直到函数成功执行.
  20. *
  21. * 如果我们试图修复一个真正有问题的设备,
  22. * Bourne shell 可以替代init进程。
  23. */
  24. if (execute_command) {
  25. run_init_process(execute_command);
  26. printk(KERN_WARNING "Failed to execute %s. Attempting "
  27. "defaults...\n", execute_command);
  28. }
    1. 如果execute_command有指定的init程序,就执行它。
  29. run_init_process("/sbin/init");
  30. run_init_process("/etc/init");
  31. run_init_process("/bin/init");
  32. run_init_process("/bin/sh");
  33. panic("No init found. Try passing init= option to kernel. "
  34. "See Linux Documentation/init.txt for guidance.");
    1. 在检查完ramdisk_execute_command和execute_command为空的情况下,顺序执行以下初始化程序:如果都没有找到就打印错误信息。这也是我们做系统移植的时候经常碰到的错误信息,出现这个信息很有可能是:
    2. 1、你的启动参数配置有问题,通过 指定了init程序,但是没有找到,且默认的那四个程序也不在文件系统中。
    3. 2、文件系统挂载有问题,文件不存在
    4. 3、init程序没有执行权限
    1. 至此,内核的初始化结束,正式进入了用户空间的初始化过程!!
  35. }
 

Linux内核源码分析--内核启动之(5)Image内核启动(rest_init函数)(Linux-3.0 ARMv7)【转】的更多相关文章

  1. 鸿蒙内核源码分析(进程管理篇) | 谁在管理内核资源 | 百篇博客分析OpenHarmonyOS | v2.07

    百篇博客系列篇.本篇为: v02.xx 鸿蒙内核源码分析(进程管理篇) | 谁在管理内核资源 | 51.c.h .o 进程管理相关篇为: v02.xx 鸿蒙内核源码分析(进程管理篇) | 谁在管理内核 ...

  2. Linux内核源码分析 day01——内存寻址

    前言 Linux内核源码分析 Antz系统编写已经开始了内核部分了,在编写时同时也参考学习一点Linux内核知识. 自制Antz操作系统 一个自制的操作系统,Antz .半图形化半命令式系统,同时嵌入 ...

  3. Linux内核源码分析之setup_arch (四)

    前言 Linux内核源码分析之setup_arch (三) 基本上把setup_arch主要的函数都分析了,由于距离上一篇时间比较久了,所以这里重新贴一下大致的流程图,本文主要分析的是bootmem_ ...

  4. 鸿蒙内核源码分析(根文件系统) | 先挂到`/`上的文件系统 | 百篇博客分析OpenHarmony源码 | v66.01

    百篇博客系列篇.本篇为: v66.xx 鸿蒙内核源码分析(根文件系统) | 先挂到/上的文件系统 | 51.c.h.o 文件系统相关篇为: v62.xx 鸿蒙内核源码分析(文件概念篇) | 为什么说一 ...

  5. 鸿蒙内核源码分析(文件概念篇) | 为什么说一切皆是文件 | 百篇博客分析OpenHarmony源码 | v62.01

    百篇博客系列篇.本篇为: v62.xx 鸿蒙内核源码分析(文件概念篇) | 为什么说一切皆是文件 | 51.c.h.o 本篇开始说文件系统,它是内核五大模块之一,甚至有Linux的设计哲学是" ...

  6. 鸿蒙内核源码分析(GN应用篇) | GN语法及在鸿蒙的使用 | 百篇博客分析OpenHarmony源码 | v60.01

    百篇博客系列篇.本篇为: v60.xx 鸿蒙内核源码分析(gn应用篇) | gn语法及在鸿蒙的使用 | 51.c.h.o 编译构建相关篇为: v50.xx 鸿蒙内核源码分析(编译环境篇) | 编译鸿蒙 ...

  7. 鸿蒙内核源码分析(ELF格式篇) | 应用程序入口并不是main | 百篇博客分析OpenHarmony源码 | v51.04

    百篇博客系列篇.本篇为: v51.xx 鸿蒙内核源码分析(ELF格式篇) | 应用程序入口并不是main | 51.c.h.o 加载运行相关篇为: v51.xx 鸿蒙内核源码分析(ELF格式篇) | ...

  8. 鸿蒙内核源码分析(信号生产篇) | 信号安装和发送过程是怎样的? | 百篇博客分析OpenHarmony源码 | v48.03

    百篇博客系列篇.本篇为: v48.xx 鸿蒙内核源码分析(信号生产篇) | 年过半百,依然活力十足 | 51.c.h .o 进程管理相关篇为: v02.xx 鸿蒙内核源码分析(进程管理篇) | 谁在管 ...

  9. 鸿蒙内核源码分析(特殊进程篇) | 龙生龙,凤生凤,老鼠生儿会打洞 | 百篇博客分析OpenHarmony源码 | v46.02

    百篇博客系列篇.本篇为: v46.xx 鸿蒙内核源码分析(特殊进程篇) | 龙生龙凤生凤老鼠生儿会打洞 | 51.c.h .o 进程管理相关篇为: v02.xx 鸿蒙内核源码分析(进程管理篇) | 谁 ...

  10. 鸿蒙内核源码分析(源码注释篇) | 鸿蒙必定成功,也必然成功 | 百篇博客分析OpenHarmony源码 | v13.02

    百篇博客系列篇.本篇为: v13.xx 鸿蒙内核源码分析(源码注释篇) | 鸿蒙必定成功,也必然成功 | 51.c.h .o 几点说明 kernel_liteos_a_note | 中文注解鸿蒙内核 ...

随机推荐

  1. 自学Zabbix14.1 二次开发API

    点击返回:自学Zabbix之路 点击返回:自学Zabbix4.0之路 点击返回:自学zabbix集锦 自学Zabbix14.1 二次开发API Zabbix API我们可以做很多,自己开发web界面. ...

  2. 【BZOJ2875】【NOI2012】随机数生成器(矩阵快速幂)

    [BZOJ2875]随机数生成器(矩阵快速幂) 题面 Description 栋栋最近迷上了随机算法,而随机数是生成随机算法的基础.栋栋准备使用线性同余法(Linear Congruential Me ...

  3. 添加AD RMS role时,提示密码不能被验证The password could not be validated

    "The password could not be validated" when attempting to provision an AD RMS server. Sympt ...

  4. 用powershell 批量卸载 windows 更新

    $KBID = "KB958488" $KBID1 = "KB976902" cls function Remove-Update { $HotFixes = ...

  5. .net连接ORACLE数据库

    这段时间维护客户的一个系统,该系统使用的是ORACLE数据库,之前开发的时候用的都是MSSQL,并没有使用过ORACLE.这两种数据库虽然都是关系型数据库,但是具体的操作大有不同,这里作下记录. 连接 ...

  6. ASP.NET Session的实现原理分析

    ASP.NET Session的实现原理分析 用户向服务器提交请求时,服务器都会给每个用户分配一个SessionId,保存在用户浏览器的Cookies中,SessionId是全局的,也就是说只要Coo ...

  7. typescript接口(学习笔记非干货)

    typescript的核心原则之一就是对所具有的shape类型检查结构性子类型化 One of the core principles of typescript is to check struct ...

  8. 跟我一起使用electron搭建一个文件浏览器应用吧(二)

    这个文件浏览器应用可以具备以下两种功能噢- This file browser application can have the following two functions. 一:用户浏览文件夹和 ...

  9. 有趣的async

    在项目的开发过程中,同步异步是一个很重要的概念.但是在js中,又会有稍微的不同. 依据微软的MSDN上的解说: (1) 同步函数:当一个函数是同步执行时,那么当该函数被调用时不会立即返回,直到该函数所 ...

  10. 利用千人基因组数据库查看SNP在不同地区、国家、洲的频率及个数

    首先,进入千人基因组数据库的网站:https://www.ncbi.nlm.nih.gov/variation/tools/1000genomes/ 如下图所示,在数据库的框框里输入我们感兴趣的SNP ...