《30天自制操作系统》学习笔记(七)

先体验一下编译仿真方法:

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/332416.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Docker五部曲之五:通过Docker和GitHub Action搭建个人CICD项目

文章目录 项目介绍Dockerfile解析compose.yml解析MySQL的准备工作Spring和环境变量的交互 GitHub Action解析项目测试结语 项目介绍 该项目是一个入门CICD-Demo&#xff0c;它由以下几部分组成&#xff1a; Dockerfile&#xff1a;用于构建自定义镜像compose.yml&#xff1a;…

开源免费的可私有化部署的白板excalidraw 详细部署教程

简介 excalidraw 是一款开源免费的虚拟白板&#xff0c;提供一个在线的实时协作白板工具&#xff0c;使用户能够创建简单的图形和图示。 excalidraw 的设计目标是提供一个易于使用的绘图工具&#xff0c;支持团队协作&#xff0c;同时具有跨平台和实时协作的功能。 简单易用&…

DAY04_Spring—Aop案例引入代理机制

目录 1 AOP1.1 AOP案例引入1.1.1 数据库事务说明 1.2 Spring实现事务控制1.2.1 代码结构如下1.2.2 编辑User1.2.3 编辑UserMapper/UserMapperImpl1.2.4 编辑UserService/UserServiceImpl1.2.5 编辑配置类1.2.6 编辑测试类 1.3 代码问题分析1.4 代理模式1.4.1 生活中代理案例1.4…

Gitlab添加ssh-key报500错误处理

Gitlab添加ssh-key报500错误 一、查看日志 发现Errno::Enoent(No such file or derectory -ssh): rootasu1:/home/caixin# tail -f /var/log/gitlab/gitlab-rails/production.log二、分析 根据日志提示&#xff0c;好像是缺少文件或目录&#xff0c;后面有个ssh,难首是依赖s…

【python】—— 字典

目录 &#xff08;一&#xff09;什么是字典 &#xff08;二&#xff09;字典的基本操作 2.1 创建字典 2.2 查找 key 2.3 新增/修改元素 2.4 删除元素 2.5 遍历字典元素 2.6 取出所有 key 和 value &#xff08;三&#xff09;合法的 key 类型 &#xff08;四&#xff09…

VUE组件--动态组件、组件保持存活、异步组件

动态组件 有些场景可能会需要在多个组件之间进行来回切换&#xff0c;在vue中则使用<component :is"..."> 来实现组件间的来回切换 // App.vue <template><component :is"tabComponent"></component><button click"change…

登陆提示:不支持你所在的地区,“Openai’s services are not available in your country…”

错误 登陆时提示“openai’s services are not available in your country”&#xff0c; 说明&#xff1a;Openai的服务在你的地区不可用解决&#xff1a;先清理下浏览器缓存&#xff0c;然后更换代理节点&#xff0c;开启全局模式&#xff0c;最好用欧美节点&#xff0c;或…

hyperf 二十一 数据库 模型关系

教程&#xff1a;Hyperf 一 定义关联 根据文档 一对一&#xff1a;Model::hasOne(被关联模型&#xff0c;被关联模型外键&#xff0c;本模型被关联的字段)一对多&#xff1a;Model::hasMany(被关联模型&#xff0c;被关联模型外键&#xff0c;本模型被关联的字段)反向一对多…

Docker 安装 PHP

Docker 安装 PHP 安装 PHP 镜像 方法一、docker pull php 查找 Docker Hub 上的 php 镜像: 可以通过 Sort by 查看其他版本的 php&#xff0c;默认是最新版本 php:latest。 此外&#xff0c;我们还可以用 docker search php 命令来查看可用版本&#xff1a; runoobrunoob:…

【51单片机】数码管的静态与动态显示(含消影)

数码管在现实生活里是非常常见的设备&#xff0c;例如 这些数字的显示都是数码管的应用。 目录 静态数码管&#xff1a;器件介绍&#xff1a;数码管的使用&#xff1a;译码器的使用&#xff1a;缓冲器&#xff1a; 实现原理&#xff1a;完整代码&#xff1a; 动态数码管&#…

python222网站实战(SpringBoot+SpringSecurity+MybatisPlus+thymeleaf+layui)-热门帖子推荐显示实现

锋哥原创的SpringbootLayui python222网站实战&#xff1a; python222网站实战课程视频教程&#xff08;SpringBootPython爬虫实战&#xff09; ( 火爆连载更新中... )_哔哩哔哩_bilibilipython222网站实战课程视频教程&#xff08;SpringBootPython爬虫实战&#xff09; ( 火…

WAF攻防相关知识点总结1--信息收集中的WAF触发及解决方案

什么是WAF WAF可以通过对Web应用程序的流量进行过滤和监控&#xff0c;识别并阻止潜在的安全威胁。WAF可以检测Web应用程序中的各种攻击&#xff0c;例如SQL注入、跨站点脚本攻击&#xff08;XSS&#xff09;、跨站请求伪造&#xff08;CSRF&#xff09;等&#xff0c;并采取相…

web前端项目-中国象棋【附源码】

中国象棋 【中国象棋】是一款历史悠久、深受人们喜爱的策略类游戏。在Web前端技术中&#xff0c;我们可以使用HTML、CSS和JavaScript等语言来制作一款中国象棋游戏。玩家使用棋子&#xff08;帅/相/士/炮/马/车/炮/卒&#xff09;在棋盘上相互对弈&#xff0c;将对手的“帅”棋…

python入门,函数的进阶

1.函数的多返回值 加上逗号&#xff0c;一个函数每次就能返回多个值 2.函数的多种参数使用形式 1.位置参数 调用函数时根据参数位置来传递参数 就是我们平时写函数时所使用的形式 注意&#xff1a; 传递的参数和定义的参数的顺序以及个数必须一致 2.关键字参数 通过键值…

【汉诺塔】经典递归问题(Java实现)图文并茂讲解

&#x1f4da;博客主页&#xff1a;爱敲代码的小杨. ✨专栏&#xff1a;《Java SE语法》 ❤️感谢大家点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb;&#xff0c;您的三连就是我持续更新的动力❤️ &#x1f64f;小杨水平有限&#xff0c;欢迎各位大佬指点&#…

Mysql 数据库DML 数据操作语言—— 对数据库表中的数据进行更改UPDATE 和删除DELETE

更改数据UPDATE UPDATE 表名&#xff08;注意这里不加TABLE&#xff09; SET 字段名1值1&#xff0c; 字段名2值2&#xff0c;...[where 条件] 示例1&#xff1a;只修改一个字段 示例二&#xff0c;修改name age字段 ; update tt4 set name 丹,age18 where id5;示例三、将所…

k8s---ingress对外服务(traefik)

目录 ingress的证书访问 traefik traefik的部署方式&#xff1a; deamonset deployment nginx-ingress与traefix-ingress相比较 nginx-ingress-controller ui访问 deployment部署 ingress的证书访问 ingress实现https代理访问: 需要证书和密钥 创建证书 密钥 secre…

Android WorkManager入门(二)

WorkManager入门 上一篇前言创建 WorkRequest并提交 定时的任务&#xff08;PeriodicWorkRequest&#xff09;配合约束使用定义执行范围失败后的重试为WorkRequest打上TAG其他取消方法 传参和返回参数总结参考资料 上一篇 Android WorkManager入门&#xff08;一&#xff09; …

【图解数据结构】深度解析时间复杂度与空间复杂度的典型问题

&#x1f308;个人主页&#xff1a;聆风吟 &#x1f525;系列专栏&#xff1a;图解数据结构、算法模板 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 一. ⛳️上期回顾二. ⛳️常见时间复杂度计算举例1️⃣实例一2️⃣实例二3️⃣实例三4️⃣实例四5…

基于R语言的NDVI的Sen-MK趋势检验

本实验拟分析艾比湖地区2010年至2020年间的NDVI数据&#xff0c;数据从MODIS遥感影像中提取的NDVI值&#xff0c;在GEE遥感云平台上将影像数据下载下来。代码如下&#xff1a; import ee import geemap geemap.set_proxy(port7890)# 设置全局网络代理 Map geemap.Map()# 指定…