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

简介:

前面粗略分析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);

  10.  

    1. 定义一个complete变量来告诉init线程:kthreads线程已经创建完成。
    2. 从前似乎不是用complete锁,而是用大内核锁。
  11. static noinline void __init_refok rest_init(void)
  12. {
  13.     int pid;
  14.     rcu_scheduler_starting();

     

  15.     /*
  16.      * 我们必须先创建init内核线程,这样它就可以获得pid为1。
  17.      * 尽管如此init线程将会挂起来等待创建kthreads线程。
  18.      * 如果我们在创建kthreadd线程前调度它,就将会出现OOPS。
  19.      */
  20.     kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);

     

    1. 创建kernel_init内核线程,内核的1号进程!!!!!
  21.     numa_default_policy();

     

    1. 设定NUMA系统的内存访问策略为默认
  22.     pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
  23.  

    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();
  24.     kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
  25.     rcu_read_unlock();
  26.     complete(&kthreadd_done);

  27.  

  28.     /*
  29.      * 为让系统运作起来,
  30.      * boot idle线程必须至少执行一次schedule():
  31.      */
  32.     init_idle_bootup_task(current);

     

  33.     preempt_enable_no_resched();

     

  34.     schedule();

     

  35.     preempt_disable();

     

  36.     /*  在抢占禁用时调用cpu_idle */
  37.     cpu_idle();

     

    1. 此时内核本体进入了idle状态,用循环消耗空闲的CPU时间片,该函数从不返回。在有其他进程需要工作的时候,该函数就会被抢占!这个函数因构架不同而异。
  38. }
    
     在以上的函数中,内核创建了两个内核线程,一个是内核线程的管理者,另一个是内核初始化线程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);

  16.  

    1. cad_pid为接收Ctrl-alt-del操作的INT信号的进程ID,此处很明显是设为了init的PID
  17.     smp_prepare_cpus(setup_max_cpus);
  18.     do_pre_smp_initcalls();
  19.     lockup_detector_init();
  20.     smp_init();
  21.     sched_init_smp();

     

    1. 以上代码是在SMP系统做准备,激活所有CPU,并开始SMP系统的调度。
  22.     do_basic_setup();

     

    1. 到此,与构架相关的部分已经初始化完成了,do_basic_setup函数主要是初始化设备驱动,完成其他驱动程序(直接编译进内核的模块)的初始化。内核中大部分的启动数据输出(都是各设备的驱动模块输出)都是这里产生的。
    2. 此函数比较重要,以后会详细分析!
  23.     /* 打开根文件系统中的 /dev/console , 此处不可失败 */
  24.     if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
  25.         printk(KERN_WARNING "Warning: unable to open an initial console.\n");

     

    1. 这是kernel_init(以后的init进程)打开的第一个文件,它也就成为了标准输入。
    2. 这里需要打开 /dev/console,如果没有这个节点,系统就出错。这个错误信息也是经常碰到的。可能的原因是:
    3. 1、制作文件系统的时候忘记创建/dev/console节点
    4. 2、文件系统挂载问题,挂载上的文件系统不是什么都没有,就是挂错了节点。
  26.     (void) sys_dup(0);
  27.     (void) sys_dup(0);

     

    1. 复制两次标准输入(0)的文件描述符(它是上面打开的/dev/console,也就是系统控制台):
    2. 一个作为标准输出(1)
    3. 一个作为标准出错(2)
    4. 现在标准输入、标准输出、标准出错都是/dev/console了。
    5. 这个console在内核启动参数中可以配置为某个串口(ttySn、ttyOn等等),也可以是虚拟控制台(tty0)。所以我们就在串口或者显示器上看到了之后的系统登录提示。
  28.     /*
  29.      * 检查是否有早期用户空间的init程序。如果有,让其执行
  30.      * 
  31.      */
  32.     if (!ramdisk_execute_command)
  33.         ramdisk_execute_command = "/init";
  34.     if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
  35.         ramdisk_execute_command = NULL;
  36.         prepare_namespace();
  37.     }
  38.     /*
  39.      * Ok, 我们已经完成了启动初始化, and
  40.      * 且我们本质上已经在运行。释放初始化用的内存(initmem)段
  41.      * 并开始用户空间的程序..
  42.      */
  43.     init_post();
  44.     return 0;
  45. }
在内核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. }
 











本文转自张昺华-sky博客园博客,原文链接:http://www.cnblogs.com/sky-heaven/p/4533771.html ,如需转载请自行联系原作者
相关文章
|
2月前
|
监控 Linux 开发者
理解Linux操作系统内核中物理设备驱动(phy driver)的功能。
综合来看,物理设备驱动在Linux系统中的作用是至关重要的,它通过与硬件设备的紧密配合,为上层应用提供稳定可靠的通信基础设施。开发一款优秀的物理设备驱动需要开发者具备深厚的硬件知识、熟练的编程技能以及对Linux内核架构的深入理解,以确保驱动程序能在不同的硬件平台和网络条件下都能提供最优的性能。
118 0
|
3月前
|
存储 负载均衡 算法
Linux2.6内核进程调度队列
本篇文章是Linux进程系列中的最后一篇文章,本来是想放在上一篇文章的结尾的,但是想了想还是单独写一篇文章吧,虽然说这部分内容是比较难的,所有一般来说是简单的提及带过的,但是为了让大家对进程有更深的理解与认识,还是看了一些别人的文章,然后学习了学习,然后对此做了总结,尽可能详细的介绍明白。最后推荐一篇文章Linux的进程优先级 NI 和 PR - 简书。
99 0
|
5月前
|
存储 Linux
Linux内核中的current机制解析
总的来说,current机制是Linux内核中进程管理的基础,它通过获取当前进程的task_struct结构的地址,可以方便地获取和修改进程的信息。这个机制在内核中的使用非常广泛,对于理解Linux内核的工作原理有着重要的意义。
211 11
|
2月前
|
监控 Linux 网络安全
Linux命令大全:从入门到精通
日常使用的linux命令整理
617 13
|
3月前
|
Linux 网络安全 数据安全/隐私保护
使用Linux系统的mount命令挂载远程服务器的文件夹。
如此一来,你就完成了一次从你的Linux发车站到远程服务器文件夹的有趣旅行。在这个技术之旅中,你既探索了新地方,也学到了如何桥接不同系统之间的距离。
417 21
|
3月前
|
JSON 自然语言处理 Linux
linux命令—tree
tree是一款强大的Linux命令行工具,用于以树状结构递归展示目录和文件,直观呈现层级关系。支持多种功能,如过滤、排序、权限显示及格式化输出等。安装方法因系统而异常用场景包括:基础用法(显示当前或指定目录结构)、核心参数应用(如层级控制-L、隐藏文件显示-a、完整路径输出-f)以及进阶操作(如磁盘空间分析--du、结合grep过滤内容、生成JSON格式列表-J等)。此外,还可生成网站目录结构图并导出为HTML文件。注意事项:使用Tab键补全路径避免错误;超大目录建议限制遍历层数;脚本中推荐禁用统计信息以优化性能。更多详情可查阅手册mantree。
linux命令—tree
|
1月前
|
监控 Linux Shell
linux命令
常用 Linux 命令汇总
|
3月前
|
监控 Linux
Linux系统中使用df命令详解磁盘使用情况。
`df`命令是Linux系统管理员和用户监控和管理磁盘空间使用的重要工具。掌握它的基本使用方法和选项可以帮助在必要时分析和解决空间相关问题。简洁但功能丰富,`df`命令确保了用户可以快速有效地识别和管理文件系统的空间使用情况。
211 13
|
3月前
|
Unix Linux
linux命令—cd
`cd` 命令是 Linux/Unix 系统中用于切换工作目录的基础命令。支持相对路径与绝对路径,常用选项如 `-L` 和 `-P` 分别处理符号链接的逻辑与物理路径。实际操作中,可通过 `cd ..` 返回上级目录、`cd ~` 回到家目录,或利用 `cd -` 在最近两个目录间快速切换。结合 Tab 补全和 `pwd` 查看当前路径,能显著提升效率。此外,需注意特殊字符路径的正确引用及脚本中绝对路径的优先使用。

热门文章

最新文章