先体验一下编译仿真方法:
30天自制操作系统光盘代码在下面链接,但是没有编译仿真工具:
https://gitee.com/zhanfei3000/30dayMakeOS
仿真工具在下面链接:
https://gitee.com/909854136/nask-code-ide
这是一个集成的编译仿真工具,只需要把上面仿真工具的文件夹:
\nask-code-ide-master\crtools
复制到源码文件加下,并改名为z_tools就可以按照书中的方法编译仿真了:
\30dayMakeOS-master\z_tools
z_tools如下:
在代码30dayMakeOS-master\01_day目录下执行下面编译仿真指令就可以看到仿真出的操作系统了。
后面章节源码写了 makefile就简单了,只要输入make就可以编辑了 然后再输入make run就可以仿真了
标题一、代码执行顺序(前内容六天的内容)
ipl10.nas–>asmhead.nas–>boopack.c
标题二、代码阅读
1.ipl10.nas(将软盘内容拷贝到内存中)
; haribote-ipl
; TAB=4
; 读取软盘内容到内存中,然后跳转到0xc200开始执行,就是asmhead.nas文件
CYLS EQU 10 ; CYLS=10 读取是10个柱面
ORG 0x7c00 ; 指明程序装载地址
; 以下这段是FAT12格式软盘专用代码 0x7c00--0x7dff
JMP entry
DB 0x90
DB "HARIBOTE" ; 启动区的名字可以是任意的,但必须是8字节
DW 512 ; 每个扇区(sector)的大小必须为512字节
DB 1 ; 簇(cluster)的大小必须为1个扇区
DW 1 ; FAT的起始位置(一般从第一个扇区开始)
DB 2 ; FAT的个数(必须为2)
DW 224 ; 根目录的大小(一般设为244项)
DW 2880 ; 该磁盘的的大小(必须为2880扇区)
DB 0xf0 ; 磁盘的种类(必须为0xfd)
DW 9 ; FAT的长度(必须为9扇区)
DW 18 ; 一个磁道(track)有几个扇区(必须为18)
DW 2 ; 磁头数(必须为2)
DD 0 ; 不使用分区(必须为0)
DD 2880 ; 重写一次磁盘大小
DB 0,0,0x29 ; 意义不明,固定
DD 0xffffffff ; (可能是)卷标号码
DB "HARIBOTEOS " ; 磁盘名称(11字节)
DB "FAT12 " ; 磁盘格式名称(8字节)
RESB 18 ; 先腾出18字节
; 程序核心
entry:
MOV AX,0 ; AX=0 初始化寄存器
MOV SS,AX ; SS=AX=0
MOV SP,0x7c00 ; SP=0x7c00
MOV DS,AX ; DS=AX=0
; 读磁盘(从软盘中读数据装到内存中0x8200--0x83ff
MOV AX,0x0820 ; AX=0x0820 设置缓存区的段地址
MOV ES,AX ; ES=AX=0x0820 ES:BX就是缓存区的地址
MOV CH,0 ; CH=0 CH表示柱面号
MOV DH,0 ; DH=0 DH表示磁头号
MOV CL,2 ; CL=2 CL表示扇区号
readloop:
MOV SI,0 ; SI=0, 用于记录错误次数,实现试错功能(非必须功能)
retry:
MOV AH,0x02 ; AH=0x02 13号中断所需参数,表示操作类型,0x02(读盘),0x03写盘,0x04校验,0x0c寻道
MOV AL,1 ; AL=1 AL处理对象的扇区数,表示一次只能读取1个扇区
MOV BX,0 ; BX=0 缓冲地址
MOV DL,0x00 ; DL=0x00 DL表示驱动器号
INT 0x13 ; BIOS提供的服务,用于操作软盘
JNC next ; CF=0,跳转到next执行
ADD SI,1 ; SI=SI+1,记录尝试的次数,实现试错功能(非必须功能)
CMP SI,5 ;
JAE error ; SI >= 5 跳转到error执行
MOV AH,0x00 ; SI<5 AH=0x00 清空INT 0x13的错误码
MOV DL,0x00 ; DL=0x00 设置驱动器号
INT 0x13 ;
JMP retry ; 跳转到retry执行
next:
MOV AX,ES ; AX=ES
ADD AX,0x0020 ; AX=AX+0x0020
MOV ES,AX ; ES=AX ES向后移动了一个扇区的大小
ADD CL,1 ; CL=CL+1 扇区号加1
CMP CL,18 ;
JBE readloop ; CL <= 18 跳转到readloop执行
MOV CL,1 ; CL > 18 CL=1
ADD DH,1 ; DH=1 准备读取磁头0的内容
CMP DH,2 ;
JB readloop ; DH < 2 跳转到readloop执行
MOV DH,0 ; DH>=2 说明已读取完成
ADD CH,1 ; CH=CH+1 准备读取下一个柱面
CMP CH,CYLS ;
JB readloop ; CH < CYLS 跳转到readloop执行
; 磁盘内容装载内容的结束地址告诉haribote.sys
MOV [0x0ff0],CH ; [0x0ff0]=CH 将CYLS的值写入到内存地址0x0ff0中,可以参考asmhead.nas中对应的变量赋值
JMP 0xc200 ; 跳转到0xc200
error:
MOV SI,msg ;SI=msg 显示错误信息
putloop:
MOV AL,[SI] ; AL=[SI] 读取[SI]内存中的信息
ADD SI,1 ; SI=SI+1
CMP AL,0 ;
JE fin ; AL==0, 错误信息显示完毕,跳转到fin
MOV AH,0x0e ; AH=0x0e 设置显示属性
MOV BX,15 ; BX=15 设置显示属性
INT 0x10 ; 调用BIOS显示服务
JMP putloop ;
fin:
HLT ; 让CPU停止等待命令
JMP fin ;
msg:
DB 0x0a, 0x0a ;
DB "load error"
DB 0x0a ;
DB 0 ;
RESB 0x7dfe-$ ;
DB 0x55, 0xaa ; 按规定设置字节
2.asmhead.nas(完成一些不能用c语言实现的功能,因为编码问题,有一些乱码,大概能看明白)
; haribote-os boot asm
; TAB=4
[INSTRSET "i486p"]
VBEMODE EQU 0x105 ; 1024 x 768 x 8bit 彩色
; 显示模式
; 0x100 : 640 x 400 x 8bit 彩色
; 0x101 : 640 x 480 x 8bit 彩色
; 0x103 : 800 x 600 x 8bit 彩色
; 0x105 : 1024 x 768 x 8bit 彩色
; 0x107 : 1280 x 1024 x 8bit 彩色
BOTPAK EQU 0x00280000 ; 加载bootpack
DSKCAC EQU 0x00100000 ; 磁盘缓存的位置
DSKCAC0 EQU 0x00008000 ; 磁盘缓存的位置(实模式)
; BOOT_INFO 相关
CYLS EQU 0x0ff0 ; 引导扇区设置
LEDS EQU 0x0ff1
VMODE EQU 0x0ff2 ; 关于颜色的信息
SCRNX EQU 0x0ff4 ; 分辨率X
SCRNY EQU 0x0ff6 ; 分辨率Y
VRAM EQU 0x0ff8 ; 图像缓冲区的起始地址
ORG 0xc200 ; 这个的程序要被装载的内存地址
; 确认VBE是否存在
MOV AX,0x9000
MOV ES,AX
MOV DI,0
MOV AX,0x4f00
INT 0x10
CMP AX,0x004f
JNE scrn320
; 检查VBE的版本
MOV AX,[ES:DI+4]
CMP AX,0x0200
JB scrn320 ; if (AX < 0x0200) goto scrn320
; 取得画面模式信息
MOV CX,VBEMODE
MOV AX,0x4f01
INT 0x10
CMP AX,0x004f
JNE scrn320
; 画面模式信息的确认
CMP BYTE [ES:DI+0x19],8 ;颜色数必须为8
JNE scrn320
CMP BYTE [ES:DI+0x1b],4 ;颜色的指定方法必须为4(4是调色板模式)
JNE scrn320
MOV AX,[ES:DI+0x00] ;模式属性bit7不是1就不能加上0x4000
AND AX,0x0080
JZ scrn320 ; 模式属性的bit7是0,所以放弃
; 画面设置
MOV BX,VBEMODE+0x4000
MOV AX,0x4f02
INT 0x10
MOV BYTE [VMODE],8 ; 屏幕的模式(参考C语言的引用)
MOV AX,[ES:DI+0x12]
MOV [SCRNX],AX
MOV AX,[ES:DI+0x14]
MOV [SCRNY],AX
MOV EAX,[ES:DI+0x28] ;VRAM的地址
MOV [VRAM],EAX
JMP keystatus
scrn320:
MOV AL,0x13 ; VGA图、320x200x8bit彩色
MOV AH,0x00
INT 0x10
MOV BYTE [VMODE],8 ; 记下画面模式(参考C语言)
MOV WORD [SCRNX],320
MOV WORD [SCRNY],200
MOV DWORD [VRAM],0x000a0000
; 通过 BIOS 获取指示灯状态
keystatus:
MOV AH,0x02
INT 0x16 ; keyboard BIOS
MOV [LEDS],AL
; PIC关闭一切中断
; 根据AT兼容机的规格,如果要初始化PIC,
; 必须在CLI之前进行,否则有时会挂起。
; 随后进行PIC的初始化。
MOV AL,0xff
OUT 0x21,AL
NOP ; 如果连续执行OUT指令,有些机种会无法正常运行
OUT 0xa1,AL
CLI ; 禁止CPU级别的中断
; 为了让CPU能够访问1MB以上的内存空间,设定A20GATE
CALL waitkbdout
MOV AL,0xd1
OUT 0x64,AL
CALL waitkbdout
MOV AL,0xdf ; enable A20
OUT 0x60,AL
CALL waitkbdout
; 切换到保护模式
[INSTRSET "i486p"] ; 说明使用486指令
LGDT [GDTR0] ; 设置临时GDT
MOV EAX,CR0
AND EAX,0x7fffffff ; 设bit31为0(禁用分页)
OR EAX,0x00000001 ; bit0到1转换(保护模式过渡)
MOV CR0,EAX
JMP pipelineflush
pipelineflush:
MOV AX,1*8 ; 可读写的段 32bit
MOV DS,AX
MOV ES,AX
MOV FS,AX
MOV GS,AX
MOV SS,AX
; bootpack传递
MOV ESI,bootpack ; 转送源
MOV EDI,BOTPAK ; 转送目标
MOV ECX,512*1024/4
CALL memcpy
; 磁盘数据最终转送到它本来的位置去
; 首先从启动扇区开始
MOV ESI,0x7c00 ; 转送源
MOV EDI,DSKCAC ; 转送目标
MOV ECX,512/4
CALL memcpy
; 剩余的全部
MOV ESI,DSKCAC0+512 ; 转送源
MOV EDI,DSKCAC+512 ; 转送源目标
MOV ECX,0
MOV CL,BYTE [CYLS]
IMUL ECX,512*18*2/4 ; 从柱面数变换为字节数/4
SUB ECX,512/4 ; 减去 IPL 偏移量
CALL memcpy
; 必须由asmhead来完成的工作,至此全部完毕
; 以后就交由bootpack来完成
; bootpack启动
MOV EBX,BOTPAK
MOV ECX,[EBX+16]
ADD ECX,3 ; ECX += 3;
SHR ECX,2 ; ECX /= 4;
JZ skip ; 没有要转送的东西时
MOV ESI,[EBX+20] ; 转送源
ADD ESI,EBX
MOV EDI,[EBX+12] ; 转送目标
CALL memcpy
skip:
MOV ESP,[EBX+12] ; 堆栈的初始化
JMP DWORD 2*8:0x0000001b
waitkbdout:
IN AL,0x64
AND AL,0x02
JNZ waitkbdout ; AND的结果如果不是0,就跳到waitkbdout
RET
memcpy:
MOV EAX,[ESI]
ADD ESI,4
MOV [EDI],EAX
ADD EDI,4
SUB ECX,1
JNZ memcpy ; 减法运算的结果如果不是0,就跳转到memcpy
RET
; memcpy地址前缀大小
ALIGNB 16
GDT0:
RESB 8 ; 初始值
DW 0xffff,0x0000,0x9200,0x00cf ; 可以读写的段(segment)32bit
DW 0xffff,0x0000,0x9a28,0x0047 ; 可执行的文件的32bit寄存器(bootpack用)
DW 0
GDTR0:
DW 8*3-1
DD GDT0
ALIGNB 16
bootpack:
3.bookpack.c(主函数文件,完成初始化等操作)
#include "bootpack.h"
#include <stdio.h>
//该结构体用于控制鼠标
struct MOUSE_DEC {
unsigned char buf[3], phase;
int x, y, btn;
};
extern struct FIFO8 keyfifo, mousefifo; //外部变量,定义在fifo.c文件中
void enable_mouse(struct MOUSE_DEC *mdec); //启动鼠标的函数
void init_keyboard(void); //初始化键盘
int mouse_decode(struct MOUSE_DEC *mdec, unsigned char dat); //处理鼠标信息
void HariMain(void) //主函数
{
struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO; //启动信息数据结构
char s[40], mcursor[256], keybuf[32], mousebuf[128];
int mx, my, i;
struct MOUSE_DEC mdec;
init_gdtidt(); //初始化gdt.idt
init_pic(); //初始化pic
io_sti(); //执行STI指令
fifo8_init(&keyfifo, 32, keybuf); //初始化键盘缓存区
fifo8_init(&mousefifo, 128, mousebuf); //初始化鼠标缓存区
io_out8(PIC0_IMR, 0xf9); //设置中断
io_out8(PIC1_IMR, 0xef); //因为键盘中断是IRQ1,鼠标中断时IRQ12,所以需要打开主从电路上的对应管脚
init_keyboard(); //初始化键盘
init_palette(); //初始化调色板
init_screen8(binfo->vram, binfo->scrnx, binfo->scrny); //初始化屏幕,形成最初的窗口界面
//获取画面中央的坐标
mx = (binfo->scrnx - 16) / 2;
my = (binfo->scrny - 28 - 16) / 2;
init_mouse_cursor8(mcursor, COL8_008484); //鼠标光标的显示
putblock8_8(binfo->vram, binfo->scrnx, 16, 16, mx, my, mcursor, 16);
sprintf(s, "(%3d, %3d)", mx, my); //将鼠标位置转换成字符串
putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_FFFFFF, s); //显示字符串,这个函数的位置在哪里?
enable_mouse(&mdec); //启动鼠标
for (;;) {
io_cli(); //关闭中断
//如果键盘缓冲区和鼠标缓冲区中都没有数据
if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) == 0) {
io_stihlt(); //打开中断并执行hlt命令
} else {
//如果键盘缓存区中有数据
if (fifo8_status(&keyfifo) != 0) {
i = fifo8_get(&keyfifo); //从缓存区中读取数据(FIFO)
sprintf(s, "%02X", i); //将数据已字符串形式输出
boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 16, 15, 31);
putfonts8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s);
} else if (fifo8_status(&mousefifo) != 0) { //如果鼠标缓存区中有函数(鼠标和键盘的数据是怎么存入到对应缓存区的?)
i = fifo8_get(&mousefifo); //从缓存区中读取数据
io_sti(); //打开中断
if (mouse_decode(&mdec, i) != 0) { //对鼠标信息进行处理
sprintf(s, "[lcr %4d %4d]", mdec.x, mdec.y);
if ((mdec.btn & 0x01) != 0) {
s[1] = 'L';
}
if ((mdec.btn & 0x02) != 0) {
s[3] = 'R';
}
if ((mdec.btn & 0x04) != 0) {
s[2] = 'C';
}
boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 32, 16, 32 + 15 * 8 - 1, 31);
putfonts8_asc(binfo->vram, binfo->scrnx, 32, 16, COL8_FFFFFF, s);
boxfill8(binfo->vram, binfo->scrnx, COL8_008484, mx, my, mx + 15, my + 15);
mx += mdec.x;
my += mdec.y;
if (mx < 0) {
mx = 0;
}
if (my < 0) {
my = 0;
}
if (mx > binfo->scrnx - 16) {
mx = binfo->scrnx - 16;
}
if (my > binfo->scrny - 16) {
my = binfo->scrny - 16;
}
sprintf(s, "(%3d, %3d)", mx, my);
boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 0, 79, 15);
putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_FFFFFF, s);
putblock8_8(binfo->vram, binfo->scrnx, 16, 16, mx, my, mcursor, 16);
}
}
}
}
}
#define PORT_KEYDAT 0x0060
#define PORT_KEYSTA 0x0064
#define PORT_KEYCMD 0x0064
#define KEYSTA_SEND_NOTREADY 0x02
#define KEYCMD_WRITE_MODE 0x60
#define KBC_MODE 0x47
//功能:等待键盘控制电路准备完毕
//如果键盘控制电路可以接受CPU指令,CPU从设备号码0x0064处所读取的数据倒数第二位应该是0,否则就是一直循环等待
void wait_KBC_sendready(void)
{
for (;;) {
if ((io_in8(PORT_KEYSTA) & KEYSTA_SEND_NOTREADY) == 0) { //判断第二位的情况
break;
}
}
return;
}
//功能:初始化键盘
void init_keyboard(void)
{
wait_KBC_sendready();
io_out8(PORT_KEYCMD, KEYCMD_WRITE_MODE);
wait_KBC_sendready();
io_out8(PORT_KEYDAT, KBC_MODE);
return;
}
#define KEYCMD_SENDTO_MOUSE 0xd4
#define MOUSECMD_ENABLE 0xf4
//功能:启用鼠标
void enable_mouse(struct MOUSE_DEC *mdec)
{
wait_KBC_sendready();
io_out8(PORT_KEYCMD, KEYCMD_SENDTO_MOUSE);
wait_KBC_sendready();
io_out8(PORT_KEYDAT, MOUSECMD_ENABLE);
mdec->phase = 0;
return;
}
//处理鼠标信息
int mouse_decode(struct MOUSE_DEC *mdec, unsigned char dat)
{
if (mdec->phase == 0) {
if (dat == 0xfa) {
mdec->phase = 1;
}
return 0;
}
if (mdec->phase == 1) {
if ((dat & 0xc8) == 0x08) {
mdec->buf[0] = dat;
mdec->phase = 2;
}
return 0;
}
if (mdec->phase == 2) {
mdec->buf[1] = dat;
mdec->phase = 3;
return 0;
}
if (mdec->phase == 3) {
mdec->buf[2] = dat;
mdec->phase = 1;
mdec->btn = mdec->buf[0] & 0x07;
mdec->x = mdec->buf[1];
mdec->y = mdec->buf[2];
if ((mdec->buf[0] & 0x10) != 0) {
mdec->x |= 0xffffff00;
}
if ((mdec->buf[0] & 0x20) != 0) {
mdec->y |= 0xffffff00;
}
mdec->y = - mdec->y;
return 1;
}
return -1;
}
4.dsctbl.c(gdt和idt设置)
#include "bootpack.h"
//初始化gdt和idt
void init_gdtidt(void)
{
struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR *) ADR_GDT; //提前设置好的GDT在内存中的地址
struct GATE_DESCRIPTOR *idt = (struct GATE_DESCRIPTOR *) ADR_IDT; //提前设置好的IDT在内存中的地址
int i;
for (i = 0; i <= LIMIT_GDT / 8; i++) { //对所有的全局描述符进行初始化
set_segmdesc(gdt + i, 0, 0, 0); //先将所有的全局描述符设置为0
}
set_segmdesc(gdt + 1, 0xffffffff, 0x00000000, AR_DATA32_RW); //设置第1号描述符,段基址为0,大小4gb,可读写32位段
set_segmdesc(gdt + 2, LIMIT_BOTPAK, ADR_BOTPAK, AR_CODE32_ER); //设置第2号描述符,段基址0x00280000,大小0x0007ffff,属性0x4092
load_gdtr(LIMIT_GDT, ADR_GDT); //载入gdtr到cpu中
for (i = 0; i <= LIMIT_IDT / 8; i++) { //对所有的idt描述符进行初始化
set_gatedesc(idt + i, 0, 0, 0); //先将所有的idt描述符设为0
}
load_idtr(LIMIT_IDT, ADR_IDT); //载入idtr到cpu中
set_gatedesc(idt + 0x21, (int) asm_inthandler21, 2 * 8, AR_INTGATE32); //对idt描述符赋值,注意第二个变量,是偏移量
set_gatedesc(idt + 0x27, (int) asm_inthandler27, 2 * 8, AR_INTGATE32);
set_gatedesc(idt + 0x2c, (int) asm_inthandler2c, 2 * 8, AR_INTGATE32);
return;
}
//功能:对段描述符赋值
//参数:段描述符地址,长度、基址、属性值
void set_segmdesc(struct SEGMENT_DESCRIPTOR *sd, unsigned int limit, int base, int ar)
{
//判断段描述大小的计数单位
if (limit > 0xfffff) { //如果段界限超过了限制1MB
ar |= 0x8000; /* G_bit = 1 */ //将G位设置为1,即大小以Kb为单位
limit /= 0x1000; //换算成KB
}
sd->limit_low = limit & 0xffff; //从低16位开始设置,即段界限的低16位
sd->base_low = base & 0xffff; //接着设置16-31的16位数据,即基地址的低16位
sd->base_mid = (base >> 16) & 0xff; //设置32-39的8位数据,即基地址的中间8位,将base右移16位,然后做与运算,取出中间8位
sd->access_right = ar & 0xff; //设置40-47位,即ar的第八位
sd->limit_high = ((limit >> 16) & 0x0f) | ((ar >> 8) & 0xf0); //设置49-56位,limit_high比较特殊,其中四位是段界限,4位是段属性值
sd->base_high = (base >> 24) & 0xff; //设置57-63位,即段基址的高8位
return;
}
//功能:设置门描述符
//参数:门描述符地址,偏移、段选择符,属性值
void set_gatedesc(struct GATE_DESCRIPTOR *gd, int offset, int selector, int ar)
{
gd->offset_low = offset & 0xffff; //设置0-15位,偏移地址的低16位
gd->selector = selector; //设置16-31位,制度段选择子
gd->dw_count = (ar >> 8) & 0xff; //设置32-39位,基本上全是0
gd->access_right = ar & 0xff; //设置40-47位,门描述符属性
gd->offset_high = (offset >> 16) & 0xffff; //设置48-63Wie,偏移地址的高16位
return;
}
5.graphic.c
//用于处理屏幕显示
#include "bootpack.h"
//初始化调色板
void init_palette(void)
{
static unsigned char table_rgb[16 * 3] = { //设置调色板变量,这里3个字符一组,组成了一个颜色,颜色应该是计算机中已经设定好的
0x00, 0x00, 0x00,
0xff, 0x00, 0x00,
0x00, 0xff, 0x00,
0xff, 0xff, 0x00,
0x00, 0x00, 0xff,
0xff, 0x00, 0xff,
0x00, 0xff, 0xff,
0xff, 0xff, 0xff,
0xc6, 0xc6, 0xc6,
0x84, 0x00, 0x00,
0x00, 0x84, 0x00,
0x84, 0x84, 0x00,
0x00, 0x00, 0x84,
0x84, 0x00, 0x84,
0x00, 0x84, 0x84,
0x84, 0x84, 0x84
};
set_palette(0, 15, table_rgb); //设置调色板
return;
}
//功能:设置调色板,将颜色和编号对上
void set_palette(int start, int end, unsigned char *rgb)
{
int i, eflags;
eflags = io_load_eflags(); //汇编语言函数
io_cli(); //关闭中断
io_out8(0x03c8, start); //写入端口
for (i = start; i <= end; i++) { //每三个一组合成一个rgb颜色
io_out8(0x03c9, rgb[0] / 4);
io_out8(0x03c9, rgb[1] / 4);
io_out8(0x03c9, rgb[2] / 4);
rgb += 3;
}
io_store_eflags(eflags); //汇编语言函数
return;
}
//功能:画一个窗口
//其中xsize表示窗口宽度,理论上应该等于x1-x0
void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1)
{
int x, y;
for (y = y0; y <= y1; y++) {
for (x = x0; x <= x1; x++)
vram[y * xsize + x] = c; //显示出字符
}
return;
}
//初始化屏幕
void init_screen8(char *vram, int x, int y)
{
boxfill8(vram, x, COL8_008484, 0, 0, x - 1, y - 29);
boxfill8(vram, x, COL8_C6C6C6, 0, y - 28, x - 1, y - 28);
boxfill8(vram, x, COL8_FFFFFF, 0, y - 27, x - 1, y - 27);
boxfill8(vram, x, COL8_C6C6C6, 0, y - 26, x - 1, y - 1);
boxfill8(vram, x, COL8_FFFFFF, 3, y - 24, 59, y - 24);
boxfill8(vram, x, COL8_FFFFFF, 2, y - 24, 2, y - 4);
boxfill8(vram, x, COL8_848484, 3, y - 4, 59, y - 4);
boxfill8(vram, x, COL8_848484, 59, y - 23, 59, y - 5);
boxfill8(vram, x, COL8_000000, 2, y - 3, 59, y - 3);
boxfill8(vram, x, COL8_000000, 60, y - 24, 60, y - 3);
boxfill8(vram, x, COL8_848484, x - 47, y - 24, x - 4, y - 24);
boxfill8(vram, x, COL8_848484, x - 47, y - 23, x - 47, y - 4);
boxfill8(vram, x, COL8_FFFFFF, x - 47, y - 3, x - 4, y - 3);
boxfill8(vram, x, COL8_FFFFFF, x - 3, y - 24, x - 3, y - 3);
return;
}
//显示字体
void putfont8(char *vram, int xsize, int x, int y, char c, char *font)
{
int i;
char *p, d /* data */;
for (i = 0; i < 16; i++) {
p = vram + (y + i) * xsize + x; //显示版面的一行,此处应明白屏幕显示原理
d = font[i]; //显示字体,按位显示
if ((d & 0x80) != 0) { p[0] = c; }
if ((d & 0x40) != 0) { p[1] = c; }
if ((d & 0x20) != 0) { p[2] = c; }
if ((d & 0x10) != 0) { p[3] = c; }
if ((d & 0x08) != 0) { p[4] = c; }
if ((d & 0x04) != 0) { p[5] = c; }
if ((d & 0x02) != 0) { p[6] = c; }
if ((d & 0x01) != 0) { p[7] = c; }
}
return;
}
void putfonts8_asc(char *vram, int xsize, int x, int y, char c, unsigned char *s)
{
extern char hankaku[4096];
for (; *s != 0x00; s++) {
putfont8(vram, xsize, x, y, c, hankaku + *s * 16);
x += 8;
}
return;
}
void init_mouse_cursor8(char *mouse, char bc)
{
static char cursor[16][16] = {
"**************..",
"*OOOOOOOOOOO*...",
"*OOOOOOOOOO*....",
"*OOOOOOOOO*.....",
"*OOOOOOOO*......",
"*OOOOOOO*.......",
"*OOOOOOO*.......",
"*OOOOOOOO*......",
"*OOOO**OOO*.....",
"*OOO*..*OOO*....",
"*OO*....*OOO*...",
"*O*......*OOO*..",
"**........*OOO*.",
"*..........*OOO*",
"............*OO*",
".............***"
};
int x, y;
for (y = 0; y < 16; y++) {
for (x = 0; x < 16; x++) {
if (cursor[y][x] == '*') {
mouse[y * 16 + x] = COL8_000000; //显示鼠标
}
if (cursor[y][x] == 'O') {
mouse[y * 16 + x] = COL8_FFFFFF;
}
if (cursor[y][x] == '.') {
mouse[y * 16 + x] = bc;
}
}
}
return;
}
//功能:显示背景
//vram和vxsize是关于vram的信息
//pxsize,pysize是想要显示的图形大小
//px0、py0制定图像在画面上的显示位置
//buf指定图形存放的地址
//bxsize指定每一行含有的像素数
void putblock8_8(char *vram, int vxsize, int pxsize,
int pysize, int px0, int py0, char *buf, int bxsize)
{
int x, y;
for (y = 0; y < pysize; y++) {
for (x = 0; x < pxsize; x++) {
vram[(py0 + y) * vxsize + (px0 + x)] = buf[y * bxsize + x];
}
}
return;
}
6.fifo.c
#include "bootpack.h"
#define FLAGS_OVERRUN 0x0001
//功能:初始化FIFO缓存区
//参数:缓存区结构体,大小,缓存区地址
void fifo8_init(struct FIFO8 *fifo, int size, unsigned char *buf)
{
fifo->size = size;
fifo->buf = buf;
fifo->free = size;
fifo->flags = 0;
fifo->p = 0;
fifo->q = 0;
return;
}
//功能:向缓存区写入数据
int fifo8_put(struct FIFO8 *fifo, unsigned char data)
{
if (fifo->free == 0) { //如果缓存区大小等于零,代表缓存区已经被写满
fifo->flags |= FLAGS_OVERRUN; //将覆盖标志置1
return -1; //返回一个错误值
}
fifo->buf[fifo->p] = data; //读取当前缓存区的第一个数据
fifo->p++; //将读取指针后移
if (fifo->p == fifo->size) { //如果已经读完缓存区
fifo->p = 0; //将读取指针指向第一个位置
}
fifo->free--; //缓存区可用位置减1
return 0;
}
//功能:从缓存区读取数据
int fifo8_get(struct FIFO8 *fifo)
{
int data;
if (fifo->free == fifo->size) { //如果缓存区是空的,返回错误
return -1;
}
data = fifo->buf[fifo->q]; //读取数据
fifo->q++; //读取指针后移
if (fifo->q == fifo->size) { //读到最后一个将读取指针指向第一个位置
fifo->q = 0;
}
fifo->free++; //可用区域加1 缓存区是循环写入的
return data;
}
int fifo8_status(struct FIFO8 *fifo) //判断缓存区的状态
{
return fifo->size - fifo->free;
}
7. int.c
#include "bootpack.h"
#include <stdio.h>
//功能:中断初始化函数,初始化pic
void init_pic(void)
{
io_out8(PIC0_IMR, 0xff ); //主片禁止所有中断
io_out8(PIC1_IMR, 0xff ); //从片禁止所有中断
//设置pic0,主片
io_out8(PIC0_ICW1, 0x11 ); //边沿触发模式
io_out8(PIC0_ICW2, 0x20 ); //IRQ0-7由INT20-27接收
io_out8(PIC0_ICW3, 1 << 2); //PIC1由IRQ2接收
io_out8(PIC0_ICW4, 0x01 ); //无缓冲区模式
//设置pic1,从片
io_out8(PIC1_ICW1, 0x11 ); //边沿触发模式
io_out8(PIC1_ICW2, 0x28 ); //IRQ8-15由INT28-2f接收
io_out8(PIC1_ICW3, 2 ); //PIC1由IRQ2连接
io_out8(PIC1_ICW4, 0x01 ); //无缓冲区模式
io_out8(PIC0_IMR, 0xfb ); //11111011,PIC1以外的全部禁止
io_out8(PIC1_IMR, 0xff ); //11111111,禁止PIC1的所有中断
return;
}
#define PORT_KEYDAT 0x0060
struct FIFO8 keyfifo;
//键盘中断处理,键盘是IRQ1,所以编写INT 0x21
//这里已经有了C语言编写的函数,为什么还要添加汇编语言的函数?
//是在汇编语言中调用该函数
void inthandler21(int *esp)
{
unsigned char data;
io_out8(PIC0_OCW2, 0x61);
data = io_in8(PORT_KEYDAT); //读取数据
fifo8_put(&keyfifo, data); //将数据写入缓存区
return;
}
struct FIFO8 mousefifo;
//功能:鼠标中断处理,编写INT 0x2c
void inthandler2c(int *esp)
{
unsigned char data;
io_out8(PIC1_OCW2, 0x64);
io_out8(PIC0_OCW2, 0x62);
data = io_in8(PORT_KEYDAT); //读取数据
fifo8_put(&mousefifo, data); //将数据写入缓存区
return;
}
void inthandler27(int *esp) */
{
io_out8(PIC0_OCW2, 0x67);
return;
}
8. naskfunc.nas(汇编和c语言文件之间的桥梁)
; naskfunc
; TAB=4
[FORMAT "WCOFF"] ;
[INSTRSET "i486p"] ;
[BITS 32] ;
[FILE "naskfunc.nas"] ;
;定义外部符号,可以从文件外进行调用
GLOBAL _io_hlt, _io_cli, _io_sti, _io_stihlt
GLOBAL _io_in8, _io_in16, _io_in32
GLOBAL _io_out8, _io_out16, _io_out32
GLOBAL _io_load_eflags, _io_store_eflags
GLOBAL _load_gdtr, _load_idtr
GLOBAL _asm_inthandler21, _asm_inthandler27, _asm_inthandler2c
EXTERN _inthandler21, _inthandler27, _inthandler2c
[SECTION .text]
_io_hlt: ; void io_hlt(void);
HLT
RET
_io_cli: ; void io_cli(void);
CLI
RET
_io_sti: ; void io_sti(void);
STI
RET
_io_stihlt: ; void io_stihlt(void);
STI
HLT
RET
_io_in8: ; int io_in8(int port); 从指定端口中读取数据
MOV EDX,[ESP+4] ; port,获取端口号
MOV EAX,0 ;清空ax寄存器
IN AL,DX ;从DX指定的端口中读取数据到al
RET
_io_in16: ; int io_in16(int port);
MOV EDX,[ESP+4] ; port
MOV EAX,0
IN AX,DX
RET
_io_in32: ; int io_in32(int port);
MOV EDX,[ESP+4] ; port
IN EAX,DX
RET
_io_out8: ; void io_out8(int port, int data); 向指定端口写入数据
MOV EDX,[ESP+4] ; port 获取端口号
MOV AL,[ESP+8] ; data 获取要写入的数据
OUT DX,AL ;将al中的数据写入到dx指定的端口中
RET
_io_out16: ; void io_out16(int port, int data);
MOV EDX,[ESP+4] ; port
MOV EAX,[ESP+8] ; data
OUT DX,AX
RET
_io_out32: ; void io_out32(int port, int data);
MOV EDX,[ESP+4] ; port
MOV EAX,[ESP+8] ; data
OUT DX,EAX
RET
_io_load_eflags: ; int io_load_eflags(void);
PUSHFD ; 将eflags寄存器压入栈中,入栈顺序是EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI
POP EAX ; 将edi的值弹出到eax中
RET
_io_store_eflags: ; void io_store_eflags(int eflags);
MOV EAX,[ESP+4]
PUSH EAX
POPFD ; 将栈中的寄存器值弹出到eflags寄存器中
RET
_load_gdtr: ; void load_gdtr(int limit, int addr);加载GDTR
MOV AX,[ESP+4] ; limit
MOV [ESP+6],AX
LGDT [ESP+6]
RET
_load_idtr: ; void load_idtr(int limit, int addr); 记载IDTR,原理与加载GDTR相同
MOV AX,[ESP+4] ; limit
MOV [ESP+6],AX
LIDT [ESP+6]
RET
;这个函数只是将寄存器的值保存在栈里,然后将DS和ES调整到与SS相等,再调用_inthandler21,返回后将所有寄存器的值再返回到原来的值,然后执行IRETD
;之所以如此小心翼翼地保护寄存器,原因在于,中断处理发生在函数处理途中,通过IREDT从中断处理后,寄存器就乱了
_asm_inthandler21:
PUSH ES
PUSH DS
PUSHAD ;PUSHAD指令压入32位寄存器,其入栈顺序是:EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI .
MOV EAX,ESP ;eax=esp
PUSH EAX 压入eax的值,即esp
MOV AX,SS
MOV DS,AX ;ds=ss
MOV ES,AX ;es=ss
CALL _inthandler21 ;调用inthandler21函数,c语言编写
POP EAX ;弹出esp的值到eax中
POPAD
POP DS
POP ES
IRETD
_asm_inthandler27:
PUSH ES
PUSH DS
PUSHAD
MOV EAX,ESP
PUSH EAX
MOV AX,SS
MOV DS,AX
MOV ES,AX
CALL _inthandler27
POP EAX
POPAD
POP DS
POP ES
IRETD
_asm_inthandler2c:
PUSH ES
PUSH DS
PUSHAD
MOV EAX,ESP
PUSH EAX
MOV AX,SS
MOV DS,AX
MOV ES,AX
CALL _inthandler2c
POP EAX
POPAD
POP DS
POP ES
IRETD
标题三、其它
根据书中的描述,整个项目的编译过程如图
最后,asmhead文件和bookpack文件会编译到一起,bookpack的内容就是从bootpack标号开始?
关于中断处理程序的调用
如果要让一个中断处理程序发挥作用,首先要将其注册到idt中,书中使用了函数set_gatedesc(idt+0x21,(int)asm_inthandler21,2*8,AR_INTGATE32)2
即将_asm_inthandler21注册为idt的第21号,如果发生中断了,cpu就会自动调用asm_inthandler21
2表示asm_inthandler属于那一个段,因低3位必须是0,所以写成2*8
(ps:一定要读源码)
————————————————
版权声明:本文为CSDN博主「qq_35041101」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_35041101/article/details/51866877