|
!此文件为linux0.11版本下的原始文件,注释是参照赵炯的<Linux内核0.11完全注释>添加的
! 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或.global用于定义随后的标识符是外部的或全局的,即使不使用也强制引入. !.test .data .bss用于分别定义当前的代码段,数据段和未初始化数据段.在联结多个目标模块时,连接程序 !(Id86)会根据他们的类别八各个目标模块中的相应段分别组合(合并)在一起,这里八伞个段都定义为同一重 !叠地址范围,因此本程序实际上并不分段.另外,后面带冒号的字符串是标号:用来表示地址. .globl begtext, begdata, begbss, endtext, enddata, endbss !定义了6个全局标识符; .text !文本段; begtext: .data !数据段 begdata: .bss !未初始化数据段(Block Started By Symbol) begbss: .text !文本段 SETUPLEN = 4 ! nr of setup-sectors !setup的扇区数目 BOOTSEG = 0x07c0 ! original address of boot-sector !bootsect的原始地址(是段地址,以下同) INITSEG = 0x9000 ! we move boot here - out of the way !将bootsect移到这里-避开: SETUPSEG = 0x9020 ! setup starts here !setup的其实地址; SYSSEG = 0x1000 ! system loaded at 0x10000 (65536). !系统起始加载位置(64k处) 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 !设备号0x306指定根文件系统设备是第二个硬盘的第一个分区.当年linus是在第2个硬盘上安装的 !linux0.11系统,所以这里ROOT_DEV被设置为0x306.在编译这个内核时你可以根据自己的根文件系统所在 !设备修改这个设备号.这个设备号是linux系统老式的硬盘号命名方式,硬盘设备号具体值的含义如下: !设备号=主设备号*256+次设备号(也即dev_no = (major<<8)+minor)) !(主设备号:1-内存,2-磁盘,3-硬盘,4-ttyx,5-tty,6-并行口,7-非命名管道) !0x300-/dev/hd0-代表整个第一个硬盘; !0x301-/dev/hd1-第一个硬盘的第一个分区; !... !0x304-/dev/hd4-第一个硬盘的第4个分区; !0x305-/dev/hd5-代表第二个硬盘; !0x306-/dev/hd6-第二个硬盘的第一个分区; !从linux0.95版后就已经使用与现在内核相同明明方法了. !伪指令entry迫使连接程序在生成的执行程序(a.out)中包含指定的表识符或符号. !第一个go语句之前的部分的作用是将自身(bootsect)从目前段位置0x7c0(31k)移动到 !0x9000(576kb)处,工256字,512字节,然后跳转到移动后代码的go标号处 entry start !告知连接程序,程序从标号start开始. start: mov ax,#BOOTSEG !将ds段寄存器置为0x7c0; mov ds,ax mov ax,#INITSEG !将es段寄存器置为0x9000; mov es,ax mov cx,#256 !设置移动计数值为256 sub si,si !源地址 ds:si=0x07c0:0x0000 sub di,di !目的地址es:di=0x9000:0x0000 rep !重复执行并递减cx的值 ,知道 cx=0为止. movw !即movs指令,这里从内存[si]处移动cx个字到[di]处 . jmpi go,INITSEG !段间跳转(jump intersegment).这里INITSEG指出跳转到的段地址 !,标号go是段内偏移地址. !从下面开始,cpu在已移动到0x90000位置处的代码中执行. !这段代码设置几个段寄存器,包括栈寄存器ss和sp.栈指针sp只要指向远大于512字节偏移(即地址0x90200) !处都可以.因为从0x90200处都可以.因为从0x90200地址开始还要放置setup程序,而此时setup程序大约为 !4个扇区,因此sp要指向大于(0x200+0x200*4+堆栈大小)处. !实际上bios把引导扇区加载0x7c00处并把执行权交给引导程序时,ss=0x00,sp=0xfffe. go: mov ax,cs !将ds,es和ss都置成移动后所在的段处(0x9000). mov ds,ax !由于程序中有堆栈操作,因此必须设置堆栈. mov es,ax ! put stack at 0x9ff00. !将堆栈指针sp指向0x9ff00(即0x9000:0xff00)处 mov ss,ax mov sp,#0xFF00 ! arbitrary value >>512 ! load the setup-sectors directly after the bootblock. ! Note that 'es' is already set up. ! 在bootsect程序块后紧跟着加载setup模块的代码数据. ! es已经设置好了.(在移动代码时es已经指想目的地段地址处0x9000) !下面几行的用途是利用bios中断INT0x13将setup模块从磁盘第二个扇区开始读到0x90200开始处,共读四个扇区. !如果读出错,则复位驱动器,并重试,没有退路.INT 0x13的使用方法如下: !读扇区: !ah = 0x02 - 读磁盘扇区到内存; al = 需要读出的扇区数量; !ch = 磁道(柱面)号的低8位; cl = 开始扇区(位0-5),磁道高2位(位6-7) !dh = 磁头号; dl =驱动器号(如果是磁盘,则要位7置1); !es:bx->指乡向数据缓冲区;如果没有出错则cf标志置位,ah中是出错码; 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 !取磁盘驱动器的参数,特别是每道的扇区数量. !取磁盘驱动器参数INT 0x13调用格式和返回信息如下: !ah = 0x08 dl = 驱动器号(如果是硬盘则要置位7为1); !返回信息: !如果出错则cf置位,并且 ah = 状态码. !ah = 0,al = 0, bl = 驱动器类型(AT/PS2) !ch 最大磁道号的低8位,c1 = 每磁道最大扇区数(位0-5),最大磁道号高2位(位6-7) !dh = 最大磁头树, d1 = 驱动器数量 !es:di ->软驱磁盘参数表. mov dl,#0x00 mov ax,#0x0800 ! AH=8 is get drive parameters int 0x13 mov ch,#0x00 !下面指令表示下一条语句的操作树在cs段寄存器所指的段中.它只影响下一条语句,实际上 !由于本程序和数据都被设置处于同一段中,即cs ds es的值相同,因此本程序中此处可以不同此语句 seg cs !下句保存每磁道扇区数.对于软盘来说(dl=0),其最大磁道号不会超过256,ch已经足够表示她,因此cl的为6-7 !肯定为0.有上面到数第二句已置ch=0,因此此时cs中是每磁道扇区数. mov sectors,cx mov ax,#INITSEG mov es,ax !因为上面取磁盘参数中断改掉了es的值,这里重新改回. ! Print some inane message !显示信息;"'loading system . . .'回车换行",共显示包括回车和换行控制字符在内的24个字符. !bios中断0x10功能号 ah = 0x03,读光标位置. !输入:bh = 页号 !返回:ch = 扫描开始线; ch = 扫描开始线;cl = 扫描结束线; dh = 行号(0x00顶端);dl = 列号(0x00最左边). ! !BIOS中断0x10功能号 ah = 0x13, 显示字符串. !输入:al = 防止光标的方式及规定属性.0x01- 表示使用b1中的属性值,光标停在字符串结尾处. !es:bp 次寄存器对指向要显示的字符串起始位置处.cx = 显示字符串字符数.bh = 显示页面号; !bl = 字符属性.dh = 行号.dl =列号; mov ah,#0x03 ! read cursor pos xor bh,bh !首先读光标位置.返回光标位置值在dx中. int 0x10 !dh -行(0--24);dl-列(0--79).供显示串用. mov cx,#24 !共显示24个字符 mov bx,#0x0007 ! page 0, attribute 7 (normal) mov bp,#msg1 !es:bp指向要显示的字符串. mov ax,#0x1301 ! write string, move cursor int 0x10 ! ok, we've 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 !关闭驱动器马达,这样就可以知道驱动器的状态了. ! 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,其中nr为0-3分别对应软驱A B C 或 D;type是软驱的类型(2->1.2MB !!或7-->1.44MB等)因为7*4+0=28,所以/dev/ps0(2,28)指的是1.44MB A驱动器,其设备号是0x021c !!同理 /dev/at0(2,8)指的是1.28MB A驱动器,其设备好为0x0208 seg cs mov ax,root_dev !取508,509字节处的根设备号并判断是否已被定义. cmp ax,#0 jne root_defined !取上面第88行保存的每磁道扇区数.如果sectors=15则说明是1.2MB的驱动器;如果sectors=18,则说明是1.4MB软驱. !因为是可引导的驱动器,所以肯定是A驱. seg cs mov bx,sectors mov ax,#0x0208 ! /dev/ps0 - 1.2Mb cmp bx,#15 !判断每磁道扇区树是否是15 je root_defined !如果等于,则ax中就是引导驱动器的设备号. 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: !到此,所有程序都加载完毕,我们就跳转到被加载在bootsect后面的程序上 !段间跳转指令(jump intersegment).跳转到0x9020:0000去执行. jmpi 0,SETUPSEG !!!over ! 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个引导扇区和setup程序所占的扇区数SETUPLEN
!!主要寄存器含义 !!sectors 磁道扇区数目 !!sread: .word 1+SETUPLEN 当前磁道中已读扇区数 !!head: .word 0 当前磁头号 !!track: .word 0 当前磁道号 !!es 当前段
sread: .word 1+SETUPLEN ! sectors read of current track !当前磁道中已读扇区数 head: .word 0 ! current head !当前磁头号 track: .word 0 ! current track !当前磁道号 read_it:
!首先测试输入的段值,从盘上读入的数据必须存放在位与64kb的边界开始处,否则进入死循环. !test以比特位相与两个操作数,结果只影响标志位ZF. mov ax,es test ax,#0x0fff die: jne die ! es must be at 64kB boundary xor bx,bx ! bx is starting address within segment !bx为段内偏移 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子程序. !读当前磁道上指定开始扇区和需读扇区数的数据到es:bx开始处. !int 0x13, ah = 2 的说明 !al - 需读扇区数;es:bx - 缓冲区开始位置. 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. */ !下面的程序中0x3f2是软盘控制器的一个端口,被成为数字输出寄存器(DOR)断口.它是一个8位的寄存器. !其7-4位分别用于控制4个软驱(D-A)的启动和关闭.位3-2用于允许禁止DMA和中断请求以及启动/复位软盘控制 !器FDC.位1-0用于选择操作的软驱.下面的设置al 为0值,就是用于选择A驱动器,关闭FDC,禁止DMA和中断请求, !关闭马达. kill_motor: push dx mov dx,#0x3f2 mov al,#0 !A驱动器 关闭FDC,禁止DMA和中断请求,关闭马达. outb !将al中的内容输出到 dx指定端口去 pop dx ret sectors: .word 0 !存放当前启动软盘每磁道的扇区数 msg1: !调用BIOS中断显示的信息 .byte 13,10 !回车 换行的ASCII码 .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:
|
一共有 1 条评论