程序6 ‑1 linux/boot/bootsect.S


  1 !

  2 ! SYS_SIZE is the number of clicks (16 bytes) to be loaded.

  3 ! 0x3000 is 0x30000 bytes = 196kB, more than enough for current

  4 ! versions of linux

    ! SYS_SIZE是要加载的系统模块长度,单位是节,每节16字节。0x3000共为0x30000字节=196KB

    ! 若以1024字节为1KB计,则应该是192KB。对于当前内核版本这个空间长度已足够了。当该值为

    ! 0x8000时,表示内核最大为512KB。因为内存0x90000处开始存放移动后的bootsectsetup

    ! 的代码,因此该值最大不得超过0x9000(表示584KB)。

    ! 这里感叹号'!'或分号';'表示程序注释语句开始。

  5 !

    ! 头文件linux/config.h中定义了内核用到的一些常数符号和Linus自己使用的默认硬盘参数块。

    ! 例如其中定义了以下一些常数:

    ! DEF_SYSSIZE  = 0x3000 - 默认系统模块长度。单位是节,每节为16字节;

    ! DEF_INITSEG  = 0x9000 - 默认本程序代码移动目的段位置;

    ! DEF_SETUPSEG = 0x9020 - 默认setup程序代码段位置;

    ! DEF_SYSSEG   = 0x1000 - 默认从磁盘加载系统模块到内存的段位置。

  6 #include <linux/config.h>

  7 SYSSIZE = DEF_SYSSIZE           ! 定义一个标号或符号。指明编译连接后system模块的大小。

  8 !

  9 !       bootsect.s              (C) 1991 Linus Torvalds

 10 !       modified by Drew Eckhardt

 11 !

 12 ! bootsect.s is loaded at 0x7c00 by the bios-startup routines, and moves

 13 ! iself out of the way to address 0x90000, and jumps there.

 14 !

 15 ! It then loads 'setup' directly after itself (0x90200), and the system

 16 ! at 0x10000, using BIOS interrupts.

 17 !

 18 ! NOTE! currently system is at most 8*65536 bytes long. This should be no

 19 ! problem, even in the future. I want to keep it simple. This 512 kB

 20 ! kernel size should be enough, especially as this doesn't contain the

 21 ! buffer cache as in minix

 22 !

 23 ! The loader has been made as simple as possible, and continuos

 24 ! read errors will result in a unbreakable loop. Reboot by hand. It

 25 ! loads pretty fast by getting whole sectors at a time whenever possible.

    !

    ! 以下是前面文字的译文:

    !     bootsect.s              (C) 1991 Linus Torvalds

    !     Drew Eckhardt修改

    !

    ! bootsect.s ROM BIOS启动子程序加载至0x7c00 (31KB)处,并将自己移到了地址0x90000

    ! (576KB)处,并跳转至那里。

    !

    ! 它然后使用BIOS中断将'setup'直接加载到自己的后面(0x90200)(576.5KB),并将system

    ! 载到地址0x10000处。

    !

    ! 注意! 目前的内核系统最大长度限制为(8*65536)(512KB)字节,即使是在将来这也应该没有问

    ! 题的。我想让它保持简单明了。这样512KB的最大内核长度应该足够了,尤其是这里没有象

    ! MINIX中一样包含缓冲区高速缓冲。

    !

    ! 加载程序已经做得够简单了,所以持续地读操作出错将导致死循环。只能手工重启。只要可能,

    ! 通过一次读取所有的扇区,加载过程可以做得很快。

 26

    ! 伪指令(伪操作符).globl.global用于定义随后的标识符是外部的或全局的,并且即使不

    ! 使用也强制引入。 .text.data.bss用于分别定义当前代码段、数据段和未初始化数据段。

    ! 在链接多个目标模块时,链接程序(ld86)会根据它们的类别把各个目标模块中的相应段分别

    ! 组合(合并)在一起。这里把三个段都定义在同一重叠地址范围中,因此本程序实际上不分段。

    ! 另外,后面带冒号的字符串是标号,例如下面的'begtext:'

    ! 一条汇编语句通常由标号(可选)、指令助记符(指令名)和操作数三个字段组成。标号位于

    ! 一条指令的第一个字段。它代表其所在位置的地址,通常指明一个跳转指令的目标位置。

 27 .globl begtext, begdata, begbss, endtext, enddata, endbss

 28 .text                              ! 文本段(代码段)。

 29 begtext:

 30 .data                              ! 数据段。

 31 begdata:

 32 .bss                               ! 未初始化数据段。

 33 begbss:

 34 .text                              ! 文本段(代码段)。

 35

    ! 下面等号'='或符号'EQU'用于定义标识符或标号所代表的值。

 36 SETUPLEN = 4                       ! nr of setup-sectors

                                       ! setup程序代码占用磁盘扇区数(setup-sectors)值;

 37 BOOTSEG  = 0x07c0                  ! original address of boot-sector

                                       ! bootsect代码所在内存原始段地址;

 38 INITSEG  = DEF_INITSEG             ! we move boot here - out of the way

                                       ! bootsect移到位置0x90000 - 避开系统模块占用处;

 39 SETUPSEG = DEF_SETUPSEG            ! setup starts here

                                       ! setup程序从内存0x90200处开始;

 40 SYSSEG   = DEF_SYSSEG              ! system loaded at 0x10000 (65536).

                                       ! system模块加载到0x1000064 KB)处;

 41 ENDSEG   = SYSSEG + SYSSIZE        ! where to stop loading

                                       ! 停止加载的段地址;

 42

 43 ! ROOT_DEV & SWAP_DEV are now written by "build".

    ! 根文件系统设备号ROOT_DEV和交换设备号SWAP_DEV 现在由tools目录下的build程序写入。

    ! 设备号0x306指定根文件系统设备是第2个硬盘的第1个分区。当年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版后就已经使用与现在内核相同的命名方法了。

 44 ROOT_DEV = 0                       ! 根文件系统设备使用与系统引导时同样的设备;

 45 SWAP_DEV = 0                       ! 交换设备使用与系统引导时同样的设备;

 46

    ! 伪指令entry迫使链接程序在生成的执行程序(a.out)中包含指定的标识符或标号。这里是

    ! 程序执行开始点。49 -- 58行作用是将自身(bootsect)从目前段位置 0x07c0(31KB) 移动到

    ! 0x9000(576KB) 处,共256字(512字节),然后跳转到移动后代码的go标号处,也即本程

    ! 序的下一语句处。

 47 entry start                        ! 告知链接程序,程序从start标号开始执行。

 48 start:

 49         mov     ax,#BOOTSEG        ! ds段寄存器置为0x7C0

 50         mov     ds,ax

 51         mov     ax,#INITSEG        ! es段寄存器置为0x9000

 52         mov     es,ax

 53         mov     cx,#256            ! 设置移动计数值=256字(512字节);

 54         sub     si,si              ! 源地址   ds:si = 0x07C0:0x0000

 55         sub     di,di              ! 目的地址 es:di = 0x9000:0x0000

 56         rep                        ! 重复执行并递减cx的值,直到cx = 0为止。

 57         movw                       ! movs指令。从内存[si]处移动cx个字到[di]处。

 58         jmpi    go,INITSEG         ! 段间跳转(Jump Intersegment)。这里INITSEG

                                       ! 指出跳转到的段地址,标号go是段内偏移地址。

 59

    ! 从下面开始,CPU在已移动到0x90000位置处的代码中执行。

    ! 这段代码设置几个段寄存器,包括栈寄存器sssp。栈指针sp只要指向远大于512字节偏移

    ! (即地址0x90200)处都可以。因为从0x90200地址开始处还要放置setup程序,而此时setup

    ! 程序大约为4个扇区,因此sp要指向大于(0x200 + 0x200 * 4 +堆栈大小)位置处。这里sp

    ! 设置为 0x9ff00 - 12(参数表长度),即sp = 0xfef4。在此之上位置会存放一个自建的驱动

    ! 器参数表,见下面说明。实际上BIOS把引导扇区加载到0x7c00 处并把执行权交给引导程序时,

    ! ss = 0x00sp = 0xfffe

    ! 另外,第65行上push指令的期望作用是想暂时把段值保留在栈中,然后等下面执行完判断磁道

    ! 扇区数后再弹出栈,并给段寄存器 fsgs赋值(第109行)。但是由于第6768两语句修改

    ! 了栈段的位置,因此除非在执行栈弹出操作之前把栈段恢复到原位置,否则这样设计就是错误的。

    ! 因此这里存在一个bug。改正的方法之一是去掉第65行,并把第109行修改成“mov ax,cs”。

 60 go:     mov     ax,cs              ! dsesss都置成移动后代码所在的段处(0x9000)

 61         mov     dx,#0xfef4         ! arbitrary value >>512 - disk parm size

 62

 63         mov     ds,ax

 64         mov     es,ax

 65         push    ax                 ! 临时保存段值(0x9000),供109行使用。(滑头!

 66

 67         mov     ss,ax              ! put stack at 0x9ff00 - 12.

 68         mov     sp,dx

 69 /*

 70  *      Many BIOS's default disk parameter tables will not

 71  *      recognize multi-sector reads beyond the maximum sector number

 72  *      specified in the default diskette parameter tables - this may

 73  *      mean 7 sectors in some cases.

 74  *

 75  *      Since single sector reads are slow and out of the question,

 76  *      we must take care of this by creating new parameter tables

 77  *      (for the first disk) in RAM.  We will set the maximum sector

 78  *      count to 18 - the most we will encounter on an HD 1.44. 

 79  *

 80  *      High doesn't hurt.  Low does.

 81  *

 82  *      Segments are as follows: ds=es=ss=cs - INITSEG,

 83  *              fs = 0, gs = parameter table segment

 84  */

    /*

     *      对于多扇区读操作所读的扇区数超过默认磁盘参数表中指定的最大扇区数时,

     *      很多BIOS将不能进行正确识别。在某些情况下是7个扇区。

     *

     *      由于单扇区读操作太慢,不予以考虑,因此我们必须通过在内存中重创建新的

     *      参数表(为第1个驱动器)来解决这个问题。我们将把其中最大扇区数设置为

     *      18 -- 即在1.44MB磁盘上会碰到的最大数值。

     *     

     *      这个数值大了不会出问题,但是太小就不行了。

     *

     *      段寄存器将被设置成:ds=es=ss=cs - 都为INITSEG0x9000),

     *      fs = 0gs = 参数表所在段值。

     */

 85 ! BIOS设置的中断0x1E的中断向量值是软驱参数表地址。该向量值位于内存0x1E * 4 = 0x78

    ! 处。这段代码首先从内存0x0000:0x0078处复制原软驱参数表到0x9000:0xfef4处,然后修改

    ! 表中的每磁道最大扇区数为18

 86

 87         push    #0                 ! 置段寄存器fs = 0

 88         pop     fs                 ! fs:bx指向存有软驱参数表地址处(指针的指针)。

 89         mov     bx,#0x78           ! fs:bx is parameter table address

    ! 下面指令表示下一条语句的操作数在fs段寄存器所指的段中。它只影响其下一条语句。这里

    ! fs:bx 所指内存位置处的表地址放到寄存器对 gs:si 中作为原地址。寄存器对 es:di =

    ! 0x9000:0xfef4 为目的地址。

 90         seg fs

 91         lgs     si,(bx)            ! gs:si is source

 92

 93         mov     di,dx              ! es:di is destination ! dx=0xfef4,在61行被设置。

 94         mov     cx,#6              ! copy 12 bytes

 95         cld                        ! 清方向标志。复制时指针递增。

 96

 97         rep                        ! 复制12字节的软驱参数表到0x9000:0xfef4处。

 98         seg gs

 99         movw

100

101         mov     di,dx              ! es:di指向新表,修改表中偏移4处的最大扇区数为18

102         movb    4(di),*18          ! patch sector count

103

104         seg fs                     ! 让中断向量0x1E的值指向新表。

105         mov     (bx),di

106         seg fs

107         mov     2(bx),es

108

109         pop     ax                 ! 此时ax中是上面第65行保留下来的段值(0x9000)。

110         mov     fs,ax              ! 设置fs = gs = 0x9000

111         mov     gs,ax

112        

113         xor     ah,ah              ! reset FDC ! 复位软盘控制器,让其采用新参数。

114         xor     dl,dl              ! dl = 0,第1个软驱。

115         int     0x13   

116

117 ! load the setup-sectors directly after the bootblock.

118 ! Note that 'es' is already set up.

    ! bootsect程序块后紧根着加载setup模块的代码数据。

    ! 注意es已经设置好了。(在移动代码时es已经指向目的段地址处0x9000)。

119

    ! 121--137行的用途是利用ROM BIOS中断INT 0x13 setup 模块从磁盘第2个扇区开始读到

    ! 0x90200 开始处,共读 4个扇区。在读操作过程中如果读出错,则显示磁盘上出错扇区位置,

    ! 然后复位驱动器并重试,没有退路。

    ! INT 0x13读扇区使用调用参数设置如下:

    ! ah = 0x02 - 读磁盘扇区到内存;al = 需要读出的扇区数量;

    ! ch = 磁道(柱面)号的低8位;   cl = 开始扇区(0-5),磁道号高2(6-7)

    ! dh = 磁头号;                 dl = 驱动器号(如果是硬盘则位7要置位);

    ! es:bx à指向数据缓冲区;  如果出错则CF标志置位,ah中是出错码。

120 load_setup:

121         xor     dx, dx                  ! drive 0, head 0

122         mov     cx,#0x0002              ! sector 2, track 0

123         mov     bx,#0x0200              ! address = 512, in INITSEG

124         mov     ax,#0x0200+SETUPLEN     ! service 2, nr of sectors

125         int     0x13                    ! read it

126         jnc     ok_load_setup           ! ok - continue

127

128         push    ax                      ! dump error code ! 显示出错信息。出错码入栈。

129         call    print_nl                ! 屏幕光标回车。

130         mov     bp, sp                  ! ss:bp指向欲显示的字(word)。

131         call    print_hex               ! 显示十六进制值。

132         pop     ax     

133        

134         xor     dl, dl                  ! reset FDC ! 复位磁盘控制器,重试。

135         xor     ah, ah

136         int     0x13

137         j       load_setup              ! j jmp指令。

138

139 ok_load_setup:

140

141 ! Get disk drive parameters, specifically nr of sectors/track

    ! 这段代码取磁盘驱动器的参数,实际上是取每磁道扇区数,并保存在位置sectors处。

    ! 取磁盘驱动器参数INT 0x13调用格式和返回信息如下:

    ! ah = 0x08     dl = 驱动器号(如果是硬盘则要置位71)。

    ! 返回信息:

    ! 如果出错则CF置位,并且ah = 状态码。

    ! ah = 0 al = 0          bl = 驱动器类型(AT/PS2

    ! ch = 最大磁道号的低8位,  cl = 每磁道最大扇区数(0-5),最大磁道号高2(6-7)

    ! dh = 最大磁头数,          dl = 驱动器数量,

    ! es:di -à 软驱磁盘参数表。

142

143         xor     dl,dl

144         mov     ah,#0x08           ! AH=8 is get drive parameters

145         int     0x13

146         xor     ch,ch

    ! 下面指令表示下一条语句的操作数在 cs段寄存器所指的段中。它只影响其下一条语句。实际

    ! 上,由于本程序代码和数据都被设置处于同一个段中,即段寄存器csdses的值相同,因

    ! 此本程序中此处可以不使用该指令。

147         seg cs

    ! 下句保存每磁道扇区数。对于软盘来说(dl=0),其最大磁道号不会超过256ch已经足够表

    ! 示它,因此cl的位6-7肯定为0。又146行已置ch=0,因此此时cx中是每磁道扇区数。

148         mov     sectors,cx

149         mov     ax,#INITSEG

150         mov     es,ax              ! 因为上面取磁盘参数中断改了es值,这里重新改回。

151

152 ! Print some inane message

    ! 显示信息:“'Loading'+回车+换行”,共显示包括回车和换行控制字符在内的9个字符。

    ! BIOS中断0x10功能号 ah = 0x03,读光标位置。

    ! 输入:bh = 页号

    ! 返回:ch = 扫描开始线;cl = 扫描结束线;dh = 行号(0x00顶端)dl = 列号(0x00最左边)

    !

    ! BIOS中断0x10功能号 ah = 0x13,显示字符串。

    ! 输入:al = 放置光标的方式及规定属性。0x01-表示使用bl中的属性值,光标停在字符串结尾处。

    ! es:bp 此寄存器对指向要显示的字符串起始位置处。cx = 显示的字符串字符数。bh = 显示页面号;

    ! bl = 字符属性。dh = 行号;dl = 列号。

153

154         mov     ah,#0x03           ! read cursor pos

155         xor     bh,bh              ! 首先读光标位置。返回光标位置值在dx中。

156         int     0x10               ! dh - 行(0--24);dl - (0--79)

157        

158         mov     cx,#9              ! 共显示9个字符。

159         mov     bx,#0x0007         ! page 0, attribute 7 (normal)

160         mov     bp,#msg1           ! es:bp指向要显示的字符串。

161         mov     ax,#0x1301         ! write string, move cursor

162         int     0x10               ! 写字符串并移动光标到串结尾处。

163

164 ! ok, we've written the message, now

165 ! we want to load the system (at 0x10000)

    ! 现在开始将system模块加载到0x1000064KB)开始处。

166

167         mov     ax,#SYSSEG

168         mov     es,ax              ! segment of 0x010000 ! es = 存放system的段地址。

169         call    read_it            ! 读磁盘上system模块,es为输入参数。

170         call    kill_motor         ! 关闭驱动器马达,这样就可以知道驱动器的状态了。

171         call    print_nl           ! 光标回车换行。

172

173 ! After that we check which root-device to use. If the device is

174 ! defined (!= 0), nothing is done and the given device is used.

175 ! Otherwise, either /dev/PS0 (2,28) or /dev/at0 (2,8), depending

176 ! on the number of sectors that the BIOS reports currently.

    ! 此后,我们检查要使用哪个根文件系统设备(简称根设备)。如果已经指定了设备(!=0)

    ! 就直接使用给定的设备。否则就需要根据BIOS报告的每磁道扇区数来确定到底使用/dev/PS0

    ! (2,28),还是 /dev/at0 (2,8)

    !! 上面一行中两个设备文件的含义:

    !! Linux中软驱的主设备号是2(参见第43行的注释),次设备号 = type*4 + nr,其中

    !! nr0-3分别对应软驱ABCDtype是软驱的类型(2à1.2MB7à1.44MB等)。

    !! 因为7*4 + 0 = 28,所以 /dev/PS0 (2,28)指的是1.44MB A驱动器,其设备号是0x021c

    !! 同理 /dev/at0 (2,8)指的是1.2MB A驱动器,其设备号是0x0208

 

    ! 下面root_dev定义在引导扇区508509字节处,指根文件系统所在设备号。0x0306指第2

    ! 个硬盘第1个分区。这里默认为0x0306是因为当时 Linus 开发Linux系统时是在第2个硬

    ! 盘第1个分区中存放根文件系统。这个值需要根据你自己根文件系统所在硬盘和分区进行修

    ! 改。例如,如果你的根文件系统在第1个硬盘的第1个分区上,那么该值应该为0x0301,即

    ! 0x01, 0x03)。如果根文件系统是在第2Bochs软盘上,那么该值应该为0x021D,即

    ! 0x1D,0x02)。当编译内核时,你可以在Makefile文件中另行指定你自己的值,内核映像

    ! 文件Image的创建程序tools/build会使用你指定的值来设置你的根文件系统所在设备号。

177

178         seg cs

179         mov     ax,root_dev        ! 508,509字节处的根设备号并判断是否已被定义。

180         or      ax,ax

181         jne     root_defined

    ! 取上面第148行保存的每磁道扇区数。如果sectors=15则说明是1.2MB的驱动器;如果

    ! sectors=18,则说明是1.44MB软驱。因为是可引导的驱动器,所以肯定是A驱。

182         seg cs

183         mov     bx,sectors

184         mov     ax,#0x0208         ! /dev/ps0 - 1.2Mb

185         cmp     bx,#15             ! 判断每磁道扇区数是否=15

186         je      root_defined       ! 如果等于,则ax中就是引导驱动器的设备号。

187         mov     ax,#0x021c         ! /dev/PS0 - 1.44Mb

188         cmp     bx,#18

189         je      root_defined

190 undef_root:                        ! 如果都不一样,则死循环(死机)。

191         jmp undef_root

192 root_defined:

193         seg cs

194         mov     root_dev,ax        ! 将检查过的设备号保存到root_dev中。

195

196 ! after that (everyting loaded), we jump to

197 ! the setup-routine loaded directly after

198 ! the bootblock:

    ! 到此,所有程序都加载完毕,我们就跳转到被加载在bootsect后面的setup程序去。

    ! 下面段间跳转指令(Jump Intersegment)。跳转到0x9020:0000(setup.s程序开始处)去执行。

199

200         jmpi    0,SETUPSEG         !!!! 到此本程序就结束了。!!!!

 

    ! 下面是几个子程序。read_it用于读取磁盘上的system模块。kill_moter用于关闭软驱马达。

    ! 还有一些屏幕显示子程序。

201

202 ! This routine loads the system at address 0x10000, making sure

203 ! no 64kB boundaries are crossed. We try to load it as fast as

204 ! possible, loading whole tracks whenever we can.

205 !

206 ! in:   es - starting address segment (normally 0x1000)

207 !

    ! 该子程序将系统模块加载到内存地址0x10000处,并确定没有跨越64KB的内存边界。

    ! 我们试图尽快地进行加载,只要可能,就每次加载整条磁道的数据。

    ! 输入:es – 开始内存地址段值(通常是0x1000

    !

    ! 下面伪操作符.word定义一个2字节目标。相当于C语言程序中定义的变量和所占内存空间大小。

    ! '1+SETUPLEN'表示开始时已经读进1个引导扇区和setup程序所占的扇区数SETUPLEN

208 sread:  .word 1+SETUPLEN        ! sectors read of current track !当前磁道中已读扇区数。

209 head:   .word 0                 ! current head    !当前磁头号。

210 track:  .word 0                 ! current track   !当前磁道号。

211

212 read_it:

    ! 首先测试输入的段值。从盘上读入的数据必须存放在位于内存地址 64KB 的边界开始处,否则

    ! 进入死循环。清bx寄存器,用于表示当前段内存放数据的开始位置。

    ! 153行上的指令test以比特位逻辑与两个操作数。若两个操作数对应的比特位都为1,则结果

    ! 值的对应比特位为1,否则为0。该操作结果只影响标志(零标志ZF等)。例如若AX=0x1000

    ! 那么test指令的执行结果是(0x1000 & 0x0fff) = 0x0000,于是ZF标志置位。此时即下一条

    ! 指令jne 条件不成立。

213         mov ax,es

214         test ax,#0x0fff

215 die:    jne die                 ! es must be at 64kB boundary ! es值必须位于64KB边界!

216         xor bx,bx               ! bx is starting address within segment! bx为段内偏移。

217 rp_read:

    ! 接着判断是否已经读入全部数据。比较当前所读段是否就是系统数据末端所处的段(#ENDSEG)

    ! 如果不是就跳转至下面ok1_read标号处继续读数据。否则退出子程序返回。

218         mov ax,es

219         cmp ax,#ENDSEG          ! have we loaded all yet?    ! 是否已经加载了全部数据?

220         jb ok1_read

221         ret

222 ok1_read:

    ! 然后计算和验证当前磁道需要读取的扇区数,放在ax寄存器中。

    ! 根据当前磁道还未读取的扇区数以及段内数据字节开始偏移位置,计算如果全部读取这些未读

    ! 扇区,所读总字节数是否会超过64KB段长度的限制。若会超过,则根据此次最多能读入的字节

    ! (64KB –段内偏移位置),反算出此次需要读取的扇区数。

223         seg cs

224         mov ax,sectors             ! 取每磁道扇区数。

225         sub ax,sread               ! 减去当前磁道已读扇区数。

226         mov cx,ax                  ! cx = ax = 当前磁道未读扇区数。

227         shl cx,#9                  ! cx = cx * 512 字节 + 段内当前偏移值(bx)

228         add cx,bx                  !   = 此次读操作后,段内共读入的字节数。

229         jnc ok2_read               ! 若没有超过64KB字节,则跳转至ok2_read处执行。

230         je ok2_read

    ! 若加上此次将读磁道上所有未读扇区时会超过64KB,则计算此时最多能读入的字节数:

    ! (64KB–段内读偏移位置),再转换成需读取的扇区数。其中0减某数就是取该数64KB的补值。

231         xor ax,ax

232         sub ax,bx

233         shr ax,#9

234 ok2_read:

    ! 读当前磁道上指定开始扇区(cl)和需读扇区数(al)的数据到 es:bx开始处。然后统计当前

    ! 磁道上已经读取的扇区数并与磁道最大扇区数 sectors作比较。如果小于sectors说明当前磁

    ! 道上的还有扇区未读。于是跳转到ok3_read处继续操作。

235         call read_track            ! 读当前磁道上指定开始扇区和需读扇区数的数据。

236         mov cx,ax                  ! cx = 该次操作已读取的扇区数。

237         add ax,sread               ! 加上当前磁道上已经读取的扇区数。

238         seg cs

239         cmp ax,sectors             ! 若当前磁道上的还有扇区未读,则跳转到ok3_read处。

240         jne ok3_read

    ! 若该磁道的当前磁头面所有扇区已经读取,则读该磁道的下一磁头面(1号磁头)上的数据。

    ! 如果已经完成,则去读下一磁道。

241         mov ax,#1

242         sub ax,head                ! 判断当前磁头号。

243         jne ok4_read               ! 如果是0磁头,则再去读1磁头面上的扇区数据。

244         inc track                  ! 否则去读下一磁道。

245 ok4_read:

246         mov head,ax                ! 保存当前磁头号。

247         xor ax,ax                  ! 清当前磁道已读扇区数。

248 ok3_read:

    ! 如果当前磁道上的还有未读扇区,则首先保存当前磁道已读扇区数,然后调整存放数据处的开

    ! 始位置。若小于64KB边界值,则跳转到rp_read(217)处,继续读数据。

249         mov sread,ax               ! 保存当前磁道已读扇区数。

250         shl cx,#9                  ! 上次已读扇区数*512字节。

251         add bx,cx                  ! 调整当前段内数据开始位置。

252         jnc rp_read

    ! 否则说明已经读取64KB数据。此时调整当前段,为读下一段数据作准备。

253         mov ax,es

254         add ah,#0x10               ! 将段基址调整为指向下一个64KB内存开始处。

255         mov es,ax

256         xor bx,bx                  ! 清段内数据开始偏移值。

257         jmp rp_read                ! 跳转至rp_read(217)处,继续读数据。

258

    ! read_track 子程序。读当前磁道上指定开始扇区和需读扇区数的数据到 es:bx 开始处。参见

    ! 67行下对BIOS磁盘读中断int 0x13ah=2的说明。

    ! al – 需读扇区数;es:bx – 缓冲区开始位置。

259 read_track:

    ! 首先调用BIOS中断0x10,功能0x0e(以电传方式写字符),光标前移一位置。显示一个'.'

260         pusha                      ! 压入所有寄存器(push all)。

261         pusha                      ! 为调用显示中断压入所有寄存器值。

262         mov ax, #0xe2e             ! loading... message 2e = .

263         mov bx, #7                 ! 字符前景色属性。

264         int 0x10

265         popa           

266

    ! 然后正式进行磁道扇区读操作。

267         mov dx,track               ! 取当前磁道号。

268         mov cx,sread               ! 取当前磁道上已读扇区数。

269         inc cx                     ! cl = 开始读扇区。

270         mov ch,dl                  ! ch = 当前磁道号。

271         mov dx,head                ! 取当前磁头号。

272         mov dh,dl                  ! dh = 磁头号,dl = 驱动器号(0表示当前A驱动器)

273         and dx,#0x0100             ! 磁头号不大于1

274         mov ah,#2                  ! ah = 2,读磁盘扇区功能号。

275        

276         push dx                    ! save for error dump

277         push cx                    ! 为出错情况保存一些信息。

278         push bx

279         push ax

280

281         int 0x13

282         jc bad_rt                  ! 若出错,则跳转至bad_rt

283         add sp,#8                  ! 没有出错。因此丢弃为出错情况保存的信息。

284         popa

285         ret

286

    ! 读磁盘操作出错。则先显示出错信息,然后执行驱动器复位操作(磁盘中断功能号0),再跳转

    ! read_track处重试。

287 bad_rt: push ax                    ! save error code

288         call print_all             ! ah = error, al = read

289        

290        

291         xor ah,ah

292         xor dl,dl

293         int 0x13

294        

295

296         add sp, #10                ! 丢弃为出错情况保存的信息。

297         popa   

298         jmp read_track

299

300 /*

301  *      print_all is for debugging purposes. 

302  *      It will print out all of the registers.  The assumption is that this is

303  *      called from a routine, with a stack frame like

304  *      dx

305  *      cx

306  *      bx

307  *      ax

308  *      error

309  *      ret <- sp

310  *

311 */

    /*

     *      子程序print_all用于调试目的。它会显示所有寄存器的内容。前提条件是需要从

     *      一个子程序中调用,并且栈帧结构为如下所示:(见上面)

     */

    ! 若标志寄存器的CF=0,则不显示寄存器名称。

312 

313 print_all:

314         mov cx, #5                 ! error code + 4 registers  ! 显示值个数。

315         mov bp, sp                 ! 保存当前栈指针sp

316

317 print_loop:

318         push cx                    ! save count left   ! 保存需要显示的剩余个数。

319         call print_nl              ! nl for readability  ! 为可读性先让光标回车换行。

320         jae no_reg                 ! see if register name is needed

321                                    ! FLAGS的标志CF=0则不显示寄存器名,于是跳转。

    ! 对应入栈寄存器顺序分别显示它们的名称“AX:”等。

322         mov ax, #0xe05 + 0x41 - 1  ! ah =功能号(0x0e);al =字符(0x05 + 0x41 -1)。

323         sub al, cl

324         int 0x10

325

326         mov al, #0x58              ! X     ! 显示字符'X'

327         int 0x10

328

329         mov al, #0x3a              ! :     ! 显示字符':'

330         int 0x10

331

    ! 显示寄存器bp所指栈中内容。开始时bp指向返回地址。

332 no_reg:

333         add bp, #2                 ! next register   ! 栈中下一个位置。

334         call print_hex             ! print it        ! 以十六进制显示。

335         pop cx

336         loop print_loop

337         ret

338

    ! 调用BIOS中断0x10,以电传方式显示回车换行。

339 print_nl:

340         mov ax, #0xe0d             ! CR

341         int 0x10

342         mov al, #0xa               ! LF

343         int 0x10

344         ret

345

346 /*

347  *      print_hex is for debugging purposes, and prints the word

348  *      pointed to by ss:bp in hexadecmial.

349 */

    /*

     *      子程序print_hex用于调试目的。它使用十六进制在屏幕上显示出

     *      ss:bp指向的字。

     */

350

    ! 调用BIOS中断0x10,以电传方式和4个十六进制数显示ss:bp指向的字。

351 print_hex:

352         mov     cx, #4             ! 4 hex digits         ! 要显示4个十六进制数字。

353         mov     dx, (bp)           ! load word into dx    ! 显示值放入dx中。

354 print_digit:

    ! 先显示高字节,因此需要把dx中值左旋4比特,此时高4比特在dx的低4位中。

355         rol     dx, #4             ! rotate so that lowest 4 bits are used

356         mov     ah, #0xe           ! 中断功能号。

357         mov     al, dl             ! mask off so we have only next nibble

358         and     al, #0xf           ! 放入al中并只取低4比特(1个值)。

    ! 加上'0' ASCII码值0x30,把显示值转换成基于数字'0' 的字符。若此时al 值超过 0x39

    ! 表示欲显示值超过数字9,因此需要使用'A'--'F'来表示。

359         add     al, #0x30          ! convert to 0 based digit, '0'

360         cmp     al, #0x39          ! check for overflow

361         jbe     good_digit

362         add     al, #0x41 - 0x30 - 0xa       ! 'A' - '0' - 0xa

363

364 good_digit:

365         int     0x10

366         loop    print_digit        ! cx--。若cx>0则去显示下一个值。

367         ret

368

369

370 /*

371  * This procedure turns off the floppy drive motor, so

372  * that we enter the kernel in a known state, and

373  * don't have to worry about it later.

374  */

    /* 这个子程序用于关闭软驱的马达,这样我们进入内核后就能

     * 知道它所处的状态,以后也就无须担心它了。

     */

    ! 下面第377行上的值0x3f2是软盘控制器的一个端口,被称为数字输出寄存器(DOR)端口。它是

    ! 一个8位的寄存器,其位7--4分别用于控制4个软驱(D--A)的启动和关闭。位3--2用于

    ! 允许/禁止DMA和中断请求以及启动/复位软盘控制器FDC。 位1--0用于选择选择操作的软驱。

    ! 378行上在al中设置并输出的0值,就是用于选择A驱动器,关闭FDC,禁止DMA和中断请求,

    ! 关闭马达。有关软驱控制卡编程的详细信息请参见kernel/blk_drv/floppy.c程序后面的说明。

375 kill_motor:

376         push dx

377         mov dx,#0x3f2              ! 软驱控制卡的数字输出寄存器端口,只写。

378         xor al, al                 ! A驱动器,关闭FDC,禁止DMA和中断请求,关闭马达。

379         outb                       ! al中的内容输出到dx指定的端口去。

380         pop dx

381         ret

382

383 sectors:

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

385

386 msg1:                              ! 开机调用BIOS中断显示的信息。共9个字符。

387         .byte 13,10                ! 回车、换行的ASCII码。

388         .ascii "Loading"

389

    ! 表示下面语句从地址508(0x1FC)开始,所以root_dev在启动扇区的第508开始的2个字节中。

390 .org 506

391 swap_dev:

392         .word SWAP_DEV             ! 这里存放交换系统所在设备号(init/main.c中会用)

393 root_dev:

394         .word ROOT_DEV             ! 这里存放根文件系统所在设备号(init/main.c中会用)

 

    ! 下面是启动盘具有有效引导扇区的标志。仅供BIOS中的程序加载引导扇区时识别使用。它必须

    ! 位于引导扇区的最后两个字节中。

395 boot_flag:

396         .word 0xAA55

397

398 .text

399 endtext:

400 .data

401 enddata:

402 .bss

403 endbss:

404