路基填料不得使用的土:6.2 内核的最后引导过程

来源:百度文库 编辑:偶看新闻 时间:2024/04/30 18:48:27
6.2 内核的最后引导过程
前一章已经介绍了在系统引导最后阶段的内核动作,为方便起见,代码清单6-2列出了位于.../init/main.c的这最后一部分代码。

代码清单6-2 main.c中的最后引导过程

  1. ...  
  2.   if (execute_command) {  
  3.            run_init_process(execute_command);  
  4.            printk(KERN_WARNING "Failed to execute %s.  Attempting "  
  5.                                  "defaults...\n", execute_command);  
  6.   }  
  7.  
  8.   run_init_process("/sbin/init");  
  9.   run_init_process("/etc/init");  
  10.   run_init_process("/bin/init");  
  11.   run_init_process("/bin/sh");  
  12.  
  13.   panic("No init found.  Try passing initoption to kernel."); 

在引导的最后阶段,内核会产生一个名为init的内核线程,代码清单6-2正是该内核线程执行的最后一系列事件。其中,run_init_ process()是一个小巧的execve()函数的包装器 ,后者是一个系统调用,其行为相当有趣。如果在该函数调用过程没有发生错误,execve()就不会返回。调用(该函数的)线程执行所在的内存空间由被调用程序的内存映像覆写(overwrite)。实际上,被调用程序会直接取代调用线程,包括继承其进程ID(PID)。

在Linux内核开发过程中,这样的初始化流程模式保持了很长时间。其实,1.0版本的Linux包含一个类似的模式,本质上,这是用户空间 处理过程的开始。正如代码清单6-2所示,除非Linux内核成功执行了四个程序中的一个,否则内核即告停止(halt),同时显示传入panic()系统调用的信息。如果你从事嵌入式开发已经有了一段时间,特别是有过根文件系统上的开发经验,就会非常熟悉上面的内核panic()及其产生的信息!在Google上搜索panic()产生的错误信息,你会看到关于该问题一页又一页的FAQ。当你掌握了本章的内容之后,就会成为擅长处理这个常见问题的专家。

注意这些程序的一个关键点:它们都必须位于根文件系统中的特定位置,其结构与代码清单6-1相仿。因此,我们至少要满足Linux内核对于init程序的要求,也即init程序在其环境中是可执行的。

看一下代码清单6-2,这意味着至少有一个run_init_process()函数调用必须成功执行,此外内核会试图按一定次序执行这四个程序。正如上面的代码清单所示,如果这四个程序没有一个执行成功,启动中的内核就会调用可怕的panic()函数并立即终止。需要记住的是,.../init/main.c中的这部分代码在引导阶段只执行一次,如果执行没有成功,内核除了通过调用panic()函数进行报错并终止引导过程之外,几乎不再有任何动作。 6.2.1 用户空间下第一个程序

在绝大部分Linux系统中,内核在启动过程中会执行/sbin/init,这也正是代码清单6-2中首先尝试执行/sbin/init的原因。实际上,这也成了用户空间下第一个要运行的程序,可以回顾下面的引导次序:

(1) 挂载根文件系统;

(2) 创建第一个用户空间下的程序,这里即是init。

在代码清单6-1所示的最小根文件系统中,前三次创建用户空间程序的尝试都没有成功,因为在这个文件系统的任何地方都找不到一个名为init的可执行文件。回顾代码清单6-1,我们有一个名为sh的软链接,指向可执行文件busybox。现在你应该意识到这个软链接的作用了:它会使Linux将busybox作为初始化进程加以执行,同时也满足了在用户空间中的一个可执行shell的一般要求 。 6.2.2 解决依赖

简单地将一个init这样的可执行程序添加到用户文件系统中并期望系统引导正常,还远远不够。对于添加到根文件系统的每一个程序,你还必须满足该程序的依赖。绝大部分程序有两种依赖:解析动态链接可执行程序的未定参考(unresolved reference)所需的文件(库);以及应用程序可能需要的外部配置文件或数据文件。对于前者,我们可以用一个工具找出相关文件;而对于后者,只能通过大致了解出现问题的应用程序进行确认。

举个例子有助于理解上述内容。init进程是一个动态链接的可执行程序。要运行init进程,我们需要提供它依赖的相关库,为此专门开发了一个工具:ldd。要明确一个给定程序依赖哪些库,只需对这个二进制文件直接执行交叉版本的ldd命令:

  1. $ ppc_4xxFP-ldd init  
  2.        libc.so.6 => /opt/eldk/ppc_4xxFP/lib/libc.so.6  
  3.        ld.so.1 => /opt/eldk/ppc_4xxFP/lib/ld.so.1  

从这个ldd命令输出可以看到,例子中的PowerPC init可执行文件依赖于两个库,即C标准库(libc.so.6)和Linux动态加载器(ld.so.1)。

要满足可执行文件的第二种依赖,即它可能需要的配置文件和数据文件,需要知道这个子系统是如何工作的。举个例子,init进程要从/etc目录下一个称为inittab的数据文件中读取运行配置,除非你使用的是内置了这些信息的工具,例如6.1.6节描述的那些工具,否则就必须自行提供这些信息。

6.2.3 定制初始化程序

值得注意的是,对于启动过程中所执行的初始化程序,开发人员是可以控制的,其实现方法就是通过使用内核命令行参数。由代码清单6-2中对panic()函数调用所用到的字符串信息可以得到一些提示,下面的命令可能就是开发人员指定init进程时会用到的:

  1. console=ttyS0,115200 ip=bootp root=/dev/nfs init=/sbin/myinit 
要用这种方式在内核命令行中指定init=,必须在/sbin目录下提供一个名为myinit的二进制可执行文件,这将是内核引导过程结束之际取得系统控制权的第一个进程。