1 功能分析
大写的.s
后缀名, 是为了说明是一个16位, 实模式下的汇编语言, 小写的 s 是保护模式下的汇编语言;
1.1 使用中断,读取机器参数
setup.S
是一个操作系统的加载程序, 主要作用使用 ROM BIOS 中断读取机器系统数据, 并将这些数据保存到0X90000
开始的位置, 即覆盖掉原先bootsect 程序所在地方,
所取得的参数和保留的内存位置见表 6–2 所示。
这些参数将被内核中相关程序使用,例如字符设备驱动程序集中的 console.c 和 tty_io.c程序等。
利用BIOS中断程序填下面这张表格的内容:
如表中,
1)保存光标的位置
2)得到扩展内存的大小
3)得到显示卡当前的显示模式
4)检测显示方式
5)读取硬盘参数表信息
1.2 setup
移动 system
模块
然后, setup 程序将 system 模块从 0x10000-0x8ffff 整块向下移动到内存绝对地址 0x00000 处(当时认为内核系统模块 system 的长度不会超过此值:512KB)。
因为我们已经使用完所有的BIOS中断程序,所以由BIOS在0地址处建立的中断向量表也可以覆盖掉。
; 之前bootsect引导程序将system模块移动到(0x10000)处,
; 并把自己移动到(0x90000)处,把setup加载在它后面
; setup 程序将整个system模块移动到0x00000处,
; 即把从0x10000到0x8ffff的内存数据块整块的向内存地址低端移动了0x10000的位置
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 ! 循环移动,循环次数,每次循环完次数减 移动0x8000字
rep ! 用于把内容从ds:si 复制es:di 以字节单位
movsw ! rep是repeat,rep配合 movw(movsb) 就是多次复制直到cx=0为止 复制的次数放在cx中
jmp do_move
1.3 加载描述符表
原始的实模式下, 16位时, 寻址方式是: 段寄存器左移四位 + 偏移寄存器。
此时,加载的描述符表包含两种:
- . 中断描述符表
- . 全局描述符表 GDT (global descriptor table )
全局描述符表的出现, 是为了解决32位 保护模式下的寻址方式问题,
GDT 表: 是使用段寄存器CS
作为一个索引在一个地址表里找到32位的基地址,
然后再和偏移寄存器EIP 中的32位 数值相加,
得到最终的地址放到地址总线上去选定内存。
而为了让硬件找到这个表, GDT 表的起始地址被放在了一个GDTR 的寄存器中;
为进入保护模式做准备,
- 加载中断描述符表寄存器(IDTR)和全局描述符表寄存器(GDTR),
- 开启 A20 地址线,
- 重新设置两个中断控制芯片 8259A,
- 将硬件中断号重新设置为 0x20 - 0x2f。
实模式和保护模式下的寻址方式的区别
a)图记住一点,段的最大长度固定为64KB;
b)图段寄存器中保存的不再是段基地址而是描述符表的索引,并且段的最大长度是可变的。
1.4 模式切换
setup.s
从实模式 切换到 保护模式,
设置CPU的机器状态字寄存器CR0的PE位,进入 32 位保护模式运行,并跳转到位于 system 模块最前面部分的 head.s 程序继续运行。
进入保护模式:jmpi 0,8
加载机器状态字(控制寄存器CR0),将0位置1,CPU切换到保护模式
;进入保护模式,只是跳转到绝对地址0x00000处
; 加载机器状态字(控制寄存器CR0),将0位置1,CPU切换到保护模式
mov ax,#0x0001 ! protected mode (PE) bit 保护模式比特位(PE)
lmsw ax ! This is it! 加载状态寄存器
;段选择符8表示请求特权0级,使用GDT第二个段描述符
jmpi 0,8 ! jmp offset 0 of segment 8 (cs) 跳转至cs段偏移地址位0处(system已经移动到0x00000处)
2 .设备的划分
2.1. 磁盘
-
软盘: 是早期的产物, 负责从计算机上搬运出数据, 现在这个基本功能使用U盘完成了。 软盘并不是装在电脑里面的,而是可移动的,一般用来存储文件和不同电脑之间进行拷贝文件,就功能上来说它和现在的U盘是一样的,只是外形、存储原理不一样,它的容量要比硬盘小的多,比如最常用的3.5英寸的软盘容量只有1.44MB。
-
硬盘:一般都装在机箱里面,容量较大,用来存储数据。
磁盘包括软盘和硬盘。
一个磁盘由多个盘片(如下图中的 0 号盘片)叠加而成。盘片的表面涂有磁性物质,这些磁性物质用来记录二进制数据。因为正反两面都可涂上磁性物质,故一个盘片可能会有两个盘面。
2.2 磁道
每个盘片被划分成多个圆圈, 由内向外,圆圈逐个变大, 这样每个圆圈就形成了所谓的磁道。
在对每个圈,进行分段的划分弧度, 这样一个个分段的弧, 就形成了一个个扇区。
由此,
- 不同的磁道, 容量大小不同, 因为圆圈的大小不同;
- 同一个磁道下的各个扇区相同, 因为是在同一个磁道下划分的。
2.3 柱面
所有的盘面中相对位置相同的磁道组成柱面, 类似于一个圆柱面,
每个盘面对应一个磁头。所有的磁头都是连在同一个磁臂上的,因此所有磁头只能“共进退”。
磁盘的物理地址
可用(柱面号,盘面号,扇区号)来定位任意一个“磁盘块”
可根据该地址读取一个“块”,操作如下:
① 根据“柱面号”移动磁臂,让磁头指向指定柱面;
② 激活指定盘面对应的磁头;
③ 磁盘旋转的过程中,指定的扇区会从磁头下面划过,这样就完成了对指定扇区的读/写
3. 描述符表
描述符表其实就是内存中描述符项的一个阵列。
描述符表有两类:
-
全局描述符表(Gobal descriptor table-GDT)和
-
局部描述符表(Local descri ptantable−LDT)。
处理器是通过:
使用GDTR寄存器来定位GDT表。
LDTR寄存器来定位当前的LDT表。
这两个寄存器以线性地址的方式保存了描述符表的基地址和表的长度。
完整setup.s 代码
!
! setup.s (C) 1991 Linus Torvalds
!
! setup.s is responsible for getting the system data from the BIOS,
! and putting them into the appropriate places in system memory.
! both setup.s and system has been loaded by the bootblock.
!
! This code asks the bios for memory/disk/other parameters, and
! puts them in a "safe" place: 0x90000-0x901FF, ie where the
! boot-block used to be. It is then up to the protected mode
! system to read them from there before the area is overwritten
! for buffer-blocks.
!
; setup从BIOS中获取数据,并将这些数据保存到0x90000开始的位置处(0x90000-0x901FF覆盖了原来bootsect程序所在的地方)
; 此时setup和system已经由bootsect引导块加载到内存中
;
! NOTE! These had better be the same as in bootsect.s!
INITSEG = 0x9000 ! we move boot here - out of the way 原来bootsect所在段
SYSSEG = 0x1000 ! system loaded at 0x10000 (65536). system所在0x10000处
SETUPSEG = 0x9020 ! this is the current segment 本程序所在段地址
.globl begtext, begdata, begbss, endtext, enddata, endbss
.text
begtext:
.data
begdata:
.bss
begbss:
.text
entry start
start:
! ok, the read went well so we get current cursor position and save it for
! posterity.
; 保存光标的位置
; 使用BIOS中断取屏幕当前光标的位置(列,行),保存到内存(0x90000)处,2个byte
; 控制台初始化程序会到此处读取该值
; BISO 中断0x10 功能号 ah = 0x30 ,读光标的位置
; 输入:bh=页号
; 返回:返回:ch = 扫描开始线,cl = 结束开始线,dh = 行号(0x00顶端),dl=列号(0x00最左边)
mov ax,#INITSEG ! this is done in bootsect already, but...
mov ds,ax
mov ah,#0x03 ! read cursor pos 功能号 ah = 0x30 ,读光标的位置
xor bh,bh
int 0x10 ! save it in known place, con_init fetches
mov [0],dx ! it from 0x90000. 将ds设置成0x90000(INITSEG)
! Get memory size (extended mem, kB)
; 得到扩展内存的大小
; 利用BIOS中断0x15 功能号 ah= 0x88取系统所含扩展内存大小并保存到0x90002处
; 返回: ax= 0x10000(1M)处开始的扩展内存大小,若出错CF置位,ax=出错码
mov ah,#0x88
int 0x15
mov [2],ax !扩展内存的大小保存到0x90002处
! Get video-card data:
; 得到显示卡当前的显示模式
; 调用BIOS中断0x10,功能号 ah = 0x0f
; 返回:ah=字符列数,al=显示模式,bh=显示当前页数
mov ah,#0x0f
int 0x10
mov [4],bx ! bh = display page
mov [6],ax ! al = video mode, ah = window width
! check for EGA/VGA and some config parameters
; 检测显示方式
; 调用BIOS中断0x10, 功能号 ah=0x12,bl=0x10
mov ah,#0x12
mov bl,#0x10
int 0x10
mov [8],ax ! 0x90008 =ax
mov [10],bx ! 0x9000A = 安装的显示内存,0x9000B = 显示状态
mov [12],cx !0X9000C = 显卡特性参数
! Get hd0 data
; 取第一个硬盘信息
; 第一个硬盘参数表的首地址是中断向量0x41的向量值
; 第二个紧跟着对应着中断向量0x46
; 下面两个程序分别复制BIOS有关硬盘参数表,
; 第一个硬盘存放在0x90080,第二个硬盘存放在0x90090
mov ax,#0x0000
mov ds,ax
lds si,[4*0x41] !取中断向量0x41对应的地址 ,hd0参数表的地址--> ds:si
mov ax,#INITSEG
mov es,ax
mov di,#0x0080 !传输的目的地址(0x9000:0x0080) -->es:di
mov cx,#0x10 ! 循环次数,每次循环完次数减一,共传输16个字节
rep ! rep是repeat,rep配合 movw(movsb) 就是多次复制直到cx=0为止 复制的次数放在cx中
movsb ! 用于把内容从ds:si 复制es:di 以字节单位
! Get hd1 data
mov ax,#0x0000
mov ds,ax
lds si,[4*0x46] !取中断向量0x41对应的地址 ,hd0参数表的地址--> ds:si
mov ax,#INITSEG
mov es,ax
mov di,#0x0090
mov cx,#0x10
rep
movsb
! Check that there IS a hd1 :-)
; 检测是否有第二个硬盘,如果没有则把第2个清零
; 利用BIOS中断调用0x13的取盘的类型,功能号 ah =0x15
mov ax,#0x01500
mov dl,#0x81 ! dl = 驱动器号(0x8X是硬盘,0x81是第一个硬盘,0x82是第二个硬盘)
int 0x13
jc no_disk1 ! 第二个不存在
cmp ah,#3 ! ah =类型码 指硬盘
je is_disk1 ! 存在
; 第二个硬盘不存在,对第二个硬盘表清零
no_disk1:
mov ax,#INITSEG
mov es,ax
mov di,#0x0090
mov cx,#0x10
mov ax,#0x00
rep
stosb
; 第二个硬盘存在,进入保护模式,从此开始不允许中段
is_disk1:
! now we want to move to protected mode ...
cli ! no interrupts allowed !
! first we move the system to it's rightful place
; bootsect引导程序将system模块移动到(0x10000)处,
; 并把自己移动到(0x90000)处,把setup加载在它后面
; 下面这段程序将整个system模块移动到0x00000处,
; 即把从0x10000到0x8ffff的内存数据块整块的向内存地址低端移动了0x10000的位置
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 ! 循环移动,循环次数,每次循环完次数减 移动0x8000字
rep ! 用于把内容从ds:si 复制es:di 以字节单位
movsw ! rep是repeat,rep配合 movw(movsb) 就是多次复制直到cx=0为止 复制的次数放在cx中
jmp do_move
! then we load the segment descriptors
; 加载段描述符,设置全局描述符表和中断描述表
end_move:
mov ax,#SETUPSEG ! right, forgot this at first. didn't work :-)
mov ds,ax
; lidt指令用于加载中断描述符表(IDT)寄存器
; 中断描述符表中每一个8个字节对应每个中断发生时所需要的中断程序地址入口
lidt idt_48 ! load idt with 0,0
; lgdt指令用于加载全局描述符表(GDT)寄存器
; 全局描述符表中每个描述符项(8字节)描述了保护模式下数据段和代码段的信息
lgdt gdt_48 ! load gdt with whatever appropriate
! that was painless, now we enable A20
; 开启A20地址线,为了能够访问和使用1MB以上的物理内存
call empty_8042 ! 测试8042状态寄存器,等待输入缓冲器空,
mov al,#0xD1 ! command write 0xD1命令码表示写数据到8042的P2端口
out #0x64,al
call empty_8042 !等待输入缓冲器空,看命令是否被接受
mov al,#0xDF ! A20 on
out #0x60,al
call empty_8042 !若此时输入缓冲器为空,则表示A20线也选通
! well, that went ok, I hope. Now we have to reprogram the interrupts :-(
! we put them right after the intel-reserved hardware interrupts, at
! int 0x20-0x2F. There they won't mess up anything. Sadly IBM really
! messed this up with the original PC, and they haven't been able to
! rectify it afterwards. Thus the bios puts interrupts at 0x08-0x0f,
! which is used for the internal hardware interrupts as well. We just
! have to reprogram the 8259's, and it isn't fun.
; 重新对中断进行编程
mov al,#0x11 ! initialization sequence
out #0x20,al ! send it to 8259A-1 发送到8259A主芯片
; 0x00eb直接使用机器码表示两条相对跳转指令,起延时作用
.word 0x00eb,0x00eb ! jmp $+2, jmp $+2
out #0xA0,al ! and to 8259A-2 再发送到8259A从芯片
.word 0x00eb,0x00eb
; 系统硬件中断号被设置成0x20开始
mov al,#0x20 ! start of hardware int's (0x20)
out #0x21,al !送主芯片ICW2命令字,设置起始中断,要送奇端口
.word 0x00eb,0x00eb
mov al,#0x28 ! start of hardware int's 2 (0x28)
out #0xA1,al !送主芯片ICW2命令字,从芯片的起始中断号
.word 0x00eb,0x00eb
mov al,#0x04 ! 8259-1 is master
out #0x21,al !ICW3
.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
! well, that certainly wasn't fun :-(. Hopefully it works, and we don't
! need no steenking BIOS anyway (except for the initial loading :-).
! The BIOS-routine wants lots of unnecessary data, and it's less
! "interesting" anyway. This is how REAL programmers do it.
!
! Well, now's the time to actually move into protected mode. To make
! things as simple as possible, we do no register set-up or anything,
! we let the gnu-compiled 32-bit programs do that. We just jump to
! absolute address 0x00000, in 32-bit protected mode.
; 进入保护模式,只是跳转到绝对地址0x00000处
; 加载机器状态字(控制寄存器CR0),将0位置1,CPU切换到保护模式
mov ax,#0x0001 ! protected mode (PE) bit 保护模式比特位(PE)
lmsw ax ! This is it! 加载状态寄存器
;段选择符8表示请求特权0级,使用GDT第二个段描述符
jmpi 0,8 ! jmp offset 0 of segment 8 (cs) 跳转至cs段偏移地址位0处(system已经移动到0x00000处)
! This routine checks that the keyboard command queue is empty
! No timeout is used - if this hangs there is something wrong with
! the machine, and we probably couldn't proceed anyway.
; 检差键盘命令队列是否为空
; 只有当输入缓冲器为空(键盘控制器状态寄存器位1 = 0)才可以进行写命令
empty_8042:
.word 0x00eb,0x00eb !延时作用
in al,#0x64 ! 8042 status port
test al,#2 ! is input buffer full?
jnz empty_8042 ! yes - loop
ret
; GDT全局描述符表开始处,描述符表由多个8字节长的描述符项组成,
; 3个描述符项
; 第一项没有作用,但是必须存在
; 第二项是系统代码段描述符
; 第三项是系统数据段描述符
gdt:
.word 0,0,0,0 ! dummy 第一个描述符 不用
; 在GDT表这里的偏移量是0x80,它是内核代码段选择符的值
.word 0x07FF ! 8Mb - limit=2047 (2048*4096=8Mb)
.word 0x0000 ! base address=0
.word 0x9A00 ! code read/exec
.word 0x00C0 ! granularity=4096, 386
; 在GDT表这里的偏移量是0x10,它是内核数据段选择符的值
.word 0x07FF ! 8Mb - limit=2047 (2048*4096=8Mb)
.word 0x0000 ! base address=0
.word 0x9200 ! data read/write
.word 0x00C0 ! granularity=4096, 386
; 加载中断描述符表寄存器(idtr)
; 这里设置一个长度为0的空表
idt_48:
.word 0 ! idt limit=0
.word 0,0 ! idt base=0L
; 加载全局描述符表寄存器(gdtr)
; GDT表长度为2kb
gdt_48:
.word 0x800 ! gdt limit=2048, 256 GDT entries
.word 512+gdt,0x9 ! gdt base = 0X9xxxx
.text
endtext:
.data
enddata:
.bss
endbss: