本文目錄:
#14.1 按下電源和bios階段
14.2 MBR和各種bootloader階段
14.2.1 boot loader
14.2.2 分割區表
#14.2.3 採用VBR/EBR方式引導作業系統
14.3 grub階段
#14.3.1 使用grub2時的啟動過程
#14.3.2 使用傳統grub時的啟動過程
14.4 核心載入階段
#14.4.1 載入init ramdisk
14.4.2 initrd
#14.4.3 initramfs
#14.5 作業系統初始化
#14.5.1 運行等級
14.5.2 系統環境初始化
#14.5 .3 運行等級環境初始化
14.6 終端初始化與登入系統
##14.6. 1 終端初始化
14.6.2 登入程序
##電腦啟動分為核心載入前、載入時和加載後3個大階段,這3個大階段又可以分成許多小階段,本文將非常細化分析每一個重要的小階段。核心載入前的階段和作業系統無關,Linux或Windows在這部分的順序是一樣的。由於使用anaconda安裝Linux時,預設的圖形介面是不支援GPT分區的,即使是目前最新的CentOS 7.3也仍然不支持,所以在本文中主要介紹傳統BIOS平台(MBR方式)的啟動方式(其實是本人愚笨,看不懂uefi啟動方式)。
在核心載入時和載入後階段,由於CentOS 7採用的是systemd,和CentOS 5或CentOS 6的sysV風格的init大不相同,所以本文也只介紹sysV風格的init。
14.1 按下電源和bios階段
bios是按下開機鍵後第一個運行的程序,它會讀取CMOS中的信息,以了解
部分硬體的信息,例如硬件自我檢測(post)、硬體上的時間、硬碟大小和型號等。其實,手動進入bios介面看到的訊息,都是在這階段取得到的,如下圖。對本文來說,最重要的還是獲取到了啟動設備以及它們的啟動順序(順序從上到下)資訊。
當硬體檢測和資訊獲取完畢,開始初始化硬件,最後從排在第一位的啟動設備中讀取MBR,如果第一個啟動設備中沒有找到合理的MBR,則繼續從第二個啟動設備中查找,直到找到正確的MBR。
14.2 MBR和各種bootloader階段
MBR是主開機記錄,位於磁碟的第一個磁區,和分割區無關,和作業系統無關,bios一定會讀取MBR中的記錄。
在MBR中儲存了bootloader/分區表/BRID。 bootloader佔用446個字節,用於引導載入;分區表佔用64個字節,每個主分區或擴展分區佔用16個字節,如果16個字節中的第一個字節為0x80,則表示該分割區為啟動的分割區(活動分割區),且只允許有一個啟動的分割區;最後2個位元組是BRID(boot record ID),它固定為0x55AA,用來識別該儲存設備的MBR是否為合理有效的MBR,如果bios讀取MBR發現最後兩個位元組不是0x55AA,就會讀取下一個啟動設備。
#MBR中的bootloader只佔用446位元組,所以可儲存的程式碼有限,能載入引導的東西也有限,所以在磁碟的不同位置上設計了多種boot loader。以下將說明各種情況。
在建立檔案系統時,是否還記得有些分割區的第一個block是boot sector?這個啟動磁區也放了boot loader,尺寸也很有限。 如果是主分區上的boot sector,則該段boot loader所在扇區稱為VBR(volumn boot record),如果是邏輯分區上的boot sector,則該段boot loader所在扇區稱為EBR(Extended boot sector)。但很不幸,這兩種方式的boot loader都很少被使用上了,因為它們很不方便,加上後面出現了啟動管理器(LILO和GRUB),它們就被遺忘了。但即使如此,在分區中還是存在boot sector。
硬碟分割區的好處之一就是可以在不同的分割區中安裝不同的作業系統,但boot loader必須要知道每個作業系統具體是在哪個分割區。
分區表的長度只有64個位元組,裡面又分成四項,每項16個位元組。所以,一個硬碟最多只能分四個主分割區。
每個主分割表項的16個位元組,都由6個部分組成:
(1).第1個位元組:只能為0或0x80 。單磁碟只能有一個主分割區是啟動的。
(2).第2-4個位元組:主分割區第一個磁區的實體位置(柱面、磁頭、磁區號等等)。
(3).第5個位元組:主分割區類型。
(4).第6-8個位元組:主分割區最後一個磁區的實體位置。
(5).第9-12位元組:該主分割區第一個磁區的邏輯位址。
(6).第13-16位元組:主分割區的磁區總數。
最後的四個位元組"主分割區的區總數",決定了這個主分割區的長度。也就是說,一個主分割區的區總數最多不超過2的32次方。如果每個磁區為512個字節,就表示單一分割區最大不超過2TB。
暫且先不討論grub如何管理啟動作業系統的,以VBR和EBR引導操作系統為例。
當bios讀取到MBR中的boot loader後,會繼續讀取分區表。分為兩種情況:
(1)如果尋找分割表時發現某個主分割表的第一個位元組是0x80,也就是啟動的分割區,那麼就說明作業系統裝在了該主分割區,然後執行已載入的MBR中的boot loader程式碼,載入該啟動主分割區的VBR中的boot loader,至此,控制權就交給了VBR的boot loader了;
(2)如果作業系統不是裝在主分割區,那麼肯定是裝在邏輯分割區中,所以查找完主分割表後會繼續找擴充分割區表,直到找到EBR所在的分割區,然後MBR中的boot loader將控制權交給該EBR的boot loader。
也就是說,如果一塊硬碟上裝了多個作業系統,那麼boot loader會分佈在多個地方,可能是VBR,也可能是EBR,但MBR是一定有的,這是被bios給"綁定"了的。在裝LINUX作業系統時,其中有一個步驟就是詢問你MBR裝在哪裡的,但這個MBR並非一定真的是MBR,可能是MBR,也可能是VBR,還可能是EBR,並且想要單磁碟多系統共存,則MBR一定不能被覆蓋(此處不考慮grub)。
如下圖,是我測試單磁碟裝3個作業系統時的分割結構。其中/dev/sda{1,2,3}為第一個CentOS 6系統,/dev/sda{5,6,7}是第二個CentOS 7系統,/dev/sda{8,9,10}是第三個CentOS 6系統,每一個作業系統的分區序號從前向後都是/boot分區、根分區、swap分區。
再看下圖,是裝第三個作業系統時的詢問boot loader安裝位置的步驟。
裝第一個作業系統時,boot loader可以裝在/dev/sda上,也可以選擇裝在/dev/sda1上,這時裝的是MBR和VBR,任選一個都會將另一個也裝上,從第二個作業系統開始,裝的是EBR而非MBR,且應該指定boot loader位置(如/dev/sda5和/dev/sda8),否則預設選項是裝在/dev/sda上,但這會覆蓋原有的MBR。
另外,在指定boot loader安裝路徑的下方,還有一個方框是作業系統列表,這就是作業系統選單,其中可以指定預設的作業系統,這裡的預設指的是MBR預設跳轉到哪個VBR或EBR上。
所以,MBR/VBR與EBR之間的跳轉關係如下圖。
使用這種方式的選單管理作業系統啟動,無需什麼stage1,stage1.5和stage2的概念,只要跳到了分區上的VBR或EBR,那麼直接就可以載入引導該分割區上的作業系統。
但是,這種管理作業系統啟動的選單已經沒有意義了,現在都是使用grub來管理,所以裝第二個作業系統或第n個作業系統時不手動指定boot loader安裝位置,覆蓋掉MBR也無所謂,想要實現單磁碟多系統共存所需要做的,僅僅只是修改grub的設定檔而已。
使用grub管理引導選單時,VBR/EBR就毫無用處了,具體的見下文。
使用grub管理啟動,則MBR中的boot loader是由grub程式安裝的,此外還會安裝其他的boot loader 。 CentOS 6使用的是傳統的grub,而CentOS 7使用的是grub2。
如果使用的是傳統的grub,則安裝的boot loader為stage1、stage1_5和stage2,如果使用的是grub2,則安裝的是boot.img和core.img。傳統grub和grub2的差別還是挺大的,所以下面分開解釋,如果對於grub有不理解之處,請參見我的另一篇文章grub2詳解。
grub2程式安裝grub後,會在/boot/grub2/i386-pc/目錄下生成boot.img和core.img文件,另外還有一些模組文件,其中包括文件系統類別的模組。
[root@xuexi ~]# find /boot/grub2/i386-pc/ -name '*.img' -o -name "*fs.mod" -o -name "*ext[0-9].mod" /boot/grub2/i386-pc/affs.mod/boot/grub2/i386-pc/afs.mod/boot/grub2/i386-pc/bfs.mod/boot/grub2/i386-pc/btrfs.mod/boot/grub2/i386-pc/cbfs.mod/boot/grub2/i386-pc/ext2.mod # ext2、ext3和ext4都使用该模块/boot/grub2/i386-pc/hfs.mod/boot/grub2/i386-pc/jfs.mod/boot/grub2/i386-pc/ntfs.mod/boot/grub2/i386-pc/procfs.mod/boot/grub2/i386-pc/reiserfs.mod/boot/grub2/i386-pc/romfs.mod/boot/grub2/i386-pc/sfs.mod/boot/grub2/i386-pc/xfs.mod/boot/grub2/i386-pc/zfs.mod/boot/grub2/i386-pc/core.img /boot/grub2/i386-pc/boot.img
其中boot.img就是安装在MBR中的boot loader。当然,它们的内容是不一样的,安装boot loader时,grub2-install会将boot.img转换为合适的代码写入MBR中的boot loader部分。
core.img是第二段Boot loader段,grub2-install会将core.img转换为合适的代码写入到紧跟在MBR后面的空间,这段空间是MBR之后、第一个分区之前的空闲空间,被称为MBR gap,这段空间最小31KB,但一般都会是1MB左右。
实际上,core.img是多个img文件的结合体。它们的关系如下图:
这张图解释了开机过程中grub2阶段的所有过程,boot.img段的boot loader只有一个作用,就是跳转到core.img对应的boot loader的第一个扇区,对于从硬盘启动的系统来说,该扇区是diskboot.img的内容,diskboot.img的作用是加载core.img中剩余的内容。
由于diskboot.img所在的位置是以硬编码的方式写入到boot.img中的,所以boot.img总能找到core.img中diskboot.img的位置并跳转到它身上,随后控制权交给diskboot.img。随后diskboot.img加载压缩后的kernel.img(注意,是grub的kernel不是操作系统的kernel)以初始化grub运行时的各种环境,控制权交给kernel.img。
但直到目前为止,core.img都还不识别/boot所在分区的文件系统,所以kernel.img初始化grub环境的过程就包括了加载模块,严格地说不是加载,因为在安装grub时,文件系统类的模块已经嵌入到了core.img中,例如ext类的文件系统模块ext2.mod。
加载了模块后,kernel.img就能识别/boot分区的文件系统,也就能找到grub的配置文件/boot/grub2/grub.cfg,有了grub.cfg就能显示启动菜单,我们就能自由的选择要启动的操作系统。
当选择某个菜单项后,kernel.img会根据grub.cfg中的配置加载对应的操作系统内核(/boot目录下vmlinuz开头的文件),并向操作系统内核传递启动时参数,包括根文件系统所在的分区,init ramdisk(即initrd或initramfs)的路径。例如下面是某个菜单项的配置:
menuentry 'CentOS 6' --unrestricted { search --no-floppy --fs-uuid --set=root f5d8939c-4a04-4f47-a1bc-1b8cbabc4d32 linux16 /vmlinuz-2.6.32-504.el6.x86_64 root=UUID=edb1bf15-9590-4195-aa11-6dac45c7f6f3 ro quiet initrd16 /initramfs-2.6.32-504.el6.x86_64.img }
加载完操作系统内核后grub2就将控制权交给操作系统内核。
总结下,从MBR开始后的过程是这样的:
1.执行MBR中的boot loader(即boot.img)跳转到diskboot.img。
2.执行diskboot.img,加载core.img剩余的部分,并跳转到kernel.img。
3.kernel.img读取/boot/grub2/grub2.cfg,并显示启动管理菜单。
4.选中某菜单后,kernel.img加载该菜单项配置的操作系统内核/boot/vmlinux-XXX,并传递内核启动参数,包括根文件系统所在分区和init ramdisk的路径。
5.控制权交给操作系统内核。
传统grub对应的boot loader是stage1和stage2,从stage1跳转到stage2大多数情况下还会用到stage1_5对应的boot loader。
与grub2相比,stage1和boot.img的作用是类似的,都在MBR中。当该段boot loader执行后,它的目的是跳转到stage1_5的第一个扇区上,然后由该扇区的代码加载剩余的内容,并跳转到stage2的第一个扇区上。
stage1_5存在的理由是因为stage2功能较多,导致其文件体积较大(一般至少都有100多K),所以并没有像core.img一样嵌入到磁盘上,而是简单地将其放在了boot分区上,但stage1并不识别boot分区的文件系统类型,所以借助中间的辅助boot loader即stage1_5来跳转。
stage1_5的目的之一是识别文件系统,但文件系统的类型有很多,所以对应的stage1_5也有很多种。
[root@xuexi ~]# ls -C /boot/grub/*stage1_5*/boot/grub/e2fs_stage1_5 /boot/grub/jfs_stage1_5 /boot/grub/vstafs_stage1_5 /boot/grub/fat_stage1_5 /boot/grub/minix_stage1_5 /boot/grub/xfs_stage1_5 /boot/grub/ffs_stage1_5 /boot/grub/reiserfs_stage1_5 /boot/grub/iso9660_stage1_5 /boot/grub/ufs2_stage1_5
虽然有很多种stage1_5,但每个boot分区也只能对应一种stage1_5。这个stage1_5对应的boot loader一般会被嵌入到MBR后、第一个分区前的中间那段空间(即MBR gap)。
当执行了stage1_5对应的boot loader后,stage1_5就能识别出boot所在的分区,并找到stage2文件的第一个扇区,然后跳转过去。
当控制权交给了stage2,stage2就能加载grub的配置文件/boot/grub/grub.conf并显示菜单并初始化grub的运行时环境,当选中操作系统后,stage2将和kernel.img一样加载操作系统内核,传递内核启动参数,并将控制权交给操作系统内核。
所以,stage1、stage1_5和stage2之间的关系如下图:
虽然绝大多数都提供了stage1_5,但它不是必须的,它的作用仅仅只是识别boot分区的文件系统类型,对于一个会编程的人来说,可以将固定boot分区的文件系统识别代码嵌入到stage1中,这样stage1自身就能识别boot分区,就不需要stage1_5了。
看看安装grub时,grub到底做了些什么工作。
grub> setup (hd0) Checking if "/boot/grub/stage1" exists... yes Checking if "/boot/grub/stage2" exists... yes Checking if "/boot/grub/e2fs_stage1_5" exists... yes Running "embed /boot/grub/e2fs_stage1_5 (hd0)"... 15 sectors are embedded. succeeded Running "install /boot/grub/stage1 (hd0) (hd0)1+15 p (hd0,0)/boot/grub/stage2 /boot/grub/menu.lst"... succeeded Done.
首先检测各stage文件是否存在于/boot/grub目录下,随后嵌入stage1_5到磁盘上,该文件系统类型的stage1_5占用了15个扇区,最后安装stage1,并告知stage1 stage1_5的位置是第1到第15个扇区,之所以先嵌入stage1_5再嵌入stage1就是为了让stage1知道stage1_5的位置,最后还告知了stage1 stage2和配置文件menu.lst(它是grub.conf的软链接)的路径。
提前说明,下文所述均为sysV init系统启动风格,systemd的启动管理方式大不相同,所以不要将systemd管理的启动方式与此做比较。
到目前为止,内核已经被加载到内存掌握了控制权,且收到了boot loader最后传递的内核启动参数以及init ramdisk的路径。
所有的内核都是以bzImage方式压缩过的,压缩后CentOS 6的内核大小大约为4M,CentOS 7的内核大小大约为5M。内核要能正常运作下去,它需要进行解压释放。
解压释放之后,将创建pid为0的idle进程,该进程非常重要,后续内核所有的进程都是通过fork它创建的,且很多cpu降温工具就是强制执行idle进程来实现的。
然后创建pid=1和pid=2的内核进程。pid=1的进程也就是init进程,pid=2的进程是kthread内核线程,它的作用是在真正调用init程序之前完成内核环境初始化和设置工作,例如根据grub传递的内核启动参数找到init ramdisk并加载。
在前面,已经创建了pid=1的init进程和pid=2的kthread进程,但注意,它们都是内核线程,全称是kernel_init和kernel_kthread,而真正能被ps捕获到的pid=1的init进程是由kernel_init调用init程序后形成的。
要加载/sbin/init程序,首先要找到根分区,根分区是有文件系统的,所以内核需要先识别文件系统并加载文件系统的驱动,但文件系统的驱动又是放在根分区的,这就出现了先有鸡还是先有蛋的矛盾。
解决的方法之一是像grub2识别boot分区的文件系统一样,将根文件系统驱动模块嵌入到内核中,但文件系统的种类太多,而且会升级,这样就导致内核不断的嵌入新的文件系统驱动模块,内核不断增大,这显然是不合适的。
解决方法之二则像传统grub借助中间过渡引导段stage1_5一样,将根文件系统的驱动模块放入一个中间过渡文件,在加载根文件系统之前先加载这个过渡文件,再由过渡文件跳转到根文件系统。
方法二正是现在采用的,其采用的中间过渡文件称为init ramdisk,它是在安装完操作系统时生成的,这样它会收集到当前操作系统的根文件系统是什么类型的文件系统,也就能只嵌入一个对应的文件系统驱动模块使其变得足够小。如下图,它是安装操作系统时安装完所有软件包后执行的一个收集过程。
在CentOS 5上采用的init ramdisk称为initrd,而CentOS 6和CentOS 7采用的则是initramfs,它们的目的是一样的,但在实现上却大有不同。但它们都存放在/boot目录下。
[root@xuexi ~]# ll -h /boot/init* -rw-------. 1 root root 19M Feb 25 11:53 /boot/initramfs-2.6.32-504.el6.x86_64.img
可以看到,它们的大小有十多兆,由此也可知道init ramdisk的作用肯定不仅仅只是找到根文件系统,它还会做其他工作。具体还做什么工作,请继续阅读下文。
initrd其实是一个镜像文件系统,是在内存中划分一片区域模拟磁盘分区,在该文件中包含了找到根文件系统的脚本和驱动。
既然是文件系统,那么内核也必须要带有对应文件系统的驱动,另外文件系统要使用就必须有根"/",这个根是内存中的"虚根"。由于内核加载到这里已经初始化一些运行环境了,所以内核的运行状态等参数也要保存下来,保存的位置就是内存中虚根下的/proc和/sys,此外还有收集到的硬件设备信息以及设备的运行环境也要保存下来,保存的位置是/dev。到此为止,pid=2的内核线程kernel_kthread就完成了基本工作,开始转到kernel_init进程上了。
再之后就是kernel_init挂载真正的根文件系统并从虚根切换到实根,最后kernel_init将调用init程序,也就是真正的能被我们看见的pid=1的init进程,然后将控制权交给init,所以从现在开始,将切换到用户空间,后续剩余的事情都将由用户空间的程序完成。
以下是CentOS 5.8中initrd文件的解压过程和捷报后的目录结构。
[root@localhost ~]# cp /boot/initrd-2.6.18-308.el5.img /tmp/initrd.gz [root@localhost tmp]# gunzip initrd.gz [root@localhost tmp]# cpio -id < initrd [root@localhost tmp]# lsbin dev etc init initrd lib proc sbin sys sysroot
initramfs比initrd先进了一些,initrd必须是一个文件系统,是在内存中模拟出磁盘分区的,所以内核必须要带有它的文件系统驱动,而initramfs则仅仅只是一个镜像压缩文件而非文件系统,所以它不需要带文件系统驱动,在加载时,内核会将其解压的内容装入到一个tmpfs 中。
initramfs和initrd最大的区别在于init进程的区别对待。initramfs为了尽早进入用户空间,它将init程序集成到了initramfs镜像文件中,这样就可以在initramfs装入tmpfs时直接运行init进程,而不用去找根文件系统下的/sbin/init,由此挂载根文件系统的工作将由init来完成,而不再是内核线程kernel_init完成。最后从虚根切换到实根。
那根分区下的/sbin/init是干嘛的呢?可以认为是init ramdisk中init的一个备份,如果ramdisk中找不到init就会去找/sbin/init。另外,在正常运行的操作系统环境下,/sbin/init还经常用来完成其他工作,如发送信号。
其实initramfs完成了很多工作,解开它的镜像文件就能发现它的目录结构和真实环境下的目录结构类似。以下是CentOS 7上initramfs-3.10.0-327.el7.x86_64解包过程和解包后的目录结构。
[root@xuexi ~]# cp /boot/initramfs-3.10.0-327.el7.x86_64.img /tmp/initramfs.gz [root@xuexi ~]# cd /tmp; gunzip /tmp/initramfs.gz [root@xuexi tmp]# cpio -id < initramfs [root@xuexi tmp]# ls -l total 8lrwxrwxrwx 1 root root 7 Jun 29 23:28 bin -> usr/bin drwxr-xr-x 2 root root 42 Jun 29 23:28 dev drwxr-xr-x 11 root root 4096 Jun 29 23:28 etc lrwxrwxrwx 1 root root 23 Jun 29 23:28 init -> usr/lib/systemd/systemd lrwxrwxrwx 1 root root 7 Jun 29 23:28 lib -> usr/lib lrwxrwxrwx 1 root root 9 Jun 29 23:28 lib64 -> usr/lib64 drwxr-xr-x 2 root root 6 Jun 29 23:28 proc drwxr-xr-x 2 root root 6 Jun 29 23:28 root drwxr-xr-x 2 root root 6 Jun 29 23:28 run lrwxrwxrwx 1 root root 8 Jun 29 23:28 sbin -> usr/sbin-rwxr-xr-x 1 root root 3041 Jun 29 23:28 shutdown drwxr-xr-x 2 root root 6 Jun 29 23:28 sys drwxr-xr-x 2 root root 6 Jun 29 23:28 sysroot drwxr-xr-x 2 root root 6 Jun 29 23:28 tmp drwxr-xr-x 7 root root 61 Jun 29 23:28 usr drwxr-xr-x 2 root root 27 Jun 29 23:28 var
另外,还可以在其sbin目录下发现init程序。
[root@xuexi tmp]# ll sbin/init lrwxrwxrwx 1 root root 22 Jun 29 23:28 sbin/init -> ../lib/systemd/systemd
下文解释的是sysV风格的系统环境,与systemd初始化大不相同。
当init进程掌握控制权后,意味着已经进入了用户空间,后续的事情也将以用户空间为主导来完成。
init的名称是initialize的缩写,是初始化的意思,所以它的作用也就是初始化的作用。在内核加载阶段,也有初始化动作,初始化的环境是内核的环境,是由kernel_init、kernel_thread等内核线程完成的。而init掌握控制权后,已经可以和用户空间交互,意味着真正的开始进入操作系统,所以它初始化的是操作系统的环境。
操作系统初始化涉及了不少过程,大致如下:读取运行级别;初始化系统类的环境;根据运行级别初始化用户类的环境;执行rc.local文件完成用户自定义开机要执行的命令;加载终端;
在sysV风格的系统下,使用了运行级别的概念,不同运行级别初始化不同的系统类环境,你可以认为windows的安全模式也是使用运行级别的一种产物。
在Linux系统中定义了7个运行级别,使用0-6的数字表示。
0:halt,即关机
1:单用户模式
2:不带NFS的多用户模式
3:完整多用户模式
4:保留未使用的级别
5:X11,即图形界面模式
6:reboot,即重启
实际上,执行关机或重启命令的本质就是向init进程传递0或6这两个运行级别。
sysV的init程序读取/etc/inittab文件来获取默认的运行级别,并根据此文件所指定的配置执行默认运行级别对应的操作。注意,systemd管理的系统是没有/etc/inittab文件的,即使有也仅仅只是出于提醒的目的,因为systemd没有了运行级别的概念,说实话,systemd管的真的太多了。
CentOS 6.6上该文件内容如下:
[root@xuexi ~]# cat /etc/inittab # inittab is only used by upstart for the default runlevel. # # ADDING OTHER CONFIGURATION HERE WILL HAVE NO EFFECT ON YOUR SYSTEM. ## System initialization is started by /etc/init/rcS.conf # # Individual runlevels are started by /etc/init/rc.conf # # Ctrl-Alt-Delete is handled by /etc/init/control-alt-delete.conf # # Terminal gettys are handled by /etc/init/tty.conf and /etc/init/serial.conf, # with configuration in /etc/sysconfig/init.# # For information on how to write upstart event handlers, or how # upstart works, see init(5), init(8), and initctl(8). # # Default runlevel. The runlevels used 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:3:initdefault:
该文件告诉我们,系统初始化过程由/etc/init/rcS.conf完成,运行级别类的初始化过程由/etc/init.conf来完成,按下CTRL+ALT+DEL键要执行的过程由/etc/init/control-alt-delete.conf来完成,终端加载的过程由/etc/init/tty.conf和/etc/init/serial.conf读取配置文件/etc/sysconfig/init来完成。再文件最后,还有一行"id:3:initdefault",表示默认的运行级别为3,即完整的多用户模式。
确认了要进入的运行级别后,init将先读取/etc/init/rcS.conf来完成系统环境类初始化动作,再读取/etc/init/rc.conf来完成运行级别类动作。
先看看/etc/init/rcS.conf文件的内容。
[root@xuexi ~]# cat /etc/init/rcS.conf # rcS - runlevel compatibility # # This task runs the old sysv-rc startup scripts. # # Do not edit this file directly. If you want to change the behaviour, # please create a file rcS.override and put your changes there. start on startup stop on runlevel task # Note: there can be no previous runlevel here, if we have one it's bad# information (we enter rc1 not rcS for maintenance). Run /etc/rc.d/rc # without information so that it defaults to previous=N runlevel=S. console output pre-start scriptfor t in $(cat /proc/cmdline); docase $t inemergency) start rcS-emergency break ;;esacdoneend scriptexec /etc/rc.d/rc.sysinitpost-stop scriptif [ "$UPSTART_EVENTS" = "startup" ]; then[ -f /etc/inittab ] && runlevel=$(/bin/awk -F ':' '$3 == "initdefault" && $1 !~ "^#" { print $2 }' /etc/inittab) [ -z "$runlevel" ] && runlevel="3"for t in $(cat /proc/cmdline); docase $t in-s|single|S|s) runlevel="S" ;; [1-9]) runlevel="$t" ;;esacdoneexec telinit $runlevelfiend script
其中"exec /etc/rc.d/rc.sysinit"这一行就表示要执行/etc/rc.d/rc.sysinit文件,该文件定义了系统初始化(system initialization)的内容,包括:
(1).确认主机名。
(2).挂载/proc和/sys等特殊文件系统,使得内核参数和状态可与人进行交互。是否还记得在内核加载阶段时的/proc和/sys?
(3).启动udev,也就是启动类似windows中的设备管理器。
(4)初始化硬件参数,如加载某些驱动,设置时钟等。
(5).设置主机名。
(6).执行fsck检测磁盘是否健康。
(7).挂载/etc/fstab中除/proc和NFS的文件系统。
(8).激活swap。
(9).将所有执行的操作写入到/var/log/dmesg文件中。
执行完系统初始化后,接下来就是执行运行级别的初始化。先看看/etc/init/rc.conf的内容。
[root@xuexi ~]# cat /etc/init/rc.conf # rc - System V runlevel compatibility # # This task runs the old sysv-rc runlevel scripts. It # is usually started by the telinit compatibility wrapper. # # Do not edit this file directly. If you want to change the behaviour, # please create a file rc.override and put your changes there. start on runlevel [0123456] stop on runlevel [!$RUNLEVEL] task export RUNLEVEL console outputexec /etc/rc.d/rc $RUNLEVEL
最后一行"exec /etc/rc.d/rc $RUNLEVEL"说明调用/etc/rc.d/rc这个脚本来初始化指定运行级别的环境。Linux采用了将各运行级别初始化内容分开管理的方式,将0-6这7个运行级别要执行的初始化脚本分别放入rc[0-6].d这7个目录中。
[root@xuexi ~]# ls -l /etc/rc.d/total 60drwxr-xr-x. 2 root root 4096 Jun 11 02:42 init.d-rwxr-xr-x. 1 root root 2617 Oct 16 2014 rc drwxr-xr-x. 2 root root 4096 Jun 11 02:42 rc0.d drwxr-xr-x. 2 root root 4096 Jun 11 02:42 rc1.d drwxr-xr-x. 2 root root 4096 Jun 11 02:42 rc2.d drwxr-xr-x. 2 root root 4096 Jun 11 02:42 rc3.d drwxr-xr-x. 2 root root 4096 Jun 11 02:42 rc4.d drwxr-xr-x. 2 root root 4096 Jun 11 02:42 rc5.d drwxr-xr-x. 2 root root 4096 Jun 11 02:42 rc6.d-rwxr-xr-x. 1 root root 220 Oct 16 2014 rc.local-rwxr-xr-x. 1 root root 19914 Oct 16 2014 rc.sysinit
实际上/etc/init.d/下的脚本才是真正的脚本,放入rcN.d目录中的文件只不过是/etc/init.d/目录下脚本的软链接。注意,/etc/init.d是Linux耍的一个小把戏,它是/etc/rc.d/init.d的一个符号链接,在有些类unix系统中是没有/etc/init.d的,都是直接使用/etc/rc.d/init.d。
以/etc/rc.d/rc3.d为例。
[root@xuexi ~]# ll /etc/rc.d/rc3.d/ | headtotal 0lrwxrwxrwx. 1 root root 16 Feb 25 11:52 K01smartd -> ../init.d/smartd lrwxrwxrwx. 1 root root 16 Feb 25 11:52 K10psacct -> ../init.d/psacct lrwxrwxrwx. 1 root root 19 Feb 25 11:51 K10saslauthd -> ../init.d/saslauthd lrwxrwxrwx 1 root root 22 Jun 10 08:59 K15htcacheclean -> ../init.d/htcacheclean lrwxrwxrwx 1 root root 15 Jun 10 08:59 K15httpd -> ../init.d/httpd lrwxrwxrwx 1 root root 15 Jun 11 02:42 K15nginx -> ../init.d/nginx lrwxrwxrwx. 1 root root 18 Feb 25 11:52 K15svnserve -> ../init.d/svnserve lrwxrwxrwx. 1 root root 20 Feb 25 11:51 K50netconsole -> ../init.d/netconsole lrwxrwxrwx 1 root root 17 Jun 10 00:50 K73winbind -> ../init.d/winbind
可见,rcN.d中的文件都以K或S加一个数字开头,其后才是脚本名称,且它们都是/etc/rc.d/init.d中文件的链接。S开头表示进入该运行级别时要运行的程序,S字母后的数值表示启动顺序,数字越大,启动的越晚;K开头的表示退出该运行级别时要杀掉的程序,数值表示关闭的顺序。
所有这些文件都是由/etc/rc.d/rc这个程序调用的,K开头的则传给rc一个stop参数,S开头的则传给rc一个start参数。
打开rc0.d和rc6.d这两个目录,你会发现在这两个目录中除了"S00killall"和"S01reboot",其余都是K开头的文件。
而在rc[2-5].d这几个目录中,都有一个S99local文件,且它们都是指向/etc/rc.d/rc.local的软链接。S99表示最后启动的一个程序,所以rc.local中的程序是2345这4个运行级别初始化过程中最后运行的一个脚本。这是Linux提供给我们定义自己想要在开机时(严格地说是进入运行级别)就执行的命令的文件。
当初始化完运行级别环境后,将要准备登录系统了。
Linux是多任务多用户的操作系统,它允许多人同时在线工作。但每个人都必须要输入用户名和密码才能验证身份并最终登录。但登陆时是以图形界面的方式给用户使用,还是以纯命令行模式给用户使用呢?这是终端决定的,也就是说在登录前需要先加载终端。至于什么是终端,见我的另一篇文章Linux终端类型。
在Linux上,每次开机都必然会开启所有支持的虚拟终端,如下图。
这些虚拟终端是由getty命令(get tty)来完成的,getty命令有很多变种,有mingetty、agetty、rungettty等,在CentOS 5和CentOS 6都使用mingetty,在CentOS 7上使用agetty。getty命令的作用之一是调用登录程序/bin/login。
例如,在CentOS 6下,捕获tty终端情况。
[root@xuexi ~]# ps -elf | grep tt[y]4 S root 1412 1 0 80 0 - 1016 n_tty_ Jun21 tty2 00:00:00 /sbin/mingetty /dev/tty24 S root 1414 1 0 80 0 - 1016 n_tty_ Jun21 tty3 00:00:00 /sbin/mingetty /dev/tty34 S root 1417 1 0 80 0 - 1016 n_tty_ Jun21 tty4 00:00:00 /sbin/mingetty /dev/tty44 S root 1419 1 0 80 0 - 1016 n_tty_ Jun21 tty5 00:00:00 /sbin/mingetty /dev/tty54 S root 1421 1 0 80 0 - 1016 n_tty_ Jun21 tty6 00:00:00 /sbin/mingetty /dev/tty64 S root 1492 1410 0 80 0 - 27118 n_tty_ Jun21 tty1 00:00:00 -bash
在CentOS 7下,捕获tty终端情况。
[root@xuexi tmp]# ps -elf | grep tt[y]4 S root 8258 1 0 80 0 - 27507 n_tty_ 04:17 tty2 00:00:00 /sbin/agetty --noclear tty2 linux4 S root 8259 1 0 80 0 - 27507 n_tty_ 04:17 tty3 00:00:00 /sbin/agetty --noclear tty3 linux4 S root 8260 1 0 80 0 - 27507 n_tty_ 04:17 tty4 00:00:00 /sbin/agetty --noclear tty4 linux4 S root 8262 915 0 80 0 - 29109 n_tty_ 04:17 tty1 00:00:00 -bash4 S root 8307 8305 0 80 0 - 29109 n_tty_ 04:17 tty5 00:00:00 -bash4 S root 8348 8346 0 80 0 - 29136 n_tty_ 04:17 tty6 00:00:00 -bash
细心一点会发现,有的tty终端仍然以/sbin/mingetty进程或/sbin/agetty进程显示,有些却以bash进程显示。这是因为getty进程在调用/bin/login后,如果输入用户名和密码成功登录了某个虚拟终端,那么gettty程序会融合到bash(假设bash是默认的shell)进程,这样getty进程就不会再显示了。
虽然getty不显示了,但并不代表它消失了,它仍以特殊的方式存在着。是否还记得/etc/inittab文件?此文件中提示了终端加载的过程由/etc/init/tty.conf读取配置文件/etc/sysconfig/init来完成。
[root@xuexi ~]# grep tty -A 1 /etc/inittab # Terminal gettys are handled by /etc/init/tty.conf and /etc/init/serial.conf, # with configuration in /etc/sysconfig/init.
那么就看看/etc/init/tty.conf文件。
[root@xuexi ~]# cat /etc/init/tty.conf # tty - getty # # This service maintains a getty on the specified device. # # Do not edit this file directly. If you want to change the behaviour, # please create a file tty.override and put your changes there. stop on runlevel [S016] respawninstance $TTYexec /sbin/mingetty $TTYusage 'tty TTY=/dev/ttyX - where X is console id'
此文件中的respawn表示进程由init进程监视,一发现被杀掉了init会立即重启它。所以,只要getty进程一结束,init会立即监视到而重启该进程。因此,用户登录成功后getty只是融合到了bash进程中,并非退出,否则init会立即重启它,而它会调用login程序让你再次输入用户和密码。
再看看/etc/sysconfig/init文件。
[root@xuexi ~]# cat /etc/sysconfig/init # color => new RH6.0 bootup # verbose => old-style bootup # anything else => new style bootup without ANSI colors or positioning BOOTUP=color # column to start "[ OK ]" label inRES_COL=60# terminal sequence to move to that column. You could change this # to something like "tput hpa ${RES_COL}" if your terminal supports it MOVE_TO_COL="echo -en \\033[${RES_COL}G"# terminal sequence to set color to a 'success' color (currently: green) SETCOLOR_SUCCESS="echo -en \\033[0;32m"# terminal sequence to set color to a 'failure' color (currently: red) SETCOLOR_FAILURE="echo -en \\033[0;31m"# terminal sequence to set color to a 'warning' color (currently: yellow) SETCOLOR_WARNING="echo -en \\033[0;33m"# terminal sequence to reset to the default color. SETCOLOR_NORMAL="echo -en \\033[0;39m"# Set to anything other than 'no' to allow hotkey interactive startup... PROMPT=yes # Set to 'yes' to allow probing for devices with swap signatures AUTOSWAP=no# What ttys should gettys be started on? ACTIVE_CONSOLES=/dev/tty[1-6] # Set to '/sbin/sulogin' to prompt for password on single-user mode # Set to '/sbin/sushell' otherwise SINGLE=/sbin/sushell
其中ACTIVE_CONSOLES指令決定了要開啟哪些虛擬終端。 SINGLE決定了在單一使用者模式下要呼叫哪個login程式和哪個shell。
如果不在虛擬終端登錄,而是透過為ssh分配的偽終端登錄,那麼到創建完getty進程那一步其實開機流程已經完成了。但不管在哪種終端下登錄,登入過程也可以算是開機流程的一部分,所以也簡單說明下。
getty進程啟用虛擬終端後將呼叫login程序提示用戶輸入用戶名或密碼(或偽終端的連接程序如ssh提示輸入用戶名和密碼),當用戶輸入完成後,將驗證輸入的用戶名是否合法,密碼是否正確,使用者名稱是否是明確被禁止登陸的,PAM模組對此使用者的限制是如何的等等,還要將登入程序記錄到各個日誌檔案中。如果登入成功,將載入該使用者的bash,載入bash過程需要讀取各種設定文件,初始化各種環境等等。但不管怎麼說,只要登入成功就表示開機流程全部完成了。
以上是分享Linux開機的詳細步驟的詳細內容。更多資訊請關注PHP中文網其他相關文章!