摘自:《嵌入式Linux应用程序开发详解》
用户开机启动 Linux 过程总体上是这样的:
首先当用户打开 PC 的电源时,CPU 将自动进入实模式,并从地址 0xFFFF0 开始自动执行程序代码,这个地址通常是 ROM-BIOS 中的地址。这时 BIOS 进行开机自检,并按 BIOS 中设置的启动设备(通常是硬盘)进行启动,接着启动设备上安装的引导程序lilo 或 grub 开始引导 Linux(也就是启动设备的第一个扇区),这时,Linux 才获得了启动权。
接下来的第二阶段,Linux 首先进行内核的引导,主要完成磁盘引导、读取机器系统数据、实模式和保护模式的切换、加载数据段寄存器以及重置中断描述符表等。
第三阶段执行 init 程序(也就是系统初始化工作) init 程序调用了 rc.sysinit 和 rc 等程序,而 rc.sysinit 和 rc 在完成系统初始化和运行服务的任务后,返回 init。
之后的第四阶段,init 启动 mingetty,打开终端供用户登录系统,用户登录成功后进入了Shell,这样就完成了从开机到登录的整个启动过程。
Linux 启动总体流程图如图所示, 其中的 4 个阶段分别由同步棒隔开。由于第一阶段不涉及 Linux 自身的启动过程,因此,下面分别对第二和第三阶段进行详细讲解。
在 grub 或 lilo 等引导程序成功完成引导 Linux 系统的任务后,Linux 就从它们手中接管了 CPU 的控制权。用户可以从 www.kernel.org 上下载最新版本的源码进行阅读,其目录为:linux-2.6.*.*/arch/i386/boot。在这过程中主要用到该目录下的这几个文件:bootsect.S、setup.S以及 compressed 目录下的 head.S 等。
首先要介绍一下,Linux 的内核通常是压缩过后的,包括如上述提到的那几个重要的汇编程序,它们都是在压缩内核 vmlinuz 中的。因为 Linux 中提供的内核包含了众多驱动和功能,因而比较大,所以在采用压缩内核可以节省大量的空间。
(1)bootsect 阶段
当 grub 读入 vmlinuz 后,会根据 bootsect(正好 512bytes)把它自身和 setup 程序段读到不大于 0x90000 开始的的内存里(注意:在以往的引导协议里是放在 0x90000,但现在有所变化) ,然后 grub 会跳过 bootsect 那 512bytes 的程序段,直接运行 setup 里的第一条指令。就是说 bzImage 里 bootsect 的程序没有再被执行了, bootsect.S 在完成了指令搬移以后就退出了。之后执行权就转到了 setup.S 的程序中。
(2)setup 阶段
setup.S 的主要功能就是利用 ROM BIOS 中断读取机器系统数据, 并将系统参数(包括内存、磁盘等)保存到 0x90000~0x901FF 开始的内存中位置。
此外,setup.S 还将 video.S 中的代码包含进来,检测和设置显示器和显示模式。
最后,它还会设置 CPU 的控制寄存器 CR0(也称机器状态字) ,从而进入 32 位保护模式运行,并跳转到绝对地址为 0x100000(虚拟地址 0xC0000000+0x100000)处。当 CPU 跳到 0x100000 时,将执行 "arch/i386/kernel/head.S"中的 startup_32。
(3)head.S 阶段
当运行到 head.S 时,系统已经运行在保护模式, head.S 完成的一个重要任务就是将内核解压。就如本节前面提到的,内核是通过压缩的方式放在内存中的。head.S 通过调用 misc.c中定义的 decompress_kernel()函数,将内核 vmlinuz 解压到 0x100000 的。
接下来 head.S 程序完成完成寄存器、分页表的初始化工作,但要注意的是,这个 head.S程序与完成解压缩工作的 head.S 程序是不同的,它在源代码中的位置是arch/i386/kernel/head.S。
在完成了初始化之后,head.S 就跳转到 start_kernel()函数中去了。
(4)main.c 阶段
start_kernel()是“init/main.c”中的定义的函数,start kernel()调用了一系列初始化函数,进行内核的初始化工作。要注意的是,在初始化之前系统中断仍然是被屏蔽的,另外内核也处于被锁定状态,以保证只有一个 CPU 用于 Linux 系统的启动。
在 start_kernel()的最后,调用了 init()函数,也就是下面要讲述的 INIT 阶段。
在加载了内核之后,由内核执行引导的第一个进程就是 INIT 进程,该进程号始终是“1” 。INIT 进程根据其配置文件“/etc/inittab”主要完成系统的一系列初始化的任务。由于该配置文件是 INIT 进程执行的惟一依据,因此先对它的格式进行统一讲解。
inittab 文件中除了注释行外,每一行都有如下格式:
id:runlevels:action:process
(1)id
id 是配置记录标识符,由 1~4 个字符组成,对于 getty 或 mingetty 等其他 login 程序项,要求 id 与 tty 的编号相同,否则 getty 程序将不能正常工作。
(2)runlevels
runlevels 是运行级别记录符,一般使用 0~6 以及 S 和 s。其中,0、1、6 运行级别为系统保留:0 作为 shutdown 动作,1 作为重启至单用户模式,6 为重启;S 和 s 意义相同,表示单用户模式,且无需 inittab 文件,因此也不在 inittab 中出现。7~9 级别也是可以使用的,传统的 UNIX 系统没有定义这几个级别。
runlevel 可以是并列的多个值,对大多数 action 来说,仅当 runlevel 与当前运行级别匹配成功才会执行。
(3)action
action 字段用于描述系统执行的特定操作,它的常见设置有:initdefault、sysinit、boot、bootwait、respawn 等。
initdefault 用于标识系统缺省的启动级别。当 init 由内核激活以后,它将读取 inittab 中的initdefault 项,取得其中的 runlevel,并作为当前的运行级别。如果没有 inittab 文件,或者其中没有 initdefault 项,init 将在控制台上请求输入 runlevel。
sysinit、boot、bootwait 等 action 将在系统启动时无条件运行,忽略其中的 runlevel。
respawn 字段表示该类进程在结束后会重新启动运行。
(4)process
process 字段设置启动进程所执行的命令。
以下结合笔者系统中的 inittab 配置文件详细讲解该配置文件完成的功能:
1.确定用户登录模式
在“/etc/inittab”中列出了如下所示的登录模式,主要有单人维护模式、多用户无网络模式、文字界面多用户模式、X-Windows 多用户模式等。其中的单人维护模式(run level 为 1)是类似于 Windows 中的“安全模式”,在这种情况下,系统不加载复杂的模式从而使系统能够正常启动。在这些模式中最为常见的是 3 或 5,其中本系统中默认的为 5,也就是 X-Windows多用户模式。
# Default runlevel. The runlevels used by RHS are:
# 0 - halt (Do NOT set initdefault to this)
# 1 - Single user mode
# 2 - Multiuser, without NFS (The same as 3, if you do not have networking)
# 3 - Full multiuser mode
# 4 - unused
# 5 - X11
# 6 - reboot (Do NOT set initdefault to this)
#
id:5:initdefault:
2.执行内容/etc/rc.d/rc.sysinit
在确定了登录模式之后,就要开始将 Linux 的主机信息读入 Linux 系统,其内容就是文件“/etc/rc.d/rc.sysinit”中的。查看此文件可以看出,在这里确定了默认路径、主机名称、“/etc/sysconfig/network”中所记录的网络信息等。
# System initialization.
si::sysinit:/etc/rc.d/rc.sysinit
3.启动内核的外挂模块及各运行级的脚本
在此,主要是选择模块的型态以进行驱动程序的加载。接下来会根据不同的运行级(runlevel)加载不同的模块,启动系统服务。
l0:0:wait:/etc/rc.d/rc 0
l1:1:wait:/etc/rc.d/rc 1
l2:2:wait:/etc/rc.d/rc 2
l3:3:wait:/etc/rc.d/rc 3
l4:4:wait:/etc/rc.d/rc 4
l5:5:wait:/etc/rc.d/rc 5
l6:6:wait:/etc/rc.d/rc 6
# Trap CTRL-ALT-DELETE
ca::ctrlaltdel:/sbin/shutdown -t3 -r now
# When our UPS tells us power has failed, assume we have a few minutes
# of power left. Schedule a shutdown for 2 minutes from now.
# This does, of course, assume you have powerd installed and your
# UPS connected and working correctly.
pf::powerfail:/sbin/shutdown -f -h +2 "Power Failure; System Shutting Down"
# If power was restored before the shutdown kicked in, cancel it.
pr:12345:powerokwait:/sbin/shutdown -c "Power Restored; Shutdown Cancelled"
# Run gettys in standard runlevels
1:2345:respawn:/sbin/mingetty tty1
2:2345:respawn:/sbin/mingetty tty2
3:2345:respawn:/sbin/mingetty tty3
4:2345:respawn:/sbin/mingetty tty4
5:2345:respawn:/sbin/mingetty tty5
6:2345:respawn:/sbin/mingetty tty6
# Run xdm in runlevel 5
x:5:respawn:/etc/X11/prefdm -nodaemon