5 初始化PIC(harib03d)
那好,现在欠债(指昨天没讲完的部分)也还清了,就继续往后讲吧。我们接着昨天继续做鼠标指针的移动。为达到这个目的必须使用中断,而要使用中断,则必须将GDT和IDT正确无误地初始化。
那就赶紧使用中断吧……但是,还有一件该做的事没做——还没有初始化PIC。那么我们现在就来做。
所谓PIC是“programmable interrupt controller”的缩写,意思是“可编程中断控制器”。PIC与中断的关系可是很密切的哟。它到底是什么呢?在设计上,CPU单独只能处理一个中断,这不够用,所以IBM的大叔们在设计电脑时,就在主板上增设了几个辅助芯片。现如今它们已经被集成在一个芯片组里了。
PIC是将8个中断信号集合成一个中断信号的装置。PIC监视着输入管脚的8个中断信号,只要有一个中断信号进来,就将唯一的输出管脚信号变成ON,并通知给CPU。IBM的大叔们想要通过增加PIC来处理更多的中断信号,他们认为电脑会有8个以上的外部设备,所以就把中断信号设计成了15个,并为此增设了2个PIC。
那它们的线路是如何连接的呢?如下页图所示。
与CPU直接相连的PIC称为主PIC(master PIC),与主PIC相连的PIC称为从PIC(slave PIC)。主PIC负责处理第0到第7号中断信号,从PIC负责处理第8到第15号中断信号。master意为主人,slave意为奴隶,笔者搞不清楚这两个词的由来,但现在结果是不论从PIC如何地拼命努力,如果主PIC不通知给CPU,从PIC的意思也就不能传达给CPU。或许是从这种关系上考虑,而把它们一个称为主人,一个称为奴隶。
另外,从PIC通过第2号IRQ与主PIC相连。主板上的配线就是这样,无法用软件来改变。
为什么是第2号IRQ呢?事实上笔者也搞不清楚。是不是因为第0号和第1号已经被占用了,而第2号现在还空着,所以就用它了呢。嗯……如果有人想进一步了解这个问题,请一定打电话问问IBM的大叔们。
■■■■■
有人可能会纳闷儿,怎么突然讲起硬件来了?这是因为,如果不懂得这部分的硬件结构,就无法顺利设定PIC。
int.c的主要组成部分
void init_pic(void) /* PIC的初始化 */ { io_out8(PIC0_IMR, 0xff ); /* 禁止所有中断 */ io_out8(PIC1_IMR, 0xff ); /* 禁止所有中断 */ io_out8(PIC0_ICW1, 0x11 ); /* 边沿触发模式(edge trigger mode)*/ io_out8(PIC0_ICW2, 0x20 ); /* IRQ0-7由INT20-27接收 */ io_out8(PIC0_ICW3, 1 << 2); /* PIC1由IRQ2连接 */ io_out8(PIC0_ICW4, 0x01 ); /* 无缓冲区模式 */ io_out8(PIC1_ICW1, 0x11 ); /* 边沿触发模式(edge trigger mode)*/ 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 禁止所有中断 */ return; }
以上是PIC的初始化程序。从CPU的角度来看,PIC是外部设备,CPU使用OUT指令进行操作。程序中的PIC0和PIC1,分别指主PIC和从PIC。PIC内部有很多寄存器,用端口号码对彼此进行区别,以决定是写入哪一个寄存器。
具体的端口号码写在bootpack.h里,请参考这个程序。但是,端口号相同的东西有很多,可能会让人觉得混乱。不过笔者并没有搞错,写的是正确的。因为PIC有些很细微的规则,比如写入ICW1之后,紧跟着一定要写入ICW2等,所以即使端口号相同,也能够很好地区别开来。
■■■■■
现在简单介绍一下PIC的寄存器。首先,它们都是8位寄存器。IMR是“interrupt mask register”的缩写,意思是“中断屏蔽寄存器”。8位分别对应8路IRQ信号。如果某一位的值是1,则该位所对应的IRQ信号被屏蔽,PIC就忽视该路信号。这主要是因为,正在对中断设定进行更改时,如果再接受别的中断会引起混乱,为了防止这种情况的发生,就必须屏蔽中断。还有,如果某个IRQ没有连接任何设备的话,静电干扰等也可能会引起反应,导致操作系统混乱,所以也要屏蔽掉这类干扰。
ICW是“initial control word”的缩写,意为“初始化控制数据”。因为这里写着word,所以我们会想,“是不是16位”?不过,只有在电脑的CPU里,word这个词才是16位的意思,在别的设备上,有时指8位,有时也会指32位。PIC不是仅为电脑的CPU而设计的控制芯片,其他种类的CPU也能使用,所以这里word的意思也并不是我们觉得理所当然的16位。
ICW有4个,分别编号为1~4,共有4个字节的数据。ICW1和ICW4与PIC主板配线方式、中断信号的电气特性等有关,所以就不详细说明了。电脑上设定的是上述程序所示的固定值,不会设定其他的值。如果故意改成别的什么值的话,早期的电脑说不定会烧断保险丝,或者器件冒烟;最近的电脑,对这种设定起反应的电路本身被省略了,所以不会有任何反应。
ICW3是有关主—从连接的设定,对主PIC而言,第几号IRQ与从PIC相连,是用8位来设定的。如果把这些位全部设为1,那么主PIC就能驱动8个从PIC(那样的话,最大就可能有64个IRQ),但我们所用的电脑并不是这样的,所以就设定成00000100。另外,对从PIC来说,该从PIC与主PIC的第几号相连,用3位来设定。因为硬件上已经不可能更改了,如果软件上设定不一致的话,只会发生错误,所以只能维持现有设定不变。
■■■■■
因此不同的操作系统可以进行独特设定的就只有ICW2了。这个ICW2,决定了IRQ以哪一号中断通知CPU。“哎?怎么有这种事?刚才不是说中断信号的管脚只有1根吗?”嗯,话是那么说,但PIC还有个挺有意思的小窍门,利用它就可以由PIC来设定中断号了。
大家可能会对此有兴趣,所以再详细介绍一下。中断发生以后,如果CPU可以受理这个中断,CPU就会命令PIC发送2个字节的数据。这2个字节是怎么传送的呢?CPU与PIC用IN或OUT进行数据传送时,有数据信号线连在一起。PIC就是利用这个信号线发送这2个字节数据的。送过来的数据是“0xcd 0x? ? ”这两个字节。由于电路设计的原因,这两个字节的数据在CPU看来,与从内存读进来的程序是完全一样的,所以CPU就把送过来的“0xcd 0x? ? ”作为机器语言执行。这恰恰就是把数据当作程序来执行的情况。这里的0xcd就是调用BIOS时使用的那个INT指令。我们在程序里写的“INT 0x10”,最后就被编译成了“0xcd 0x10”。所以,CPU上了PIC的当,按照PIC所希望的中断号执行了INT指令。
这次是以INT 0x20~0x2f接收中断信号IRQ0~15而设定的。这里大家可能又会有疑问了。“直接用INT 0x00~0x0f就不行吗?这样与IRQ的号码不就一样了吗?为什么非要加上0x20? ”不要着急,先等笔者说完再问嘛。是这样的,INT 0x00~0x1f不能用于IRQ,仅此而已。
之所以不能用,是因为应用程序想要对操作系统干坏事的时候,CPU内部会自动产生INT 0x00~0x1f,如果IRQ与这些号码重复了,CPU就分不清它到底是IRQ,还是CPU的系统保护通知。
这样,我们就理解了这个程序,把它保存为int.c。今后要进行中断处理的还有很多,所以我们就给它另起了一个名字。从bootpack.c的HariMain调用init_pic。
我们来运行一下“make run”。因为这只是内部设定,所以画面上没有什么变化,虽然觉得不过瘾没有特别大的成就感,但看起来可以正常运行。