不论是Linux还是windows的启动过程大致都遵循下列过程:
而Linux和windows启动的分歧出现在MBR(主引导记录)之后,实际上也只是一些程序的名称或者细节不同而已,本质上还是一样的。
第一阶段BIOS:
BIOS首先进行家电自检,也就是说为的POST。如果没有加电自检功能,那么BIOS基本没有存在的意义。因为系统的启动涉及到一个先有鸡还是先有蛋的问题。很明显磁盘驱动程序存放在硬盘上,而加载驱动的内核部分也存放在磁盘上。那么到底是先有磁盘驱动程序还是先有驱动加载程序呢?BIOS的出现解决了这个问题。因为BIOS的存储介质是ROM,所由于ROM的访问方式使得无需驱动就可以访问BIOS当中的程序。所以启动之初BIOS进行自检找到相应的设备,以便于下一步的访问。自检之后BIOS提供了一个设置BIOS的接口。如果不进行自定义设置的话,那么BIOS会根据POST自检获得的设备信息以及设置当中的启动顺序来搜索用于系统启动的驱动器。一般是以硬盘启动,所以接下来BIOS读取此硬盘的第一个扇区(也就是我们常说的MBR)。
第二阶段MBR:
由于在磁盘上只有一个MBR,那么接下来MBR会将控制权转交给系统启动程序来引导系统启动。貌似遗忘了多系统选择菜单的存在,当系统存在多个系统启动项的时候,在MBR阶段会跳出一个相应的菜单选项供我们选择。那么这个多系统启动是怎么实现的呢?先看下图整个磁盘的分布如下,开头是MBR部分(总共512字节)然后是存放系统的主分区,而在每个系统的主分区下面都有一小块区间,用于系统的boot。这块区间被称为bootsector。所以在MBR当中可以将控制权转到bootsector,然后才启动系统。然而由于安装Linux的时候可以对是否写MBR进行设置,而windows的安装默认是写MBR,所以需要先安装windows再安装Linux。
整个开机选项菜单的流程如下图所示:
那么在这里就有一个问题,MBR只有可量的512字节,怎么能够存放开机选项菜单?由于现在的磁盘分区都足够大,所以导致MBR和第一个分区之间存在较大的空闲磁盘,而这一部分则被GRUB利用。当然这一部分并不存在开机选项。因为gurb的相应设置文件在Linux文件下面的/BOOT文件夹里面。但是MBR和第一个分区之间的空余空间可以用于建立一个较小的文件系统,这个文件系统可以用于辅助访问Linux系统下面的/BOOT文件夹。这样就实现了开机选项菜单的启动。也许有人有疑问为什么MBR不需要文件系统访问呢?因为MBR可以通过硬件直接访问,而硬件访问的限制是8GB。由于在BIOS当中只给CHS24位,而每一个扇区512字节的话,刚好就只能寻址8GB。有人可能有疑问,如果按照CPU的地址线进行读取磁盘不是32为地址线吗?其实因为到目前位置还没有进入到保护模式,所以32为地址线是可望而不可及。接下来是Linux和windows有差异的地方了。
第三阶段Kernel启动:
进入到上一步之后,GRUB通过Linux下面的/boot文件中的内核文件加载到内存中,就可以实现将控制转向内核了。内核文件是/boot文件夹下面的vmlinuz,然而有时候仅仅只有这个文件是不够的,因为我们还是不能通过文件系统找到驱动程序。所以还需要找到驱动程序,然后将这些驱动加载到内存当中。所以grub也将nitrd加载到内存里,让后者将其中的内容释放到内存中,内核便去执行initrd中的init脚本,这时内核将控制权交给了init文件处理。init脚本主要是加载各种存储介质相关的设备驱动程序。当所需的驱动程序加载完后,会创建一个根设备,然后将根文件系统rootfs以只读的方式挂载。到这一步结束才可以比较*的访问文件系统当中的文件里。也正因为这样才可以执行/Sbin/init程序,并将控制权交给init程序。至于为什么需要线设置rootfs,因为设备具有依赖性,就好比如果想要访问A文件夹下面的文件B,你必须想找到文件夹A才能找到B一样。所以需要先找到根目录才能找到相应的子目录。
控制权转到init之后,init程序首先会启动/etc/init文件夹当中的服务,然后根据/etc/inittab文件夹的设置,进行相应的服务启动以及系统初始化。系统首先调用/etc/rc.sysinit脚本文件对系统进行一些设置,然后会根据inittab文件当中获得的run level启动相应运行级别下面的服务。
rc.sysinit脚本主要进行下面的设置:
· 设置网络环境/etc/sysconfig/network,如主机名,网关,IP,DNS等。
· 挂载/proc。
· 根据内核在开机时的结果/proc/sys/kernel/modprobe开始进行周边设备的侦测。
· 载入用户自定义的模块/etc/sysconfig/modules/*.modules
· 读取/etc/sysctl。conf文件对内核进行设定。
· 设定时间,终端字体,硬盘LVM或RAID功能,以fsck进行磁盘检测。
· 将开机状况记录到/var/log/dmesg中。(可以用命令dmesg查看结果)
到这里整个系统就跑起来了,但是还缺少一步才可以见到桌面,在这一步进行用户名和密码验证。
一般而言,用户登陆包括三种方式。
1.命令行登陆
2.Ssh登陆
3.图形界面登陆
这三种情况存在相应的认证方式,并根据认证方式对shell环境进行配置。
1.init进程调用getty程序用于用户输入用户名和密码,然后对密码进行核对(/etc/pam.d/login)。如果密码正确就根据/etc/passwd启动制定的shell。
2.有init调用sshd程序,然后运行/etc/pam.d/ssh进行认证,最后启动shell。
3.Init程序调用显示管理器,gnome图形管理器对应的显示管理器为gdm,然后进行认证,认证成功则读取/etc/gdm3/Xsession,启动用户会话。
系统调用shell之后,会根据登陆方式读入一系列的配置文件。
(1)命令行登录:首先读入 /etc/profile,这是对所有用户都有效的配置;然后依次寻找下面三个文件,这是针对当前用户的配置。
~/.bash_profile
~/.bash_login
~/.profile
需要注意的是,这三个文件只要有一个存在,就不再读入后面的文件了。比如,要是 ~/.bash_profile 存在,就不会再读入后面两个文件了。
(2)ssh登录:与第一种情况完全相同。
(3)图形界面登录:只加载 /etc/profile 和 ~/.profile。也就是说,~/.bash_profile 不管有没有,都不会运行。
用户进入操作系统以后,常常会再手动开启一个shell。这个shell就叫做 non-login shell,意思是它不同于登录时出现的那个shell,不读取/etc/profile和.profile等配置文件。non-login shell的重要性,不仅在于它是用户最常接触的那个shell,还在于它会读入用户自己的bash配置文件 ~/.bashrc。大多数时候,我们对于bash的定制,都是写在这个文件里面的。
你也许会问,要是不进入 non-login shell,岂不是.bashrc就不会运行了,因此bash 也就不能完成定制了?事实上,Debian已经考虑到这个问题了,请打开文件 ~/.profile,可以看到下面的代码:
if [ -n "$BASH_VERSION" ]; then
if [ -f "$HOME/.bashrc" ]; then
. "$HOME/.bashrc"
fi
fi
上面代码先判断变量 $BASH_VERSION 是否有值,然后判断主目录下是否存在 .bashrc 文件,如果存在就运行该文件。第三行开头的那个点,是source命令的简写形式,表示运行某个文件,写成"source ~/.bashrc"也是可以的。
因此,只要运行~/.profile文件,~/.bashrc文件就会连带运行。但是上一节的第一种情况提到过,如果存在~ /.bash_profile文件,那么有可能不会运行~/.profile文件。解决这个问题很简单,把下面代码写入.bash_profile就行 了。
if [ -f ~/.profile ]; then
. ~/.profile
fi
这样一来,不管是哪种情况,.bashrc都会执行,用户的设置可以放心地都写入这个文件了。
Linux启动完毕,看到这里,不仅让人想起嵌入式当中的bootloader,从上面的整个分析可以看出来,bootloader其实就是BIOS和GRUB的简化。下面进行windows启动的分析
windows启动过程,由于前面的BIOS和MBR和Linux类似,所以这里就不再重复,现在重新从MBR加载后开始分析。
windows启动的时候,MBR扫描自身所保存的512字节当中的分区表,找到一个指示自己可以引导的分区,然后从这个分区当中的第一个扇区给读到内存当中,并且将这个控制权交给这个扇区代码。其实这里的原理和上面的grub很类似。通过硬件存储第一个扇区,只要这个扇区在BIOS可以访问的8G范围内就可以了。由于windows的安装会覆盖MBR,所以就很容易保证第一个可引导的分区中的第一个扇区在8G内。然而,windows在升级的时候,可以保存原来的系统用于多系统启动,这又是什么原因呢?因为windows安装的时候会保存原来的引导扇区的第一个分区,结合上面的GRUB分析,只要在windows进入内核之前加载了相应的文件系统,windows也可以通过启动菜单的方式进行启动。接下来windows的启动控制转到加载到内存的引导扇区,引导扇区内部包含相应的文件系统信息,这些信息可以用于找到相应的NTLDR,这个文件根据相应的boot.ini文件加载系统。而boot.ini文件类似于GRUB的启动菜单。
到目前为止,系统还处于实模式下面。由于处理器的限制,使得系统只能访问1M内存。所以NTldr第一件事就是开启保护模式,这使得系统可以访问32位地址空间。不过开启了保护模式不代表存在虚拟地址到物理地址的映射,接下来NTLDR创建页表为开启分页机制做准备。不过跑到现在,貌似ntldr都在一个人孤独的裸奔。所以需要利用原来的BIOS和引导扇区的相应的代码加载一些驱动到内存当中。接下来ntldr读取boot.ini文件,根据这个文件选择相应的开机启动项。如果启动原来的系统,那么将会利用原来的引导扇区重新进行上面的启动。否则直接进行下一步。如果没有选择,那么将会启动默认的系统。然后调用ntdetect.com对BIOS获取的设备信息进行收集,并且保存到相应的注册表选项当中(HKLM\HARDWARE)。接下来是一个press F8界面,然后Ntldr进行相应的模块加载。
1.Ntldr加载相应的内核和硬件抽象层(hal).
2.读入系统的配置文件C:/windows/system32/Config/system,实际上就是注册表的system部分,是不是和Linux下面的/etc很像。
3.根据加载到内存当中的HKLM/SYSTEM/CURRENTCONTROLSET/SERVICES键值下面的子键找到START_UP值为0的项,将这些服务的二进制文件找到,形成一个启动列表。然后根据相应的依赖关系加载驱动,当然磁盘文件系统肯定会优先被加载。
4.设置相应的寄存器,以便于执行ntoskrnl。
接下来是ntoskrnl的运行过程,nroskrnl获得的数据包括boot.ini当中相应的参数,以及上面的system和hardware注册表在内存当中的位置,还有加载的驱动列表。
首先ntoskrnl的主要目的是创建运行的系统环境。首先,ntoskrnl会调用KisystemStartup,而在KiSystemStartup函数当中会为每一个CPU调用HalInitializeProcessor和KiInitializekernel函数。如果KiInitializeKernel运行在引导CPU上面,会执行全局的内核初始化,然后调用ExpIniializeExecutive哈数,这个函数用于初始化相应的控制部件。ExpInitializeExecutive函数首先调用HalInitSystem,这个函数用于初始化中断控制器,中断处理当然离不开上面的驱动列表的参与。运行到这里,基本整个系统都已近起来了。如果按照Linux的进度,接下来需要登陆了。然而,在windows下面还有一些其他的事情需要处理。首先是我们的注册表项。到目前为止仅仅之后两项,这个我们在regedit命令里面看到的不相符。所以,接下来需要将所有的注册表项给完善。也就是构造应用程序运行的配置环境。然后是进行子系统的初始化,很明显子系统可能会用到注册表项。这两项都是在一个叫做smss的进程当中执行,而smss则是由前面的对象管理器创建。smss在初始化之后就无限等待两个进程句柄。一个是csrss,另一个则是winlogon。windlogon激发SCM服务器,然后等待gina认证,gina会根据HKLM\SOFTWARE\MICROSOFT\WINDOWS NT\CURRENTVERSION\WINLOGON\USERINIT中指定的一个或多个程序。这个注册表键值,类似于上面的三个部分的shell设置,然后会调用真正的shell。windows下面的shell就是有名的explorer.exe文件。
经过之前的Linux和windows启动过程分析,不难理解为什么在嵌入式当中需要BootLoader,而不是BIOS和GRUB。其实是因为BootLoader集成了BIOS和GRUB的功能。首先看下面的嵌入式Linux的软件结构与分布图。
首先的Boot Parameter会单独列出一栏是因为BootLoder启动的时候并没有设置好堆栈,所以这些数据需要一个单独的地方给保存起来用于运行。所以设置堆栈是BootLoader的功能之一。那么为什么需要BootLoader呢,如果把内核的第一条指令放在0号位置不也同样可以启动内核的执行吗?(当嵌入式系统是NOR flash的时候可以不用将代码拷贝到内存当中就执行)其实在疑问当中就提出了BootLoader的第二个功能,如果系统是在NAND系统下面怎么启动?其实还有一个问题就是,如果没有BootLoader,那么内核程序怎么进行烧写?其实通过上面的疑问,我们已经可以知道BootLoader的应该实现哪一些功能了。然而,有人可能会问,这些功能都是可以集成到内核当中的啊。的确,BootLoader的代码都可以集成在kernel当中,然而这样的话就失去了灵活性。因为在计算机当中,每增加一层就可以屏蔽下层的差异。并且作为BootLoader的功能不需要常驻内存,这一点和kernel是不相同的,所以可以分离出来。另外Linux作为一个系统本身具有多平台的特性,而开发BootLoader已经超出了Linux项目的范畴。