bootsect.s解读

bootsect.s

	SYSSIZE = 0x3000	#system模块的长度
	
	.global begtext, begdata, begbss, endtext, enddata, endbss
	.text		#代码段
	begtext:	
	.data		#初始化的全局变量和静态变量
	begdata:	
	.bss		#为初始化的全局变量和静态变量
	begbss:
	.text		#返回到代码段

	SETUPLEN = 4				#setup程序所占的扇区
	BOOTSEG = 0x07c0			#启动扇区加载到内存的段地址
	INITSEG = 0x9000			#启动扇区搬移后的地址
	SETUPSEG = 0x9020			#setup程序的加载地址
	SYSSEG = 0x1000				#system加载的段地址
	EDNSEG = SYSSEG+SYSSIZE		#system加载的结束地址

	ROOT_DEV = 0x306			#第二个硬盘的第一个分区		

	entry _start	#告知链接程序,程序从 _start 标号开始执行
	_start:			#程序入口
			mov ax,#BOOTSEG		#将引导扇区的段地址0x07c0加载到ax
    		mov	ds,ax			#将启动代码与ds寄存器相关联,这样DS:00就对应实际内存0x07c00,这是BIOS加载的起始位置
    		mov ax,#INITSEG		#启动代码要被复制的目的地址的段地址为0x9000
    		mov es,ax			#将目的地址与es寄存器相关联,这样ES:00就对应实际内存0x90000,这是启动代码即将被复制的地址
    		mov cx,#256			#循环控制字节,搬运256次,一次2字节,共256字节
    		sub si,si			#si清零,ds:si即0x07c00
    		sub di,di			#di清零,es:si即0x90000
			rep					#循环直到cx=0
    		movw				#传送一个字,将ds:si复制到es:di,每复制一次,si和di加2,将引导扇区的内容搬到0x90000处
    		jmpi go,INITSEG		#段间跳转,跳转到0x9000:go处

	go:		mov ax,cs		    #因为启动代码复制到了新的位置,需要更改相应寄存器的值		
			mov ds,ax	
			mov es,ax			#cs = ds = es = ss = 0x9000
			mov ss,ax			#开始引入栈
			mov sp,#0xFF00		#栈空间的起始地址为0x9ff00
/*
	setup程序是BIOS加载到内存中执行的第二阶段引导程序
	setup从0x90200地址开始,大概占用四个扇区,一个扇区是0x200字节,所以一共占用0x800字节,
	程序还多预留了一个扇区空间,所以setup一共占用5个扇区,即0xA00字节
	栈的大小设置成0xFF00-0xA00 = 0xF500,即0xF500字节,足够大
	内存地址增长方向 →
	0x90000 --------------------------> 0x9FFFF
            |---- setup 占用 ----|
	0x90200 --- 0x90A00               |
                                      |
               	栈 (Stack)             |
               	← ← ← ← ←             |
                (生长方向)              |
                                      |
	0x9FF00 --------------------------
*/    
	load_setup:
			mov dx,#0x0000				#驱动器号(DL)0,磁头号(DH)0
			mov cx,#0x0002				#柱面的低8为(CH)0,柱面的高两位(CL[7:6])0,起始扇区(CL[5:0])2
			mov bx,#0x0200				#ES:BX读出数据的缓存位置
			mov	ax,#0x0200+SETUPLEN		#功能号(AH)0x02读取磁盘,要读取的扇区数(AL)4
			int 0x13					#进入中断服务程序,将磁盘setup.s对应的程序加载至内存指定位置即0x90200
/*
    功能号 0x02 — 读取磁盘扇区
    ---------------------------------------
    - 功能描述:从指定的磁道、磁头、扇区读取数据到内存
    - 入口参数:
        AH = 0x02          ; 功能号,表示读取扇区
        AL = 扇区数量      ; 读取的扇区数量
        CH = 柱面号 (低 8 位)
        CL = 扇区号 (低 6 位) + 柱面号 (高 2 位)
        DH = 磁头号
        DL = 驱动器号 (0x00 表示软盘,0x80 表示硬盘)
        ES:BX = 目标内存地址

    - 返回值:
        CF = 0 (成功), 1 (失败)
        AH = 错误码 (如果 CF = 1)
        AL = 实际读取到的扇区数
    ---------------------------------------
*/
			jnc ok_load_setup			#CF标志寄存器,中断的返回值会传入CF,CF=0则跳转至ok_load_setup
			mov dx,#0x0000				#驱动器号(DL)0,磁头号(DH)0
			mov ax,#0x0000				#功能号(AH)0x00,复位磁盘
			int 0x13					#进入中断服务程序,复位磁盘
/*
    功能号 0x00 — 重置磁盘
    ---------------------------------------
    - 功能描述:复位磁盘系统,相当于重新初始化磁盘接口
    - 入口参数:
        AH = 0x00         ; 功能号,表示复位磁盘
        DL = 驱动器号 (0x00 表示软盘,0x80 表示硬盘)

    - 返回值:
        CF = 0 (成功), 1 (失败)
        AH = 错误码 (如果 CF = 1)
    ---------------------------------------
*/
			j load_setup				#再次读取
	
	ok_load_setup:
			mov dl,#0x00				#驱动器号(DL)0
			mov ax,#0x0800				#功能号(AH)0x08,表示获取驱动器的参数
			int 0x13					#进入中断服务程序
/*
    功能号 0x08 — 获取磁盘参数
    ---------------------------------------
    - 功能描述:获取磁盘的几何信息(柱面数、磁头数、扇区数)
    - 入口参数:
        AH = 0x08         ; 功能号,表示获取磁盘参数
        DL = 驱动器号 (0x00 表示软盘,0x80 表示硬盘)

    - 返回值:
        CF = 0 (成功), 1 (失败)
        AH = 错误码 (如果 CF = 1)
        BL = 驱动器类型
        CH = 最大磁道号(低 8 位)
        CL = 每磁道最大扇区号(低 6 位) +最大磁道号(高 2 位)
        DH = 最大磁头数
        DL = 驱动器数量
        ES:DI = 指向 DPT (Drive Parameter Table) 的地址,软驱磁盘参数表
    ---------------------------------------
*/
			mov ch,#0x00				#对ch置零,因为保存了最大磁道号,避免与下面获取扇区数造成干扰
			seg cs						#指示下一条指令的操作数在 CS 段中
			mov sectors,cx				#将最大扇区数存放在cs:sectors中
			mov ax,#INITSEG				#由于中断返回值修改了es,因此需要恢复es
			mov es,ax					#es恢复成0x9000
	
			mov	ah,#0x03				#读取光标位置
			xor	bh,bh					#页号设置成0
			int	0x10					#进入中断服务程序
/*
    功能号 0x03 — 获取光标位置和页面信息
    ---------------------------------------
    - 中断号:0x10
    - 功能描述:获取光标在指定页面的列号和行号,同时返回当前光标形状。
    - 入口参数:
        AH = 0x03         ; 功能号 0x03,获取光标位置
        BH = 页号         ; 0 表示默认页
        
    - 返回值:
        CH = 光标开始扫描线号   ; 光标形状的起始位置
        CL = 光标结束扫描线号   ; 光标形状的结束位置
        DH = 光标所在的行号    ; 光标所在屏幕的行位置
        DL = 光标所在的列号    ; 光标所在屏幕的列位置
    ---------------------------------------
*/
			mov	cx,#24				#字符串长度24
			mov	bx,#0x0007			#页面后为0,字符属性0x7,黑底白字
			mov	bp,#msg1			#指向要显示的字符串地址
			mov	ax,#0x1301			#功能号(AH)0x13,写字符串,显示方式(AL)0x01
			int	0x10				#进入中断服务程序
/*
    功能号 0x13 — 显示字符串并移动光标
    ---------------------------------------
    - 中断号:0x10
    - 功能描述:在指定位置显示字符串,并根据参数选择是否移动光标。
    - 入口参数:
        AH = 0x13                ; 功能号 0x13,显示字符串
        AL = 显示方式            ; 
               位 0:字符串中只含显示字符,显示属性在BL中;显示后,光标位置不变
               位 1:字符串中只含显示字符,显示属性在BL中;显示后,光标位置跟随字符串改变
               位 2:字符串中含有显示字符和显示属性;显示后,光标位置不变
               位 3:字符串中含有显示字符和显示属性;显示后,光标位置跟随字符串改变
        BH = 页号                ; 0 表示默认页
        BL = 颜色属性            ; 0x07 表示黑底白字
        CX = 字符串长度          ; 要显示的字符数
        DH = 行号                ; 如果使用光标位置,指定行号
        DL = 列号                ; 如果使用光标位置,指定列号
        ES:BP = 字符串地址       ; 要显示的字符串内存地址
    - 返回值:
        无  
*/	
			mov	ax,#SYSSEG			#设置ax=0x1000
			mov	es,ax				#设置es=0x1000
			call	read_it			#调用read_it方法,将system模块存放在0x1000:0x0000处
			call	kill_motor		#调用kill_motor方法,关闭驱动器马达
			
/*
    设备号计算与软驱类型识别
    ---------------------------------------
    - 主设备号:
    	0x01 - 内存设备 (RAM Disk)
        0x02 - 代表软盘设备 (Floppy Disk)
        0x03 - IDE 硬盘 (IDE Disk)
        0x04 - 串口设备 (TTY) 
    - 次设备号计算公式:
        次设备号 = type * 4 + nr
        - type:软驱类型
            - 2 表示 1.2MB 的软驱
            - 7 表示 1.44MB 的软驱
        - nr:软驱编号
            - 0 表示软驱 A:
            - 1 表示软驱 B:
            - 2 表示软驱 C:
            - 3 表示软驱 D:
        
        由于是可引导的驱动器,所以默认是 A:,即 nr = 0
    - 设备号计算:
        设备号 = (主设备号 << 8) + 次设备号
        - 对于 1.2MB 的软驱:
            次设备号 = 2 * 4 + 0 = 8
            设备号 = (0x02 << 8) + 0x08 = 0x0208
        - 对于 1.44MB 的软驱:
            次设备号 = 7 * 4 + 0 = 28
            设备号 = (0x02 << 8) + 0x1C = 0x021C
*/
			
			seg cs				
			mov	ax,root_dev			#设置ax=root_dev
			cmp	ax,#0	
			jne	root_defined		#如果root_dev不等于则跳转到root_defined
			seg cs
			mov	bx,sectors			#取每磁道扇区数
			mov	ax,#0x0208			#/dev/ps0 - 1.2Mb
			cmp	bx,#15				#如果等于15,说明是1.2MB的软盘
			je	root_defined		
			mov	ax,#0x021c			#/dev/PS0 - 1.44Mb
			cmp	bx,#18				#如果等于18,说明是1.44MB的软盘
			je	root_defined
	undef_root:
			jmp undef_root			#死循环
	root_defined:
			seg cs		
			mov	root_dev,ax			#将检查过的设备号保存到root_dev中

	jmpi	0,SETUPSEG			#到此本程序就结束了,跳转到setup去执行
	
	sread:	.word 1+SETUPLEN	#当前磁道已经读取的扇区数(引导扇区 + setup模块 = 5)
	head:	.word 0				#当前磁头号
	track:	.word 0				#当前磁道号

	read_it:
			mov ax,es						
			test ax,#0x0fff		#test指令执行与运算,当运算结果为0时,test设置零标志ZF=1,这里使ax与0xfff按位与,测试es是否为0x1000的整数倍
	die: 	jne die	    		#jne的跳转条件是ZF=0,因此不会跳转,如果test结果不为0,说明es不是0x1000的整倍数,则陷入死循环
    		xor bx,bx			#bx作为段内偏移地址清零
    rp_read:
            mov ax,es
            cmp ax,#ENDSEG    	#实际上求ax-ENDSEG,看是否到了末尾,如果不相等,则继续读取
            jb ok1_read			#当CF=1,ax<ENDSEG,则跳转
			ret					#如果没有跳转到ok1_read,即ax超过或等于ENDSEG时,执行ret,返回调用处
	ok1_read:
			seg cs				
			mov ax,sectors		#将cs:[sectors]中存放的每磁道的扇区数赋值给ax
			sub ax,sread		#ax=ax-sread,得出本磁道未读取扇区数
			mov cx,ax			#将剩余未读的扇区数赋值给cx
			shl cx,#9			#cx*512代表剩余扇区需要读取的总字节数
			add cx,bx			#上面三行相当于cx=cx*512+bx,bx是段内偏移地址
			jnc ok2_read		#判断这次加法是否产生了进位,是否16位溢出即cx<0x1000,CF=0没有进位,则跳转到ok2_read
			je ok2_read			#判断加法结果是否为0,如果是0,ZF=1,说明刚好没有越界,则跳转到ok2_read
			xor ax,ax			#执行到这里说明越界,则ax=0
			sub ax,bx			#计算bx离边界多远,用0-bx结果存放在ax中
			shr ax,#9			#除以512,得到要读的扇区数,AL作为参数,传给read_track
	ok2_read:
   			call read_track		#传递al参数,读取al个扇区到ES:BX
			mov cx,ax			#返回值al,本次读取的扇区数,cx=ax
			add ax,sread		#当前磁道已经读取的扇区数
			seg cs				
			cmp ax,sectors		#如果当前磁道还有扇区没有读完,跳转到ok3_read
			jne ok3_read
			mov ax,#1			#说明当前磁道已经读完
			sub ax,head			#ax=1-磁头号
			jne ok4_read		#判断sub值是否为0,如果不是0则跳转,此时ax=1即磁头号是1,磁道号是0
			inc track			#此时说明磁头号是1,ax等于0,设置磁头号等于0,磁道号增加1
	ok4_read:
     		mov head,ax			#保存当前磁头号
     		xor ax,ax			#清楚已读扇区,设置为0
    ok3_read:
     		mov sread,ax		#更新当前磁道已经读取的扇区数
     		shl cx,#9			#cx是该次操作已经读取的扇区数,乘以512字节
     		add bx,cx			#更新偏移地址
     		jnc rp_read			#没有进位,则跳转到rp_read
     		mov ax,es			#有进位,说明bx达到了64kb边界
     		add ax,#0x1000		
    		mov es,ax			#es增加0x1000
     		xor bx,bx			#bx等于0
     		jmp rp_read			#继续读取
	read_track:
			push ax				#保存ax,bx,cx,dx寄存器
			push bx
			push cx
     		push dx
     		mov dx,track		#获取当前的磁道号
     		mov cx,sread		#获取当前磁道上已经读取的扇区数
     		inc cx				#CL起始扇区号
  			mov ch,dl			#CH当前磁道号
    	 	mov dx,head			#DH当前磁头号
    		mov dh,dl			#DH是磁头号
    		mov dl,#0			#DL驱动器号
     		and dx,#0x0100		#将dx与0x0100相与,目的是使磁头号小于等于1
     		mov ah,#2			#ah=2,读磁盘扇区功能号
    		int 0x13			#进入中断服务程序,读取剩余的扇区数al到es:bx
     		jc bad_rt			#如果出错,CF=1,表示出错,跳转到bad_rt复位磁盘
     		pop dx				#取出ax,bx,cx,dx寄存器
     		pop cx
    		pop bx
    		pop ax
    		ret					#返回
 	bad_rt: mov ax,#0			#ah=0,磁盘复位功能
     		mov dx,#0			#dl是驱动器号,DH当前磁头号
     		int 0x13			#进入中断服务程序
    		pop dx				#取出ax,bx,cx,dx寄存器
    		pop cx
     		pop bx
			pop ax
			jmp read_track		#重新获取
	kill_motor:
			push dx				
			mov dx,#0x3f2		#软盘控制器的端口-数字输出寄存器端口,只写
			mov al,#0			#驱动器A,关闭FDC,禁止DMA和中断请求,关闭马达
			outb				#将al的值写入端口dx
			pop dx
			ret

	sectors:
			.word 0			
	msg1:
			.byte 13,10						#13 是回车 (Carriage Return, CR),10 是换行 (Line Feed, LF)
			.ascii "Loading system ..."		# 输出的 ASCII 字符串 "Loading system ..."
			.byte 13,10,13,10				# 两次回车换行
/*
    .org 508 伪指令的作用
    ---------------------------------------
    - `.org` 是汇编伪指令 (Assembler Directive)。
    - 作用是告诉编译器接下来的代码或数据应该放置在特定的偏移地址。
    - 这里的 `508` 是十进制,换算成十六进制是 `0x1FC`。
    
    ➡️ 0x1FC 是引导扇区 (Boot Sector) 中的重要位置:
        - 引导扇区是 BIOS 从磁盘读取的第一个扇区,共 512 字节。
        - BIOS 把这个扇区加载到内存地址 `0x7C00`,并跳转到那里执行。
        
    ➡️ 引导扇区的内存布局:
        | 偏移地址 (十六进制) | 大小 | 描述                         |
        |----------------------|------|----------------------------|
        | 0x000               | 446  | 主引导程序 (Bootstrap Code) |
        | 0x1BE               |  64  | 分区表 (Partition Table)   |
        | 0x1FC               |   2  | 自定义数据 (通常存放设备号) |
        | 0x1FE               |   2  | 引导签名 (0x55AA)          |
        
        - 0x1FC 和 0x1FD 可以存放引导加载器自定义的内容,比如设备号、标识符等。
        - 0x1FE 和 0x1FF 是固定的签名,如果是 `0x55AA`,表示引导扇区合法。
        
    ➡️ `.org 508` 的意义:
        - 508 是十进制,对应十六进制是 `0x1FC`。
        - 在这个位置通常会放一些特殊的数据:
            - 设备号
            - 校验码
            - 魔数 (Magic Number)
        - 这一步是为了让引导加载器能正确识别设备类型。
        
    ➡️ 例子:
        - 如果在 `.org 508` 之后写:
            mov ax, #0x0208
        - 那么 `0x0208` 会被放在偏移地址 `0x1FC` 和 `0x1FD` 上。
        
        | 偏移地址 | 内容  |
        |-----------|------|
        | 0x1FC     | 08   |
        | 0x1FD     | 02   |
        | 0x1FE     | 55   |
        | 0x1FF     | AA   |

    ➡️ BIOS 加载过程:
        - BIOS 从磁盘读取第一个扇区 (512 字节) 到内存 `0x7C00`。
        - 检查 `0x1FE` 和 `0x1FF` 是否是 `0x55AA`,如果不是,加载失败。
        - 如果是合法引导扇区,继续执行从 `0x7C00` 开始的引导代码。
        - 0x1FC 的设备号会被引导代码读取,用来判断是软盘还是硬盘。
 */  
    .org 508           ; 偏移地址设置到 0x1FC 位置
	root_dev:
			.word ROOT_DEV		#存放根文件系统所在设备号(init/main.c中会用)
	boot_flag:
			.word 0xAA55		
	.text
	endtext:
	.data
	enddata:
	.bss
	endbss:
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值