一、总体功能
1.1 Linux操作系统启动主要流程
Linux操作系统启动部分的主要执行流程:当PC的电源打开后,80x86结构的CPU将自动进入实模式,并从地址0xFFF0开始自动执行程序代码,这个地址通常是ROM-BIOS中的地址。PC的BIOS将执行某些系统的检测,并在物理地址0处开始初始化中断向量。此后,它将启动设备的第一个扇区(磁盘引导扇区,512B)读入内存绝对地址0x7C00处,并跳转到这个地方。启动设备通常是软驱或硬盘。
Linux的最前面部分是用8086汇编语言编写的(boot/boootsect.S),它将由BIOS读入到内存绝对地址0x7C00 (31KB)处,当它被执行时就会把自己移动到内存绝对地址0x90000(576KB)处,并把启动设备盘中后2KB代码(boot/setup.S)读入到内存0x90200处,而内核的其他部分(system模块)则被读入到内存地址0x10000(64KB)开始处。
因为当时system模块的长度不会超过0x80000字节大小(512KB),所以bootsect程序把system模块读入物理地址0x10000开始位置处不会覆盖从0x90000(576KB)处开始的bootsect模块和setup模块。后面setup程序将会把system模块移动到物理内存起始位置处,这样system模块中代码的地址即等于实际的物理地址,便于对内核代码和数据进行操作。
启动部分识别主机的某些特性以及VGA卡的类型。如果需要,它会要求用户为控制台选择显示模式。然后将整个系统从地址0x10000移至0x0000处,进入保护模式并跳转至系统的余下部分。此时所有32位运行方式的设置启动被完成:IDT、GDT以及LDT被加载,处理器和协处理器也已确认,分页工作也设置好了。最终调用init/main.c中的main()程序。
1.2 bootsect的代码为什么不把系统模块直接加载到物理地址0x0000开始处而要在setup程序中再进行移动?
- 因为随后执行的setup开始部分的代码还需要利用ROM BIOS提供的中断调用功能来获取有关机器配置的一些参数(例如显示卡模式、硬盘参数表等)。当BIOS初始化时会在物理内存开始处放置一个大小为0x400字节(1KB)的中断向量表,因此在使用完BIOS的中断调用后才能将这个区域覆盖掉。
- 另外,作为完整可运行的Linux系统还需要有一个基本的文件系统支持,即根文件系统。根文件系统通常在另一个软盘上或一个硬盘分区中。为了通知内核所需要的根文件系统在什么地方,bootsect.S程序中给出了根文件系统所在的默认块设备号ROOT_DEV,在内核初始化时会使用编译内核时放在引导扇区第509、510(0x1fc~0x1fd)字节中的指定设备号。
二、bootsect.S程序
2.1 功能描述
bootsect.S代码是磁盘引导块程序,驻留在磁盘的第一个扇区中(引导扇区,0磁道,0磁头,第1个扇区)。在PC加电、ROM BIOS自检后,ROM BIOS会把引导扇区代码bootsect加载到内存地址0x7C00开始处并执行。在bootsect代码执行期间,它会将自己移动到内存绝对地址0x90000开始处并继续执行。
该程序的主要作用是首先把从磁盘第2个扇区开始的4个扇区的setup模块(由setup.S编译而成)加载到内存紧接着bootsect后面位置处(0x90200),然后利用BIOS中断0x13取磁盘参数表中当前启动引导盘的参数,接着在屏幕上显示“Loading system …”字符串。再把磁盘上setup模块后面的system模块加载到内存0x10000开始的地方。随后确定根文件系统的设备号,若没有指定,则根据所保存的引导盘的每磁道扇区数判别出盘的类型和种类并保存其设备号于root_dev(引导块的508地址处)中,最后长跳转到setup程序开始处(0x90200)去执行setup程序。2.2 其他信息
1. Linux0.12硬盘设备号
| 主设备号 | 代表设备 | | —- | —- | | 1 | 内存 | | 2 | 磁盘 | | 3 | 硬盘 | | 4 | ttyx | | 5 | tty | | 6 | 并行口 | | 7 | 非命名管道 |
由于1个硬盘中可以有1~4个分区,因此硬盘还依据分区的不同次设备号进行指定分区。因此硬盘的逻辑设备号由:设备号=主设备号×256+次设备号。
三、setup.S程序
3.1 功能描述
setup.S是一个操作系统加载程序,它的主要作用是利用ROM BIOS中断读取机器系统数据,并将这些数据保存到0x90000开始的位置(覆盖掉了bootsect程序所在的地方),所取得的参数和保留的内存位置如表所示。
| 内存地址 | 长度/B | 名称 | 描述 |
|---|---|---|---|
| 0x90000 | 2 | 光标位置 | 列号(0x00-最左端),行号(0x00-最顶端) |
| 0x90002 | 2 | 扩展内存数 | 系统从1MB开始的扩展内存数值(KB) |
| 0x90004 | 2 | 显示页面 | 当前显示页面 |
| 0x90006 | 1 | 显示模式 | |
| 0x90007 | 1 | 字符列数 | |
| 0x90008 | 2 | ?? | |
| 0x9000A | 1 | 显示内存 | 显示内存(0x00-64KB,0x0-128KB,0x02-192KB,0x03-256KB) |
| 0x9000B | 1 | 显示状态 | 0x00-彩色,I/O=0x3dX;0x01-单色,I/O=0X3bX |
| 0x9000C | 2 | 特性参数 | 显示卡特性参数 |
| 0x9000E | 1 | 屏幕行数 | 屏幕当前显示行数 |
| 0x9000F | 1 | 屏幕列数 | 屏幕当前显示列数 |
| … | |||
| 0x90080 | 16 | 硬盘参数表 | 第1个硬盘的参数表 |
| 0x90090 | 16 | 硬盘参数表 | 第2个硬盘的参数表(如果没有,则清零) |
| 0x901FC | 2 | 根设备号 | 根文件系统所在的设备号(bootsect.S中设置) |
然后setup程序将system模块从0x10000~0x8ffff(当时认为内核系统模块system的长度不会超过此值:512KB)整块向下移动到内存绝对地址0x00000处。接下来加载中断描述符表寄存器idtr和全局描述符表寄存器gdtr,开启A20地址线,重新设置两个中断控制芯片8259A,将硬件中断号重新设置为0x20~0x2f。最后设置CPU的控制寄存器CR0,从而进入32位保护模式运行,并跳转到位于system模块最前面部分的head.s程序继续运行。
四、head.s程序
4.1 功能描述
head.s程序在被编译生成目标文件后会与内核其他程序一起被链接成system模块,位于system模块的最前面开始部分。system模块将被放置在磁盘上setup模块之后开始的扇区中,即从磁盘上第6个扇区开始放置。一般情况下,Linux 0.12内核的system模块大约有120KB左右,因此在磁盘上大约占240个扇区。
从这里开始,内核完全都是在保护模式下运行了。head.s汇编程序与前面的语法格式不同,它采用的是AT&T的汇编语言格式,并且需要使用GUN的gas和gld进行编译链接。
这段程序实际上处于内存绝对地址0处开始的地方。这个程序的功能比较单一。首先是加载各个数据段寄存器,重新设置中断描述符表idt,共256项,并使各个表项均指向一个只报错误的哑中断子程序ignore_int。中断描述符表中每个描述符项也占8B。
