Linux0.11源码笔记
bootsect.s_1
loaded at 0x7c00 by the bios-startup routines, and moves
iself out of the way to address 0x90000, and jumps there.
SETUPLEN = 4 ; nr of setup-sectors
BOOTSEG = 0x07c0 ; original address of boot-sector
INITSEG = 0x9000 ; we move boot here - out of the way
SETUPSEG = 0x9020 ; setup starts here
SYSSEG = 0x1000 ; system loaded at 0x10000 (65536).
ENDSEG = SYSSEG + SYSSIZE ; where to stop loading
; ROOT_DEV: 0x000 - same type of floppy as boot.
; 0x301 - first partition on first drive etc
ROOT_DEV = 0x306
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
go: mov ax,cs
mov ds,ax
mov es,ax
; put stack at 0x9ff00.
mov ss,ax
mov sp,#0xFF00 ; arbitrary value >>512
在系统启动时,BIOS会将硬盘中启动区的512字节数据,原封不动的放到**0x7c00**也就对应着源码中定义的: BOOTSEG = 0x07c0 地址处,并跳转到那个位置进行执行
启动区的定义非常简单,只要硬盘中的 0 盘 0 道 1 扇区的 512 个字节的最后两个字节分别是 0x55 和 0xaa,那么 BIOS 就会认为它是个启动区
mov ax,#BOOTSEG
mov ds,ax
在x86 16位实模式下(当时也没有保护模式的概念),内存地址是通过两个16位的地址拼成20位的地址来完成的,实际的操作就是 段地址<< 1 + 偏移地址 给出
所以此处,我们将0x07c0传入ds中作为数据段的地址,这么做的原因在于,我们开头说了BIOS会将硬盘中的512字节数据原封不动放到0x7c00处,而根据16位实模式下内存地址的规则,此处我们将0x07c0传入ds后,左移一位之后就会变成0x7c00,正好指向此处,方便以后通过这种基址的方式来访问内存中的数据
mov ax,#INITSEG
mov es,ax
mov cx,#256
sub si,si
sub di,di
说这段之前先来看一下#INITSEG在源代码中的注释:; we move boot here - out of the way,意思是接下来我们要将boot区这512字节的代码由0x07c0处复制到0x9000处,为什么呢,out of the way!
为了完成这个操作,我么需要完成以下前置铺垫
-
将
0x9000存入另一个段寄存器用于复制mov ax,#INITSEG mov es,ax -
要复制的数据有512字节,所以肯定要借助循环实现
mov cx,#256 sub si,si sub di,di将cx寄存器置为256:接下来要循环256次,由此可推断,每次要复制2字节(16位)的数据
si di两个寄存器清0,作为接下来复制操作偏移地址的寄存器
rep
movw
jmpi go,INITSEG
此处的作用的是将ds:si开始的512字节复制到es:di处,然后再跳到**0x9000:go**处继续执行,这样也就成功的将cs:ip指向了0x9000段下go的位置(此时0x7c00段的数据并没有被清空)
此处发生了一个很有意思的事情,当我到网上搜rep movw含义时,普遍给出的答案是:rep的作用是重复执行后面的代码cx次,movw的作用是将ds:si的数据以每次复制2字节(word)的方式,复制到es:di
可是这解释很不对劲,movw的功能未免太过于强大了,于是查阅了Intel® 手册卷2,果然上述解释是有问题的,这个功能来自于rep
Move CX bytes/words/double words DS:[(E)SI] to ES:[(E)DI] 将cx单位的数据从ds:si移动到es:di(16位)
go: mov ax,cs
mov ds,ax
mov es,ax
; put stack at 0x9ff00.
mov ss,ax
mov sp,#0xFF00 ; arbitrary value >>512
此时已转移到0x9000处(cs:0x9000),接下来将ds、es、ss寄存器都分别置为0x9000
由于栈是自上而下增长的,所以将sp寄存器的值置为0xFF00,此时栈顶指针就指向了0x9FF00
此处由注释可知对于
sp的值只要 arbitrary value >> 512 (任意值大于512即可 0x90200)因为从0x90200地址开始处要放置setup程序
bootsect.s_2
It then loads 'setup' directly after itself (0x90200), and the system
at 0x10000, using BIOS interrupts.
load_setup:
mov dx,#0x0000 ; drive 0, head 0
mov cx,#0x0002 ; sector 2, track 0
mov bx,#0x0200 ; address = 512, in INITSEG
mov ax,#0x0200+SETUPLEN ; service 2, nr of sectors
int 0x13 ; read it
jnc ok_load_setup ; ok - continue
mov dx,#0x0000
mov ax,#0x0000 ; reset the diskette
int 0x13
j load_setup
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
; After that we check which root-device to use. If the device is
; defined (!= 0), nothing is done and the given device is used.
; Otherwise, either /dev/PS0 (2,28) or /dev/at0 (2,8), depending
; on the number of sectors that the BIOS reports currently.
seg cs
mov ax,root_dev
cmp ax,#0
jne root_defined
seg cs
mov bx,sectors
mov ax,#0x0208 ; /dev/ps0 - 1.2Mb
cmp bx,#15
je root_defined
mov ax,#0x021c ; /dev/PS0 - 1.44Mb
cmp bx,#18
je root_defined
undef_root:
jmp undef_root
root_defined:
seg cs
mov root_dev,ax
; after that (everyting loaded), we jump to
; the setup-routine loaded directly after
; the bootblock:
jmpi 0,SETUPSEG
如作者的介绍说,接下来的代码作用在于:使用BIOS中断程序将setup(由system.s编译而成)直接加载到启动子程序后(0x900 << 1 + 200h = 0x90200h),并将system(由head.s编译而成)加载到地址0x10000处
加载setup(setup.s)
load_setup:
mov dx,#0x0000 ; drive 0, head 0
mov cx,#0x0002 ; sector 2, track 0
mov bx,#0x0200 ; address = 512, in INITSEG
mov ax,#0x0200+SETUPLEN ; service 2, nr of sectors
int 0x13 ; read it
jnc ok_load_setup ; ok - continue
INT 0X13传参的含义
0x13 号中断的处理程序是 BIOS 提前给我们写好的,读取磁盘的相关功能的函数。
-
dx
dh:磁头号dl:驱动器号(若为硬盘则位7要置位)
-
cx
ch:磁道(柱面)号的低8位cl:开始扇区(05位),磁道号(67位)
-
bx
es:bx指向数据缓冲区(出错则CF标志位置位)
-
ax
ah:0x02 - 读磁盘扇区到内存al:需要读出的扇区数量
所以对于上述传参的含义为:
mov dx,#0x0000 ; drive 0, head 0
mov cx,#0x0002 ; sector 2, track 0
mov bx,#0x0200 ; address = 512, in INITSEG
mov ax,#0x0200+SETUPLEN ; service 2, nr of sectors
int 0x13 ; read it
将0号驱动器0号磁头的第cx(2)个扇区开始的数据读取到es:bx处(0x90200h),共读取SETUPLEN(4)个扇区
jnc ok_load_setup ; ok - continue
若CF=0(没有出现错误)则跳转到ok_load_setup
mov dx,#0x0000
mov ax,#0x0000 ; reset the diskette
int 0x13
j load_setup
若上面jnc的跳转失败,则复位驱动器,并重复执行上述过程,一直不成功则一直循环
加载system(head.s开头)
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 ;重置es的值为0x9000
; 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 ;读取system模块
call kill_motor ;关闭驱动马达
; After that we check which root-device to use. If the device is
; defined (!= 0), nothing is done and the given device is used.
; Otherwise, either /dev/PS0 (2,28) or /dev/at0 (2,8), depending
; on the number of sectors that the BIOS reports currently.
seg cs
mov ax,root_dev
cmp ax,#0
jne root_defined
seg cs
mov bx,sectors
mov ax,#0x0208 ; /dev/ps0 - 1.2Mb
cmp bx,#15
je root_defined
mov ax,#0x021c ; /dev/PS0 - 1.44Mb
cmp bx,#18
je root_defined
undef_root:
jmp undef_root
root_defined:
seg cs
mov root_dev,ax
; after that (everyting loaded), we jump to
; the setup-routine loaded directly after
; the bootblock:
jmpi 0,SETUPSEG
ok_load_setup部分的代码的主要作用
-
取磁盘驱动器参数
-
在显示器上显示一些信息
-
将
system模块加载到0x10000处 (重要)把从硬盘第 6 个扇区开始往后的 240 个扇区,加载到内存 0x10000 处
jmpi 0,SETUPSEG
bootsect.s结束,并跳转到0x90200开始执行setup