《30天自制操作系统》笔记(05)——启用鼠标键盘
从最开始的(01)篇到上一篇为止,已经解决了开发环境问题和OS项目的顶层设计问题,并且知道了如何在320*200像素的模式下使用显示器。这意味着处理和输出部分已经有了最基本的版本,因此本篇来完成输入功能,即启用键盘和鼠标。
以前基于.net做app的时候,必须了解一些.net虚拟机、AppDomain、.net类库、socket、面向对象等相关的知识。现在要基于物理机做一个被称为"操作系统"的app,当然要对物理机有一些认识。
本篇将整理一些关于CPU的知识点。整理这些的目的是实现对硬件的封装(写一些供C语言调用的函数),对硬件进行封装的目的当然是隐藏硬件细节,为写操作系统这个app服务了。不过大动干戈地封装不宜在此时进行,因为我对后续的内存管理、多任务、窗口这些东西还没有概念。放到整个操作系统完成后进行重构时再仔细封装比较稳妥。
事件,小名中断
Windows窗体编程基于事件机制。简单来说,就是鼠标单击时,应用程序会执行某个函数;你可以自行随意编写这个函数的实现代码。这个机制自然是操作系统提供给应用程序的。
但是,操作系统又是如何获取鼠标的单击事件的?一种方式是让CPU死循环不停地查询,但这太浪费,而且CPU就没时间处理应用程序的逻辑运算了。那么唯一的可能就是计算机在硬件层次上提供给操作系统一种类似的事件机制,也就是中断(interrupt,简写做INT)。也就是说,硬件能够产生鼠标点击的事件(物理事件),并调用一个由操作系统指定的函数A,函数A则通知相应的应用程序"喂,发生了鼠标点击事件M"(实际上稍微复杂一点,函数A是将M放入消息队列,间接地让应用程序获知了M);当CPU执行该应用程序时,就会根据M执行应用程序的事件函数。
因此,中断可以看做幼儿期的事件,通过操作系统、应用程序之间的传递过程,就被封装成能干活的大人了。
传个纸条
根据这一原理,操作系统只需给硬件写个纸条说"CPU同志,发生鼠标点击事件时,请你调用函数M"。CPU用几个特定的寄存器和一点点内存保存好这个纸条就可以了。
这个纸条用代码写出来,就是下面这个样子的。
1 void init_gdtidt(void) 2 { 3 struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR *) ADR_GDT; 4 struct GATE_DESCRIPTOR *idt = (struct GATE_DESCRIPTOR *) ADR_IDT; 5 int i; 6 7 /* GDT的初始化 */ 8 for (i = 0; i <= LIMIT_GDT / 8; i++) { 9 set_segmdesc(gdt + i, 0, 0, 0); 10 } 11 set_segmdesc(gdt + 1, 0xffffffff, 0x00000000, AR_DATA32_RW); 12 set_segmdesc(gdt + 2, LIMIT_BOTPAK, ADR_BOTPAK, AR_CODE32_ER); 13 load_gdtr(LIMIT_GDT, ADR_GDT); 14 15 /* IDT的初始化 */ 16 for (i = 0; i <= LIMIT_IDT / 8; i++) { 17 set_gatedesc(idt + i, 0, 0, 0); 18 } 19 load_idtr(LIMIT_IDT, ADR_IDT); 20 21 /* IDT的设定 */ 22 set_gatedesc(idt + 0x21, (int) asm_inthandler21, 2 * 8, AR_INTGATE32); 23 set_gatedesc(idt + 0x27, (int) asm_inthandler27, 2 * 8, AR_INTGATE32); 24 set_gatedesc(idt + 0x2c, (int) asm_inthandler2c, 2 * 8, AR_INTGATE32); 25 26 return; 27 } 28 29 void set_segmdesc(struct SEGMENT_DESCRIPTOR *sd, unsigned int limit, int base, int ar) 30 { 31 if (limit > 0xfffff) { 32 ar |= 0x8000; /* G_bit = 1 */ 33 limit /= 0x1000; 34 } 35 sd->limit_low = limit & 0xffff; 36 sd->base_low = base & 0xffff; 37 sd->base_mid = (base >> 16) & 0xff; 38 sd->access_right = ar & 0xff; 39 sd->limit_high = ((limit >> 16) & 0x0f) | ((ar >> 8) & 0xf0); 40 sd->base_high = (base >> 24) & 0xff; 41 return; 42 } 43 44 void set_gatedesc(struct GATE_DESCRIPTOR *gd, int offset, int selector, int ar) 45 { 46 gd->offset_low = offset & 0xffff; 47 gd->selector = selector; 48 gd->dw_count = (ar >> 8) & 0xff; 49 gd->access_right = ar & 0xff; 50 gd->offset_high = (offset >> 16) & 0xffff; 51 return; 52 }