BIOS和启动区

CPU中的程序计数器寄PC中存放着CPU下一条执行指令的地址,在计算机通电后CPU会访问PC寄存器中的地址。
英特尔的芯片PC寄存器中的初始值为0xFFFF0,这意味着在计算机通电后CPU会执行0xFFFF0这个地址空间中的地址。我们把这块地址空间中存放的程序叫做BIOS(basic input output system)

Bios程序是硬件厂商写好的,无法改变。BIOS中的程序做的事情是将硬盘0盘0到1扇区的512字节的内容到内存0x7c00中。硬盘的0盘0道1扇区这块区域又叫做启动区MBR(Master Boot Record,主引导记录),启动区最后两个字节分别是0x550xaa,这是BIOS识别启动区的标志。

总结一下在计算机通电之后,CPU执行BIOS程序,BIOS搬运启动区代码到内存0x07c00中。

image-20240216120049371

bootsect.s

在linux0.11中,启动区的源码文件名为bootsect.s,它将被编译为二进制程序,存放在硬盘的0盘0道1扇区中,作为MBR。

当BIOS搬运完启动区的代码到内存0x7c00后,CPU将执行启动区的bootsect程序

让我们来看一下启动区的代码

复制自身到0x90000

1
2
3
4
5
6
7
8
9
10
11
_start:
mov ax,BOOTSEG
mov ds,ax ; ds=0x7c00
mov ax,INITSEG
mov es,ax ;es=0x9000
mov cx,256 ;循环256次,一次移动一字节
sub si,si
sub di,di
rep
movsw ;ds:di ---> es:di
jmp INITSEG:go ;跳转到(0x90000:go)

这段代码的目的是将内存地址0x7c00的512字节即启动区程序复制到0x9000处,进行调转指令,跳转地址为[0x9000:go],执行位于0x9000的bootsect程序的go位置

image-20240216120119381

加载setup.s程序到0x90200

bootssect在复制自身数据到内存0x90000后,会将setup程序移动到内存当中

1
2
3
4
5
6
7
8
9
10
11
12
load_setup:
mov dx,#0x0000 ; drive 0, head 0 驱动器和磁头参数
mov cx,#0x0002 ; sector 2, track 0 扇区参数
mov bx,#0x0200 ; address = 512, in INITSEG 存放地址 0x90200
mov ax,#0x0200+SETUPLEN ; service 2, nr of sectors 设置读功能,读取大小为4个扇区
int 0x13 ; read it
jnc ok_load_setup ; ok - continue 判断是否成功
mov dx,#0x0000
mov ax,#0x0000 ; reset the diskette 重置磁盘,重新加载setup
int 0x13
j load_setup

13号中断是BIOS提供的,用于读写磁盘。在这个程序中,我们使用了13号中断来将磁盘的setup程序搬运到内存0x90200处,共四个扇区大小,如果复制成功,则跳转到ok_load_setup处,否则重置磁盘,重复执行之前的代码

image-20240219171540677

加载system

bootsect在执行完load_setup后跳转到了ok_load_setup

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
ok_load_setup:

;获取磁盘信息
; Get disk drive parameters, specifically nr of sectors/track

mov dl,#0x00
mov ax,#0x0800 ; AH=8 is get drive parameters
int 0x13
mov ch,#0x00
seg cs
mov sectors,cx
mov ax,#INITSEG
mov es,ax

; Print some inane message

mov ah,#0x03 ; read cursor pos
xor bh,bh
int 0x10

mov cx,#24
mov bx,#0x0007 ; page 0, attribute 7 (normal)
mov bp,#msg1
mov ax,#0x1301 ; write string, move cursor
int 0x10

; ok, we've written the message, now

;加载操作系统代码
; we want to load the system (at 0x10000)

mov ax,#SYSSEG
mov es,ax ; segment of 0x010000
call read_it
call kill_motor

ok_load_setup使用13号中断获取了磁盘信息,并在屏幕上显示了一些message,之后将head.s和硬盘中的其他代码(system)的代码搬运到了0x10000

image-20240219172731440

setup.s

获取硬件信息

setup函数首先使用bios的中断获取计算机的一些硬件信息

1
2
3
4
5
6
7
8
9
start:
; ok, the read went well so we get current cursor position and save it for
; posterity.
mov ax,#INITSEG ; this is done in bootsect already, but...
mov ds,ax
mov ah,#0x03 ; read cursor pos
xor bh,bh
int 0x10 ; save it in known place, con_init fetches
mov [0],dx ; it from 0x90000.

上面代码使用10号中断读取光标位置,并存放在0x90000处,基地址为0x9000,偏移地址为0

1
2
3
4
5
; Get memory size (extended mem, kB)

mov ah,#0x88
int 0x15
mov [2],ax

使用15号中断获取内存大小

1
2
3
4
5
6
; Get video-card data:

mov ah,#0x0f
int 0x10
mov [4],bx ; bh = display page
mov [6],ax ; al = video mode, ah = window width

使用10号中断获取显存信息

1
2
3
4
5
6
7
8
; check for EGA/VGA and some config parameters

mov ah,#0x12
mov bl,#0x10
int 0x10
mov [8],ax
mov [10],bx
mov [12],cx

检查显示信息

1
2
3
4
5
6
7
8
9
10
11
; Get hd0 data

mov ax,#0x0000
mov ds,ax
lds si,[4*0x41]
mov ax,#INITSEG
mov es,ax
mov di,#0x0080
mov cx,#0x10
rep
movsb

获取第一块硬盘信息

1
2
3
4
5
6
7
8
9
10
11
; Get hd1 data

mov ax,#0x0000
mov ds,ax
lds si,[4*0x46]
mov ax,#INITSEG
mov es,ax
mov di,#0x0090
mov cx,#0x10
rep
movsb

获取第二块硬盘信息

1
2
;关闭中断
cli ; no interrupts allowed ;

关闭中断

给system挪个位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
; first we move the system to it's rightful place

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
jmp do_move

; then we load the segment descriptors

setup使用中断读取了硬件信息后将内存0x100000x90000的数据内容搬运到内存0开始的地方

linux系统启动的全过程

image-20240220103155128

实模式与保护模式

实模式的寻址方式

8086CPU的寄存器为16位,地址线为20位,为了扩展CPU的寻址能力,有效利用20位的地址线,cpu采用基地址偏移地址的方式来进行寻址。具体格式为物理地址=16位段地址*16+16位偏移地址

段地址*16也可以理解为段地址(2进制)左移4位后加上偏移地址。如何理解这种寻址方式呢?举个例子来说,180CM又可以表示为1米80CM,这里1可以理解为段地址,80CM对应着偏移地址.段地址和偏移地址拥有不同权重。

保护模式

在8086下,实模式寻址方式极不安全性,程序寻址不受限制,可以访问和修改内存中任意位置的数据。为了保证内存数据的安全,对权限进行控制,80386采用了一种新的方式来访问内存,我们称之为保护模式。当然80386也向下兼容88086实模式的寻址方式。

段选择子

在实模式下我们16位寄存器存放的是基地址,而在保护模式下,这16位有些位被赋予了不同的含义

image-20240215201954617

段选择子就是原来的CS和DS的16位寄存器,它划分为了段描述符索引,TI和RPL三部分。段描述符索引是全局描述符的表项下标索引。当TI=0时,查全局描述符GDT表;TI=1时,查LDT表。RPL表示请求特权级别,在linux中0表示高特权,1表示低特权

段描述符

描述符表GDT存在于内存当中,CPU的GDTR寄存器保存着GDT在内存中的地址,描述符表由若干个段描述符组成。

段描述符主要由基地址,段界限和访问权限组成。

界限限制了基地址的最大访问地址,使得程序不能越界访问地址

访问权限:比较段选择子的RPL,来对内存的进行权限控制

image-20240215200916287

段选择子中含有一个全局段描述符的索引。通过索引,可以获取到段描述符中的基地址。得到基地址之后,和偏移地址(偏移地址存放在esi中)进行合成,得到线性地址,如果不进行分页,这个线性地址就是物理地址。

image-20240216110717177

分页机制

image-20240220103525737

在进行分段后,如果不进行分页,那么线性地址就是逻辑地址。

使用分段机制后我们得提前划分内存空间,程序在内存中将被连续存储,这使得内存中一些碎片化的空间不能被有效利用起来,

于是我们引出了分页机制解决内存碎片化的问题。在分段后,我们得到线性地址。线性地址被划分为了三部分。

image-20240220104121388

一本书可以怎么样定位到某一页呢?我们的书一般被划分若干的章节,而每一章又被划分为了若干节,在书的目录页记录了每一章的页码。所以我们如果要查找第一章第二节往后10页的内容,我们可以这样操作,我们根据目录页查找到第一章对应的页码,跳转到第一章,再根据节号,定位到第一章第二节,往后翻十页。通过这样的访问方式,我们定位到了我们想要访问的页。

image-20240220110553519