30天自制操作系统(第03天)–进入32位模式并引入C语言

使用汇编语言编辑的是接近于机器语言的程序,但是汇编语言操作的基本都是寄存器、内存之类的底层的东西,阅读起来其实挺困难的。就如上一天的循环,不仔细阅读并模拟代码逻辑,还真的很难看出来是个循环呢。于是我们希望在今天开始引入C语言,相对来说比较友好一些。

但实际上,本篇绝大部分篇幅还是使用汇编语言,姑且先忍耐一下吧

磁盘的分区

由于本篇内容主要是在介绍如何使用汇编程序读取磁盘内容,因而在正式开讲前,先来介绍一下磁盘是如何划分区域的。

以前我们都是用的磁盘,是一张带有磁粉的碟。在读取的时候,需要有一个特殊的磁头来读取。反正挺高深的,至于有磁性为什么就可以存储数据,这个我也不懂。

一张软盘被人为划分了若干个区域。首先在一张软盘上以同心圆划分出了80个区域(编号从0开始),这个称为柱头;然后再以直径方向将一个柱面划分成了18等分,这个称为扇区,每个扇区可以存放512字节。如果是一张双面的软盘,则在软盘背面也有这样一个区域。

那么计算一下软盘的大小:
512*18(扇区)*80(柱面)2(双面)=14401024=1440 KB

磁盘中断0x13

这个中断是让CPU调用磁盘的BIOS。与这个中断有关的设置如下:
AH=0x02; (读盘)
AH=0x03; (写盘)
AH=0x04; (校验)
AH=0x0C; (寻道)
AL=处理对象的扇区数;(只能同时处理连续的扇区)
CH=柱面号  & 0xff;
CL=扇区号
DH=磁头号;
DL=驱动器号;
ES : BS=缓冲地址;
返回值:
	FLAGS.CF==0;没有错误
	FLAGS.CF==1;有错误

缓冲区和地址计算

缓冲区是内存中一个区域,从磁盘中读取出来的数据会存放到这个区域等待进一步处理。
我们知道,一个十六位的寄存器,最大是一个16位的二进制数,也就是2的16次方,也就是64KB。如果只用一个寄存器来存放地址,那么最大的内存访问量也就是64KB,很多早期的电脑就是这样。
在这里ES和BX寄存器一起用来表示这个地址,其形式就是[ES:BX],计算规则为ES*16+BX,最大就能表示1M的内存地址了。

段寄存器

事实上,在制定内存中某个地址时(即用方括号表示),实际上都考虑了段寄存器DS,只不过DS可以忽略。比如下面两句诗等效的:

MOV CS,[1234]
MOV CS,[DS:1234]

为了避免错误的发生,需要将DS设置为0。

读取磁盘程序

程序如下:

; hello-os
; TAB=4
CYLS	EQU	10		; 一共读10个柱面
        ORG     0x7c00          ; 指名程序的装载地址
; 以下一段是标准FAT12格式软盘专用的代码
        JMP     entry
        DB      0x90
        DB      "HELLOIPL"      ; 引导扇区的名称,随意写,8字节
        DW      512             ; 每个扇区(sector)的大小,必须是512
        DB      1               ; 簇(cluster)的大小,必须为1
        DW      1               ; FAT起始位置,一般从第一个扇区开始
        DB      2               ; FAT的个数,必须为2
        DW      224             ; 根目录大小,一般设置成224
        DW      2880            ; 磁盘的大小,必须为2880扇区
        DB      0xf0            ; 磁盘的种类,必须为0xf0
        DW      9               ; FAT的长度,必须是9扇区
        DW      18              ; 1个磁道(track)有几个扇区,必是18
        DW      2               ; 磁头数,必须为2
        DD      0               ; 因为不使用分区,所以必须是0,4字?
        DD      2880            ; 重写一次磁盘大小,4字节
        DB      0,0,0x29        ; 意义不明,固定
        DD      0xffffffff      ; 卷标号
        DB      "HELLO-OS   "   ; 磁盘名称,11字节
        DB      "FAT12   "      ; 磁盘格式名称,8字节
        RESB    18              ; 空出18字节
; 程序主体
entry:
	MOV	AX,0		; 寄存器初始化
	MOV	SS,AX
	MOV	SP,0x7c00
	MOV	DS,AX
; 读取磁盘
	MOV	AX,0x0820
	MOV	ES,AX
	MOV	CH,0		; 柱面0
	MOV	DH,0		; 磁头0
	MOV	CL,2		; 扇区2,第二个扇区开始
readloop:
	MOV	SI,0		; 记录失败次数的寄存器
retry:
	MOV	AH,0x02		; AH=0x02 : 读取磁盘
	MOV	AL,1		; 1个扇区
	MOV	BX,0
	MOV	DL,0x00		; A驱动器
	INT	0x13		; 调用磁盘BIOS中端
	JNC	next		; 没出错时跳转到NEXT
	ADD	SI,1		; SI加1
	CMP	SI,5		; SI与5比较
	JAE	error		; SI 大于或等于 5 时,跳转到error
	MOV	AH,0x00
	MOV	DL,0x00		; A驱动器
	INT	0x13		; 重置驱动器
	JMP	retry
next:
	MOV	AX,ES		; 内存地址后移0x200,即512个字节
	ADD	AX,0x0020
	MOV	ES,AX		; 因为没有ADD ES,0x020 ,所以曲线救国
	ADD	CL,1		; CL加1,下一个扇区
	CMP	CL,18		; CL与18比较
	JBE	readloop	; CL 小于或等于 18 时,如果未读完18个扇区,跳转到readloop
	MOV	CL,1            ; 从第一个扇区开始
	ADD	DH,1            ; 磁头+1
	CMP	DH,2            ; 
	JB	readloop	; DH 小于 2 时,两个磁头未读完,跳转到readloop
	MOV	DH,0            ; 磁头0
	ADD	CH,1            ; 下一个柱面
	CMP	CH,CYLS
	JB	readloop	; CH 小于 CYLS 时,跳转到readloop
	MOV	SI,msg
	JMP	putloop

fin:
	HLT				; 让CPU停止,等待指令
	JMP	fin		; 无限循环

        
        
error:
	MOV	SI,errormsg     ; 将msg的地址存放到SI
putloop:
	MOV	AL,[SI]         ; 将SI中的地址存放到AL
	ADD	SI,1		; SI+1
	CMP	AL,0
	JE	fin
	MOV	AH,0x0e		; 显示一个文字
	MOV	BX,15		; 指定颜色
	INT	0x10		; 调用显卡BIOS
	JMP	putloop
errormsg:
	DB	0x0a, 0x0a	; 2个换行
	DB	"load error"
	DB	0x0a		; 换行
	DB	0
msg:
	DB	0x0a, 0x0a	; 2个换行
	DB	"hello world"
	DB	0x0a		; 换行
	DB	0 
	RESB    0x7dfe-$	; 填写0x00,直到0x001fe
	DB	0x55, 0xaa

这段程序使用了3个嵌套的循环,按照柱面、磁头、扇区的顺序遍历了10个柱面、2个磁头、总共360个扇区。每次读取一个扇区,即512个字节,将其存放在0x08200开始的内存中(虽然在程序中看到的是0x0820,但是由于ES寄存器中的地址要乘以16,因而实际是0x08200的地址处)。
需要注意的是,最早的512个字节是留给引导程序也就是这个程序本身的,所以在最开始是从0柱头第1磁头的第2个扇区开始读起。
总的而言,这段引导程序本身(共512字节)被装入了0x7c00一直到0x7dff内存中。然后利用了这段程序读取磁盘中剩余的359个扇区,存放到了0x08200开始的内存中,总共180KB。

此处出现的指令有:

JC和JNC

这两个指令是用来判断CF标识有没有值而进行跳转的。本例中,当读取没有错误时,该标识是0,反之则为1。因而可以根据该值采取不同的操作。

EQU

这是一个常量声明的语句。语法为:

常量名 EQU 值

此后,该常量可以被之后的程序所引用。

JA, JAE, JE, JBE, JB

这几个是条件跳转的语句。分别表示

  • JA(Jump if Above ) 大于
  • JAE(Jump if Above or Equal) 大于或等于
  • JE(Jump if Equal) 等于
  • JBE(Jump if Below or Equal) 小于或等于
  • JB(Jump if Below ) 小于

32位模式并引入C语言

磁盘中的文件系统

在空磁盘中,如果写入一个文件,那么:

  1. 文件名写入地址0x002600以后的地方
  2. 文件内写入0x004200以后的地方

引导程序后续

根据这个特性,我们可以通过引导程序运行第一个文件内的程序。首先,在引导程序中,最开始的512个字节存放在了0x07c00的地方,之后读取的磁盘内容从0x08200开始,那么第一个文件的地址应该是0x08200-0x200(最早的512字节)+0x4200=0xc200.
因而只要将引导程序再跳转到0xc200处,然后在文件中指明ORG 0xc200,那么可以将我们需要在启动之后运行的程序写在一个文件中,并在引导程序执行之后执行。
如此一来,就将引导程序和系统程序分开了。

VGA模式

VGA模式需要用到显卡中断,编号位0x10
设置基本信息如下:

AH = 0x00
AL = 模式
   0x03 : 80 * 25 * 16 ;16 色
   0x12 : VGA 640 * 480 * 4 ;4位颜色
   0x13 : VGA 320 * 200 * 8 ;8位颜色
   0x6a : VGA 800 * 600 * 4 ;4位颜色

当然还有些其他的设置,都是直接写在规定的内存地址的。此次的程序如下:

; haribote-os
; TAB=4

; BOOT_INFO信息
CYLS	EQU		0x0ff0			; 设定启动区
LEDS	EQU		0x0ff1
VMODE	EQU		0x0ff2			; 关于颜色数目的信息,位数
SCRNX	EQU		0x0ff4			; x分辨率
SCRNY	EQU		0x0ff6			; y分辨率
VRAM	EQU		0x0ff8			; 图像缓冲区的开始地址
	ORG		0xc200			; 程序装载的位置
	MOV		AL,0x13			; VGA图像、320x200x8bit颜色
	MOV		AH,0x00
	INT		0x10
	MOV		BYTE [VMODE],8	; 画面模式
	MOV		WORD [SCRNX],320
	MOV		WORD [SCRNY],200
	MOV		DWORD [VRAM],0x000a0000
		
; 获取键盘各种LED指示灯状态
	MOV		AH,0x02
	INT		0x16 			; 键盘 BIOS
	MOV		[LEDS],AL
		
fin:
	HLT
	JMP		fin

这样就可以启动成一个一片乌漆抹黑的界面了。

引入C语言

接着就到了重点,引入C语言。此处,作者写了一堆汇编代码,但是说以后再解释,那就以后再说。
最终,我们的程序被拆成了3个汇编程序文件和一个C语言文件:

  1. 就是最早的512字节的引导程序
  2. 作者没做解释的部分,说是为了引入C语言写了这部分
  3. 汇编写的函数,可以供C语言直接调用。
    这个文件的代码如下:
; naskfunc
; TAB=4
[FORMAT "WCOFF"]			; 制作目标文件的模式
[BITS 32]				; 制作32位模式用的机器语言

; 制作目标文件的信息

[FILE "naskfunc.nas"]			; 源文件名信息
		GLOBAL	_io_hlt		; 程序包含的函数名


; 以下是实际的函数

[SECTION .text]				; 目标文件中写了这些再写函数

_io_hlt:				; void io_hlt(void);
		HLT
		RET

这里要说明的是:

  1. 函数名称要用下划线开头,但是C语言中调用省略前置的下划线
  2. 函数名称需要用GLOBAL来声明
    函数部分就没啥好解释的了,HLT之后就返回(RET)

然后是C语言的调用

/* 告诉C编译器,有一个函数在别的文件里 */

void io_hlt(void);

/* 函数的声明,不用{},直接;意思是函数在别的文件里面,需要找一下 */

void HariMain(void)
{

fin:
	io_hlt(); /* 执行naskfunc.nas中的_io_hlt函数 */
	goto fin;

}

然后编译过程大致是这样的:

  1. 先把c文件和函数的汇编编译成目标文件
  2. 然后把1的目标文件连接起来
  3. 然后把为了引进C语言写的汇编编译一下
  4. 把2和3的文件组合起来生成一个sys文件
  5. 最后,用引导程序生成img文件,再将4的文件复制进去

这一章我断断续续花了3天才写完这些,需要仔细看源程序文件以及书中的说明,才能知道在干些什么。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值