本文主要讲述关于计算机开机启动的一些思考
关于Boot
Boot的中文意思是引导;开机;靴子
- 计算机启动怎么非得用Boot这个单词呢?用START不更直观吗?
其实这里Boot是Bootstrap(鞋带)的缩写,有一句谚语叫pull oneself up by one's bootstraps
拽着鞋带将某人提起来
这怎么可能呢?
最早的时候,工程师们用它来比喻,计算机启动是一个很矛盾的过程:必须先运行程序,然后计算机才能启动,但是计算机不启动就无法运行程序!早期真的是这样,必须想尽各种办法,把一小段程序装进内存,然后计算机才能正常运行。所以,工程师们把这个过程叫做"拉鞋带",久而久之就简称为boot了。
按照我的理解:Boot的作用有点类似汽车的马达(起动机),通过它的电动机将庞大的内燃机启动起来。
关于BIOS
BIOS是基本输入输出系统(Basic Input/Output System)的英文单词缩写。
- 计算机为什么需要BIOS呢?这么小这么老的一个芯片为什么还不淘汰掉它呢?
在PC引导的过程中,BIOS担负着检测、初始化硬件的功能,以及引导操作系统的责任。
它其实是一段固件(firmware~一类特殊的底层软件)程序,主要是用于PC个人电脑启动过程中的硬件参数初始化以及给OS操作系统和应用程序运行时提供特殊的中断服务。在PC发展历史中早期是存储在主板的EPROM芯片中,所以是无法被轻易修改的。但是上世纪90年代在PC上电启动自检过程又希望有些顺序可以调整或者有些错误希望被忽略,因此增加了BIOS的配置或设置功能,一开始这种配置是通过DIP拨码开关(这个就不展开了)实现,接着是采用随机软盘带的配置程序对钮扣电池供电的RAM芯片进行参数配置,而ROM芯片中的BIOS代码会从DIP开关或RAM中读取不同参数而采用不同的启动自检流程。这里的RAM正是其他答主所说的“CMOS”,当年我也听过“卸下电池再装上,可以清除CMOS设置的启动密码”这样的说法。
来源:知乎老关
硬件自检POST
联想到自己做飞思卡尔的时候,上场比赛是不允许你烧录程序的,但是有一些像速度设置等就需要临场发挥,所以电路板上会带着DIP的拨码开关,通过上电读取开关0与1的信息来进行保命挡,GG挡,冲刺挡的选择,但是这种DIP拨码开关的选择万一选错了就真的GG了,相信那些硬件厂商可不希望用户瞎按一通,所以才采用随机软盘带的配置程序对钮扣电池供电的RAM芯片进行参数配置。
ROM里面的程序如果让我去写的话我可能会按照写STM32单片机的思维去进行操作:
void main(){
NVIC_Configuration(); //中断配置初始化
KeyBoard_Init(); //键盘初始化
Lcd_Init(); //显示器初始化
try:
CPU_Init(Param1,Param2)//CPU初始化,这块的参数就需要向CMOS RAM进行读取了,也就是说这块是可以用户自定义设置的,比如超频与否等等与CPU相关的设置
catch Exception e:
Beep_ON(2)
print e
//如果硬件出现问题,主板会发出不同含义的蜂鸣,启动中止。如果没有问题,屏幕就会显示出CPU、内存、硬盘等信息
}
当然真正的POST不会像我这样简单的了:
1.引导系统重置REST引导CPU。
2.CPU指向BIOS自我测试的地址FFFFOH并打开CPU运行第一个指令。
3.CPU内部寄存器的测试。
4.CMOS 146818 SRAM检查。
5.ROM BIOS检查码测试。
6.8254计时/计数器测试。
7.8237 DMA控制器测试。
8.74612页寄存器测试。
9.REFRESH刷新电路测试。
10.8042键盘控制器测试。
11.DRAM 64KB基本存储器测试。
12.CPU保护模式的测试。
13.8259中断控制器的测试。
14.CMOS 146818电力及检查码检查。
15.DRAM 1MB以上存储器检查。
16.显卡测试。
17.NMI强制中断测试。
18.8254计时/计数器声音电路测试。
19.8254计时/计数器计时测试。
20.CPU保护模式SHUT DOWN测试。
21.CPU回至实模式(REAL MODE)。
22.键盘鼠标测试。
23.8042键盘控制器测试。
24.8259中断控制器IRQ0至IRQ18建立。
25.磁盘驱动器及界面测试。
26.设置并行打印机及串列RS232的界面。
27.检查CMOS IC时间、日期。
28.检查完成
补充
1.在CPU加电之后,会把CPU所有寄存器的值设为默认值,除了CS寄存器的值改为0xFFFF,其他寄存器的值都为0,这样,根据CS 和 IP的值(段地址*16+偏移地址)就可以找到指令的物理地址0xFFFF:0x0000,也就是0xFFFF0。
2. 这时CPU就开始执行在这个位置开始执行,这里存放的一条无条件跳转指令JMP,跳转到BIOS的真正启动代码处。
3.BIOS首先先进行POST(Power-On Self Test,加电后自检)
4. BIOS 程序在执 行一些必要的开机自检和初始化后,会将自己复制到从 0xA0000 开始的物理内存中并继续执行
5. 然后,BIOS 开始搜寻可引导的存储设备(即根据用户指定的引导顺序从软盘、硬盘或是可移动设备)。如果找到,则将存储设备中的引导扇区读入物理内存 0x7C00 处,并跳转到 0x7C00 继续执行,从而将 CPU 交给引导扇区中的 Boot 程序。
0x7C00这个地址来自Intel的第一代个人电脑芯片8088,以后的CPU为了保持兼容,一直使用这个地址。
当时,搭配的操作系统是86-DOS。这个操作系统需要的内存最少是32KB。我们知道,内存地址从0x0000开始编号,32KB的内存就是0x0000~0x7FFF。
8088芯片本身需要占用0x0000~0x03FF,用来保存各种中断处理程序的储存位置。(主引导记录本身就是中断信号INT 19h的处理程序。)所以,内存只剩下0x0400~0x7FFF可以使用。
为了把尽量多的连续内存留给操作系统,主引导记录就被放到了内存地址的尾部。(操作系统启动后,主引导记录就没有用处了,此后它所在的内存地址可以被操作系统重新利用)由于一个扇区是512字节,主引导记录本身也会产生数据,需要另外留出512字节保存。所以,它的预留位置就变成了:
0x7FFF - 512 - 512 + 1 = 0x7C00
+--------------------- 0x0
| Interrupts vectors
+--------------------- 0x400
| BIOS data area
+--------------------- 0x5??
| OS load area
+--------------------- 0x7C00
| Boot sector
+--------------------- 0x7E00
| Boot data/stack
+--------------------- 0x7FFF
| (not used)
+--------------------- (...)
启动顺序
硬件自检完成后,BIOS把控制权转交给下一阶段的启动程序。
我们重装系统最常用的是用U盘制作一个启动盘,然后开机进入BIOS界面选择该从U盘、磁盘、固态硬盘、CD中以什么启动顺序来进行下一步的操作。注意这块对启动顺序的存储是保存在CMOS中的。
主引导记录MBR
BIOS按照"启动顺序",把控制权转交给排在第一位的储存设备。
这时,计算机读取该设备的第一个扇区,也就是读取最前面的512个字节。如果这512个字节的最后两个字节是0x55和0xAA,表明这个设备可以用于启动;如果不是,表明设备不能用于启动,控制权于是被转交给"启动顺序"中的下一个设备。
这最前面的512个字节,就叫做"主引导记录"(Master boot record,缩写为MBR)。
- 446Bytes:主引导记录最开头是第一阶段引导代码。其中的硬盘引导程序的主要作用是检查分区表是否正确并且在系统硬件完成自检以后将控制权交给硬盘上的引导程序(如GNU GRUB)。它不依赖任何操作系统,而且启动代码也是可以改变的,从而能够实现多系统引导。
- 64Bytes:硬盘分区表占据主引导扇区的64个字节(偏移01BEH--偏移01FDH),可以对四个分区的信息进行描述,其中每个分区的信息占据16个字节。包括如下几个部分:
(1) 第1个字节:分区状态:0x00-->非活动分区;0x80-->激活分区,控制权要转交给这个分区。四个主分区里面只能有一个是激活的。
(2) 第2个字节:分区起始的磁头号(Head),用到全部8位(也就是说有256个磁头?)
(3) 第3~4个字节:该分区起始扇区号(Sector),占据第3字节的0~5位(也就是说单个磁道有64个扇区,每个扇区的弧度为360/64=5.625° 由此想到,一个磁道上的一个扇区的存储量是固定的(比如512字节),所以半径小的磁道上的介质密度要高一些,半径大的磁道上介质密度要小一些)
该分区起始磁柱号(Cylinder)占据第3字节的6~7位和第四字节的全部8位(也就是说有1024个磁道)
(4)第5字节:文件系统标志位
(5)第6字节:分区结束的磁头号(Head),用到全部8位
(6)第7~8字节:该分区结束扇区号(Sector),占据第3字节的0~5位
该分区结束磁柱号(Cylinder)占据第3字节的6~7位和第四字节的全部8位
(7)第9~12字节:分区起始相对扇区号
(8)第12~15字节:分区总的扇区号
最后的四个字节("主分区的扇区总数"),决定了这个主分区的长度。也就是说,一个主分区的扇区总数最多不超过2的32次方。
硬盘启动
这时,计算机的控制权就要转交给硬盘的某个分区了,这里又分成两种情况。
1.卷引导
上一节提到,四个主分区里面,只有一个是激活的。计算机会读取激活分区的第一个扇区,叫做"卷引导记录"(Volume boot record,缩写为VBR)。
"卷引导记录"的主要作用是,告诉计算机,操作系统在这个分区里的位置。然后,计算机就会加载操作系统了。
2.启动管理器
在这种情况下,计算机读取"主引导记录"前面446字节的机器码之后,不再把控制权转交给某一个分区,而是运行事先安装的"启动管理器"(boot loader),由用户选择启动哪一个操作系统。
Linux环境中,目前最流行的启动管理器是Grub。
操作系统
控制权转交给操作系统后,操作系统的内核首先被载入内存。
以Linux系统为例,先载入/boot目录下面的kernel。内核加载成功后,第一个运行的程序是/sbin/init。它根据配置文件(Debian系统是/etc/initab)产生init进程。这是Linux启动后的第一个进程,pid进程编号为1,其他进程都是它的后代。
然后,init线程加载系统的各个模块,比如窗口程序和网络程序,直至执行/bin/login程序,跳出登录界面,等待用户输入用户名和密码。
至此,全部启动过程完成。
关于GPT
全局唯一标识分区表(GUID Partition Table,缩写:GPT)
为了兼容不支持GPT磁盘的操作系统,GPT分区格式保留了磁盘第一个扇区MBR的使用,并且也填写MBR的第一个分区表项,这个扇区又被称为保护MBR,而实际上EFI根本不使用这个分区表。在第一个分区表项中,分区开始扇区为1,分区大小扇区数为FFFFFFFF,分区类型为0xEE,它的目的是使计算机认为这个磁盘是合法的,并且已经被划分了分区,从而使系统不再试图重新初始化或格式化磁盘。
真正的GPT区域从硬盘的第二个扇区即1号扇区开始,主要包括GPT头、分区表、GPT分区、分区表备份、GPT头备份这几个部分。
原文链接
对于计算机来说,掉电储存类似于我们经常说的记忆,一个人如果睡了一觉醒来什么也记不得的话想想该多么恐怖。计算机的记忆就是0和1的掉电保持,这是在我看来也很矛盾的一件事:本来计算机就靠有没有电或者高低电压来区分0和1,你这一掉电,啥也没有了,我怎么去储存0和1呢?
一种解决方法是可以利用磁性来储存0与1,掉电之后电虽然没有了,但是磁性还是存在的,典型的就是磁盘磁带。
另外一种解决方法是防止电子在掉电之后逃掉,典型的是Flash,其实这个具体的实现我也没有看懂,光是想想不让电子跑掉我就感觉很难了。摘自wikipedia进入的电子会被困在里面。在一般的条件下电荷经过多年都不会逸散。
UEFI
要详细了解UEFI,还得从BIOS讲起。我们都知道,每一台普通的电脑都会有一个BIOS,用于加载电脑最基本的程式码,担负着初始化硬件,检测硬件功能以及引导操作系统的任务。UEFI就是与BIOS相对的概念,这种接口用于操作系统自动从预启动的操作环境,加载到一种操作系统上,从而达到开机程序化繁为简节省时间的目的。传统BIOS技术正在逐步被UEFI取而代之,在最近新出厂的电脑中,很多已经使用UEFI,使用UEFI模式安装操作系统是趋势所在。 UEFI抛去了传统BIOS需要长时间自检的问题,让硬件初始化以及引导系统变得简洁快速。换种方式说,UEFI已经把电脑的BIOS变得不像是BIOS,而是一个小型固化在主板上的操作系统一样,加上UEFI本身的开发语言已经从汇编转变成C语言,高级语言的加入让厂商深度开发UEFI变为可能。
MBR的局限性
MBR的意思是“主引导记录”,最早在1983年在IBM PC DOS 2.0中提出。
之所以叫“主引导记录”,是因为它是存在于驱动器开始部分的一个特殊的启动扇区。这个扇区包含了已安装的操作系统的启动加载器和驱动器的逻辑分区信息。所谓启动加载器,是一小段代码,用于加载驱动器上其他分区上更大的加载器。如果你安装了Windows,Windows启动加载器的初始信息就放在这个区域里——如果MBR的信息被覆盖导致Windows不能启动,你就需要使用Windows的MBR修复功能来使其恢复正常。如果你安装了Linux,则位于MBR里的通常会是GRUB加载器。
bootloader
在嵌入式操作系统中,BootLoader是在操作系统内核运行之前运行。可以初始化硬件设备、建立内存空间映射图,从而将系统的软硬件环境带到一个合适状态,以便为最终调用操作系统内核准备好正确的环境。在嵌入式系统中,通常并没有像BIOS那样的固件程序(注,有的嵌入式CPU也会内嵌一段短小的启动程序),因此整个系统的加载启动任务就完全由BootLoader来完成。在一个基于ARM7TDMI core的嵌入式系统中,系统在上电或复位时通常都从地址0x00000000处开始执行,而在这个地址处安排的通常就是系统的BootLoader程序。
使板子从裸机变成带操作系统,最少需要三个文件:bootloader、kernel、rootfs;(启动引导程序、内核、文件系统);
由bootloader引导cpu从哪里开始执行kernel程序,在启动起来内核后,系统中还要有对应的根文件系统。
对于bootloader来说,它可以分为两部分来说,一个是硬件初始化,一个是加载操作系统。
下边是nor flash的启动过程:
nor flash一般放在总线的0x00地址。
首先bootloader这段代码存放在nor flash的0x00这个地址中,所以在cpu启动时,cpu直接执行这段代码,由于nor flash支持片上执行,
所以cpu可以直接在nor flash上执行完三个步骤,但是由于nor flash的读取速度相对来说比较慢,所以,有时候会把bootl、kernel和rootfs拷贝到sdram中
,在sdram中执行启动过程。
Nand flash的启动过程:—(下边说道的BL1和BL2指定的是bootloader代码的两部分,分别硬件初始化程序和加载操作系统程序)
cpu在启动过程中最初会执行irom中的一段代码,这段代码会指引cpu到nand flash中,把nand flash中的BL1拷贝到iram中,
执行这段代码,在执行完这部分代码的末尾会告诉cpu把原来在nand flash中的bootloader、kernel、rootfs程序都拷贝到sdram中,然后从BL2代码的开始处执行程序,最终执行完所有的启动程序。
Linux开机启动过程分析
开机过程指的是从打开计算机电源直到LINUX显示用户登录画面的全过程。分析LINUX开机过程也是深入了解LINUX核心工作原理的一个很好的途径。
启动第一步--加载BIOS
当 你打开计算机电源,计算机会首先加载BIOS信息,BIOS信息是如此的重要,以至于计算机必须在最开始就找到它。这是因为BIOS中包含了CPU的相关 信息、设备启动顺序信息、硬盘信息、内存信息、时钟信息、PnP特性等等。在此之后,计算机心里就有谱了,知道应该去读取哪个硬件设备了。在BIOS将系 统的控制权交给硬盘第一个扇区之后,就开始由Linux来控制系统了。
启动第二步--读取MBR
硬 盘上第0磁道第一个扇区被称为MBR,也就是Master Boot Record,即主引导记录,它的大小是512字节,可里面却存放了预启动信息、分区表信息。可分为两部分:第一部分为引导(PRE-BOOT)区,占了 446个字节;第二部分为分区表(PARTITION PABLE),共有66个字节,记录硬盘的分区信息。预引导区的作用之一是找到标记为活动(ACTIVE)的分区,并将活动分区的引导区读入内存。
系统找到BIOS所指定的硬盘的MBR后,就会将其复制到0×7c00地址所在的物理内存中。其实被复制到物理内存的内容就是Boot Loader,而具体到你的电脑,那就是lilo或者grub了。
启动第三步--Boot Loader
Boot Loader 就是在操作系统内核运行之前运行的一段小程序。通过这段小程序,我们可以初始化硬件设备、建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的状 态,以便为最终调用操作系统内核做好一切准备。通常,BootL oade:是严重地依赖于硬件而实现的,不同体系结构的系统存在着不同的Boot Loader。
Linux的引导扇区内容是采用汇编语言编写的程序,其源代码在arch/i386/boot中(不同体系的CPU有其各自的boot目录),有4个程序文件:
◎bootsect.S,引导扇区的主程序,汇编后的代码不超过512字节,即一个扇区的 大 小
◎setup.S, 引导辅助程序
◎edd.S,辅助程序的一部分,用于支持BIOS增强磁盘设备服务
◎video.S,辅助程序的另一部分,用于引导时的屏幕显示
Boot Loader有若干种,其中Grub、Lilo和spfdisk是常见的Loader,这里以Grub为例来讲解吧。
系统读取内存中的grub配置信息(一般为menu.lst或grub.lst),并依照此配置信息来启动不同的操作系统。
启动第四步--加载内核
根据grub设定的内核映像所在路径,系统读取内存映像,并进行解压缩操作。此时,屏幕一般会输出“Uncompressing Linux”的提示。当解压缩内核完成后,屏幕输出“OK, booting the kernel”。
系统将解压后的内核放置在内存之中,并调用start_kernel()函数来启动一系列的初始化函数并初始化各种设备,完成Linux核心环境的建立。至此,Linux内核已经建立起来了,基于Linux的程序应该可以正常运行了。
start_kenrel() 定义在init/main.c中,它就类似于一般可执行程序中的main()函数,系统在此之前所做的仅仅是一些能让内核程序最低限度执行的初始化操作, 真正的内核初始化过程是从这里才开始。函数start_kerenl()将会调用一系列的初始化函数,用来完成内核本身的各方面设置,目的是最终建立起基 本完整的Linux核心环境。
start_kernel()中主要执行了以下操作:
(1) 在屏幕上打印出当前的内核版本信息。
(2) 执行setup_arch(),对系统结构进行设置。
(3) 执行sched_init(),对系统的调度机制进行初始化。先是对每个可用CPU上的runqueque进行初始化;然后初始化0号进程(其task struct和系统空M堆栈在startup_32()中己经被分配)为系统idle进程,即系统空闲时占据CPU的进程。
(4)执行parse_early_param()和parsees_args()解析系统启动参数。
(5)执行trap_in itQ,先设置了系统中断向量表。0-19号的陷阱门用于CPU异常处理;然后初始化系统调用向量;最后调用cpu_init()完善对CPU的初始化,用于支持进程调度机制,包括设定标志位寄存器、任务寄存器、初始化程序调试相关寄存器等等。
(6)执行rcu_init(),初始化系统中的Read-Copy Update互斥机制。
(7)执行init_IRQ()函数,初始化用于外设的中断,完成对IDT的最终初始化过程。
(8)执行init_timers(), softirq_init()和time_init()函数,分别初始系统的定时器机制,软中断机制以及系统日期和时间。
(9)执行mem_init()函数,初始化物理内存页面的page数据结构描述符,完成对物理内存管理机制的创建。
(10)执行kmem_cache_init(),完成对通用slab缓冲区管理机制的初始化工作。
(11)执行fork_init(),计算出当前系统的物理内存容量能够允许创建的进程(线程)数量。
(12)执行proc_caches_init() , bufer_init(), unnamed_dev_init() ,vfs_caches_init(), signals_init()等函数对各种管理机制建立起专用的slab缓冲区队列。
(13 )执行proc_root_init()Wl数,对虚拟文件系统/proc进行初始化。
在 start_kenrel()的结尾,内核通过kenrel_thread()创建出第一个系统内核线程(即1号进程),该线程执行的是内核中的 init()函数,负责的是下一阶段的启动任务。最后调用cpues_idle()函数:进入了系统主循环体口默认将一直执行 default_idle()函数中的指令,即CPU的halt指令,直到就绪队列中存在其他进程需要被调度时才会转向执行其他函数。此时,系统中唯一存 在就绪状态的进程就是由kerne_hread()创建的init进程(内核线程),所以内核并不进入default_idle()函数,而是转向 init()函数继续启动过程。
启动第五步--用户层init依据inittab文件来设定运行等级
内核被加载后,第一个运行的程序便是/sbin/init,该文件会读取/etc/inittab文件,并依据此文件来进行初始化工作。
其实/etc/inittab文件最主要的作用就是设定Linux的运行等级,其设定形式是“:id:5:initdefault:”,这就表明Linux需要运行在等级5上。Linux的运行等级设定如下:
0:关机
1:单用户模式
2:无网络支持的多用户模式
3:有网络支持的多用户模式
4:保留,未使用
5:有网络支持有X-Window支持的多用户模式
6:重新引导系统,即重启
启动第六步--init进程执行rc.sysinit
在 设定了运行等级后,Linux系统执行的第一个用户层文件就是/etc/rc.d/rc.sysinit脚本程序,它做的工作非常多,包括设定PATH、 设定网络配置(/etc/sysconfig/network)、启动swap分区、设定/proc等等。如果你有兴趣,可以到/etc/rc.d中查看 一下rc.sysinit文件。
线程init的最终完成状态是能够使得一般的用户程序可以正常地被执行,从而真正完成可供应用程序运行的系统环境。它主要进行的操作有:
(1) 执行函数do_basic_setup(),它会对外部设备进行全面地初始化。
(2) 构建系统的虚拟文件系统目录树,挂接系统中作为根目录的设备(其具体的文 件系统已经在上一步骤中注册)。
(3) 打开设备/dev/console,并通过函数sys_dup()打开的连接复制两次,使得文件号0,1 ,2 全部指向控制台。这三个文件连接就是通常所说的“标准输入”stdin,“标准输出”stdout和“标准出错信息”stderr这三个标准I/O通道。
(4) 准备好以上一切之后,系统开始进入用户层的初始化阶段。内核通过系统调用execve()加载执T子相应的用户层初始化程序,依次尝试加载程 序"/sbin/initl"," /etc/init"," /bin/init',和“/bin/sh。只要其中有一个程序加载获得成功,那么系统就将开始用户层的初始化,而不会再回到init()函数段中。至 此,init()函数结束,Linux内核的引导 部分也到此结束。
启动第七步--启动内核模块
具体是依据/etc/modules.conf文件或/etc/modules.d目录下的文件来装载内核模块。
启动第八步--执行不同运行级别的脚本程序
根据运行级别的不同,系统会运行rc0.d到rc6.d中的相应的脚本程序,来完成相应的初始化工作和启动相应的服务。
启动第九步--执行/etc/rc.d/rc.local
你如果打开了此文件,里面有一句话,读过之后,你就会对此命令的作用一目了然:
# This script will be executed after all the other init scripts.
# You can put your own initialization stuff in here if you don’t
# want to do the full Sys V style init stuff.
rc.local就是在一切初始化工作后,Linux留给用户进行个性化的地方。你可以把你想设置和启动的东西放到这里。
启动第十步--执行/bin/login程序,进入登录状态
此时,系统已经进入到了等待用户输入username和password的时候了,你已经可以用自己的帐号登入系统了