《30天自制操作系统》笔记五六

书上第五六天所涉及的内容主要是GDT、IDT以及PIC。这两部分我就合在一起写好了。(主要的原因是做完第五天的以后看到代码乱得惨不忍睹,就自己开始整理了一下,随后又发现第六天就是讲分割源文件的,所以就继续看了下去。)


第五天一上来,作者就介绍了结构体。于是,出现了struct boot_info,struct seg_desc,以及struct gate_desc三者。(命名我是按照自己的习惯来改动的。)

C语言语法上的东西就不提了。


有点意思的是,继之前恶心简洁的图形化界面之后,我们这次来做文字显示。

字体怎么来呢?最简单的一种方法就是按照之前做图像的时候的方法来做,也就是用boxfill8(),但是这样也太不专业了吧= =

我们有一种好一点的方法,就是重写一个putfont8()函数,用来做字符显示。(ps:后来我改名为print_char()了)直接用作者给的hankaku.txt来导入字体。(其实这个字体包也没有多高端嘛……要用到新的工具makefont.exe。可以从hankaku.txt得到hankaku.bin。之后我们再用bin2obj.exe来将其转化为hankaku.obj文件。同时,对应的修改Makefile。

值得注意的是:一定要加上"$BIN2OBJ ......"的说明!当时就是因为抄漏而导致了大量的失败。

而显示变量名按照上面的来做基本也没问题。不过不知道为什么,到后面我多加"#include "bootpack.h""语句之后,直接就只声明“char s[40]”是不够的,要初始化!可以是"char s[40]={'0'}"。如果没有初始化,就会发现那个字符就是显示不出来,然后你还以为是print_str或者print_char的问题,对照源代码N次!!!(TAT)


再来是鼠标的指针,这个其实还是蛮无聊有趣的。为了凸显我跟作者的不一样,我把指针弄成了8x8大小的,结果……其实也还是能用的,而且大小我(我不是处女座……)也刚好合适。

文件分割不难,略过。也就是多出了graphic.c,dsctbl.c,以及后面的int.c。

有一点是新学习到的,那就是Makefile的一般规则:

# general rules for *.gas, *.nas and *.obj
%.gas : %.c Makefile
	$(CC1) -o $*.gas $*.c
%.nas : %.gas Makefile
	$(GAS2NASK) $*.gas $*.nas
%.obj : %.nas Makefile
	$(NASK) $*.nas $*.obj $*.lst
关于Makefile,有个很详细的教程: http://bbs.chinaunix.net/thread-408225-1-1.html
之后得找时间认真学习一下才行。

本次学习遇到的第一个重难点在于GDT以及IDT。

其实作者的文笔还是挺好的,写得很顺畅。(虽然到后来发现他有些地方机智的绕掉了……)

该记住的有:

GDT大小为2^13*(8B) = 64KB

/* segment descriptor, 8B */
/* base	       : base_low(2B), base_mid(1B) and base_high(1B)
 * limit       : limit_low(1B) and limit_high(1B)
 * access_right: the highest bit is Gbit, when Gbit==1, 
                 the unit of limit is PAGE; the highest 4bits are put into the 
		 highest 4bits of limit_high. thus, to program, access_right
		 is xxxx0000xxxxxxxx; the highest 4bits are "GD00"(used after 
		 386), G is Gbit, D means 32-mode or 16-mode.
		 lowest 8bits:
		 0x00: unused descriptor table
		 0x92: for system. RW-.
		 0x9a: for system. R-X.
		 0xf2: for applications. RW-.
		 0xfa: for applications. R-X.
 */
struct seg_desc {
	short 	limit_low;
	short 	base_low;
	char 	base_mid; 
	char 	access_right;
	char 	limit_high; 
	char 	base_high;
};
而后面的gate_desc,类比一下就好。

set_seg_desc,set_gate_desc,init_gdt_idt就慢慢看代码吧。

C不能直接给GDTR赋值,所以要用load_gdtr,在naskfunc.nas中:

_load_gdtr:		; void load_gdtr(int limit, int addr);
	; "MOV [ESP+6] [ESP+4]"
	MOV	AX, [ESP+4]	; limit
	MOV	[ESP+6], AX
	LGDT	[ESP+6]
	RET
看起来,这段函数似乎做的就是"MOV [ESP+6] [ESP+4]"但是为什么要这么做呢?为什么不直接"LGDT [ESP+4]"呢?

这其中可是大有奥秘的。

首先,GDTR的低16位是段上限,高32位是地址。我们不能直接用MOV来赋值,而只能直接指定一个内存地址,让它去读这48位。想想看,假设我们传的段上限是0x0000ffff,而地址是00270000(事实上我们用的也就是这个)。这时候,地址从ESP+4往高处走是:【FF FF 00 00 00 00 27 00】。但我们希望给GDTR的是这样一部分:【FF FF 00 00 27 00】那可以看到,作者在这里把ESP+4赋值到ESP+6,然后就变成了【FF FF FF FF 00 00 27 00】,只要直接从ESP+6开始读就可以了。

哎,在这里实在不得不感叹一声作者太神了!事实上如果你传参数的时候,两个参数的位置如果换一下的话,你会发现很不好处理!(我一开始是想这么干来着。)


set_seg_desc太高深,跳过。(作者也没讲多少)


然后就到PIC。一到硬件就各种蛋疼哎!

PIC指的是Programmable interrupt controller。

然后看图:


用图比较容易理解。

简而言之,PIC监视着输入管脚的8个中断信号,只要有一个中断信号进来,就将唯一的输出管脚信号变成ON,并通知CPU。

然后下面是一段咋看之下很不明觉厉的代码:

void init_pic(void);

#define PIC0_ICW1	0x0020
#define PIC0_OCW2	0x0020
#define PIC0_IMR	0x0021
#define PIC0_ICW2	0x0021
#define PIC0_ICW3	0x0021
#define PIC0_ICW4	0x0021
#define PIC1_ICW1	0x00a0
#define PIC1_OCW2	0x00a0
#define PIC1_IMR	0x00a1
#define PIC1_ICW2	0x00a1
#define PIC1_ICW3	0x00a1
#define PIC1_ICW4	0x00a1 

/* Initialization of pic */
void init_pic (void) {
	io_out8(PIC0_IMR,  0xff  ); /* disable all interrupts */
	io_out8(PIC1_IMR,  0xff  ); /* disable all interrupts */

	io_out8(PIC0_ICW1, 0x11  ); /* edge trigger mode*/
	io_out8(PIC0_ICW2, 0x20  ); /* IRQ-7 is received by INT20-27 */
	io_out8(PIC0_ICW3, 1 << 2); /* PIC1 is connected by IRQ2*/
	io_out8(PIC0_ICW4, 0x01  ); /* no-buffer mode */

	io_out8(PIC1_ICW1, 0x11  ); /* edge trigger mode */
	io_out8(PIC1_ICW2, 0x28  ); /* IRQ-15 is received by INT28-2f */
	io_out8(PIC1_ICW3, 2     ); /* PIC1 is connected by IRQ2 */
	io_out8(PIC1_ICW4, 0x01  ); /* no-buffer mode */

	io_out8(PIC0_IMR,  0xfb  ); /* 11111011 disable all except PIC1 */
	io_out8(PIC1_IMR,  0xff  ); /* 11111111 disable all interrupt */
}
简单的翻译一下,IMR指的是interrupt mask register,ICW指的是"initial control word",都是8位寄存器。

IMR中8位分别对应8个IRQ信号,如果某一位为1,则该为对应的信号被屏蔽,PIC就忽略之。

ICW不一定是16位,因硬件而不同。有4个,分别编号1~4,共有4个字节的数据。

ICW1和4与硬件有关,忽略之。

ICW3是有关主从连接的设定,对主PIC而言,第几号IRQ与从PIC相连,是用8位来决定的。如果把这些为全部设为1,则主PIC能驱动8个从PIC。

不过呢……这个是硬件决定的,我们也无力。

所以只能改ICW2咯。

ICW2决定了IRQ以哪一号中断通知CPU。通过PIC用数据信号线传送给CPU“0xcd 0x??”来实现的。这里的0xcd实际上就是调用BIOS时用的INT指令。


这次以INT0x20~0x2f接收中断信号IRQ0~15而设定的。


开始看程序,注意鼠标时IRQ12, 键盘是IRQ1。

void inthandler21(int *esp)
/* interrupt from PS/2 keyboard */
{
	struct boot_info *binfo = (struct boot_info *) BOOT_ADDR;
	boxfill8(binfo->vram, binfo->scrnx, BLACK, 0, 0, 32 * 8 - 1, 15);
	print_str(binfo->vram, binfo->scrnx, 0, 0, WHITE, "INT 21 (IRQ-1) : PS/2 keyboard");
	for (;;) {
		io_hlt();
	}
}
完了之后还得让它执行IRETD:

_asm_inthandler21:
		PUSH	ES
		PUSH	DS
		PUSHAD
		MOV		EAX,ESP
		PUSH	EAX
		MOV		AX,SS
		MOV		DS,AX
		MOV		ES,AX
		CALL	_inthandler21
		POP		EAX
		POPAD
		POP		DS
		POP		ES
		IRETD

关于栈,不解释。


这些差不多就是今天全部的内容了。(唉,累……)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值