第一章 linux系统启动过程分析

 最近准备将自己从事linux系统开发以来遇到的问题及总结写成博文分享出来,希望对自己是一个提高,对他人是一个帮助,当然有理解不到位的地方,欢迎指正。

 Linux系统从用户打开电源直到屏幕出现命令行提示符是一个很复杂的过程,其中要加载很多硬件信息与系统文件,下面就以x86平台上面linux的运行为例,进行分析linux启动过程。

1、加载bios

 BIOS是英文”Basic Input Output System”的缩略语,直译过来后中文名称就是”基本输入输出系统”。其实,它是一组固化到计算机内主板上一个ROM芯片上的程序,它保存着计算机最重要的基本输入输出的程序、系统设置信息、开机后自检程序和系统自启动程序。 其主要功能是为计算机提供最底层的、最直接的硬件设置和控制。早期的BIOS芯片确实是”只读”的,里面的内容是用一种烧录器写入的,一旦写入就不能更改,除非更换芯片。现在的主机板都使用一种叫Flash EPROM的芯片来存储系统BIOS,里面的内容可通过使用主板厂商提供的擦写程序擦除后重新写入,这样就给用户升级BIOS提供了极大的方便。除了bios,主板上还有一个cmos,cmos是记录各项硬件参数且嵌入主板上面的存储器。

 BIOS的功能由两部分组成,分别是POST码和Runtime服务。POST阶段完成后它将从存储器中被清除,而Runtime服务会被一直保留,用于目标操作系统的启动。BIOS两个阶段所做的详细工作如下:

 步骤1:上电自检POST(Power-on self test),主要负责检测系统外围关键设备(如:CPU、内存、显卡、I/O、键盘鼠标等)是否正常。例如,最常见的是内存松动的情况,BIOS自检阶段会报错,系统就无法启动起来;
 步骤2:步骤1成功后,便会执行一段小程序用来枚举本地设备并对其初始化。这一步主要是根据我们在BIOS中设置的系统启动顺序来搜索用于启动系统的驱动器,如硬盘、光盘、U盘、软盘和网络等。我们以硬盘启动为例,BIOS此时去读取硬盘驱动器的第一个扇区(MBR,512字节),然后执行里面的代码。实际上这里BIOS并不关心启动设备第一个扇区中是什么内容,它只是负责读取该扇区内容、并执行。

 至此,BIOS的任务就完成了,此后将系统启动的控制权移交到MBR部分的代码。

2、读取MBR

 MBR(Main Boot Record 主引导记录区)位于整个硬盘的0磁道0柱面1扇区。要理解MBR我们需要首先对硬盘有所了解。硬盘根据台式机和笔记本又分为3.5英寸及2.5英寸的大小,我们以3.5英寸硬盘来说明,硬盘其实是有许多的盘片,机械手臂,磁头和主轴马达组成的,实际的数据都是写在具有磁性物质的盘片片,读写硬盘主要是通过机械手臂上的读取磁头来完成的,一个硬盘根据大小的不同会含有一个或一个以上的盘片。磁盘的最小存储单位是扇区,扇区是以盘片的圆心呈放射状分割出来的磁盘的最小存储单位,在物理组成方面,每个扇区大小为512字节,而所有扇区组成一个圆就是磁道,如果是多盘片的磁盘,所有盘片上的同一个磁道就组成了一个柱面,现在我们知道MBR的大小为512个字节,在512字节的MBR中,主引导程序占用了其中的446个字节,另外的64个字节交给了 DPT(Disk Partition Table硬盘分区表),最后两个字节“55,AA”是分区的结束标志。这个整体构成了硬盘的主引导扇区。

 主引导记录中包含了一段系统启动引导程序boot loader(如grub或lilo)和硬盘分区的一系列参数DPT。其中的系统启动引导程序的主要作用是检查DPT是否正确并且在系统硬件完成自检以后引导具有激活标志的分区上的操作系统,并将控制权交给启动程序。那么什么是DPT呢?

 DPT起始存储的是每一个分区的起始柱面与结束柱面。其实我们刚拿到一块硬盘时什么都不能做,你必须首先对硬盘进行分区,这样硬盘才能被你使用。分区的过程其实就是将硬盘格式化为某种文件系统(FAT32,ntfs,ext2,ext3,ext4等)的过程,以便能被操作系统识别和读取,一块硬盘可以被分成多个分区,但主分区和扩展分区的总数不能超过四个,而每个分区的起始柱面和结束柱面的信息就存储在DPT中,DPT大小为64B,而每个分区的分区信息需要占用16个字节,DPT最多只能存储四个分区的信息,这也是为什么硬盘主分区和扩展分区总数不能超过四个的原因,但这并不意味着我们只能有四个分区,因为我们还有一个扩展分区,有了扩展分区,我们就可以分出多个逻辑分区,而这些逻辑分区的分区表信息都记录在扩展分区中,所以扩展分区相当于逻辑分区的分区表,对于ide硬盘我们可以分区59个逻辑分区(5-63),对于sata硬盘,我们可以分出11个逻辑分区(5-15)。

 下面,我们以一个实例让大家更直观地来了解DPT:
例:80 01 01 00 0B FE BF FC 3F 00 00 00 7E 86 BB 00
在这里我们可以看到,最前面的“80”是一个分区的激活标志,表示系统可引导;“01 01 00”表示分区开始的磁头号为01,开始的扇区号为01,开始的柱面号为00;“0B”表示分区的文件类型是FAT32,其他比较常用的有 04(FAT16)、07(NTFS);“FE BF FC”表示分区结束的磁头号为254,分区结束的扇区号为63、分区结束的柱面号为764;“3F 00 00 00”表示首扇区的相对扇区号为63;“7E 86 BB 00”表示总扇区数为12289622。
最后的四个字节(”主分区的扇区总数”),决定了这个主分区的长度。也就是说,一个主分区的扇区总数最多不超过2的32次方。
如果每个扇区为512个字节,就意味着单个分区最大不超过2TB。再考虑到扇区的逻辑地址也是32位,所以单个硬盘可利用的空间最大也不超过2TB。如果想使用更大的硬盘,只有2个方法:一是提高每个扇区的字节数,二是增加扇区总数。

3、启动bootloader

 Boot Loader 就是在操作系统内核运行之前运行的一段小程序。通过这段小程序,我们可以初始化硬件设备、建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统内核做好一切准备。

 Boot Loader有若干种,其中Grub、Lilo和spfdisk是常见的Loader。我们以grub为例来分析这个引导过程
 grub引导也分为两个阶段stage1阶段和stage2阶段(有些较新的grub又定义了stage1.5阶段):
1)、stage1:stage1是直接被写入到MBR中去的,这样机器一启动检测完硬件后,就将控制权交给了GRUB的代码。也就是上图所看到的前446个字节空间中存放的是stage1的代码。BIOS将stage1载入内存中0x7c00处并跳转执行。stage1(/stage1/start.S)的任务非常单纯,仅仅是将硬盘0头0道2扇区读入内存。而0头0道2扇区内容是源代码中的/stage2/start.S,编译后512字节,它是stage2或者stage1_5的入口。而此时,stage1是没有识别文件系统能力的。
 定位硬盘的0头0道2扇区的过程:
BIOS将stage1载入内存0x7c00处并执行,然后调用BIOS INIT13中断,将硬盘0头0道2扇区内容载入内存0x7000处,然后调用copy_buffer将其转移到内存0x8000处。在定位0头0道2扇区时通常有两种寻址方式:LBA和CHS。
2)、stage2:严格来说这里还应该再区分个stage1.5的,就一并把stage1.5放在这里一起介绍了,我们继续说0头0道2扇区的/stage2/start.S文件,当它的内容被读入到内存之后,它的主要作用就是负责将stage2或stage1.5从硬盘读到内存中。如果是stage2,它将被载入到0x820处;如果是stage1.5,它将被载入到0x2200处。这里的stage2或者stage1_5不是/boot分区/boot/grub目录下的文件,因为这个时候grub还没有能力识别任何文件系统。

情形1:如果start.S加载stage1.5:stage1.5它存放在硬盘0头0道3扇区向后的位置,stage1.5作为stage1和stage2中间的桥梁,stage1.5有识别文件系统的能力,此后grub才有能力去访问/boot分区/boot/grub目录下的 stage2文件,将stage2载入内存并执行。

情形2:如果start.S加载stage2:同样,这个stage2也不是/boot分区/boot/grub目录下的stage2,这个时候start.S读取的是存放在/boot分区Boot Sector的stage2。这种情况下就有一个限制:因为start.S通过BIOS中断方式直接对硬盘寻址(而非通过访问具体的文件系统),其寻址范围有限,限制在8GB以内。因此这种情况需要将/boot分区分在硬盘8GB寻址空间之前。

假如是情形2,我们将/boot/grub目录下的内容清空,依然能成功启动grub;假如是情形1,将/boot/grub目录下stage2删除后,则系统启动过程中grub会启动失败。

4、加载内核

 当stage2被载入内存执行时,它首先会去解析grub的配置文件
/boot/grub/grub.conf,产生grub界面, 然后将initrd加载到内存里,将其中的内容释放到内存中,由于此时真正的跟文件系统还不能载,所以内核在访问真正跟文件系统之前首先会去访问initrd中的内容,然后执行initrd中的init脚本,这时内核将控制权交给了init文件处理。我们简单浏览一下init脚本的内容,发现它也主要是加载各种存储介质相关的设备驱动程序。当所需的驱动程序加载完后,会创建一个根设备,然后将根文件系统rootfs以只读的方式挂载。这一步结束后,释放未使用的内存,转换到真正的根文件系统上面去,同时运行/sbin/init程序,执行系统的1号进程。此后将系统的控制权全部交给/sbin/init进程。
 /sbin/init进程是系统其他所有进程的父进程,当它接管了系统的控制权之后,它首先会去读取/etc/inittab文件来执行相应的脚本进行系统初始化,如设置键盘、字体,装载模块,设置网络等。主要包括以下工作:

1)、执行系统初始化脚本(/etc/rc.d/rc.sysinit),对系统进行基本的配置,以读写方式挂载根文件系统及其它文件系统,到此系统算是基本运行起来了,后面需要进行运行级别的确定及相应服务的启动。rc.sysinit所做的事情(不同的Linux发行版,该文件可能有些差异)如下:

(1)获取网络环境与主机类型。首先会读取网络环境设置文件”/etc/sysconfig/network”,获取主机名称与默认网关等网络环境。

(2)测试与载入内存设备/proc及usb设备/sys。除了/proc外,系统会主动检测是否有usb设备,并主动加载usb驱动,尝试载入usb文件系统。

(3)决定是否启动SELinux。

(4)接口设备的检测与即插即用(pnp)参数的测试。

(5)用户自定义模块的加载。用户可以再”/etc/sysconfig/modules/*.modules”加入自定义的模块,此时会加载到系统中。

(6)加载核心的相关设置。按”/etc/sysctl.conf”这个文件的设置值配置功能。

(7)设置系统时间(clock)。

(8)设置终端的控制台的字形。

(9)设置raid及LVM等硬盘功能。

(10)以读写方式查看检验磁盘文件系统。

(11)进行磁盘配额quota的转换。

(12)重新以读写模式载入系统磁盘。

(13)启动quota功能。

(14)启动系统随机数设备(产生随机数功能)。

(15)清除启动过程中的临时文件。

(16)将启动信息加载到”/var/log/dmesg”文件中。

 当/etc/rc.d/rc.sysinit执行完后,系统就可以顺利工作了,只是还需要启动系统所需要的各种服务,这样主机才可以提供相关的网络和主机功能,因此便会执行下面的脚本。

2)、执行/etc/rc.d/rc脚本。该文件定义了服务启动的顺序是先K后S,而具体的每个运行级别的服务状态是放在/etc/rc.d/rc*.d(=0~6)目录下,所有的文件均是指向/etc/init.d下相应文件的符号链接。rc.sysinit通过分析/etc/inittab文件来确定系统的启动级别,然后才去执行/etc/rc.d/rc.d下的文件。

/etc/init.d-> /etc/rc.d/init.d

/etc/rc ->/etc/rc.d/rc

/etc/rc*.d ->/etc/rc.d/rc*.d

/etc/rc.local-> /etc/rc.d/rc.local

/etc/rc.sysinit-> /etc/rc.d/rc.sysinit

 也就是说,/etc目录下的init.d、rc、rc*.d、rc.local和rc.sysinit均是指向/etc/rc.d目录下相应文件和文件夹的符号链接。我们以启动级别3为例来简要说明一下。

 /etc/rc.d/rc3.d目录,该目录下的内容全部都是以 S 或 K 开头的链接文件,都链接到”/etc/rc.d/init.d”目录下的各种shell脚本。S表示的是启动时需要start的服务内容,K表示关机时需要关闭的服务内容。/etc/rc.d/rc*.d中的系统服务会在系统后台启动,如果要对某个运行级别中的服务进行更具体的定制,通过chkconfig命令来操作,或者通过setup、ntsys、system-config-services来进行定制。如果我们需要自己增加启动的内容,可以在init.d目录中增加相关的shell脚本,然后在rc*.d目录中建立链接文件指向该shell脚本。这些shell脚本的启动或结束顺序是由S或K字母后面的数字决定,数字越小的脚本越先执行。例如,/etc/rc.d/rc3.d /S01sysstat就比/etc/rc.d/rc3.d /S99local先执行。

3)、执行用户自定义引导程序/etc/rc.d/rc.local。其实当执行/etc/rc.d/rc3.d/S99local时,它就是在执行/etc/rc.d/rc.local。S99local是指向rc.local的符号链接。就是一般来说,自定义的程序不需要执行上面所说的繁琐的建立shell增加链接文件的步骤,只需要将命令放在rc.local里面就可以了,这个shell脚本就是保留给用户自定义启动内容的。

4)、完成了系统所有的启动任务后,linux会启动终端或X-Window来等待用户登录。tty1,tty2,tty3…这表示在运行等级1,2,3,4的时候,都会执行”/usr/sbin/getty”,而且执行了6个,所以linux会有6个纯文本终端,getty就是启动终端的命令。
5)、linux的登录主要是由两个文件在控制,/usr/sbin/getty启动终端,等待用户登陆,获得用户名,并进行检查用户名是否存在,然后启动/usr/bin/login,并将用户名传递给/usr/bin/login来获取用户输入密码和并检查用户密码是否正确.
1.getty实现的主要功能是:
1)打开指定的tty;
2)提示用户登录(login:);
3)获得登录用户名;
4)把用户名传递给login命令

2.login实现的主要功能是:
1)先检查是不是超级用户;
2)提示用户输入密码(通过getpass()实现);
3)检查密码并检查是否登录;
4)设置登录的用户的ID和组ID,并设置相应的环境变量.
 除了这6个终端之外,对于桌面版的linux系统还会执行”/etc/X11/prefdm-nodaemon”启动X-Window system,x-window是linux的图形界面,l其实inux本身是没有图形界面,linux桌面版的图形界面的实现只是通过linux下的应用程序实现的。图形界面并不是linux的一部分,linux只是个基于命令行的操作系统。Linux内核为linux系统中的图形界面提供了显示设备驱动。要了解x-window,我们首先要了解几个概念,下面我们专门开启一个章节用于讲解x-window.

5、X window启动流程(linux桌面版的基本图形层)

 在学习linux图形方面的知识时会遇到一些概念,如:X、X11、 Xfree86、WM、KDE、GNOME、QT、QT/E等等。理解它们之间是什么关系,对我们学习来说是非常重要的。

5.1 X是什么?

 X 是一个协议,就像HTTP协议,IP协议一样。这个概念很多初学者甚至学习LINUX有一定时间的人都混淆,一个基于X的应用程序需要运行并显示内容时,它就连接到X服务器,开始用X协议与服务器交谈。比如一个X应用程序(X Client)要在屏幕上输出一个圆,那么它就用X协议对X服务器请求说:你好,服务器,我需要在屏幕上画一个圆。X应用程序只负责告诉X服务器在屏幕的什么地方用什么颜色画一个多大的圆,而具体的”画”的动作,比如这个圆怎样生成,用什么显卡的驱动程序去指挥显卡完成等工作是由X服务器来完成的。X服务器还负责捕获键盘和鼠标的动作。假设X服务器捕获到鼠标的左键被按下了,他就告诉X应用程序:它发现鼠标被按下了,假如X应用程序被设计成当按下鼠标左健后再在屏幕上画一个正方形的话,X应用程式就对X服务器说:请再画一个正方形,当然他会告诉服务器在什么地方用什么颜色画多大的正方形,但不关心具体怎么画–那是服务器的事情。

5.2 X11、X11R6的概念

  X11表示X协议的第11版。X11R6是X协议第11版第六次发行。

5.3 XFree86是什么?

 XFree86 是X Windows server的X11R6执行程序。有了协议就需要具体的软件来实现这个协议。Xfree86是按照X协议的规定来完成X应用程序提交的在屏幕上显示的任务。实现X协议的软件也并不只有XFree86, XFree86只是实现X协议的一个免费X服务器软件。有些甚至能够在WINDOWS上有X服务器运行,这样您能够在linux系统上运行一个X应用程序然后在另一台windows系统上显示。只是在LINUX上最常用的是XFree86。不过现在的linux发行版大多用Xorg了。

XFree86内容包括X服务器,字体, 一个X下的终端程序(xterm),一个简单的窗口管理器(twm),很多有用的小工具, 显示输出驱动和包括键盘鼠标在内的许多输入模块。

5.4 WM(window manager 窗口管理器)

 很多人不知道窗口管理器是什么。他们认为 Gnome 和 KDE或者lxde 是窗口管理器,认为窗口管理器就是能够提供一个面板,能够配置桌面背景,能够设置很多菜单的东西。而其实,窗口管理器只是 Gnome 和 KDE等 的一部分,它的主要功能是:移动窗口,改变窗口大小,图标化(最小化)窗口,改变窗口层叠顺序……

 通常的X客户程序不需要知道有人想移动它,它只知道乖乖听窗口管理器的话。如果没有窗口管理器运行,你的程序会一个堆一个,你没有办法操纵被盖在下面的程序,你只能使用最上面一个程序,而且你不能移动它,你不能改变它的大小,也不能关闭它。这样的系统根本不能用!

 其实你的窗口上的标题,按钮,漂亮的边框,全都是窗口管理器提供的,而不是程序自己的,这样你用窗口管理器就能改变任何窗口的样式了。当你点击关闭窗口的那个按钮,你其实点击的是窗口管理器放在你的程序窗口上面的一个小窗口,发现它受到点击后,窗口管理器就会通知那个程序退出。

 不同的机器在本机显示的窗口,由窗口管理器统一装饰和指挥。比如,窗口管理器决定: xterm窗口上面都应该有四个按钮,一个在左边,点击它会显示窗口操作菜单,另外三个在右边,分别是最大化,最小化和关闭。窗口都使用 7 pixel 厚的边框,窗口首次出现的时候首先在桌面上找一个空位置,如果找不到,就找一个能够最少的遮盖其它窗口的位置……这些都是窗口管理器的职责。

 那么kde,gnome等又是什么呢?有人把 Gnome 和 KDE 叫做窗口管理器,甚至还有人把它们叫做 Xwindow。经常看到有人问:“装哪种 Xwindow 好啊?Gnome 还是 KDE?” 其实你不安装 Gnome 和 KDE 也可以使用 Xwindow,Gnome和Kde都是运行在x-window之上的

 Gnome 和 KDE 是一整套的“桌面系统”,一种很多程序和函数库的集合。它们的设计目的是提供一致的方便的操作方式来满足普通用户的需要。它们不但包含窗口管理器,面板程序,文件管理器,还有其它很多实用程序,比如配置程序,工具条,编辑器,绘图工具…… 其实 Gnome 可以和很多窗口管理器一起使用,在历史上,Gnome 使用过的窗口管理器包括 englightenment, sawmill, sawfish, metacity。KDE 的窗口管理器叫做 kwin。

 你也知道,设计整整一套应用程序:编辑器,绘图程序,浏览器,…… 是非常不容易的。所以它们肯定是不如专用的编辑器,绘图程序,浏览器的。不过要求不太高的用户也可以用它们。

5.5 QT

  QT 泛指QT的所有桌面版本,如:QT/X11,QT Windows,Qt Mac等。由于QT最早是在Linux中随KDE流行而来,所以通常认为QT都是基于linux下的QT/X11。QT是一个完整的C++应用程序开发框架。它包含一个类库,和用于跨平台开发及国际化的工具。Qt API在所有支持的平台上都是相同的,Qt工具在这些平台上的使用方式也一致, 因而Qt应用的开发和部署与平台无关。在桌面系统中,QT程序通常是符合X协议的应用程序,它需要X服务器的支持。

 QT/E 是用于嵌入式Linux系统的QT版本。QT/E中去掉了X lib的依赖而直接工作于Frame Buffer上,因而应用程序处理过程效率比较高。它的运行只要有Framebuffer设备就可以了,不需要X服务器的支持。但其本身运行也是有服务器和客户端的概念。在服务器没有启动前运行QT/E程序,需要在加“-qws”来启动服务器。

5.6 Xwindow基本运行原理

 X Window System采用C/S结构,但和常见的C/S不同。常见的C/S结构中,称提供服务的一方为server,即服务器端(如HTTP服务,FTP服务等),使用服务的称为client,即客户端。但在X Window System中,client是执行程序的一方,在上面执行各种X程序,而server则是负责显示client运行程序的窗口的一方。
 X Window System的组成可以分为X server,X client,X protocol三部分。X server主要控制输入输出,维护字体,颜色等相关资源。它接受输入设备的输入信息并传递给X client,X client将这些信息处理后所返回的信息,也由X server负责输出到输出设备(即我们所见的显示器)上。X server传递给X client的信息称为Event,主要是键盘鼠标输入和窗口状态的信息。X client传递给X server的信息则称为Request,主要是要求X server建立窗口,更改窗口大小位置或在窗口上绘图输出文字等。
X client主要是完成应用程序计算处理的部分,并不接受用户的输入信息,输入信息都是输入给X server,然后由X server以Event的形式传递给X client(这里感觉类似Windows的消息机制,系统接收到用户的输入信息,然后以消息的形式传递给窗口,再由窗口的消息处理过程处理)。X client对收到的Event进行相应的处理后,如果需要输出到屏幕上或更改画面的外观等,则发出Request给X server,由X server负责显示。
 常见的情况是X server与X client都在同一台电脑上运行,但它们也可分别位于网络上不同的电脑上。在X Window System中,X client是与硬件无关的,它并不关心你使用的是什么显卡什么显示器什么键盘鼠标,这些只与X server相关。我们平常安装完XFree86后运行xf86config或xf86cfg进行的配置实际上只是与X server有关,可以说就是配置X server吧,不配置照样可以运行X client程序(如:xeyes -display xserver:0就可以在xserver这台机器上的0号屏幕(屏幕编号displaynumber为0)上显示那对大眼睛了)。
 X Window其实是一种规范,它有很多不同的实现,在Linux系统下最流行的是实现Xorg和XFree86,微软Windows系统下也有X Window的实现,苹果的Mac也是X Window的一种。要了解自己机器上运行的X Window究竟是哪一个,可以使用查看进程的ps命令,ps -e | grep X可以查看当前系统的X环境,使用命令ps -ef | grep X还可以查看启动X环境时使用的参数。
 X protocol就是X server与X client之间通信的协议。X protocol支持现在常用的网络通信协议。我只能测试TCP/IP,可以看到X server侦听在tcp 6000端口上。那X protocol就是位于运输层以上,应该属于应用层吧。
 网上很多介绍X Window的文章都是先让系统进入字符界面,然后手动启动一个X Server。其实这完全没有必要,因为在同一台机器上完全可以运行多个X Server,只需要让每个X Server的display不同即可。那么display究竟是什么?

 在X Window中,可以通过hostname:display_number.screen_number来指定一个屏幕。可以这样理解:一台主机(hostname)可以有多个display(display_number),一个display可以有多个屏幕(screen_number)。所以,display相当于是计算机配备的一套输入输出设备,一般情况下,一台电脑只配一套键盘鼠标和一个显示器,特殊情况下,可以配多个显示器。

 现在问题出来了,我的电脑只有一套键盘鼠标和一个显示器,也就是只有一个display,那又怎么能运行多个X Server呢?那是因为在Linux中,还有虚拟控制台这样的高级特性。只需要同时按下Ctrl+Alt+F1、Ctrl+Alt+F2、…、Ctrl+Alt+F7,就可以在不同的虚拟控制台中进行切换。例如在Ubuntu 14.04中,虚拟控制台1到6运行的getty,也就是字符界面,虚拟控制台7运行的是Xorg。(Fedora中不一样,虚拟控制台1运行的是图形界面,其它的是字符界面。)

 我们可以直接运行X Server程序来启动X Server。/usr/bin/X和Xorg都是X Server程序。其实/usr/bin/X是Xorg的符号链接,用哪一个都是一样的。

 启动X Server的时候可以指定display参数,因为本地可以省略掉hostname和screen_number,所以可以用:0,:1这样的格式来指定display。在我的机器上,本来就有一个X Server在运行,display :0已经被占用了,所以我使用sudo X :1 -retro来在display :1上再运行一个X Server,其中的-retro参数是为了让X Server的背景显示为斜纹,否则背景为纯黑色,那就看不出来是否启动了X Server,按Ctrl+Alt+F7回到display :0,再用ps命令看一下,会发现系统中有两个Xorg在运行,一个运行在虚拟控制台7,一个运行在虚拟控制台8。在新启动的X Server中运行一个GVim看看效果。运行GVim -display :1 -geometry 700x500+20+20,参数指定窗口显示在新启动的X Server上,使用-geometry参数指定窗口的大小和位置。然后按Ctrl+Alt+F8切换虚拟控制台,看到在xserver上启动了gvim,
总结下运行过程:
(1) 用户通过鼠标键盘对X server下达操作命令
(2) X server利用Event传递用户操作信息给X client
(3) X client进行程序运算
(4) X client利用Request传回所要显示的结果
(5) X server将结果显示在屏幕上

5.7 x-window启动过程

 X Server的启动方式有两种,一种是通过显示管理器启动,另一种是手动启动。在前面的例子中,我通过直接运行/usr/bin/X :1来启动了一个X Server。直接启动X Server的方法还有运行startx或者xinit。手动启动X Server的缺点就是启动的X Server不好看。而显示管理器启动的不仅有X Server,还有一大堆的Client程序,构成了一个完整的桌面环境,界面当然就漂亮多了。

 显示管理器(Display Manager)是什么呢?前面我讲到display就是一个电脑配备的一套键盘鼠标和显示器,那么显示管理器就是这一套设备的管理器了。显示管理器可以直接管理这些设备,所以它可以控制X Server的运行,由它来启动X Server那是再合适不过了。系统启动过程是这样的:

 内核加载–>init程序运行–>显示管理器运行–>X Server运行–>显示管理器连接到X Server->显示登录界面–>用户登录后->登录界面关闭->加载桌面环境

 从上面的流程可以看出,显示管理器是X Server的父进程,它负责启动X Server,当X Server启动后,它又变成了X Server的一个Client程序,连接到X Server显示欢迎界面和登录界面,最后,显示管理器又是所有桌面环境的父进程,它负责启动桌面环境需要的其它Client程序。

 在Ubuntu 14.04中,使用lightdm取代了传统的xdm、gdm等显示管理器。简单来说,就是由lightdm负责启动X Server和其它的X程序。lightdm在启动X Server的时候,给X Server加上了-nolisten tcp参数,所以远程计算机就没有办法连接到Ubuntu的桌面了。
 Lightdm的启动流程可以通过log查看,使用sudo dpkg -L lightdm查看该软件包的文件时,发现它的log文件放在/var/log/lightdm文件夹下,如果想了解lightdm启动流程,进入该文件夹查看即可,可以看到lightdm加载配置文件的目录。

接下来我们看一下从控制台进入x的过程。
 从控制台进入X一般是用startx命令。下面就从startx分析起。首先man startx和man xinit可以看到staratx和xinit的使用方法:
startx [[client] options .....] [-- [server] options ....]
xinit [[client] options ] [-- [server] [display] options]
 把上面[client]和[server]分别称为client程序和server程序。man手册里写明其必须以/或者./开头。
 下面看看startx这个脚本,中文为注释,这个脚本是安装x-window-system-core后得到的,都是XFree86,不同发行版的linux里该脚本应该大同小异:

#!/bin/sh
userclientrc=$HOME/.xinitrc #用户的client定义文件
userserverrc=$HOME/.xserverrc #用户的server定义文件
sysclientrc=/usr/X11R6/lib/X11/xinit/xinitrc #系统的client
sysserverrc=/usr/X11R6/lib/X11/xinit/xserverrc #系统的server
defaultclient=/usr/X11R6/bin/xterm #默认的client程序
defaultserver=/usr/X11R6/bin/X #默认的server程序
defaultclientargs="" #下面定义了client和server的参数变量
defaultserverargs=""
clientargs=""
serverargs=""
#如果用户client文件存在则使用用户文件里定义的client,否则使用系统定义的client
if [ -f $userclientrc ]; then
    defaultclientargs=$userclientrc
elif [ -f $sysclientrc ]; then
    defaultclientargs=$sysclientrc
fi
#如果用户server文件存在则使用用户文件里定义的server,否则使用系统定义的server
if [ -f $userserverrc ]; then
    defaultserverargs=$userserverrc
elif [ -f $sysserverrc ]; then
    defaultserverargs=$sysserverrc
fi
#下面循环处理client和server的参数
whoseargs="client"
while [ x"$1" != x ]; do 
#若第一个参数为空,退出循环
    case "$1" in
# '' required to prevent cpp from treating "/*" as a C comment.
    /''*|\./''*)
     #如果$1是/*或者./*形式(xinit程序要求其参数里的client程序和server程序必须以/或./开头,否则会被视为client程序和server程序的参数,见man xinit)
if [ "$whoseargs" = "client" ]; then 
#如果当前是在处理client的参数
#如果clientargs为空,则赋值$1给client变量,也即上面#startx使用方法里的[client]参数
        client="$1"
    else
        clientargs="$clientargs $1" 
        #否则clientargs赋值为$clientargs $1,即上面#startx使用#方法里的options参数
    fi
else 
#当前在处理server的参数,代码的含义同上
    if [ x"$serverargs" = x ]; then
        server="$1"
    else
        serverargs="$serverargs $1"
    fi
fi
;;
--)
#如果$1为--,则表示开始处理server的参数,--为client和server参数的分界
whoseargs="server"
;;
*)
if [ "$whoseargs" = "client" ]; then
#处理给client程序的参数
    clientargs="$clientargs $1"
else 
#处理给server程序的参数
# display must be the FIRST server argument
#屏幕编号必须为第一个给server程序的参数,以:x的形式(x为数字),这可从上面startx和xinit的使用方法的区别看出,xinit多了个[display],这里即过滤出这个[display]。试试看这两个命令:
#xinit /usr/bin/X11/xeyes -display localhost:1 -- /usr/bin/X11/X :1 -dpi 70&
#xinit /usr/bin/X11/xeyes -display localhost:1 -- /usr/bin/X11/X -dpi 70 :1&
#即可看出不把屏幕编号作为第一个server参数的后果
    if [ x"$serverargs" = x ] && expr "$1" : ':[0-9][0-9]*$' > /dev/null 2>&1; then

        display="$1"
    else 
    #处理屏幕编号以外的参数
        serverargs="$serverargs $1"
    fi
fi
;;
esac
shift 
#所有参数左移一次
done
# process client arguments
if [ x"$client" = x ]; then 
#如果client程序为空
# if no client arguments either, use rc file instead
    if [ x"$clientargs" = x ]; then #且clientargs为空,赋值$defaultclientargs给client程序
        client="$defaultclientargs"
    else
        client=$defaultclient 
        #使用默认的client程序
    fi
fi
# process server arguments处理server参数,同上
if [ x"$server" = x ]; then
# if no server arguments or display either, use rc file instead
    if [ x"$serverargs" = x -a x"$display" = x ]; then
        server="$defaultserverargs"
    else
        server=$defaultserver
    fi
fi
#…………省略授权代码若干

xinit $client $clientargs -- $server $display $serverargs 
#把处理过的参数交由xinit程序处理
#…………

 由上面代码可以得出startx主要是置X client和X server所在的位置,并处理相关参数,最后交给xinit处理。可以看出startx 设置X client的位置是先搜寻 HOME/.xinitrc/etc/X11/xinit/xinitrcXserver HOME/.xserverrc,然后是/etc/X11/xinit/xserverrc。这就解释了平常为什么说启动X Window时用户目录下的.xinitrc和.xserverrc文件优先级要高。所以用startx命令启动X时,如果用户目录存在. xinitrc和.xserverrc文件,则实际上等价于命令xinit HOME/.xinitrc HOME/.xserverrc 。如果用户目录不存在那两个文件,则等价于xinit /usr/X11R6/lib/X11/xinit/xinitrc – /usr/X11R6/lib/X11/xinit/xserver。别的情况类推。
 至于xinit,则根据startx传过来的参数启动X server,成功后根据xinitrc启动X client。
 以上即为X Window System的启动过程,startx只是负责一些参数传递,真正的X启动由xinit实现。实际上可以分为启动X server和启动X client两部分。
 下面在用户目录下构造.xinitrc(即X client)和.xserverrc(即X server)文件。在.xserverrc里写入/usr/bin/X11/X :1。.xinitrc里写入/usr/bin/X11/xeyes -display localhost:1。这就是最简单的X server+ X client了,只不过把屏幕编号从默认的0改为了1,这里X server即是/usr/bin/X11/X 程序,X client即是/usr/bin/X11/xeyes 程序。

总结下单机用startx启动过程吧:
 (1) startx置X client和X server的位置,处理参数并调用xinit
 (2) xinit根据传过来的参数启动X server,成功后呼叫X client
 (3) 根据xinitrc设置相关资源,启动窗口管理器,输入法和其他应
  用程序等X client程序。

 但还未搞清楚gnome是怎么起来的!gnome当然属于X client了,看上面启动过程第(3)步。这里分两种情况看吧,第一种是用系统的xinitrc文件。看/etc/X11/xinit/xinitrc文件。里面只包含了. /etc/X11/Xsession一句话。接着看/etc/X11/Xsession这个脚本,只看关键部分吧。最后面有:

SESSIONFILES=$(run_parts $SYSSESSIONDIR)
if [ -n "$SESSIONFILES" ]; then
    for SESSIONFILE in $SESSIONFILES; do
        . $SESSIONFILE
    done
fi
exit 0

接着看run_parts(),位于本文件中间:

run_parts () {
# until run-parts --noexec is implemented
if [ -z "$1" ]; then
    internal_errormsg 
    "run_parts() called without an argument."
fi
if [ ! -d "$1" ]; then
    internal_errormsg
     "run_parts() called, but \"$1\" does not exist or is not a directory."
fi
for F in $(ls $1); do
    if expr "$F" : '[[:alnum:]_-]\+$' > /dev/null 2>&1; then
        if [ -f "$1/$F" ]; then
            echo "$1/$F"
        fi
    fi
done
}

 大概意思就是run_parts () 把$SYSSESSIONDIR目录下的文件名取出来赋值给$SESSIONFILES,然后循环运行该目录下的文件。看看该目录,即 /etc/X11/Xsession.d目录,可以看到几个以数字开头的文件,实际上这些数值就表示了这几个文件被运行的优先级,数字小的优先级高,因为在上面的run_parts () 里是用ls命令显示该目录下的文件,所以前面数字小的被ls时显示在前面,所以执行

for SESSIONFILE in $SESSIONFILES; do
    . $SESSIONFILE
done

 这个for循时也被先执行。看到/etc/X11/Xsession.d目录下有个
55gnome-session_gnomerc文件,里面提到了STARTUP变量。然后运行:

/etc/X11/Xsession.d $ grep STARTUP *

看到50xfree86-common_determine-startup文件。里面有

if [ -z "$STARTUP" ]; then 
#(-z判断指定的字符串长度是否为零,为零则为真)
    if [ -x /usr/bin/x-session-manager ]; then
        STARTUP=x-session-manager
    elif [ -x /usr/bin/x-window-manager ]; then
        STARTUP=x-window-manager
    elif [ -x /usr/bin/x-terminal-emulator ]; then
        STARTUP=x-terminal-emulator
    fi
fi

 即设置启动程序,实际上设置STARTUP变量,如果以上程序都没有找到,则会报错退出,即X环境没有被启动。再运行

/etc/X11/Xsession.d$ grep STARTUP *

 看到优先级最低也即最后被运行的99xfree86-common_start文件,里面只有一句话:

exec $STARTUP

 好了,到这里就启动X client了。总结下这第一种方式的启动过程,简单的说就是依次顺序查找/usr/bin/x-session-manager ,x-window-manager,/usr/bin/x-terminal-emulator 这三个文件。如果存在则启动之,也即X client。如果三个都不存在则报错退出了。看/usr/bin/x-session-manager文件可以看到是个符号连接,最终连接到 /usr/bin/gnome-session,也就是gnome 了。至于在gnome 启动时可能会设置启动输入法等程序,那就归gnome-session管了。可以试着把/usr/bin/x-session- manager 改为指向xfce4-session(如果安装了的话) ,再startx就会启动xfce4环境了。
 下面看第二种情况,即用户目录的 xinitrc文件$HOME/.xinitrc。对比hiweed-debian-desktop_0.55_i386,存在$HOME/.xinitrc文件,在里面有exec xfce4-session。故其X client可以说最主要的x-session-manger是从$HOME/.xinitrc启动的。也就不会经过上面第一种情况的执行过程了。
 xinit 程序同时启动X server和X client,这在单机上还可。要是位于网络上的两台电脑分别是client和server,则xinit就无能为力了。这时就得靠纯“手工”来启动X 了。下面简单的“手工”启动X server和X client:在CUI模式下运行命令:

` X :1&`

 看到了一个灰色的全屏幕和一个鼠标指针,这就是X server了,其屏幕编号为1。下面构造X client,按Ctrl+Alt+F1回到刚才的CUI(Ctrl+Alt+F7对应本机的第一个启动的X server,Ctrl+Alt+F8对应第二个,有人说F7对应屏幕编号为0的X server实际上是不对的,如果第一个启动的屏幕编号为1,第二个启动的编号为0,则F7对应1屏幕,F8对应0屏幕),运行命令:
xeyes -display localhost:1&
然后按Ctrl+Alt+F7,将看到X client也就是xeyes。再回到CUI,运行
X&
开启一个屏幕编号0的X server,CUI下再运行
xterm&
这时Ctrl+Alt+F7对应屏幕编号1;而F8对应屏幕编号0,且其X client为xterm。先退出上面的两个X server,下面复杂点手动启动gnome;首先
X&
然后
gnome-session
 看到的就和用startx 启动的X一样了,这时X server是X这个程序,X client是gnome-session及其启动的窗口管理器等程序
 对于X Window System,搞清楚X server与X client关系很重要。一般X server很简单,就是/usr/bin/X11/X程序;X client则花样繁多,从高级的CDE,GNOME,KDE,到低级一点的只有twm,Window Maker,blackbox等窗口管理器,再到最简陋的只有xterm,rxvt,xeyes等单个x程序。正是由于X client的各种搭配,使得的X Window System看起来多样化。

5.8 跨网络运行X Window System

 一般用来做服务器的系统(Linux,FreeBSD,Solaris等等) 都不会装X server,甚至很多都没有显示器。这样可以在这些系统里安装简单的X client,以GUI的方式远程显示在管理员们所坐的X server里。一般在本地机器起个X server,然后ssh或telnet上运行X client程序显示在本地显示器上,当然,也可用XDMCP(X Display Manager Control Protocol),man xsession里提到/etc/X11/Xsession一般被startx或display manager调用,但有的display manager只调用Xsession而不是xinitrc,故为了startx和display manager两种方式下都可正常启动GUI,最好把X client启动的程序放在Xsession文件里。远程运行X client程序需要设置DISPLAY环境变量,设置为 主机名称:屏幕编号(如192.168.1.2:0,则表示X server是192.168.1.2这台机器上的0号屏幕);或是给X client程序加个—display参数。

1) Windows系统做X server
a) 用ssh或telnet方式
 Windows下面的X server软件有很多种,例如X-win32。在Windows里运行X-win32程序,则相当于本地机器是个X server。远程登录上Debian,运行:
export DISPLAY=192.168.1.2:0
xterm&
 这时即在Windows里的X server里看到了xterm了,至于X client还运行什么程序就看你的需要了,文件管理器阿,资源查看器,gnome-session等。当然,这里X-win32要设置好授权,好像默认是禁止接入控制。
b) XDMCP方式
 常见的Display Manager有xdm,gdm,kdm等。需要修改gdm的配置文件/etc/X11/gdm/gdm.conf,修改[xdmcp]段的Enable=true,使得可以远程登录,在X client运行gdm。
 在X-win32里建一个XDMCP的session,查询方式,填入IP为运行gdm的机器地址。连接,即可看到登录界面)
2) Linux与Linux互联
a) ssh或telnet方式
 在linux 本地起个X server,需要注意授权问题,建立文件/etc/X0.hosts,填入X client的IP192.168.1.1,其中X0.hosts表示本地第0个屏幕允许连接的X client地址,建立X1.hosts文件则是本地第1个屏幕允许连接的X client地址,以此类推,man xserver里有。运行
X&
运行该程序时别加-nolisten参数,否则不会在网络上侦听。
这个时候Ctrl+Alt+F7是X server,返回Ctrl+Alt+F1还可以ssh上X client机器上。
然后登录上X client,运行
xterm –display 192.168.1.2:0
即可在本地的X server里看到xterm了,如果有的话,还可把gnome-session也显示在本地来。
b) XDMCP方式
 在X client里运行gdm(别忘了修改gdm.conf),然后在本地X server的CUI下面运行X -query 192.168.1.1(X client开gdm机器的地址)。
 在linux 里的VMware里做的测试: 在Ctrl+Alt+F1的CUI下正常运行startx&启动GUI,这时 Ctrl+Alt+F7即为X server,X client启动的gnome,然后在这里运行VMware打开Debian虚拟机,并运行gdm。然后回到Ctrl+Alt+F1,运行X :1 -query 192.168.1.1。看到登录界面了吧。这时Ctrl+Alt+F7为我的0号屏幕,里面运行了虚拟机。Ctrl+Alt+F8为1号屏幕,在远程 GUI登录X client。相当于在本地起了两个X server。

6.linux 文件系统的目录结构

 接下来,我想介绍一些linux启动完成之后,系统目录结构的一些东西:

 /bin 基础系统所需要的那些命令位于此目录,也是最小系统所需要的命令;比如 ls、cp、mkdir等命令;功能和/usr/bin类似,这个目录中的文件都是可执行的,普通用户都可以使用的命令。做为基础系统所需要的最基础的命令就是放在这里。

 /boot Linux的内核及引导系统程序所需要的文件,比如 vmlinuz initrd.img 文件都位于这个目录中。在一般情况下,GRUB或LILO系统引导管理器也位于这个目录;

 /dev 设备文件存储目录,比如声卡、磁盘… …

 /etc 系统配置文件的所在地,一些服务器的配置文件也在这里;比如用户帐号及密码配置文件;

  /home 普通用户家目录默认存放目录;

 /lib 库文件存放目录

 /lost+found 在ext2或ext3文件系统中,当系统意外崩溃或机器意外关机,而产生一些文件碎片放在这里。当系统启动的过程中fsck工具会检查这里,并修复已经损坏的文件系统。 有时系统发生问题,有很多的文件被移到这个目录中,可能会用手工的方式来修复,或移到文件到原来的位置上。

 /media 即插即用型存储设备的挂载点自动在这个目录下创建,比如USB盘系统自动挂载后,会在这个目录下产生一个目录;CDROM/DVD自动挂载后,也会在这个目录中创建一个目录,类似cdrom 的目录。这个只有在最新的发行套件上才有,比如Fedora Core 4.0 5.0 等。可以参看/etc/fstab的定义;

 /mnt 这个目录一般是用于存放挂载储存设备的挂载目录的,比如有cdrom 等目录。可以参看/etc/fstab的定义。有时我们可以把让系统开机自动挂载文件系统,把挂载点放在这里也是可以的。主要看/etc/fstab中怎么定义了;比如光驱可以挂载到/mnt/cdrom 。

 /opt 表示的是可选择的意思,有些软件包也会被安装在这里,也就是自定义软件包,比如在Fedora Core 5.0中,OpenOffice就是安装在这里。有些我们自己编译的软件包,就可以安装在这个目录中;通过源码包安装的软件,可以通过 ./configure –prefix=/opt/目录 。

 /proc 操作系统运行时,进程(正在运行中的程序)信息及内核信息(比如cpu、硬盘分区、内存信息等)存放在这里。/proc目录伪装的文件系统proc的挂载目录,proc并不是真正的文件系统,它的定义可以参见 /etc/fstab 。

 /root Linux超级权限用户root的家目录;

 /sbin 大多是涉及系统管理的命令的存放,是超级权限用户root的可执行命令存放地,普通用户无权限执行这个目录下的命令,这个目录和/usr/sbin;/usr/X11R6/sbin或/usr/local/sbin目录是相似的; 我们记住就行了,凡是目录sbin中包含的都是root权限才能执行的。

 /tmp 临时文件目录,有时用户运行程序的时候,会产生临时文件。/tmp就用来存放临时文件的。/var/tmp目录和这个目录相似。

 /usr 这个是系统存放程序的目录,比如命令、帮助文件等。这个目录下有很多的文件和目录。当我们安装一个Linux发行版官方提供的软件包时,大多安装在这里。如果有涉及服务器配置文件的,会把配置文件安装在/etc目录中。/usr目录下包括涉及字体目录/usr/share/fonts ,帮助目录 /usr/share/man或/usr/share/doc,普通用户可执行文件目录/usr/bin 或/usr/local/bin 或/usr/X11R6/bin ,超级权限用户root的可执行命令存放目录,比如 /usr/sbin 或/usr/X11R6/sbin 或/usr/local/sbin 等;还有程序的头文件存放目录/usr/include。

 /var 这个目录的内容是经常变动的,看名字就知道,我们可以理解为vary的缩写,/var下有/var/log 这是用来存放系统日志的目录。/var/www目录是定义Apache服务器站点存放目录;/var/lib 用来存放一些库文件,比如MySQL的,以及MySQL数据库的的存放地;

我们再补充一下一些比较重要的目录的用途;

 /etc/init.d 这个目录是用来存放系统或服务器以System V模式启动的脚本,这在以System V模式启动或初始化的系统中常见。比如Fedora/RedHat;

 /etc/xinit.d 如果服务器是通过xinetd模式运行的,它的脚本要放在这个目录下。有些系统没有这个目录, 比如Slackware,有些老的版本也没有。在Rehat/Fedora中比较新的版本中存在。

 /etc/rc.d 这是Slackware发行版有的一个目录,是BSD方式启动脚本的存放地;比如定义网卡,服务器开启脚本等。

 /etc/X11 是X-Windows相关的配置文件存放地;

比如下面的例子:
[root@localhost ~]# /etc/init.d/sshd start 注:启动sshd服务器

[root@localhost ~]# /etc/init.d/sshd stop 注:停止sshd服务器

启动 sshd:
 这就是典型的sshd 服务器 System V模式启动脚本,通过这运行这个脚本可以启动sshd服务器了。

 /usr/bin 这个目录是可执行程序的目录,普通用户就有权限执行; 当我们从系统自带的软件包安装一个程序时,他的可执行文件大多会放在这个目录。比如安装gaim软件包时。相似的目录是/usr/local/bin; 有时/usr/bin中的文件是/usr/local/bin的链接文件;

 /usr/sbin 这个目录也是可执行程序的目录,但大多存放涉及系统管理的命令。只有root权限才能执行;相似目录是/sbin 或/usr/local/sbin或/usr/X11R6/sbin等;

 /usr/local 这个目录一般是用来存放用户自编译安装软件的存放目录;一般是通过源码包安装的软件,如果没有特别指定安装目录的话,一般是安装在这个目录中。这个目录下面有子目录。自己看看吧。

 /usr/lib 和/lib 目录相似,是库文件的存储目录;

 /usr/share 系统共用的东西存放地,比如 /usr/share/fonts 是字体目录,是用户都共用的吧。

 /usr/share/doc和/usr/share/man帮助文件,也是共用的吧;

 /usr/src 是内核源码存放的目录,比如下面有内核源码目录,比如 linux 、linux-2.xxx.xx 目录等。有的系统也会把源码软件包安装在这里。比如Fedora/Redhat,当我们安装file.src.rpm的时候,这些软件包会安装在/usr/src/redhat相应的目录中。另外Fedhat 4.0 5.0,他的内核源码包的目录位于/usr/src/kernels目录下的某个目录中(只有安装后才会生成相应目录);

 /var/adm 比如软件包安装信息、日志、管理信息等,在Slackware操作系统中是有这个目录的。在Fedora中好象没有;自己看看吧。

 /var/log 系统日志存放,分析日志要看这个目录的东西;

 /var/spool 打印机、邮件、代理服务器等假脱机目录;

7.initrd介绍

 在上面讲解linux系统启动过程第四节中,grub的stage2是先将initrd加载到内存里,执行initrd里面的init进程,在相应的驱动启动之后,才会挂载硬盘加载真正的根文件系统,那么initrd是什么呢?又有什么作用呢?这一小结,我想着重的讲解一下initrd的相关内容。

7.1 什么是 Initrd

 initrd 的英文含义是 boot loader initialized RAM disk,就是由 boot loader 初始化的内存盘。在 linux内核启动前, boot loader 会将存储介质中的 initrd 文件加载到内存,内核启动时会在访问真正的根文件系统前先访问该内存中的 initrd 文件系统。在 boot loader 配置了 initrd 的情况下,内核启动被分成了两个阶段,第一阶段先执行 initrd 文件系统中的”某个文件”,完成加载驱动模块等任务,第二阶段才会执行真正的根文件系统中的 /sbin/init 进程。这里提到的”某个文件”,Linux2.6 内核会同以前版本内核的不同,所以这里暂时使用了”某个文件”这个称呼,后面会详细讲到。第一阶段启动的目的是为第二阶段的启动扫清一切障碍,最主要的是加载根文件系统存储介质的驱动模块。我们知道根文件系统可以存储在包括IDE、SCSI、USB在内的多种介质上,如果将这些设备的驱动都编译进内核,可以想象内核会多么庞大、臃肿。

7.2 initrd的主要用途

1) linux 发行版的必备部件

 linux 发行版必须适应各种不同的硬件架构,将所有的驱动编译进内核是不现实的,initrd 技术是解决该问题的关键技术。Linux 发行版在内核中只编译了基本的硬件驱动,在安装过程中通过检测系统硬件,生成包含安装系统硬件驱动的 initrd,无非是一种即可行又灵活的解决方案。

2) livecd 的必备部件

 同 linux 发行版相比,livecd 可能会面对更加复杂的硬件环境,所以也必须使用 initrd。

3) 制作 Linux usb 启动盘必须使用 initrd

 usb 设备是启动比较慢的设备,从驱动加载到设备真正可用大概需要几秒钟时间。如果将 usb 驱动编译进内核,内核通常不能成功访问 usb 设备中的文件系统。因为在内核访问 usb 设备时, usb 设备通常没有初始化完毕。所以常规的做法是,在 initrd 中加载 usb 驱动,然后休眠几秒中,等待 usb设备初始化完毕后再挂载 usb 设备中的文件系统。

4) 在 linuxrc 脚本中可以很方便地启用个性化bootsplash。

7.3 内核对 Initrd 的处理流程

 为了使读者清晰的了解Linux2.6内核initrd机制的变化,在重点介绍Linux2.6内核initrd之前,先对linux2.4内核的initrd进行一个简单的介绍。Linux2.4内核的initrd的格式是文件系统镜像文件,本文将其称为image-initrd,以区别后面介绍的linux2.6内核的cpio格式的initrd。 linux2.4内核对initrd的处理流程如下:

1) boot loader把内核以及/dev/initrd的内容加载到内存,/dev/initrd是由boot loader初始化的设备,存储着initrd。

2) 在内核初始化过程中,内核把 /dev/initrd 设备的内容解压缩并拷贝到 /dev/ram0 设备上。

3) 内核以可读写的方式把 /dev/ram0 设备挂载为原始的根文件系统。

4) 如果 /dev/ram0 被指定为真正的根文件系统,那么内核跳至最后一步正常启动。

5) 执行 initrd 上的 /linuxrc 文件,linuxrc 通常是一个脚本文件,负责加载内核访问根文件系统必须的驱动, 以及加载根文件系统。

6) /linuxrc 执行完毕,真正的根文件系统被挂载。

7) 如果真正的根文件系统存在 /initrd 目录,那么 /dev/ram0 将从 / 移动到 /initrd。否则如果 /initrd 目录不存在, /dev/ram0 将被卸载。

 在真正的根文件系统上进行正常启动过程 ,执行 /sbin/init。 linux2.4 内核的 initrd 的执行是作为内核启动的一个中间阶段,也就是说 initrd 的 /linuxrc 执行以后,内核会继续执行初始化代码,我们后面会看到这是 linux2.4 内核同 2.6 内核的 initrd 处理流程的一个显著区别。

Linux2.6 内核对 Initrd 的处理流程

 inux2.6 内核支持两种格式的 initrd,一种是前面第 3 部分介绍的 linux2.4 内核那种传统格式的文件系统镜像-image-initrd,它的制作方法同 Linux2.4 内核的 initrd 一样,其核心文件就是 /linuxrc。另外一种格式的 initrd 是 cpio 格式的,这种格式的 initrd 从 linux2.5 起开始引入,使用 cpio 工具生成,其核心文件不再是 /linuxrc,而是 /init,本文将这种 initrd 称为 cpio-initrd。尽管 linux2.6 内核对 cpio-initrd和 image-initrd 这两种格式的 initrd 均支持,但对其处理流程有着显著的区别,下面分别介绍 linux2.6 内核对这两种 initrd 的处理流程。

cpio-initrd 的处理流程

1. boot loader 把内核以及 initrd 文件加载到内存的特定位置。

2. 内核判断initrd的文件格式,如果是cpio格式。

3. 将initrd的内容释放到rootfs中。

 执行initrd中的/init文件,执行到这一点,内核的工作全部结束,完全交给/init文件处理。

image-initrd的处理流程

1. boot loader把内核以及initrd文件加载到内存的特定位置。

2. 内核判断initrd的文件格式,如果不是cpio格式,将其作为image-initrd处理。

3. 内核将initrd的内容保存在rootfs下的/initrd.image文件中。

4. 内核将/initrd.image的内容读入/dev/ram0设备中,也就是读入了一个内存盘中。

5. 接着内核以可读写的方式把/dev/ram0设备挂载为原始的根文件系统。

6. .如果/dev/ram0被指定为真正的根文件系统,那么内核跳至最后一步正常启动。

7. 执行initrd上的/linuxrc文件,linuxrc通常是一个脚本文件,负责加载内核访问根文件系统必须的驱动, 以及加载根文件系统。

8. /linuxrc执行完毕,常规根文件系统被挂载

9. 如果常规根文件系统存在/initrd目录,那么/dev/ram0将从/移动到/initrd。否则如果/initrd目录不存在, /dev/ram0将被卸载。

10. 在常规根文件系统上进行正常启动过程 ,执行/sbin/init。

 通过上面的流程介绍可知,Linux2.6内核对image-initrd的处理流程同linux2.4内核相比并没有显著的变化, cpio-initrd的处理流程相比于image-initrd的处理流程却有很大的区别,流程非常简单,在后面的源代码分析中,读者更能体会到处理的简捷。

cpio-initrd同image-initrd的区别与优势

 没有找到正式的关于cpio-initrd同image-initrd对比的文献,根据使用体验以及内核代码的分析,总结出如下三方面的区别,这些区别也正是cpio-initrd的优势所在:

 cpio-initrd的制作方法更加简单
 cpio-initrd的制作非常简单,通过两个命令就可以完成整个制作过程

#假设当前目录位于准备好的initrd文件系统的根目录下
bash# find . | cpio -c -o > ../initrd.img
bash# gzip ../initrd.img

而传统initrd的制作过程比较繁琐,需要如下六个步骤

#假设当前目录位于准备好的initrd文件系统的根目录下
bash# dd if=/dev/zero of=../initrd.img bs=512k count=5
bash# mkfs.ext2 -F -m0 ../initrd.img
bash# mount -t ext2 -o loop ../initrd.img  /mnt
bash# cp -r  * /mnt
bash# umount /mnt
bash# gzip -9 ../initrd.img

cpio-initrd的内核处理流程更加简化

 通过上面initrd处理流程的介绍,cpio-initrd的处理流程显得格外简单,通过对比可知cpio-initrd的处理流程在如下两个方面得到了简化:
 cpio-initrd并没有使用额外的ramdisk,而是将其内容输入到rootfs中,其实rootfs本身也是一个基于内存的文件系统。这样就省掉了ramdisk的挂载、卸载等步骤。
 cpio-initrd启动完/init进程,内核的任务就结束了,剩下的工作完全交给/init处理;而对于image-initrd,内核在执行完/linuxrc进程后,还要进行一些收尾工作,并且要负责执行真正的根文件系统的/sbin/init。
 cpio-initrd的职责更加重要

 cpio-initrd不再象image-initrd那样作为linux内核启动的一个中间步骤,而是作为内核启动的终点,内核将控制权交给cpio-initrd的/init文件后,内核的任务就结束了,所以在/init文件中,我们可以做更多的工作,而不必担心同内核后续处理的衔接问题。当然目前linux发行版的cpio-initrd的/init文件的内容还没有本质的改变,但是相信initrd职责的增加一定是一个趋势。

  • 2
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值