先体验一下编译仿真方法:
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 entryDB 0x90DB "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=0MOV SP,0x7c00 ; SP=0x7c00MOV 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=ESADD AX,0x0020 ; AX=AX+0x0020MOV ES,AX ; ES=AX ES向后移动了一个扇区的大小ADD CL,1 ; CL=CL+1 扇区号加1CMP 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.sysMOV [0x0ff0],CH ; [0x0ff0]=CH 将CYLS的值写入到内存地址0x0ff0中,可以参考asmhead.nas中对应的变量赋值JMP 0xc200 ; 跳转到0xc200error:MOV SI,msg ;SI=msg 显示错误信息
putloop:MOV AL,[SI] ; AL=[SI] 读取[SI]内存中的信息ADD SI,1 ; SI=SI+1CMP AL,0 ; JE fin ; AL==0, 错误信息显示完毕,跳转到finMOV 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,0x9000MOV ES,AXMOV DI,0MOV AX,0x4f00INT 0x10CMP AX,0x004fJNE scrn320; 检查VBE的版本MOV AX,[ES:DI+4]CMP AX,0x0200JB scrn320 ; if (AX < 0x0200) goto scrn320; 取得画面模式信息MOV CX,VBEMODEMOV AX,0x4f01INT 0x10CMP AX,0x004fJNE scrn320; 画面模式信息的确认CMP BYTE [ES:DI+0x19],8 ;颜色数必须为8JNE scrn320CMP BYTE [ES:DI+0x1b],4 ;颜色的指定方法必须为4(4是调色板模式)JNE scrn320MOV AX,[ES:DI+0x00] ;模式属性bit7不是1就不能加上0x4000AND AX,0x0080JZ scrn320 ; 模式属性的bit7是0,所以放弃; 画面设置MOV BX,VBEMODE+0x4000MOV AX,0x4f02INT 0x10MOV BYTE [VMODE],8 ; 屏幕的模式(参考C语言的引用)MOV AX,[ES:DI+0x12]MOV [SCRNX],AXMOV AX,[ES:DI+0x14]MOV [SCRNY],AXMOV EAX,[ES:DI+0x28] ;VRAM的地址MOV [VRAM],EAXJMP keystatusscrn320:MOV AL,0x13 ; VGA图、320x200x8bit彩色MOV AH,0x00INT 0x10MOV BYTE [VMODE],8 ; 记下画面模式(参考C语言)MOV WORD [SCRNX],320MOV WORD [SCRNY],200MOV DWORD [VRAM],0x000a0000; 通过 BIOS 获取指示灯状态keystatus:MOV AH,0x02INT 0x16 ; keyboard BIOSMOV [LEDS],AL; PIC关闭一切中断
; 根据AT兼容机的规格,如果要初始化PIC,
; 必须在CLI之前进行,否则有时会挂起。
; 随后进行PIC的初始化。MOV AL,0xffOUT 0x21,ALNOP ; 如果连续执行OUT指令,有些机种会无法正常运行OUT 0xa1,ALCLI ; 禁止CPU级别的中断; 为了让CPU能够访问1MB以上的内存空间,设定A20GATECALL waitkbdoutMOV AL,0xd1OUT 0x64,ALCALL waitkbdoutMOV AL,0xdf ; enable A20OUT 0x60,ALCALL waitkbdout; 切换到保护模式[INSTRSET "i486p"] ; 说明使用486指令LGDT [GDTR0] ; 设置临时GDTMOV EAX,CR0AND EAX,0x7fffffff ; 设bit31为0(禁用分页)OR EAX,0x00000001 ; bit0到1转换(保护模式过渡)MOV CR0,EAXJMP pipelineflush
pipelineflush:MOV AX,1*8 ; 可读写的段 32bitMOV DS,AXMOV ES,AXMOV FS,AXMOV GS,AXMOV SS,AX; bootpack传递MOV ESI,bootpack ; 转送源MOV EDI,BOTPAK ; 转送目标MOV ECX,512*1024/4CALL memcpy; 磁盘数据最终转送到它本来的位置去
; 首先从启动扇区开始MOV ESI,0x7c00 ; 转送源MOV EDI,DSKCAC ; 转送目标MOV ECX,512/4CALL memcpy; 剩余的全部MOV ESI,DSKCAC0+512 ; 转送源MOV EDI,DSKCAC+512 ; 转送源目标MOV ECX,0MOV CL,BYTE [CYLS]IMUL ECX,512*18*2/4 ; 从柱面数变换为字节数/4SUB ECX,512/4 ; 减去 IPL 偏移量CALL memcpy; 必须由asmhead来完成的工作,至此全部完毕
; 以后就交由bootpack来完成; bootpack启动MOV EBX,BOTPAKMOV ECX,[EBX+16]ADD ECX,3 ; ECX += 3;SHR ECX,2 ; ECX /= 4;JZ skip ; 没有要转送的东西时MOV ESI,[EBX+20] ; 转送源ADD ESI,EBXMOV EDI,[EBX+12] ; 转送目标CALL memcpy
skip:MOV ESP,[EBX+12] ; 堆栈的初始化JMP DWORD 2*8:0x0000001bwaitkbdout:IN AL,0x64AND AL,0x02JNZ waitkbdout ; AND的结果如果不是0,就跳到waitkbdoutRETmemcpy:MOV EAX,[ESI]ADD ESI,4MOV [EDI],EAXADD EDI,4SUB ECX,1JNZ memcpy ; 减法运算的结果如果不是0,就跳转到memcpyRET
; memcpy地址前缀大小ALIGNB 16
GDT0:RESB 8 ; 初始值DW 0xffff,0x0000,0x9200,0x00cf ; 可以读写的段(segment)32bitDW 0xffff,0x0000,0x9a28,0x0047 ; 可执行的文件的32bit寄存器(bootpack用)DW 0
GDTR0:DW 8*3-1DD GDT0ALIGNB 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.idtinit_pic(); //初始化picio_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,属性0x4092load_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) { //如果段界限超过了限制1MBar |= 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位,基本上全是0gd->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; //将覆盖标志置1return -1; //返回一个错误值}fifo->buf[fifo->p] = data; //读取当前缓存区的第一个数据fifo->p++; //将读取指针后移if (fifo->p == fifo->size) { //如果已经读完缓存区fifo->p = 0; //将读取指针指向第一个位置}fifo->free--; //缓存区可用位置减1return 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 0x0060struct 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_stihltGLOBAL _io_in8, _io_in16, _io_in32GLOBAL _io_out8, _io_out16, _io_out32GLOBAL _io_load_eflags, _io_store_eflagsGLOBAL _load_gdtr, _load_idtrGLOBAL _asm_inthandler21, _asm_inthandler27, _asm_inthandler2cEXTERN _inthandler21, _inthandler27, _inthandler2c[SECTION .text]_io_hlt: ; void io_hlt(void);HLTRET_io_cli: ; void io_cli(void);CLIRET_io_sti: ; void io_sti(void);STIRET_io_stihlt: ; void io_stihlt(void);STIHLTRET_io_in8: ; int io_in8(int port); 从指定端口中读取数据MOV EDX,[ESP+4] ; port,获取端口号MOV EAX,0 ;清空ax寄存器IN AL,DX ;从DX指定的端口中读取数据到alRET_io_in16: ; int io_in16(int port);MOV EDX,[ESP+4] ; portMOV EAX,0IN AX,DXRET_io_in32: ; int io_in32(int port);MOV EDX,[ESP+4] ; portIN EAX,DXRET_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] ; portMOV EAX,[ESP+8] ; dataOUT DX,AXRET_io_out32: ; void io_out32(int port, int data);MOV EDX,[ESP+4] ; portMOV EAX,[ESP+8] ; dataOUT DX,EAXRET_io_load_eflags: ; int io_load_eflags(void);PUSHFD ; 将eflags寄存器压入栈中,入栈顺序是EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDIPOP EAX ; 将edi的值弹出到eax中RET_io_store_eflags: ; void io_store_eflags(int eflags);MOV EAX,[ESP+4]PUSH EAXPOPFD ; 将栈中的寄存器值弹出到eflags寄存器中RET_load_gdtr: ; void load_gdtr(int limit, int addr);加载GDTR MOV AX,[ESP+4] ; limitMOV [ESP+6],AXLGDT [ESP+6]RET_load_idtr: ; void load_idtr(int limit, int addr); 记载IDTR,原理与加载GDTR相同MOV AX,[ESP+4] ; limitMOV [ESP+6],AXLIDT [ESP+6]RET;这个函数只是将寄存器的值保存在栈里,然后将DS和ES调整到与SS相等,再调用_inthandler21,返回后将所有寄存器的值再返回到原来的值,然后执行IRETD
;之所以如此小心翼翼地保护寄存器,原因在于,中断处理发生在函数处理途中,通过IREDT从中断处理后,寄存器就乱了
_asm_inthandler21:PUSH ESPUSH DSPUSHAD ;PUSHAD指令压入32位寄存器,其入栈顺序是:EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI .MOV EAX,ESP ;eax=espPUSH EAX 压入eax的值,即espMOV AX,SS MOV DS,AX ;ds=ssMOV ES,AX ;es=ssCALL _inthandler21 ;调用inthandler21函数,c语言编写POP EAX ;弹出esp的值到eax中POPADPOP DS POP ESIRETD_asm_inthandler27:PUSH ESPUSH DSPUSHADMOV EAX,ESPPUSH EAXMOV AX,SSMOV DS,AXMOV ES,AXCALL _inthandler27POP EAXPOPADPOP DSPOP ESIRETD_asm_inthandler2c:PUSH ESPUSH DSPUSHADMOV EAX,ESPPUSH EAXMOV AX,SSMOV DS,AXMOV ES,AXCALL _inthandler2cPOP EAXPOPADPOP DSPOP ESIRETD
标题三、其它
根据书中的描述,整个项目的编译过程如图
最后,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