我们要运行程序,必须先启动操作系统,但是刚开机的时候又是谁运行了操作系统呢?开机的那刻究竟发生了什么?接下来让我们一起来揭开操作系统启动过程的神秘面纱~
我先引用《Linux内核注释》的一段原话来让大家对开机后发生的情况有段简要的认识,然后结合代码来详细描述这个过程:
Linux 的最最前面部分是用 8086 汇编语言编写的(boot/bootsect.s),它将由 BIOS 读入到内存绝对地址 0x7C00(31KB)处,当它被执行时就会把自己移到绝对地址 0x90000(576KB)处,并把启动设备中后2kB 字节代码(boot/setup.s)读入到内存 0x90200 处,而内核的其它部分(system 模块)则被读入到从地址 0x10000 开始处,因为当时 system 模块的长度不会超过 0x80000 字节大小(即 512KB),所以它不会覆盖在 0x90000 处开始的 bootsect 和 setup 模块。随后将 system 模块移动到内存起始处,这样 system模块中代码的地址也即等于实际的物理地址。便于对内核代码和数据的操作。图 3.1 清晰地显示出 Linux系统启动时这几个程序或模块在内存中的动态位置。其中,每一竖条框代表某一时刻内存中各程序的映像位置图。在系统加载期间将显示信息"Loading..."。然后控制权将传递给 boot/setup.s 中的代码,这是另一个实模式汇编语言程序。
下面我将针对上图的1~6号步骤结合Linux代码进行详解,这节都是汇编,所以讲解起来可能很枯燥,如果你只是想知道这个时候操作系统做了什么,可以不必详读每步的内容,只需看标签上的总结和上面的图3.1基本上就可以了解了(学习资料传送门)
- 步骤<1>硬件自动加载硬盘第一个扇区内容到内存
为什么不直接让BOIS加载操作系统?由于BOIS只会自动加载硬盘的第一个扇区的512字节的内容,而操作系统的大小远远大于这个值,所以才会先加载操作系统自己的加载程序(这个可以很小),然后通过操作系统的加载程序加载操作系统(SYSTEM模块)到内存中。
- 步骤<2>将引导扇区内容移到0x90000
- .text
- BOOTSEG = 0x07c0
- INITSEG = 0x9000
- SYSSEG = 0x1000 | system loaded at 0x10000 (65536).
- ENDSEG = SYSSEG + SYSSIZE
- entry start
- start:
- mov ax,#BOOTSEG
- mov ds,ax
- mov ax,#INITSEG
- mov es,ax
- mov cx,#256
- sub si,si
- sub di,di
- rep
- movw
- jmpi go,INITSEG
这段汇编不是很难,应该不难看出,就是将ds(0x07c0):si指向的内容赋值给es(0x9000):di,一共赋值256字(MOVW)即512字节,然后跳到go这个标签地方,并且指明代码段为CS=INITSEG,即下条指令从INITSEG(0x9000)+offset go开始执行(段内跳转是跳转偏移不是绝对地址),执行的指令就等于现在的go标签后的内容(步骤3)。之所以要移动是因为0x10000-0x90000(实地址模式下地址是等于段寄存器值左移4位加上段内偏移的,所以这边是4个0不是前面段寄存器里面的3个了)等下要放操作系统的内核代码
- 步骤<3>完成新的段寄存器设置以及打印系统加载提示字符(注意0.11内核和0.12不同,没有setup.s)
- go: mov ax,cs
- mov ds,ax
- mov es,ax
- mov ss,ax
- mov sp,#0x400 | arbitrary value >>512
- ;这边AH=0x03是BIOS调用读取光标位置,返回值CH=光标起始行 DH,DL=行,列
- mov ah,#0x03 | read cursor pos
- xor bh,bh
- int 0x10
- ;这边AH=0x13是BIOS调用显示字符串,具体调用参数可以百度
- mov cx,#24
- mov bx,#0x0007 | page 0, attribute 7 (normal)
- mov bp,#msg1
- mov ax,#0x1301 | write string, move cursor
- int 0x10
这段主要是通过BOIS调用(这个时候操作系统还未启动,所有系统功能都是通过调用BOIS中断完成INT 0x10)完成打印"Loading system ..."字符串
- 步骤<4>加载真正的Linux操作系统到内存
- mov ax,#SYSSEG
- mov es,ax | segment of 0x010000
- call read_it
- call kill_motor
完成加载SYSTEM模块到0x10000的工作,关闭软驱马达用于读取其静态参数,有关read_it的流程图我已上传到本节学习资料
- 步骤<5>把Linux操作系统内核从0x10000移动到0x00000,在设置全局描述符后开启保护模式,并跳到0x0处执行操作系统内核指令
- mov ah,#0x03 | read cursor pos
- xor bh,bh
- int 0x10 | save it in known place, con_init fetches
- mov [510],dx | it from 0x90510.
- cli | no interrupts allowed !
- mov ax,#0x0000
- cld | 'direction'=0, movs moves forward
- do_move:
- mov es,ax | destination segment
- add ax,#0x1000
- cmp ax,#0x9000
- jz end_move
- mov ds,ax | source segment
- sub di,di
- sub si,si
- mov cx,#0x8000
- rep
- movsw
- j do_move
- end_move:
- mov ax,cs | right, forgot this at first. didn't work :-)
- mov ds,ax
- lidt idt_48 | load idt with 0,0
- lgdt gdt_48 | load gdt with whatever appropriate
- | that was painless, now we enable A20
- call empty_8042
- mov al,#0xD1 | command write
- out #0x64,al
- call empty_8042
- mov al,#0xDF | A20 on
- out #0x60,al
- call empty_8042
- mov al,#0x11 | initialization sequence
- out #0x20,al | send it to 8259A-1
- .word 0x00eb,0x00eb | jmp $+2, jmp $+2
- out #0xA0,al | and to 8259A-2
- .word 0x00eb,0x00eb
- mov al,#0x20 | start of hardware int's (0x20)
- out #0x21,al
- .word 0x00eb,0x00eb
- mov al,#0x28 | start of hardware int's 2 (0x28)
- out #0xA1,al
- .word 0x00eb,0x00eb
- mov al,#0x04 | 8259-1 is master
- out #0x21,al
- .word 0x00eb,0x00eb
- mov al,#0x02 | 8259-2 is slave
- out #0xA1,al
- .word 0x00eb,0x00eb
- mov al,#0x01 | 8086 mode for both
- out #0x21,al
- .word 0x00eb,0x00eb
- out #0xA1,al
- .word 0x00eb,0x00eb
- mov al,#0xFF | mask off all interrupts for now
- out #0x21,al
- .word 0x00eb,0x00eb
- out #0xA1,al
- mov ax,#0x0001 | protected mode (PE) bit
- lmsw ax | This is it!
- jmpi 0,8 | jmp offset 0 of segment 8 (cs)
- gdt:
- ;gdt[0]
- .word 0,0,0,0 | dummy
- ;gdt[1]
- .word 0x07FF | 8Mb - limit=2047 (2048*4096=8Mb)
- .word 0x0000 | base address=0
- .word 0x9A00 | code read/exec
- .word 0x00C0 | granularity=4096, 386
- ;gdt[2]
- .word 0x07FF | 8Mb - limit=2047 (2048*4096=8Mb)
- .word 0x0000 | base address=0
- .word 0x9200 | data read/write
- .word 0x00C0 | granularity=4096, 386