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

一、代码执行顺序(前内容六天的内容)

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

BOTPAK	EQU		0x00280000		; BOTPAK=0X00280000 ?置?量BOTPAK的?
DSKCAC	EQU		0x00100000		; DSKCAC=0x00100000 ?置?量DSKCAC的?
DSKCAC0	EQU		0x00008000		; DSKCAC0=0x00008000 ?置?量DSKCAC0的?

; BOOT_INFO信息
CYLS	EQU		0x0ff0			; CYLS=0x0ff0 ?置?量CYLS的?,??的地址0x0ff0,存?的是?取的柱面数
LEDS	EQU		0x0ff1			; LEDS=0x0ff1 ?置?量LEDS的?
VMODE	EQU		0x0ff2			; VMODE=0x0ff2 ?置?量VMODE的?,?于?色数目的信息,?色的位数
SCRNX	EQU		0x0ff4			; SCRNX=0x0ff4 ?置?量SCRNX的?,分辨率的X	
SCRNY	EQU		0x0ff6			; SCRNY=0x0ff6 ?置?量SCRNY的?,分辨率的Y
VRAM	EQU		0x0ff8			; VRAM=0x0ff8 ?置?量VRAM的?,?像?冲区的起始地址

		ORG		0xc200			; ?个程序将要被装?到的位置
								; 根据?保存文件的特点,文件名会写在0x002600以后的地方,文件内容会写在0x004200以后的地方
								; ?在的程序从??区?始,把磁?内容装到0x8000号地址,所以磁?0x4200的内容?位于内存0x8000+0x4200=0xc200号地址
								; 注意,ipl10.nas中?取?存区从0x8200号地址?始,但是因?是从2号扇区?始,在2号扇区之前?有一个扇区,所以?算磁?的起始地址是0x8000

; ?定画面

		MOV		AL,0x13			; AL=0x13 ?置?示器模式,13H:640×480 256色
		MOV		AH,0x00			; AH=0x00 ?置?示模式
		INT		0x10			; ?用BIOS?示服?
		MOV		BYTE [VMODE],8	; [VMODE]=8, 在0x0ff2地址的位置写入8(占用8位内存),作用是?置画面模式
		MOV		WORD [SCRNX],320	; [SCRNX]=320, 在0x0ff4地址的位置写入320(占用16位内存),作用是?置分辨率X
		MOV		WORD [SCRNY],200	; [SCRNY]=200, 在0x0ff6地址的位置写入200(占用16位内存),作用是?置分辨率Y
		MOV		DWORD [VRAM],0x000a0000	; [VRAM]=0x000a0000, 在0x0ff8地址的位置写入0x000a0000(占用32位内存),作用是存??像?存区的?始地址

; 用BIOS取得??上各个LED指示灯的状太

		MOV		AH,0x02			; AH=0x02 INT 0x16服?的参数,表示?取??状???
		INT		0x16 			; keyboard BIOS
		MOV		[LEDS],AL		; [LEDS]=AL, 在0x0ff1的位置写入AL寄存器(占用8位内从),AL存?16号中断的返回?,就是??的状?信息
		
; PIC中止一切中断
;	根据AT兼容机的?格,如果要初始化PIC,
;	必?在CLI之前?行,否?有?会挂起
;	随后才能?行PIC初始化

; out 21h,al
; OUT 是?出指令,21h是端口地址;
; AL是8位寄存器,作?目的操作数;
; 作用:将源操作数代表的端口的内容送?目的操作数,所以整条指令是将21h端口的内容送?AL寄存器。 
		MOV		AL,0xff			; AL=0xff
		OUT		0x21,AL			; 将AL的?写入到21号端口(?里?什?是21号端口),禁止主PIC的全部中断
		NOP						; 	
		OUT		0xa1,AL			; 将AL的?写入到a1号端口(?里?什?是a1号端口),禁止从PIC的全部中断

		CLI						; 禁止CPU??的中断

; ?定A20地址?

		CALL	waitkbdout		; ?用waitkbdout
		MOV		AL,0xd1			; AL=0xd1
		OUT		0x64,AL			; 将AL的内容写入到64号端口
		CALL	waitkbdout
		MOV		AL,0xdf			; AL=0xdf
		OUT		0x60,AL			; 将AL的内容写入到60号端口
		CALL	waitkbdout

; ?始切?到保?模式

[INSTRSET "i486p"]				; 想用486指令的叙述

		LGDT	[GDTR0]			; ?入GDTR
		MOV		EAX,CR0			; EAX=CR0
		AND		EAX,0x7fffffff	; EAX&0x7fffffff CR0最高位置0,表示禁止分?
		OR		EAX,0x00000001	; EAX|0x00000001 CR0最低位置1,打?A20地址?
		MOV		CR0,EAX			; CR0=EAX
		JMP		pipelineflush	; 
pipelineflush:
		MOV		AX,1*8			; AX=8 0x0008 相当于gdt+1
		MOV		DS,AX			; DS=AX
		MOV		ES,AX			
		MOV		FS,AX
		MOV		GS,AX
		MOV		SS,AX

; bootpack内容拷?

		MOV		ESI,bootpack	; ESI=bootpack bootpack的?号地址 源地址
		MOV		EDI,BOTPAK		; EDI=BOTPAK=0x00280000	目的地址
		MOV		ECX,512*1024/4	; 循?量控制,因数据的大小是以双字32位,所以?里需要除以4
		CALL	memcpy			; 

; 将磁?数据拷?到它本来的位置去

; 先从起始扇区

		MOV		ESI,0x7c00		; 源地址
		MOV		EDI,DSKCAC		; 目的地址
		MOV		ECX,512/4
		CALL	memcpy
		; 以上?句的功能
		; memcpy(0x7c00,DSKCAC,512/4)
		; 就是从0x7c00?制512字?到0x00100000,就是将起始扇区?制到1MB以后的内存去

; 剩下的数据

		MOV		ESI,DSKCAC0+512	; 源地址
		MOV		EDI,DSKCAC+512	; 目的地址
		MOV		ECX,0			; ECX=0
		MOV		CL,BYTE [CYLS]	; 将0x0ff0地址的一个字拷?到CL中,?里存的是柱面数
		IMUL	ECX,512*18*2/4	; ECX=柱面数*512*18*2/4
		SUB		ECX,512/4		; ECX=ECX-512/4 ?去以?制扇区的数目,就是跳?第一个扇区
		CALL	memcpy
		; 以上?句的功能
		; memcpy(DSKCAC0+512,DSKCAC+512,CYLS*512*18*2/4-512/4)
		; 就是?始于0x00008200的磁?内容,?制到0x00100200
		
; 必?由asmhead.nas完成的工作至此全部?束
; 之后的工作将交由bookpack完成

; bootpack?制

		MOV		EBX,BOTPAK		; EBX=BOTPAK=0x00280000,bootpack的内存收地址(自己?定的)
		MOV		ECX,[EBX+16]	; ECX=[EBX+16] 
		ADD		ECX,3			; ECX += 3;
		SHR		ECX,2			; ECX /= 4;
		JZ		skip			; 如果没有内容?,跳?到skip?号
		MOV		ESI,[EBX+20]	; 源地址
		ADD		ESI,EBX			; ESI=ESI+EBX
		MOV		EDI,[EBX+12]	; 目的地址
		CALL	memcpy
		; 以上?句
		; memcpy(bootpack,BOTPAK,512*1024/4)
		; 从bootpack的地址?始的512KB的内容?制到0x00280000号地址中去
skip:
		MOV		ESP,[EBX+12]	; 
		JMP		DWORD 2*8:0x0000001b	; 将2*8 ?制到CS,同?移?到0x1b号地址
										; ?里是从第二个段?始,段基址是0x280000,所以程序地址?0x28001b??始?行的
		
waitkbdout:
		IN		 AL,0x64
		AND		 AL,0x02
		JNZ		waitkbdout		
		RET

memcpy:
		MOV		EAX,[ESI]
		ADD		ESI,4
		MOV		[EDI],EAX
		ADD		EDI,4
		SUB		ECX,1
		JNZ		memcpy			; 
		RET
; memcpy

		ALIGNB	16
GDT0:
		RESB	8				; ?置第0号段描述符
		DW		0xffff,0x0000,0x9200,0x00cf	; 可以?写的段 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:一定要读源码)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值