系统的开机流程:
1. 加载BIOS的硬件信息并进行自我检测,依据设定取得第一个可开机的装置
2. 读取并执行第一个开机装置内MBR的boot loader(如grub等)
3. 依据boot loader的设定加载kernel,kernel会开始侦测硬件与加载驱动程序
4. 在硬件驱动成功后,kernel会主动呼叫init程序,而init会取得run level信息
5. init执行/etc/init/rc-sysinit.conf档案来准备软件执行的环境
6. init执行run level的各个服务(以script的方式)
7. init执行/etc/rc.local档案
8. init执行终端机仿真程序来启动login程序,等待用户登录
boot loader是一个开机管理程序,它用来处理核心档案加载(load)的问题,主要功能就是认识操作系统的文件格式并加载核心到主存储器中。boot loader在开机装置的第一个sector内,也就是MBR(Master Boot Record,主引导记录)。每个操作系统的loader都不同,BIOS是通过硬件的INT 13中断功能来读取MBR的,只要BIOS能够侦测到硬盘,就能够通过INT 13来读取磁盘第一个扇区的MBR
每一个文件系统(file system或partition)都会保留一块启动扇区(boot sector)提供操作系统安装boot loader,并且操作系统默认会安装一份boot loader到根目录所在文件系统的boot sector上
boot loader的主要功能如下:
1. 提供选单:用户可以选择不同的开机项目,这也是多重引导的重要功能
2. 载入核心档案:直接指向可开机的程序区段来开始操作系统
3. 转交其他loader:将开机管理功能转交给其他loader负责
当boot loader读取核心档案后,Linux会将核心解压缩到主存储器中,并利用核心的功能开始测试与驱动各个周边装置,此时Linux核心会以自己的功能重新侦测硬件,而不一定会使用BIOS侦测到的硬件信息。Linux核心是可以动态加载核心模块的,核心模块放在/lib/modules/下。由于模块放置到磁盘根目录内(/lib和/必须在同一个partition),因此开机过程中核心必须要挂载根目录,这样才能读取核心模块加载驱动程序。为了保护文件系统,开机过程中根目录是以只读的方式挂载的
一般非必要的功能且可以编译成为模块的核心功能都会被编译成模块(如USB,SATA,SCSI等)。所以如果是利用SATA硬盘启动则需要读取SATA模块以挂载根目录,但SATA的驱动在/lib/modules/之中,不挂载就无法读取,于是陷入了死循环。Linux依靠虚拟文件系统(Initial RAM Disk)来解决这个问题,它一般使用的档名为/boot/initrd。initrd可以将/lib/modules/...内开机过程中必需的模块打包成一个档案,这个档案能够被boot loader加载到内存中,然后通过INT 13硬件功能将其读出并解压,在内存中仿真成为根目录。这个仿真的根目录提供了能够加载开机过程中所需核心模块(如SATA, USB,LVM,RAID等)的程序。载入完成后,会帮助核心重新呼叫/sbin/init来开始后续正常的开机流程
由此可见,如果没有initrd,只要根目录不在特殊的磁盘接口(USB,SATA,SCSI)也没有使用特殊的文件系统(LVM,RAID等),如Linux安装在IDE磁盘上且使用默认的ext2/ext3/ext4文件系统,则依然可以顺利开机。所以一般需要initrd的时刻为:(1)根目录所在磁盘为SATA、USB或SCSI等接口;(2)根目录所在文件系统为LVM、RAID等特殊格式;(3)根目录所在文件系统为非传统的Linux所认识的文件系统;(4)有其他必须要在核心加载时需要的模块
重制initrd档案可以使用mkinitramfs指令
mkinitramfs [-v] -o 输出文件名
-v:显示运作过程
-o:后接输出文件的名称
在硬件检测完成、核心和模块加载完成后,主机硬件已经准备就绪,系统会呼叫第一支程序/sbin/init,它的主要功能是准备软件执行的环境,包括系统的主机名、网络设定、语言设定等其他服务的启动。所有的动作都会通过/etc/init/下init的配置文件来规划,其中一个重要的项目就是run level。Linux是藉由run level来规定系统使用不同的服务来启动而让Linux的使用环境不同。常见distribution的run level有以下7个等级:
0 - halt(关机)
1 - single user mode(单人维护模式,用在系统出问题时进行维护)
2 - multi-user without NFS(类似run level 3,但无NFS服务)
3 - full multi-user mode(完整含有网络功能的纯文本模式)
4 - unused(系统保留功能)
5 - X11(如run level 3类似,但加载X window)
6 - reboot(重启)
Ubuntu的run level设定如下:
0 - shutdown
1 - single user mode
2 - multi-user graphical mode(3, 4, 5与2相同)
6 - reboot
/etc/init/下init配置文件中设定的关键词的含义有以下:
Ubuntu下,不同run level启动的各个shell script放在/etc/rc[0-6].d/下。这些档案全部是以Sxx或Kxx("xx"表示数字,表示执行顺序)开头("S"代表start,"K"代表stop)并且全部是连结到/etc/init.d/中stand alone服务的连结档
如果用户想添加开机自启动的工作,写入/etc/rc.local即可
run level的切换:
1. 要切换默认开机的run level则修改/etc/init/rc-sysinit.conf
2. 如果只是切换当前的run level,则执行init[0-6]即可。例如从run level 2改为3,则系统会先对比/etc/rc2.d/和/etc/rc3.d/下K和S开头的文件,在新的run level内多的K开头的档案对应的服务予以关闭,多的S开头档案进行启动。可以通过指令runlevel来观察当前的run level
目前的核心都具有“可读取模块化驱动程序”的功能,也就是所谓的“模块化”功能。如果有个新的硬件操作系统不支持,可以:
1. 重新编译核心并加入新的硬件驱动程序原始码
2. 将该硬件的驱动程序编译成模块,在开机时加载该模块
核心的模块放置在/lib/modules/$(uname -r)/kernel/下,主要有以下几个目录:
arch:与硬件平台有关的项目,如CPU的等级等
crypto:核心所支持的加密技术,如md5或des等
drivers:一些硬件的驱动程序,如显示器、网卡、PCI相关硬件等
fs:一些核心所支持的file system,如vfat,reiserfs,nfs等
lib:一些函数库
net:与网络有关的各项协议数据,还有防火墙模块(net/ipv4/netfilter/*)
sound:与音效有关的模块
核心的模块之间有相依性,/lib/modules/$(uname -r)/modules.dep这个档案就记录了核心支持各项模块的相依性,它会影响modprobe指令的使用
depmod [-Ane]
-A:不添加任何参数时,depmod会主动的去分析当前核心的模块,并且重新写入/lib/modules/$(uname -r)/modules.dep;若加入-A参数,则会搜索比modules.dep新的模块,如果找到才会更新
-n:不写入modules.dep,而是将结果输出到屏幕上
-e:显示出目前已加载的不可执行的模块名称
Linux核心模块是以.ko结尾的,当使用depmod时该程序会跑到标准放置目录/lib/modules/$(uname -r)/kernel下,并根据相关目录的定义将所有的模块捕捉出来分析,最终将分析结果写入modules.dep
lsmod:观察目前核心加载了多少模块。结果中userd by一栏表示该模块被哪些模块使用,只有先加载该模块才能加载使用该模块的模块,这就是模块间的相依性
modinfo:观察模块信息
modinfor [-adln] [module name(核心中的模块名) / file name(模块档案名)]
-a:仅列出作者的名字
-d:仅列出该模块的说明
-l:仅列出授权
-n:仅列出该模块的详细信息
手动加载模块最好使用modprobe,因为它会主动搜寻modules.dep的内容,解决了模块的相依性后再进行模块加载;而insmod则完全要由使用者自行解决模块的相依性
insmod [模块名的完整路径] [参数]
移除模块:rmmod [-fw] 模块名
-f:强制将该模块移除,无论它是否正被使用
-w:若该模块正在被使用,则等待它被使用完毕后再移除
modprobe [-lcfr] 模块名
-c:列出目前系统的所有模块
-l:列出目前在/lib/modules/$(uname -r)/kernel中所有模块完整文件名
-f:强制加载该模块
-r:移除模块
grub是Linux中主流的boot loader
MBR是整个硬盘第一个sector内的一个区块,容量很小只有446 Byte,是无法完全容纳boot loader的。为了解决这个问题,Linux将boot loader的程序代码与设定参数的加载分成两个阶段来执行:
stage 1:执行boot loader主程序。这个主程序必须要被安装在开机区(MBR或者boot sector),但因为容量有限,所以MBR和boot sector通常只安装boot loader的最小程序,并没有相关配置文件
stage 2:第二阶段为通过boot loader加载所有配置文件与相关的环境参数档案(包括文件系统定义与主要配置文件menu.lst),一般配置文件都在/boot下
grub的优点有:
1. 支持的文件系统多,并可以使用grub主程序直接在文件系统中搜寻核心档名
2. 开机时可以自行编辑与修改开机设定项目,类似bash的指令模式
3. 可以动态搜寻配置文件,而不需要在修改配置文件后重新安装grub
grub对硬盘的代号设定与Linux磁盘代号不同,它使用的格式是(hd0, 0),相关规定为:
1. 硬盘代号用小括号"()"括起
2. 硬盘以hd表示,后接一组数字
3. 以搜寻顺序作为硬盘的编号,而不是依照硬盘扁平电缆的排序
4. 第一个搜寻到的硬盘为0号,第二个为1号,以此类推
5. 每个硬盘的partition编号从0开始
menu.lst是grub主要的配置文件,其中的主要参数有:
default:表示默认使用哪一个开机选项
timeout:无操作的等待时间
hiddenmenu:倒数时是否显示出完整的选单画面
title等:后接选单内容
开机时,可以(1)直接指定核心档案开机,或(2)将boot loader控制权转移到下个loader(此过程成为chain-loader)
1. 直接指定核心开机,有两种设置方法:
a) 先指定核心档案放置的partition,再读取档案,最后才加入档案的实际文件与路径名,如(Ubuntu下使用uuid代替root):
uuid ... # 代表核心档案放置partition的uuid
kernel /vmlinuz... root=UUID=... ro selinux=1 security=selinux quiet splash/single # kernel后接的是核心档案名,root表示开机过程中挂载根目录的partition
initrd /initrd.img-3.16.0-40-generic # 前面提到的initrd制作出的RAM Disk档案名
b) 直接指定partition和档名,不需要额外指定核心档案所在装置号
kernel (hd0, 0)/vmlinuz... ro root...
initrd (hd0, 0)/initrd...
区别就在于第二种方法在kernel和initrd的根目录前添加了核心档案在grub下的硬盘与partition。对两种方法来说,如果/boot与/在同一个partition,则kernel路径应变成 /boot/vmlinuz...
2. 利用chain loader的方式转交控制权
所谓的chain loader只是将控制权转交给下一个boot loader而已,所以grub并不需要认识与找出kernel的档名,也不需要去检查下一个boot loader的文件系统。一般chain loader的设定只要两个就够了,一个是要前往的boot sector所在的partition代号,二是设定加载这个boot sector所在分割槽的第一个扇区,如:
title Windows partition
rootnoverify (hd0, 0) # 假设(hd0, 0)是Windows的C盘所在的分割槽,rootnoverify表示不检测这个分割槽
makeactive # 设定此分割槽为开机碟
chainloader +1 # +1可以想成第一个扇区,即boot sector
grub还可以隐藏某些分割槽。例如在上例中title下添加一行 hide(hd0, 4)则可以在Windows下隐藏(hd0, 4)这个分割槽
安装grub:grub-install [--root-directory=DIR] 装置名
--root-directory:后接一个目录名。不加这一项时grub默认会将所有的档案都复制到/boot/grub/下,如果想要复制到其他目录或装置则在这里更改
grub-install只会安装相关档案等待开机时被读取,单还需要设定好配置文件menu.lst之后再以grub shell来安装grub主程序到MBR或boot sector上去
例:
1. 假设/dev/sda1中有boot loader,如果要让该boot loader取得控制权,需要将uuid设为这个partition的uuid(或使用root (hdx,x)的方式),并用"chainloader +1"指出使用第一个扇区(boot sector):
title /dev/sda1 boot sector
uuid ... # 原始方法是使用 root (hd0, 0);(hd0, 0)对应/dev/sda1
chainloader +1
2. 如果要读取MBR内的boot loader,则应直接选择那个磁盘的第一个分区(我猜的)即可:
title MBR loader
uuid ... # 原是方法是直接使用 root (hd0)
chainloader +1
但是由于我们并不确定/dev/sda1和MBR的boot sector中有没有grub主程序,因此要进行安装,可以参考以下指令:
1. 用"root (hdx,x)"选择含有grub目录的那个partition编号
2. 用"find [/boot]/grub/stage1"看能否找到安装档案信息(如果boot是单独挂载在某个partition下,则不需要加入中括号内的内容;只有当boot没有单独挂载时才需要到根目录 / 下去寻找boot。下一步同理)
3. 用"find [/boot]/vmlinuz..."看能否找到kernel file(不一定要成功)
4. 用"setup (hdx,x)"或"setup (hdx)"将grub安装在boot sector或MBR
5. 用"quit"离开grub的shell
我们要安装的其实就是stage1,它就是grub的主程序,而且配置文件通常与主程序放在同一个目录下
如果硬盘较大,BIOS可能无法读取硬盘中的kernel和initrd档案而导致无法正常开机(如果使用光盘安装则在安装过程中可以正常开机,因为此时是通过光盘加载Linux核心,而不是通过BIOS),避免这一问题的方法就是将kernel和initrd放在硬盘的最前面,也就是说要为/boot建立单独的分割槽并且让这个分割槽作为硬盘的第一个分割槽
可以为个别选单添加密码,具体操作过程为:
1. 利用"grub-md5-crypt"指令获得加密后的密码
2. 将加密后的密码添加到menu.lst要添加密码的title下面的第一行,内容为:"password --md5 加密后的密码"
这样一来,只有输入密码草能进入开机流程,远程reboot启动时若远程主机前没有其他人则无法开机
另外,这种设定可能会被通过在线grub编辑删掉密码一行而被破解,如果想要避免这种情况只能将整体的密码放在所有的title之前,然后在title下添加一行"lock",这样编辑grub时也要输入密码
如果忘记root密码可以在grub选单出现时进行在线grub编辑,在kernel一行最后添加" single"进入单人模式,这时系统会以root权限提供一个bash,在这个bash下使用passwd更改密码再用init 2进入x window即可
如果init的配置文件出现错误无法正常开机,可以在grub选单出现时进行在线编辑,在kernel最后一行添加" init=/bin/bash",指定系统呼叫的第一支程序为/bin/bash,则/sbin/init就不会执行。此时可以得到root权限利用bash进行操作,但除了根目录外其他目录都没有被挂载,并且根目录是只读状态。这时候,使用"mount -o remount,rw /"即可重新将根目录挂载为可读写,然后就可以修改init的配置文件进行救援
grub2默认没有/boot/grub/device.map配置文件,可以通过指令"grub-mkdevicemap"添加。通过修改device.map可以修改每个硬盘和grub磁盘号的对应关系,进而保证多个硬盘中装有grub的那个在menu.lst中被正确地对应。通过指令"grub-install recheck /dev/sd[a, b ...]"可以更新device.map,从而获得当前的对应关系
当因为文件系统错误而无法正常开机(如因为/etc/fstab参数错误,不正常关机等),进入错误画面时按照提示输入root密码获得bash,重新挂载根目录进行救援。如果是扇区错乱,fsck会进行告知,只要用fsck去检测出错的装置,等到系统发现错误并询问"clear [Y/N]"是输入y即可,这个过程可能会很长,并且如果这个partition上的file system数据损毁过多时,可能会因为伤到系统槽导致系统关键档案被损毁而依旧无法正常进入Linux。这个时候就备份重要数据重装系统吧。。
指令"chroot"可以暂时将根目录移动到某个目录下,然后去处理某个问题,最后离开该root而回到原本的系统中。比如当一台机器上装有多个Linux系统时,如果第一个系统出了问题,可以通过启动第二个系统并挂载第一个系统,使用chroot变换到第一个系统中,就能在第一个系统的环境中进行处理了。另外,也可以将一台主机的Linux硬盘插到第二台主机上,在第二台主机上进入Linux后,建立一个新目录,然后用chroot指令将工作环境切换到第一台主机的Linux硬盘上。具体细节如下:
假设第一台系统的硬盘挂载在第二台主机的/dev/sdc上,且原系统设置为:
挂载点 装置文件名
/ /dev/sdc1
/var /dev/sdc2
/home /dev/sdc3
/usr /dev/sdc5
则可以在第二台主机上建立目录temp,按照下面的关系进行挂载:
挂载点 装置文件名
temp/ /dev/sdc1
temp/var/ /dev/sdc2
temp/home/ /dev/sdc3
temp/usr/ /dev/sdc5
全部挂载完后,再输入"chroot .../temp"就会发现根目录变成/dev/sdc1的环境了,也就是说相当于在第一台主机上对这块硬盘进行操作