【Linux0.11代码分析】02 之 bootsect.s 启动流程
- 一、boot\bootsect.s
- 1.1 将bootsect.s 从0x7c00 移动到 0x90000 (512byte)
- 1.2 使用 int 0x13 中断加载 setup.s 程序到 0x90200
- 1.3 获取并解析磁盘驱动器的参数
- 1.4 开始加载 System模块到 0x10000 地址
- 1.5 确定根文件系统 root_dev=0x306
- 1.6 跳转到 0x90200 开始执行setup.s程序,它会将system从0x10000,加载到0x0000处
系列文章如下:
1.《【Linux0.11代码分析】01 之 代码目录分析》
2.《【Linux0.11代码分析】02 之 bootsect.s 启动流程》
3.《【Linux0.11代码分析】03 之 setup.s 启动流程》
4.《【Linux0.11代码分析】04 之 head.s 启动流程》
当 PC
电源打开后,整个开机流程如下:
80x86
结构的CPU
将自动进入实模式,从ROM-BIOS
的地址(0xFFFF0
)开始自动执行BIOS
程序代码,BIOS
将执行系统检测,如找到启动设备(一般为软盘或硬盘),并在物理地址0x0
处开始初始化中断向量- 然后
BIOS
将启动设备的第一个扇区(磁盘引导扇区,512
字节)的内容读取写入内存地址0x7c00
处,并跳转到0x7c00
处。
linux
内核最开始的代码是bootselect.s
,它将被BIOS
读入内存0x7C00
处,
然后,BIOS
将 CPU
交到 Linux
手中,就开始执行 bootsect.s
代码,正式开始 Linux
之旅
一、boot\bootsect.s
开机后,RAM 内存中各地址数据如下:
下面,我们来详细分析下代码实现
1.1 将bootsect.s 从0x7c00 移动到 0x90000 (512byte)
# boot\bootsect.s
! SYS_SIZE is the number of clicks (16 bytes) to be loaded.
! 0x3000 is 0x30000 bytes = 196kB, more than enough for current versions of linux
SYSSIZE = 0x3000 // 单位是clicks(16byte),编译链接后的system 模块的大小为 0x3000*16byte = 0x30000byte = 196608byte = 192kb
! bootsect.s is loaded at 0x7c00 by the bios-startup routines, and moves iself out of the way to address 0x90000, and jumps there.
// bootsect.s 被bios-startup子程序加载到0x7c00(31k)处,然后将自身移动到0x90000(576k)处,并跳转到那里
! It then loads 'setup' directly after itself (0x90200), and the system at 0x10000, using BIOS interrupts.
// 然后使用BIOS 中断将setup 直接加载到自已的后面,地址为0x90200(576.5k),将system 加载到0x10000(64k)
// 定义了6个全局标志符,在其他代码中也能看到
.globl begtext, begdata, begbss, endtext, enddata, endbss
.text // 文本段
begtext:
.data // 数据段
begdata:
.bss // 堆栈段
begbss:
.text // 文本段
// setup程序占4个扇区,4 x 512byte = 2048byte = 2kb
SETUPLEN = 4 ! nr of setup-sectors
// bootsect 的起始地址为 0x07c00(31kb) (单位 clicks 16byte)
BOOTSEG = 0x07c0 ! original address of boot-sector
// 要将 boot移动到 0x90000(576kb)处
INITSEG = 0x9000 ! we move boot here - out of the way
// setup 程序 从 0x90200(576.5kb) 处开始运行
SETUPSEG = 0x9020 ! setup starts here
// system 模块要加载到 0x10000(64kb) 处,加载截止段地址ENDSEG为 0x10000 + 0x30000 = 0x40000(256kb)
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 // 指定根文件系统设备是第2个硬盘的第1个分区。
// 0x300 - /dev/hd0 - 代表整个第 1 个硬盘;
// 0x301 - /dev/hd1 - 第 1个盘的第 1 个分区;
// 0x302 - /dev/hd2 - 第 1个盘的第 2 个分区;
// 0x303 - /dev/hd3 - 第 1个盘的第 3 个分区;
// 0x304 - /dev/hd4 - 第 1个盘的第 4 个分区;
// 0x305 - /dev/hd5 - 代表整个第 2 个硬盘盘;
// 0x306 - /dev/hd6 - 第 2个盘的第 1 个分区;
// 0x307 - /dev/hd6 - 第 2个盘的第 2 个分区;
// 0x308 - /dev/hd6 - 第 2个盘的第 3 个分区;
// 0x309 - /dev/hd9 - 第 2个盘的第 4 个分区;
///
// 1. 将bootsect.s 从0x7c00 移动到 0x90000 (512byte)
///
entry _start // 程序从 _start 处开始执行
_start:
mov ax,#BOOTSEG // ax = 0x07c0
mov ds,ax // 数据段寄存器DS = 0x07c0
mov ax,#INITSEG // ax = 0x9000
mov es,ax // 附加段寄存器ES = 0x9000
mov cx,#256 // cx = 256
sub si,si // 源地址: ds:si = 0x07C0 : 0X0000
sub di,di // 目的地址: es:di = 0x9000 : 0X0000
rep // 重复执行 ,直到 cx = 0
movw // 移动一个字节
jmpi go,INITSEG // 段间跳转指令 此时 CS = 0x9000
go: mov ax,cs // ax = cs = 0x9000
mov ds,ax // 数据段寄存器 ds = ax = 0x9000
mov es,ax // 段地址寄存器 es = ax = 0x9000
! put stack at 0x9ff00. // 将堆栈指针SP 指向 0x9ff00 (0x9000 : 0xff00)处,由于代码段移动过了,所以要重新设置堆栈段的位置
mov ss,ax // 栈段寄存器ss = 0x9000
mov sp,#0xFF00 // 堆栈指针sp = 0x9ff00 ! arbitrary value >>512
! load the setup-sectors directly after the bootblock.
! Note that 'es' is already set up.
1.2 使用 int 0x13 中断加载 setup.s 程序到 0x90200
# boot\bootsect.s
///
// 2. 使用 int 0x13 中断加载 setup.s 程序到 0x90200
///
load_setup:
// dx=0, cx=2, bx=0x200, ax=0x204 (每个扇512byte,0x204 * 512 = 258kb)
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
// 使用 INT13 中断服务程序读取扇516个扇区
int 0x13 ! read it
// 读取成功,则跳转到 ok_load_setup 标识符开始运行
jnc ok_load_setup ! ok - continue
// 读取失败,则重置 dx=0,ax=0,重新读取运行load_setup 直到成功为止
mov dx,#0x0000
mov ax,#0x0000 ! reset the diskette
int 0x13
j load_setup
ok_load_setup:
1.3 获取并解析磁盘驱动器的参数
# boot\bootsect.s
///
// 3. 获取并解析磁盘驱动器的参数
///
// 获取磁盘驱动器的参数,物别是每道的扇区数量
! Get disk drive parameters, specifically nr of sectors/track
// 驱动器号 dl = 0x0
mov dl,#0x00
// ax = 0x0800 , ah=0x08 说明获取驱动参数, al=0x00
mov ax,#0x0800 ! AH=8 is get drive parameters
// 触发0x13中断,传参 ah=0x08, dl=0x00
int 0x13
// 配置最大磁道号低八位 ch=0x00, cx中: 最大磁道号高2位(位6-7) ,每磁道最大扇区数低6位(位0-5)
mov ch,#0x00
seg cs // 以下代码的操作数在 cs段寄存器 所指向的段中
mov sectors,cx // 保存 cx 每磁道最大扇区数 到 sectors 中
mov ax,#INITSEG // ax = 0x9000
mov es,ax // es = 0x9000 , 由于上面改过了段地址,重新配置段地址寄存器 es = ax = 0x9000
! Print some inane message // 显示信息('Loading system ...'回车换行,共24个字符)
mov ah,#0x03 // 读入光标的位置 ! read cursor pos
xor bh,bh // 清空 bh = 0x0
int 0x10 // 触发 int 0x10中断
mov cx,#24 // cx = 24,共显示24个字符
mov bx,#0x0007 // BL 为属性,字符显示属性 ! page 0, attribute 7 (normal)
mov bp,#msg1 // bp 指向要显示的字符串: .ascii "Loading system ..."
--------------->
+ msg1:
+ .byte 13,10
+ .ascii "Loading system ..."
+ .byte 13,10,13,10
<---------------
mov ax,#0x1301 // ah=0x13 显示字符串,al=0x01彩色显示 ! write string, move cursor
int 0x10 // 触发 int 0x10中断开始显示
1.4 开始加载 System模块到 0x10000 地址
# boot\bootsect.s
///
// 4. 开始加载 System模块到 0x10000 地址
///
! ok, we've written the message, now we want to load the system (at 0x10000)
mov ax,#SYSSEG // ax = 0x10000
mov es,ax // ex = 0x10000 ! segment of 0x010000
call read_it // 调用子函数 read_it 读取system ,参数为 es
call kill_motor // 关闭软驱的马达
// 检查要使用哪个根文件系统设备(根设备),如果已经指定了设备(!=0),则直接用指定的,
// 否则需要根据BIOS报告的每磁道扇区数来确定使用/dev/PS0(2,28), 还是/dev/at0(2,8)
===================================================================================================
! This routine loads the system at address 0x10000, making sure no 64kB boundaries are crossed.
! We try to load it as fast as possible, loading whole tracks whenever we can.
!
! in: es - starting address segment (normally 0x1000)
!
sread: .word 1+SETUPLEN // 当前磁道中已读的扇区数。开始时已经读进 1 扇区的引导扇区 ! sectors read of current track
head: .word 0 // 当前磁头号 ! current head
track: .word 0 // 当前磁道号 ! current track
read_it:
mov ax,es
test ax,#0x0fff
die: jne die ! es must be at 64kB boundary
xor bx,bx ! bx is starting address within segment
rp_read:
mov ax,es
cmp ax,#ENDSEG ! have we loaded all yet?
jb ok1_read
ret
ok1_read:
seg cs
mov ax,sectors
sub ax,sread
mov cx,ax
shl cx,#9
add cx,bx
jnc ok2_read
je ok2_read
xor ax,ax
sub ax,bx
shr ax,#9
ok2_read:
call read_track
mov cx,ax
add ax,sread
seg cs
cmp ax,sectors
jne ok3_read
mov ax,#1
sub ax,head
jne ok4_read
inc track
ok4_read:
mov head,ax
xor ax,ax
ok3_read:
mov sread,ax
shl cx,#9
add bx,cx
jnc rp_read
mov ax,es
add ax,#0x1000
mov es,ax
xor bx,bx
jmp rp_read
read_track:
push ax
push bx
push cx
push dx
mov dx,track
mov cx,sread
inc cx
mov ch,dl
mov dx,head
mov dh,dl
mov dl,#0
and dx,#0x0100
mov ah,#2
int 0x13
jc bad_rt
pop dx
pop cx
pop bx
pop ax
ret
bad_rt: mov ax,#0
mov dx,#0
int 0x13
pop dx
pop cx
pop bx
pop ax
jmp read_track
!/*
! * This procedure turns off the floppy drive motor, so
! * that we enter the kernel in a known state, and
! * don't have to worry about it later.
! */
kill_motor:
push dx
mov dx,#0x3f2
mov al,#0
outb
pop dx
ret
sectors:
.word 0
.org 508
root_dev:
.word ROOT_DEV
boot_flag:
.word 0xAA55
.text
endtext:
.data
enddata:
.bss
endbss:
1.5 确定根文件系统 root_dev=0x306
# boot\bootsect.s
///
// 5. 确定根文件系统 root_dev=0x306
///
// 在Linux 中软驱的主设备号是2,次设备号 = type*4 + nr,其中nr为0-3 分别对应软驱A、B、C 或D;
// type 是软驱的类型(2: 1.2M 或 7: 1.44M 等)
// 因为7*4 + 0 = 28,所以 /dev/PS0 (2,28)指的是1.44M A 驱动器,其设备号是0x021c
// 同理2*4 + 0 = 8, 所以 /dev/at0 (2,8)指的是1.2M A 驱动器,其设备号是0x0208
! 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 // 以下代码的操作数在 cs段寄存器 所指向的段中
mov ax,root_dev // ax = ROOT_DEV = 0x306
cmp ax,#0
jne root_defined // 由于已经指定 root_dev=0x306, 跳转root_defined
seg cs
mov bx,sectors
mov ax,#0x0208 // 判断软驱类型 是否为1.2Mb ! /dev/ps0 - 1.2Mb
cmp bx,#15
je root_defined
mov ax,#0x021c // 判断软驱类型 是否为1.44Mb ! /dev/PS0 - 1.44Mb
cmp bx,#18
je root_defined
undef_root: // 如果找不到根文件系统设备,则在此处halt死机
jmp undef_root
root_defined:
seg cs
mov root_dev,ax // root_dev = 0x0306
1.6 跳转到 0x90200 开始执行setup.s程序,它会将system从0x10000,加载到0x0000处
# boot\bootsect.s
///
// 6. 跳转到 0x90200 开始执行setup.s程序,它会将system从0x10000,加载到0x0000处
///
! after that (everyting loaded), we jump to the setup-routine loaded directly after the bootblock:
jmpi 0,SETUPSEG // 跳转到 0x90200 开始执行 setup.s程序