Linux内核(0.12)-bootsect.S分析

功能介绍

  1. 把自己移到0x90000处(本来在0x7c00)
  2. 从磁盘把第2-4个扇区的setup模块读入到0x90200处(紧接着bootsect)
  3. 取磁盘参数表中当前启动引导磁盘的参数
  4. 显示“Loading system”字符串
  5. system模块加载到0x10000处
  6. 确定要文件系统的设备号
  7. 跳转到setup程序开始处(0x90200)

遇到问题

汇编中包含了C语言inlcude语言:

bootsect.S中C语言代码

#include <linux/config.h>

Makefile中的处理:

boot/bootsect.s:    boot/bootsect.S include/linux/config.h  #执行C语言预处理,替代*.S文件中的宏定义生成对应的*.s文件
    $(CPP) -traditional boot/bootsect.S -o boot/bootsect.s

因为在Makefile中,对bootsect.S做了预处理操作,用C语言的预处理方式处理汇编语言。在之后的编译连接时,c语言的语法已经被预处理了。

段重叠怎么实现?

bootsect.S汇编的结构:

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

.text      !这里的.text 表示后面的内容将存放在.text中
省略......

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

Makefile中的处理:

boot/bootsect.s:    boot/bootsect.S include/linux/config.h  #执行C语言预处理,替代*.S文件中的宏定义生成对应的*.s文件
    $(CPP) -traditional boot/bootsect.S -o boot/bootsect.s

boot/bootsect:  boot/bootsect.s             #使用8086汇编编译器和连接器生成程序
    $(AS86) -o boot/bootsect.o boot/bootsect.s
    $(LD86) -s -o boot/bootsect boot/bootsect.o
  • 首先,在汇编源文件中通过将这三个段定义在一起,实现了段重叠。
  • 其次,在汇编源文件中通过将这个bootsect.s单独编译连接,实现了对段重叠。

为什么此汇编程序有256个字节

bootsect.S汇编程序代码:

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

具体解释在后面的代码中已经贴出来了。这里就讨论一下为什么这个函数是256个字的大小。因为在程序的最后,加了如下代码:

.org 506                        !指定输入下面内容的位置
swap_dev:                       
    .word SWAP_DEV              !写入一个字内容,两个字节
root_dev:
    .word ROOT_DEV              !写入一个字内容,两个字节
boot_flag:
    .word 0xAA55                !写入一个字内容,两个字节

注释代码

!
! 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
!
! SYS_SIZE是要加载的系统模块长度,单位是节,每节16字节。0x3000共为0x30000字节,以1000字节
! 表示1KB,则为196KB。若以1024字节为1KB计,则应该是192KB。对于当前的内核版本这个空间长度已足够了。
! 当该值为0x8000时,表示内核最大为512KB。因为内存0x90000处开始存放移动后的bootsectsetup的代码
! 因此该值最大不得超过0x9000(表示584KB)。
! 这里的感叹号“!”和分号“;”都表示程序注释语句的开始。

! 
! 头文件linux/config.h中定义了内核用到的一些常数符号和linux自己使用的默认硬盘参数块.
! 例如其中定义了一下一些常数:
! #define DEF_INITSEG   0x9000      -默认系统模块长度。单位为节,每节为16字节
! #define DEF_SYSSEG    0x1000      -默认本程序代码移动目的段位置
! #define DEF_SETUPSEG  0x9020      -默认setup程序代码段位置
! #define DEF_SYSSIZE   0x3000      -默认从磁盘加载系统模块到内存的段位置
#include <linux/config.h>
SYSSIZE = DEF_SYSSIZE               !定义一个标号或符号.指明编译连接后system模块的大小.

!
!   bootsect.s      (C) 1991 Linus Torvalds
!   modified by Drew Eckhardt
!
! 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 doesnt 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.
! 
! 以下是前面文字的译文:
!       bootsect.S              (C)1991 Linus Torvalds
!       Drew Eckhardt修改
!
! bootsect.SROM BIOS启动子程序加载至0x7c00(31KB)处,并将自己移到了地址0x90000
! (576KB)处,并跳转到那里.
!
! 它然后使用BIOS中断将"setup"直接加载到自己的后面(0x90200)(576.5KB),并将system加载
! 到地址0x10000处.
!
! 注意!前面内核系统最大长度限制为(8*65536)(512KB)B,也就是将前面的0x80000空间全都用作system数据存放,
! 即使是在将来这也应该没有问题的,我想让它保持简单明了.
! 这样512KB的最大内核长度应该足够了,尤其是这里没有像MINIX中一样包含缓冲区高度缓冲.
!
! 加载程序已经做到够简单了,所以持续地读操作出错将导致死循环.只能手工重启.只要可能,通过一次读取所有扇区,加载
! 过程可以做得很快.

! 伪指令(伪操作符).globl 或 .global 用于定义随后的标识符是外部的或全局的,并且即使不使用也强制引入.
! .text,.data和.bss用于分别定义当前代码段,数据段和未初始化数据段。在连接多个目标模块时,
! 连接程序(ld86)会根据他们的类别把各个目标模块中的相应段分别组合(合并)在一起。这里把三个段都定义在
! 同一重叠地址范围中,因此本程序实际上是不分段。另外,后面带冒号的字符串是标号.标号位于一条指令的第一个字段。
! 他代表其所在位置的地址,通常指明一个跳转指令的目标位置。
.globl begtext, begdata, begbss, endtext, enddata, endbss
.text                       ! 文本段(代码段)
begtext:
.data                       ! 数据段
begdata:
.bss                        ! 未初始化数据段
begbss:
.text                       ! 再次返回到文本段

! 下面等号 '=' 或符号 'EQU'用于定义标识符或者标号所代表的值。
SETUPLEN = 4                ! nr of setup-sectors
                            ! setup程序代码占用磁盘扇区(setup-sectors)的值
BOOTSEG  = 0x07c0           ! original address of boot-sector
                            ! bootsect代码所在内存原始段地址
INITSEG  = DEF_INITSEG          ! we move boot here - out of the way
                                !bootsect移到位置0x90000 - 避开系统模块占用处
SETUPSEG = DEF_SETUPSEG         ! setup starts here
                                ! setup程序从内存0x90200处开始
SYSSEG   = DEF_SYSSEG           ! system loaded at 0x10000 (65536).
                                ! system模块加载到0x10000(64KB)处
ENDSEG   = SYSSEG + SYSSIZE     ! where to stop loading
                                ! 停止加载的段地址

! ROOT_DEV & SWAP_DEV are now written by "build".
! 根文件系统设备号ROOT_DEV和交换设备号SWAP_DEV现在由tools目录下的build程序写入。
! 设备号0x306指定根文件系统设备是第2个硬盘的第一个分区。当年linus是在第2个硬盘上
! 安装了linux 0.11系统,所以这里ROOT_DEV被设置为0x306。在编译这个内核时,你可以
! 根据自己根文件系统所在设备位置修改这个设备号。这个设备号是linux系统老式的硬盘设备
! 号命名方式,硬盘设备号具体值的含义如下:
! 方式,具体值的含义如下:
! 设备号=主设备号*256 + 次设备号(也即dev_no = (major<<8) + minor! (主设备号:1-内存,2-磁盘,3-硬盘,4-ttyx,5-tty,6-并行口,7-非命名管道)
! 0x300 - /dev/hd0 - 代表整个第1 个硬盘;
! 0x301 - /dev/hd1 - 第1 个盘的第1 个分区;
!! 0x304 - /dev/hd4 - 第1 个盘的第4 个分区;
! 0x305 - /dev/hd5 - 代表整个第2 个硬盘盘;
! 0x306 - /dev/hd6 - 第2 个盘的第1 个分区;
!! 0x309 - /dev/hd9 - 第2 个盘的第4 个分区;
!linux 内核0.95 版后已经使用与现在相同的命名方法了。
ROOT_DEV = 0                ! 根文件系统设备使用与系统引导时同样的设备
SWAP_DEV = 0                ! 交换设备使用与系统引导时同样的设备

! 伪指令entry表示连接程序在生成的执行程序(a.out)中包含指定的标识符和标号。这里是程序执行
! 开始点。下面"start"作用为将bootsect自身从BOOTSEG0x07c0)移动到INITSEG0x9000)位置处,用256字。
! 最后用一个跳转到移动后代码的go标号处,即本程序的下一语句处。
entry start
start:
    mov ax,#BOOTSEG                 !ds段寄存器置为 0x07c0
    mov ds,ax
    mov ax,#INITSEG                 !es段寄存器置为 0x9000
    mov es,ax
    mov cx,#256                     ! 设置移动计数值=256字(512字节)
    sub si,si                       ! 源地址  ds:si = 0x07c0:0x0000
    sub di,di                       ! 目的地址  es:di = 0x9000:0x0000
    rep                             ! 重复执行并递减cx的值,直到cx = 0 为止
    movw                            !movw指令。从内存[si]处移动cx个字到[di]处
    jmpi    go,INITSEG              ! 表示段间跳转,跳转到新段地址(INITSRG)的标号为go的地址

! 从下面开始,CPU在已移动到0x90000位置处的代码中执行。
! 从这段代码设置几个段寄存器,包括栈寄存器sssp。栈指针sp只要指向远大于512字节偏移
! (即地址0x90200)处都可以。因为从0x90200地址开始处还要放置setup程序,而此时setup
! 程序大约为4个扇区,因此sp要指向大于(0x200 + 0x200 * 4 + 堆栈大小)位置处。这里sp
! 设置为0xff00 - 12B(参数表长度),即sp=0xfef4。在此之上位置会存放一个自建的驱动器
! 参数表,见下面说明。实际上BIOS把引导扇区加载到0x7c00处并把执行权交给引导程序时,
! ss = 0x00, sp = 0xfffe
! 另外,在go下面有这么一句话'push ax',push指令的期望作用是想暂时把段值保留在栈中,然后等下面执行完判断磁道
! 扇区数后再弹出栈,并给段寄存器fsgs赋值。但是由于'push ax'后面两句'mov ss,ax \n mov sp,dx'修改了栈段的位置,
! 因此除非在执行栈弹出操作之前把栈段复原到原位置,否则这样设计就是错误的。因此这里存在一个bug! 改正的方法之一是去掉'push ax',并把后面获取ax'pop ax'改为'mov ax,cs'
go: mov ax,cs       !ds,esss都设置成移动后代码所在的段处(0x9000)
    mov dx,#0xfef4  ! arbitrary value >>512 - disk parm size

    mov ds,ax
    mov es,ax
    push    ax      ! 临时保存段值(0x9000),供后面'pop ax'使用。(bugmov ss,ax       ! put stack at 0xff00 - 12.
    mov sp,dx
/*
 *  Many BIOSs default disk parameter tables will not 
 *  recognize multi-sector reads beyond the maximum sector number
 *  specified in the default diskette parameter tables - this may
 *  mean 7 sectors in some cases.
 *
 *  Since single sector reads are slow and out of the question,
 *  we must take care of this by creating new parameter tables
 *  (for the first disk) in RAM.  We will set the maximum sector
 *  count to 18 - the most we will encounter on an HD 1.44.  
 *
 *  High doesnt hurt.  Low does.
 *
 *  Segments are as follows: ds=es=ss=cs - INITSEG,
 *      fs = 0, gs = parameter table segment
 */
/*
 * 对于多扇区读操作所读的扇区数超过默认磁盘参数表中指定的最大扇区数时,很多BIOS将不能
 * 进行正确识别。在某些情况下是7个扇区。
 * 
 * 由于单扇区读操作太慢,不予以考虑,因此我们必须通过在内存中重创建新的参数表(为第一个
 * 驱动器)来解决这个问题。我们将把其中最大扇区数设置为18,即在1.44MB磁盘上会碰到的
 * 最大数值。
 *
 * 这个数值大了不会出现问题,但是太小就不行了。
 *
 * 段寄存器将被设置为 ds=es=ss=cs - 都为INITSEG0x9000),
 * fs=0gs=参数表所在的段值。
 *
*/
! BIOS设置的中断0x1E的中断向量值是软驱参数表地址。该向量值位于内存0x1E*4=0x78
! 处。这段代码首先从内存0x0000:0x0078处复制原软驱参数表到0x9000:0xfef4处,然后修改
! 表中的每磁道最大扇区数为18.
    push    #0          ! 置段寄存器fs=0
    pop fs              ! fs:bx 指向存有软驱参数表地址处(指针的指针)
    mov bx,#0x78        ! fs:bx is parameter table address

! 下面指令表示下一条语句的操作数在fs段寄存器所指的段中。他只影响其下一条语句。这里把fs:bx
! 所指内存位置处的表地址放在寄存器对gs:si中作为原地址。寄存器对es:di=0x9000:0xfef4为 
! 目的地址
    seg fs
    lgs si,(bx)         ! gs:si is source

    mov di,dx           ! es:di is destination
    mov cx,#6           ! copy 12 bytes
    cld                 ! 清方向标志。复制时指针递增。

    rep                 ! 复制12字节的软驱参数表到目的地址(0x9000:0xfef4)
    seg gs
    movw

    mov di,dx               ! es:di指向新表,然后修改表中偏移4处的最大扇区数。
    movb    4(di),*18       ! patch sector count

! 让中断向量0x1E的值指向新表
    seg fs                  
    mov (bx),di             
    seg fs                      
    mov 2(bx),es

    pop ax                  ! 此时ax中是面保留下来的段值(0x9000)。在上面已经指出这是一个bug
                            ! 可以改为'mov ax,cs',也是同样效果
    mov fs,ax               ! 设置fs = gs = 0x9000.
    mov gs,ax

    xor ah,ah           ! reset FDC ! 复位软盘控制器,让其采用新参数。 
    xor dl,dl           ! dl=0, 第一个软驱
    int     0x13        ! 后面有讲解 ‘INT 0x13’ 使用方法

! load the setup-sectors directly after the bootblock.
! Note that 'es' is already set up.
!bootsect程序块后紧跟着加载setup模块的代码数据
! 注意es已经设置好了。(在移动代码时es已经指向目的段地址处0x9000)

! 下面代码,首先利用ROM BIOS中断INT 0x13将setup模板从磁盘第2个扇区开始读到
! 0x90200 开始处,共读4个扇区(没个扇区大小为0x200)。在读操作过程中如果读出错,则显示
! 磁盘上出错扇区的位置,然后复位驱动器并重试,没有退路。
! INT 0x13 读扇区使用调用参数设置如下:
! ah = 0x02 - 读磁盘扇区到内存;al = 需要读出的扇区数量;
! ch = 磁道(柱面)号的低8位; cl = 开始扇区(0-5 位),磁道号高2位(6-7);
! dh = 磁头号; dl = 驱动器号(如果是硬盘则位7 要置位);
! es:bx -> 指向数据缓冲区; 如果出错则CF 标志置位,ah中的出错码。
! ah = 0x00 - 重置磁盘驱动器.
load_setup:
    xor dx, dx          ! 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

    push    ax          ! dump error code  ! 显示出错信息。 出错码入栈。
    call    print_nl    ! 屏幕光标回车。
    mov bp, sp          ! ss:bp 指向欲显示的字(word),就是刚刚入栈的出错码。
    call    print_hex   ! 显示十六进制值。

    pop ax  

    xor dl, dl          ! reset FDC ! 复位控制器,重试。
    xor ah, ah
    int 0x13
    j   load_setup      ! jjmp指令。 进入循环。

ok_load_setup:

! Get disk drive parameters, specifically nr of sectors/track
! 这段代码取磁盘驱动器的参数,实际上是取每磁道扇区数,并保存在位置sectors处。
! 取磁盘驱动器参数INT 0x13 调用格式和返回信息如下:
! ah = 0x08 dl = 驱动器号(如果是硬盘则要置位71)。 
! 返回信息: 
! 如果出错则CF 置位,并且ah = 状态码。 
! ah = 0al = 0bl = 驱动器类型(AT/PS2! ch = 最大磁道号的低8 位,cl = 每磁道最大扇区数(位0-5),最大磁道号高2 位(位6-7) 
! dh = 最大磁头数, dl = 驱动器数量, 
! es:di  -> 软驱磁盘参数表。 

    xor dl,dl
    mov ah,#0x08        ! AH=8 is get drive parameters
    int 0x13
    xor ch,ch
! 下面指令表示下一条语句的操作数在cs段寄存器所指的段中。他只影响了下一条语句。实际上,由于
! 本程序代码和数据都被设置处于同一个段中,即段寄存器csdses的值相同,因此本程序中此处
! 可以不使用该指令。
    seg cs
! 下句保存每磁道扇区数。对于软盘来说(dl=0),其最大磁道号不会超过256ch已经足够表示它,
! 因此cl的位6~7肯定为0。因此,此时cx中是每磁道扇区数。
    mov sectors,cx
    mov ax,#INITSEG     ! 因为上面取磁盘参数中断改了es的值,这里重新改回。
    mov es,ax

! Print some inane message
! 显示信息:"'Loading' + '回车' + '换行'",共显示包括回车和换行控制字符在内的9个字符。
! BIOS 中断0x10 的读光标功能号 ah = 0x03 
! 输入:bh = 页号 
! 返回:ch = 扫描开始线,cl = 扫描结束线, 
! dh = 行号(0x00 是顶端),dl = 列号(0x00 是左边)。 
!
! BIOS中断0x10功能号 ah=0x13,显示字符
! al--光标放置方式及规定属性。0x01--光标停在字符串结尾处
! es:bp 字符串的起始位置
! cx--字符串的字符数
! bh--显示页面号   bl--字符属性
! dh: dl, 行号:列号

    mov ah,#0x03        ! read cursor pos
    xor bh,bh           ! 首先读光标位置。返回光标位置值在dx中。
    int 0x10

    mov cx,#9           ! 共显示9个字符
    mov bx,#0x0007      ! page 0, attribute 7 (normal)
    mov bp,#msg1        ! es:bp指向要显示的字符串。
    mov ax,#0x1301      ! write string, move cursor
    int 0x10            ! 写字符串并移动光标到串结尾处。

! ok, weve written the message, now
! we want to load the system (at 0x10000)
! 现在开始将system模块加载到0x10000(64KB)开始处。
    mov ax,#SYSSEG
    mov es,ax           ! segment of 0x010000  es=存放system的段地址。
    call    read_it     ! 读磁盘上system模块,es为输入参数。
    call    kill_motor  ! 关闭驱动器马达,这样就可以知道驱动器的状态了。
    call    print_nl    ! 光标回车换行。

! 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.
! 此后,我们检查要使用哪个根文件系统设备(简称根设备)。如果已经指定了设备(!=0)
! 就直接使用给定的设备。否则就需要根据BIOS 报告的每磁道扇区数来
! 确定到底使用/dev/PS0 (2,28) 还是 /dev/at0 (2,8)。
! 上面一行中两个设备文件的含义:
!Linux 中软驱的主设备号是2(参考上面介绍),次设备号 = type*4 + nr,其中
! nr0-3 分别对应软驱ABCDtype 是软驱的类型(2=1.2M7=1.44M 等)。
! 因为7*4 + 0 = 28,所以 /dev/PS0 (2,28)指的是1.44M A 驱动器,其设备号是0x021c
! 同理 /dev/at0 (2,8)指的是1.2M A 驱动器,其设备号是0x0208。

! root_dev定义在引导扇区508509字节处,指根文件系统所在设备号。0x0306指第2个硬盘第1个分区。
! 这里默认为0x0306是因为当时 Linus 开发Linux系统时是在第2个硬盘第1个分区中存放根文件系统。
! 这个值需要根据你自己根文件系统所在硬盘和分区进行修改。例如,如果你的根文件系统在第1个硬盘的
!1个分区上,那么该值应该为0x0301(0x01, 0x03)。如果根文件系统是在第2Bochs软盘上,
! 那么该值应该为0x021D0x1D, 0x02)。当编译内核时,你可以在Makefile文件中另行指定你自己的值,
! tools/build程序会使用你指定的值。
    seg cs
    mov ax,root_dev     ! ax=root_dev中的值,取得508509字节处的根设备号并判断是否已被定义。
    or  ax,ax           ! 判断ax是否为0
    jne root_defined    ! root_dev不为0,则跳转

! 表示下一条语句的操作数在代码段cs寄存器所指的段中。只执行后面的一条语句;
    seg cs
    mov bx,sectors
! 判断是否为1.2MB软驱,如果sector15,则说明是1.2MB软驱
    mov ax,#0x0208      ! /dev/ps0 - 1.2Mb
    cmp bx,#15
    je  root_defined
! 判断是否为1.44MB软驱,如果sector18,则说明是1.44MB软驱
    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     ! 将检查过的设备保存到root_dev! after that (everyting loaded), we jump to
! the setup-routine loaded directly after
! the bootblock:

    jmpi    0,SETUPSEG  ! 跳转到0x9020:0000(setup.s 程序的开始处)。跳到SETUP程序中去了。

!----------------------函数到此结束    下面是几个子程序-------------------!

! 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)
!
! 该子程序将系统模块加载到内存地址0x10000 处,并确定没有跨越64KB 的内存边界。我们试图尽快
! 地进行加载,只要可能,就每次加载整条磁道的数据。
!
! 输入:es – 开始内存地址段值(通常是0x1000)

! 下面伪操作符.word 定义一个2字节目标。相当于C语言程序中定义的变量和所占内存空间大小
! '1+SETUPLEN'表示开始时已经读进1个引导扇区(boot)和setup程序所占的扇区数SETUPLENsread:  .word 1+SETUPLEN    ! sectors read of current track  当前磁道中已读扇区数
head:   .word 0         ! current head   当前磁头号
track:  .word 0         ! current track    当前磁道号

! 测试输入的段值。必须位于内存地址64KB 边界处,否则进入死循环。清bx 寄存器,用于表示当前段内
! 存放数据的开始位置。
read_it:
    mov ax,es           
    test ax,#0x0fff     ! 两个操作数的同一各位都是1才是1,否则为0die:    jne die         ! es must be at 64kB boundary
                        ! es 值必须位于64KB0x10000) 地址边界
    xor bx,bx       ! bx is starting address within segment
rp_read:
! 接着判断是否已经读入全部数据。比较当前所读段是否就是系统数据末端所处的段(#ENDSEG),
! 如果不是就跳转至下面ok1_read 标号处继续读数据。否则退出子程序返回。
    mov ax,es
    cmp ax,#ENDSEG      ! have we loaded all yet?   是否已经加载了全部数据?
    jb ok1_read         ! 不是就跳转到ok1_read
    ret                 ! 返回,复位
ok1_read:
! 计算和验证当前磁道需要读取的扇区数,放在ax 寄存器中。
! 根据当前磁道还未读取的扇区数以及段内数据字节开始偏移位置,计算如果全部读取这些未读扇区,所
! 读总字节数是否会超过64KB 段长度的限制。若会超过,则根据此次最多能读入的字节数(64KB – 段内
! 偏移位置),反算出此次需要读取的扇区数。
    seg cs 
    mov ax,sectors          ! 取每磁道扇区数。
    sub ax,sread            ! 减去当前磁道已读扇区数。
    mov cx,ax               ! cx = ax = 当前磁道未读扇区数。
    shl cx,#9               ! cx = cx * 512 字节。
    add cx,bx               ! cx = cx + 段内当前偏移值(bx)
                            ! = 此次读操作后,段内共读入的字节数。
    jnc ok2_read            ! 若没有超过64KB 字节,则跳转至ok2_read 处执行。
    je ok2_read
! 若加上此次将读磁道上所有未读扇区时会超过64KB,则计算此时最多能读入的字节数:
! (64KB-段内读偏移位置),再转化成需读取的扇区数。其中0减某数就是取该数64KB的补值。
    xor ax,ax               
    sub ax,bx
    shr ax,#9               ! 得到扇区数
ok2_read:
! 读当前磁道上指定开始扇区(cl)和需读扇区数(al)的数据到esbs开始处。然后统计当前
! 磁道上已经读取的扇区数并与磁道最大扇区数sectors作比较。如果小于sectors说明当前磁
! 道上的还有扇区未读。于是跳转到ok3_read处继续操作。
    call read_track         ! 读取当前磁道上开始扇区的数据。
    mov cx,ax               ! cx = 该次操作已读取的扇区数。
    add ax,sread            ! 当前磁道上已经读取的扇区数。
    seg cs
    cmp ax,sectors 
    jne ok3_read            ! 如果当前磁道上的还有扇区未读,则跳转到ok3_read 处。
! 若该磁道的当前磁头面所有扇区已经读取,则读该磁道的下一磁头面(1 号磁头)上的数据。
! 如果已经完成,则去读下一磁道。
    mov ax,#1               
    sub ax,head             ! 判断当前磁头号。
    jne ok4_read            ! 如果是0 磁头,则再去读1 磁头面上的扇区数据。
    inc track               ! 否则去读下一磁道。
ok4_read:
    mov head,ax             ! 保存当前磁头号。
    xor ax,ax               ! 清当前磁道已读扇区数。
ok3_read:
! 如果当前磁道上的还有未读扇区,则首先保存当前磁道已读扇区数,然后调整存放数据开始处的开始位置。
! 若小于64KB边界值,则跳转到rp_read处,继续读数据。
    mov sread,ax            ! 保存当前磁道已读扇区数。
    shl cx,#9               ! 上次已读扇区数*512 字节。
    add bx,cx               ! 调整当前段内数据开始位置。
    jnc rp_read             ! 若小于64KB 边界值,则跳转到rp_read处,继续读数据。
! 否则说明已经读取64KB数据。此时调整当前段,为读下一段数据作准备。
    mov ax,es
    add ah,#0x10            ! 将段基址调整为指向下一个64KB 段内存。
    mov es,ax
    xor bx,bx               ! 清段内数据开始偏移值。
    jmp rp_read             ! 跳转至rp_read(156 行)处,继续读数据。

! read——track 子程序。读当前磁道上指定开始扇区和需读扇区数的数据到esbx开始处。参见
!67行下对BIOS磁盘读中断int 0x13,ah=2的说明。
! al - 需读扇区数;   esbx - 缓冲区开始位置。
read_track:
! 首先调用BIOS中断0x10,功能0x0e(以电传方式写字符),光标前移一位置。显示一个'.'pusha                   ! 压入所有寄存器(push all)。
    pusha                   ! 为调用显示中断压入所有寄存器值
    mov ax, #0xe2e          ! loading... message 2e = .
    mov bx, #7              ! 字符前景色属性
    int 0x10
    popa        

! 然后正式进行磁盘扇区读操作
    mov dx,track            ! 取当前磁道号
    mov cx,sread            ! 取当前磁道上已读扇区数
    inc cx                  ! cl = 开始读扇区
    mov ch,dl               ! ch = 当前磁道号
    mov dx,head             ! 取当前磁头号
    mov dh,dl               ! dh = 磁头号, dl = 驱动器号 (为 0 表示当前A驱动器)
    and dx,#0x0100          ! 磁头号不大于1
    mov ah,#2               ! ah = 2, 读磁盘扇区功能号

    push    dx              ! save for error dump
    push    cx              ! 为出错情况保存一些信息
    push    bx
    push    ax
    int 0x13
    jc bad_rt               ! 若出错,则跳转到bad_rt
    add sp, #8              ! 没有出错。因此丢弃为出错情况保存的数据
    popa
    ret

!表示磁盘操作出错,跳转重新操作
bad_rt: push    ax              ! save error code
    call    print_all           ! ah = error, al = read


    xor ah,ah
    xor dl,dl
    int 0x13


    add sp, #10                 ! 丢弃为出错情况保存的信息
    popa    
    jmp read_track


/*
 *  print_all is for debugging purposes.  
 *  It will print out all of the registers.  The assumption is that this is
 *  called from a routine, with a stack frame like
 *  dx 
 *  cx
 *  bx
 *  ax
 *  error
 *  ret <- sp
 *
*/

/*
 *  子程序print_all用于调试目的。它会显示所有寄存器的内容。前提条件是需要从一个子程序中调用,
 *  并且栈帧结构为如下所示:(见下面)
*/
! 若标志寄存器的CF = 0,则不显示寄存器名称。
print_all:
    mov cx, #5      ! error code + 4 registers
    mov bp, sp  

print_loop:
    push    cx      ! save count left
    call    print_nl    ! nl for readability
    jae no_reg      ! see if register name is needed

    mov ax, #0xe05 + 0x41 - 1
    sub al, cl
    int 0x10

    mov al, #0x58   ! X
    int 0x10

    mov al, #0x3a   ! :
    int 0x10

! 显示寄存器bp所指栈中内容。开始时bp指向返回地址。
no_reg:
    add bp, #2      ! next register
    call    print_hex   ! print it
    pop cx
    loop    print_loop
    ret

! 电传方式显示回车换行。
print_nl:
    mov ax, #0xe0d  ! CR
    int 0x10
    mov al, #0xa    ! LF
    int     0x10
    ret

/*
 *  print_hex is for debugging purposes, and prints the word
 *  pointed to by ss:bp in hexadecmial.
*/
/*
 * 使用十六进制在屏幕上显示出ssbp指向的字。
*/

! 调用BIOS中段0x10,以电传方式和4个十六进制数显示ssbp指向的字。
print_hex:
    mov cx, #4      ! 4 hex digits   要显示4个十六进制数字
    mov dx, (bp)    ! load word into dx
print_digit:
! 先显示高字节,因此需要把dx中值左旋4位,此时高4位在dx的低4位中。
    rol dx, #4      ! rotate so that lowest 4 bits are used
    mov ah, #0xe    ! 中断功能号
    mov al, dl      ! mask off so we have only next nibble
    and al, #0xf    ! 放入al中并只取低4! 加上'0'ASCII码值0x30,把显示值转换成基于数字'0'的字符。若此时al的值超过0x39,
! 表示欲显示值超过数字9,因此需要使用'A'~'F'来表示。
    add al, #0x30   ! convert to 0 based digit, '0'
    cmp al, #0x39   ! check for overflow
    jbe good_digit
    add al, #0x41 - 0x30 - 0xa  ! 'A' - '0' - 0xa

good_digit:
    int 0x10
    loop    print_digit     ! cx--,显示下一个值
    ret


/*
 * This procedure turns off the floppy drive motor, so
 * that we enter the kernel in a known state, and
 * dont have to worry about it later.
 */
! 这个子程序用于关闭软驱的马达,这样我们进入内核后它处于已知状态,以后也就无须担心它了。
kill_motor:
    push dx
    mov dx,#0x3f2
! 软驱控制卡的驱动端口,只写。
    xor al, al      ! A 驱动器,关闭FDC,禁止DMA 和中断请求,关闭马达。
    outb            !al 中的内容输出到dx 指定的端口去。
    pop dx
    ret

sectors:
    .word 0         ! 存放当前启动软盘每磁道的扇区数。

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

.org 506
swap_dev:
    .word SWAP_DEV
root_dev:
    .word ROOT_DEV
boot_flag:
    .word 0xAA55    ! 硬盘有效标识。

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

参考

问题查询网站,不管你有没有问题,都建议你看一下。

linux内核完全剖析

你可能感兴趣的:(linux,kernel,启动函数)