kernel源码(五)bootsect.s

编译器

由于当时的特殊情况,bootsect.s使用的是XX编译器

汇编格式

bootsect.s使用的是Intel的汇编格式

1 源码

!
! 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
!
!    bootsect.s        (C) 1991 Linus Torvalds
!
! bootsect.s is loaded at 0x7c00 by the bios-startup routines, and moves
! iself out of the way to address 0x90000, and jumps there.
!
! It then loads 'setup' directly after itself (0x90200), and the system
! at 0x10000, using BIOS interrupts. 
!
! NOTE! currently system is at most 8*65536 bytes long. This should be no
! problem, even in the future. I want to keep it simple. This 512 kB
! kernel size should be enough, especially as this doesn't contain the
! buffer cache as in minix
!
! The loader has been made as simple as possible, and continuos
! read errors will result in a unbreakable loop. Reboot by hand. It
! loads pretty fast by getting whole sectors at a time whenever possible.

.globl begtext, begdata, begbss, endtext, enddata, endbss
.text
begtext:
.data
begdata:
.bss
begbss:
.text

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

! load the setup-sectors directly after the bootblock.
! Note that 'es' is already set up.

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

! 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    ! 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

msg1:
    .byte 13,10
    .ascii "Loading system ..."
    .byte 13,10,13,10

.org 508
root_dev:
    .word ROOT_DEV
boot_flag:
    .word 0xAA55

.text
endtext:
.data
enddata:
.bss
endbss:

 

 SYSSIZE = 0x3000 表示的是system模块编译后占用的大小。单位是节,1节是16字节,即0x30000字节(约196KB)

下面定义了一些.开头的指令,称之为伪指令,是给编译器用的。.globl,表示这些标号链接时可见。然后定义了代码段、数据段和bss段,比如“.text:”表示代码段的地址。因为伪指令和注释以及类似于SYSSIZE=0x3000编译后不占用内存地址空间,所以标号begtext、begdata、begbss是重合的,也就是代码段、数据段、bss段他们是重合的,说明其是不分段的。我们可以这样理解:比如bootsect.S编译完之后放到了磁盘的第一个扇区,其中的这些伪指令不存在了,当linux启动时bootsect被加载到内存的0x7c0,当指令执行时此时的代码段不存在伪指令。

因为bootsect.s编译后是放到第一个扇区里面的,bios将其加载到实模式内存0x7c00处,也就是说,代码段、数据段、bss段都是在0x7c00作为起始地址。

.globl begtext, begdata, begbss, endtext, enddata, endbss
.text
begtext:
.data
begdata:
.bss
begbss:
.text

 

SETUPLEN = 4                ! setup程序的扇区数
BOOTSEG  = 0x07c0            ! boot-sector的段地址,实模式下,左移4位为0x07c00
INITSEG  = 0x9000            ! 我们把bootsect移动到这里
SETUPSEG = 0x9020            ! setup在这里
SYSSEG   = 0x1000            ! system模块放到这个位置
ENDSEG   = SYSSEG + SYSSIZE        ! 停止加载的地址

设备号

! ROOT_DEV:    0x000 - same type of floppy as boot.
!        0x301 - 表示第一个磁盘的第一个分区
ROOT_DEV = 0x306 第2个磁盘的第一个分区。为什么?稍微有点复杂,如果想了解请参考教材

entry start,entry为伪指令,告诉链接程序,程序的入口标号为start。

cx寄存器为计数寄存器,其用处:1、可做通用寄存器,2、数据寄存器(CH,CL),3、用在循环指令中,每次循环自动减1

sub减操作,sub aaa bbb,作用是aaa-bbb,然后放到aaa中

rep循环指令,功能是根据cx的值循环执行cx下面的指令,直到cx减到0

ds:si和es:di表示相关寄存器所引用的segment:offset。这里ds:si = 0x07c0:0x0000 = 0x7c00,es:di = 0x9000:0x0000 = 0x90000

不带参数的movw指令指的是,把ds:si移动到es:di位置

这样循环256次,就实现了把0x7c00位置开始接下来的256字节移动到了0x90000位置。

接着下面的跳转指令 jmpi go,INITSEG ,跳转到段的位置为INITSEG,偏移值为go标号的位置。

entry start
start:
    mov    ax,#BOOTSEG //把0x7c00放到ax中
    mov    ds,ax //把0x7c00放到数据段寄存器ds中,也就是数据段现在是0x7c00,(参考https://www.cnblogs.com/zhenjingcool/p/15929907.html)我们知道段寄存器存放段选择符,进而可以定位数据段的位置
    mov    ax,#INITSEG //ax设置成0x90000
    mov    es,ax //把0x90000移动到附加段寄存器es中
    mov    cx,#256 //cx存放立即数256
    sub    si,si //sub为减法运算符,si为源变址寄存器。si-si然后放入si中,这句的意思就是si=0,
    sub    di,di //di为目的变址寄存器
    rep //表示循环
    movw 
    jmpi    go,INITSEG //上面完成移动之后,跳转到0x90000的go标签处执行

上一条指令,跳转到0x90000位置的go标签对应的偏移量处执行,同时,代码段寄存器cs的值默认将修改为0x90000。

我们再看一下下面的代码,

go: mov    ax,cs //代码段寄存器的值赋给ax,因为上一步已经jump到0x90000的go标签处,因此此时代码段寄存器存储的是0x90000.此处把0x90000赋值给ax    
    mov    ds,ax //代码段寄存器的值赋给数据段寄存器,即0x90000
    mov    es,ax //代码段寄存器的值赋给附加段寄存器,即0x90000

下面的代码,堆栈段ss也赋值0x90000,堆栈指针sp赋值0xFF000

! put stack at 0x9ff00.
    mov    ss,ax
    mov    sp,#0xFF00        ! arbitrary value >>512

下面代码是加载setup模块到0x90200位置。

因为bios程序还驻留在内存中(参考:https://www.cnblogs.com/zhenjingcool/p/15938330.html),我们可以借助bios中断来实现访问硬盘。其中在内存0x13处就是bios的某个中断向量用于操作硬盘。bios中断的参数是通过设置ax、bx、cx、dx、es这些寄存器来实现的,具体可参考bios手册。

load_setup:
    mov    dx,#0x0000        ! dl表示驱动器号 0, dh表示磁头号 0
    mov    cx,#0x0002        ! cl低6位为开始扇区,也就是从2号扇区开始读, ch+cl的高2位为磁道号,这里为0号磁道
    mov    bx,#0x0200        ! 指向数据缓冲区,这里是es段的bx 这里既是:0x9000:0x200,也就是0x90200.
mov ax,#0x0200+SETUPLEN ! ah为0x02,表示service 2 读磁盘扇区到内存, al为4,表示读取4个扇区(setup模块占4个扇区) int 0x13 ! BIOS中断,查找中断向量表0x13表示读取磁盘到内存 jnc ok_load_setup ! 如果CF(标志寄存器的CF位,表示进位标志)没有进位,则说明int 0x13执行成功。jnc为转移指令,表示如果CF没有进位则跳转 mov dx,#0x0000 mov ax,#0x0000 ! reset the diskette int 0x13 j load_setup

 

  mov dx,#0x0000 ! dl表示驱动器号 0, dh表示磁头号 0
  mov cx,#0x0002 ! cl低6位为开始扇区,也就是从2号扇区开始读, ch+cl的高2位为磁道号,这里为0号磁道
  mov bx,#0x0200 ! 指向数据缓冲区,这里是es段的bx 这里既是:0x9000:0x200,也就是0x90200.
  mov ax,#0x0200+SETUPLEN ! ah为0x02,表示service 2 读磁盘扇区到内存, al为4,表示读取4个扇区(setup模块占4个扇区)
  int 0x13 ! BIOS中断,0x13表示读取磁盘到内存

上面这些步骤表示:从磁盘驱动器0的0号磁道的第2个扇区开始读取连续的4个扇区到0x90200位置。

  jnc    ok_load_setup        ! 如果CF(标志寄存器的CF位,表示进位标志)没有进位,则说明int 0x13执行成功。jnc为转移指令,表示如果CF没有进位则跳转
    mov    dx,#0x0000
    mov    ax,#0x0000        ! reset the diskette
    int    0x13
    j    load_setup

如果CF有进位,则不跳转,继续执行下面指令。 mov dx,#0x0000 和 mov ax,#0x0000 、 int 0x13 表示复位软驱,如果我们使用镜像启动,这里其实是没有意义的。一般情况下CF不会有进位,此处不会执行,万一执行到此处, j load_setup 又会跳转到load_setup执行,将会出现死循环,电脑死机。

下面我们看看ok_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

还是使用BIOS的int 0x13中断,查看中断向量表(参考https://blog.csdn.net/weixin_37656939/article/details/79684611),我们知道,ah=0x08表示取得驱动器参数,dl表示哪个驱动器,0x00 ~ 0x7F:软盘;0x80~0xFF:硬盘,这里读的是软盘。(因为linus开发linux内核时使用的软盘来做的)

设置好ah和dl,调用中断

 int 0x13 

如果中断执行成功,则

CF=0 操作成功
BL =驱动器类型
CH=柱面数的低8位
CL =扇区数(位 0-5 ),柱面数的高2位(位6-7)
DH=磁头数
DL=驱动器数
ES:DI=磁盘驱动器参数表

如果执行失败。则

CF=1 操作失败
AH=状态代码

这句指令 mov ch,#0x00 把ch置位0x00,因为这里读取的是软盘,1.44M的软盘柱面数是80,所以cl的高两位肯定是0,因为ch已经足够保存柱面数,所以cl的值就是每个柱面的扇区数。

    seg cs
    mov    sectors,cx

这里seg表示接下来的一行代码只针对cs段

 mov sectors,cx 表示把cs段偏移量cx处的值放入sectors中。这里是磁道0的总扇区数。

 mov ax,#INITSEG 把0x9000放入ax

 mov es,ax ,因为执行int 0x13获取软盘参数时es发生了变化,这里再设置回来,设置为原来的0x9000.

 

这一段代码,获取软盘的扇区数,好像没什么用处。

下面代码作用是显示信息到屏幕上,显示的内容为 #msg1 ,即[CRLF]Loading system ...[CRLF][CRLF]

msg1:
    .byte 13,10
    .ascii "Loading system ..."
    .byte 13,10,13,10
! 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

首先获取光标位置,调用BIOS中断 int 0x10,查找中断向量表可知,这个中断是显示服务。参数由ah决定,这里ah传递的是0x03,表示获取光标的位置。

执行

  int 0x10 

执行完该指令后,返回光标位置存储在DX中(DH存储行,DL存储列)

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

这些指令实现写字符串,移动光标到指定位置。同样是调用BIOS中断 0x10实现的。

 

接下来,加载system模块到内存的0x10000位置。

! ok, we've written the message, now
! we want to load the system (at 0x10000)

    mov    ax,#SYSSEG
    mov    es,ax        ! 附加段寄存器es赋值0x10000,
    call    read_it !加载
    call    kill_motor !关闭软驱马达

 

接下来,判断root_dev是否为0,如果是,则跳转到root_defined;如果不是0,判断扇区数是否为15,如果扇区数是15,则为1.2Mb软盘,跳转到root_defined;如果扇区数不是15则继续判断扇区数是否为18,如果是,则为1.44Mb软盘,跳转到root_defined;如果不是则执行undef_root,死循环,表示没有可用的软盘。

如果有软盘,则会执行标号root_defined,这里面设置root_defined,以存储当前使用的软盘类型。

! 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 //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

 

接下来是一条跳转指令,跳转到0x90200位置。

! after that (everyting loaded), we jump to
! the setup-routine loaded directly after
! the bootblock:
    jmpi    0,SETUPSEG

 我们知道,0x90200处是我们的setup.s

 

上面加载system模块的代码,我们这里再深入了解一下

read_it这个标签进行system模块的加载。

sread:    .word 1+SETUPLEN    ! 当前磁道的已读扇区
head:    .word 0            ! 当前磁头
track:    .word 0            ! 当前磁道

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

附加段寄存器es指向0x10000,这里首先把0x10000赋给ax,然后 test ax,#0x0fff 是与操作,这里与操作结果是1,。 die: jne die 如果test操作结果es中不是存储的0x10000,说明es被篡改,因此执行 die: jne die ,死循环。

接下来,我们看一下rp_read,这个标签作用是判断是否已经读取全部数据了。

rp_read:
    mov ax,es
    cmp ax,#ENDSEG        ! have we loaded all yet?
    jb ok1_read
    ret

这里比较附加段寄存器es是否为0x10200,也就是说判断setup.s是否都加载完了,如果es不是0x10200执行ok1_read,否则ret

 ok1_read

计算并验证当前磁道需要读取的扇区数量,放入ax当中。

ok1_read:
    seg cs
    mov ax,sectors //sectors存储的是磁道的总扇区数
    sub ax,sread //sread是已读取的扇区数,这里计算还需要读取的扇区数放到ax中
    mov cx,ax //cx存放还需要读取的扇区数
    shl cx,#9 //cx左移9位,表示cx乘以512(因为一个扇区512字节,所以这里计算还需要读取的字节数)
    add cx,bx //bx是基址寄存器,这里计算cx=cx+bx,如果cx没有溢出,也就是flag寄存器的进位标志位CF为0表示,没有超出要读取的最大范围64KB(因为cx16位,2^16=64KB)
    jnc ok2_read //如果CF没有进位,则跳转到ok2_read
    je ok2_read //这一句还不明白
    xor ax,ax //如果CF有进位,则ax通过异或方式清零
    sub ax,bx //ax=0-bx,相当于取bx反码
    shr ax,#9 //ax右移9位,相当于ax除以512,计算结果表示把ax转换成扇区数(因为一个扇区512字节)

 

ok2_read:

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

read_track:

read_track:
    push ax //ax、bx、cx、dx入栈
    push bx
    push cx
    push dx
    mov dx,track //dx存储当前磁道号0
    mov cx,sread //cx存储已经读取的扇区数
    inc cx //下一个扇区
    mov ch,dl //ch放入当前磁道号0
    mov dx,head //dx放入当前磁头0
    mov dh,dl // dh放入当前磁头号
    mov dl,#0 //dl放入0
    and dx,#0x0100 //限定dx要么是0x0000,要么0x0100,也就是磁头号不大于1
    mov ah,#2 //ah放入2,配合int 0x13
    int 0x13 //查看中断向量表,可知ah=2表示读扇区,
    jc bad_rt
    pop dx
    pop cx
    pop bx
    pop ax
    ret

int 0x13(BIOS中断参数大全:https://blog.csdn.net/weixin_37656939/article/details/79684611):

ah=0x02表示读取扇区

上面设置了ch(磁道),cl(扇区),dh(磁头),dl(驱动器),根据这些设置读取驱动器0上的0号磁道的某一个扇区。

 

 

 如果读取出错,则跳转到bad_rt。如果没有出错,则弹出dx,cx,bx,ax。返回到ok2_read处继续执行。

al放入传输的扇区数。

ok2_read:
    call read_track
    mov cx,ax //int 0x13调用成功后,把传输的扇区数放入al中;这里cx放入读取的扇区数
    add ax,sread //ax放入已经读取的扇区数
    seg cs
    cmp ax,sectors //比较已经读取的扇区数=总扇区数
    jne ok3_read //如果不相等,说明还没有读完,跳转到ok3-read
    mov ax,#1 //如果相等,则ax放入1
    sub ax,head //ax=1-head
    jne ok4_read //如果当前磁头head不是1磁头,则跳转到ok4_read
    inc track //当前磁道号加1

 

ok3_read:

ok3_read:
    mov sread,ax //sread放入已读取的扇区数
    shl cx,#9 //cx中存放的是上次读取硬盘时传输的扇区数,这里cx左移9位(相当于乘以512,磁盘扇区大小为512)也就是cx存入了传输的字节数.
    add bx,cx //bx=bx+cx
    jnc rp_read //如果CF没有置位,说明还没读够64kb(因为bx为16位,2^16=64KB),跳转到rp_read处,rp_read在前面介绍过,这里就是循环调用这个标签,直到读取到64KB数据为止
    mov ax,es
    add ax,#0x1000 //以上这两行的目的是将段的基地址调整到指向下一个64KB内存的位置。
    mov es,ax
    xor bx,bx
    jmp rp_read

 

当读完64KB数据,回调rp_read调用ok1_read的位置

rp_read:
    mov ax,es
    cmp ax,#ENDSEG        ! have we loaded all yet?
    jb ok1_read
    ret
jb ok1_read下一条是ret,返回到
! 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

下面执行 call kill_motor 关闭软驱马达

/*
 * 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

 

 

 

posted @ 2022-02-26 02:36  zhenjingcool  阅读(155)  评论(0编辑  收藏  举报