【操作系统】30天自制操作系统--(5)分割编译与中断处理

本文详细介绍了如何通过整理源文件、初始化中断控制器(PIC)和设置中断向量表(IDT)来处理键盘和鼠标中断。首先,源代码被拆分成多个功能模块,并更新了Makefile。接着,重点讲解了中断处理的两个步骤:初始化PIC,包括设置中断屏蔽寄存器和初始化控制字,以及创建中断处理程序并将其注册到IDT中。最后,展示了中断处理程序在实际运行中的效果,尽管键盘中断已生效,但鼠标仍无响应。
摘要由CSDN通过智能技术生成

        上一章的GDT/IDT内容结束的没头没尾的,这一章会接着介绍。

        这一章做了这么两件事情,一是将目前略显冗余的源文件bootpack.c整理了一下,分成了几个具有不同功能的源文件二是继续上一章节的内容,在初始化中断向量表之后,设置新的中断关联鼠标和键盘

我觉得这一章的内容虽然短小,却还是挺重要的。

一 分割编译

目前的源文件内容已经比较多了,包含了GDT/IDT的配置初始化,调色板的初始化,屏幕(尺寸等)的初始化,还有绘制光标、显示字符等应用部分。

作者将bootpack.c分成了三个部分:

        这边的分割不难,只是对应的Makefile也要对应进行修改,原先是直接编译bootpack.c的内容,现在要编译三个源文件的内容并整合成一个:

 

         在这个过程中,使用通配符%来使得Makefile文件精简化:

%.gas : %.c Makefile
	$(CC1) -o $*.gas $*.c

%.nas : %.gas Makefile
	$(GAS2NASK) $*.gas $*.nas

%.obj : %.nas Makefile
	$(NASK) $*.nas $*.obj $*.lst

二 中断处理

        中断处理包含两个部分,一是初始化PIC、二是中断处理程序的制作。下面分别介绍:

【1】初始化PIC(programmable interrupt controller,可编程中断控制器)

        在设计上,CPU只能单独处理一个中断,而PIC是将8个中断信号1集合成一个中断信号的装置。PIC监视着输入管脚 的8个中断信号,只要有一个中断信号进来,就将唯一的输出管脚信号变成ON,并通知给CPU:

        上面的IRQ即中断请求,一个CPU有16个IRQ,一般都预分配给以下几个硬件使用:

中断请求硬件设备
IRQ0系统计时器
IRQ1键盘
IRQ2可设置中断控制卡
IRQ3COM2(串行接口2)
IRQ4COM1(串行接口1)
IRQ5
IRQ6磁盘机
IRQ7并行接口
IRQ8CMOS/时钟
IRQ9
IRQ10
IRQ11
IRQ12PS/2鼠标
IRQ13算术处理器(Arithmetic Processor)
IRQ14Primary(主)IDE控制器
IRQ15Secondary(从)IDE控制器

         接着便有了int.c中的PIC初始化:

#include "bootpack.h"

void init_pic(){    
/* 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;
}

        关于上述的初始化归纳以下几点:

        (1)从CPU的角度来看,PIC是外部设备,CPU使 用OUT指令进行操作;

        (2)上面的PIC0和PIC1分别指的是主、从PIC

        (3)我们看到这边主要是对两类寄存器进行操作IMRICW

                IMR(interrupt mask register)为“中断屏蔽寄存器”,如果某一位的值是1,则该位所对应的IRQ信号被屏蔽,PIC就忽视该路信号,这主要是因为,正在对中断设定进行更改时,如果再接受别的中断会引起混乱,为了防止这种情况的发生,就必须屏蔽中断,当然还有诸如物理因素之类的干扰。

                ICW(initial control word)为“初始化控制数据”,共有编号为1-4的四个字节数据。描述如下:

寄存器名描述
ICW1与PIC主板配线方式中断信号的电气特性等有关
ICW2决定IPR以哪一号中断通知CPU
ICW3ICW3有关主从连接的设定
ICW4与PIC主板配线方式中断信号的电气特性等有关

                 对ICW2的设置最为重要,这边单独提出来备忘:

    io_out8(PIC0_ICW2, 0x20 ); /* IRQ0-7由INT20-27接收 */
    io_out8(PIC1_ICW2, 0x28 ); /* IRQ8-15由INT28-2f接收 */

【2】中断处理程序的制作

        PIC初始化之后,下一步就是制作中断处理程序。中断处理程序包含基础的汇编部分实际应用层的中断服务程序部分以及最终写入注册表部分,这边是这一章的重点,分为下面几个步骤实现:

(1)编写中断服务程序:

        根据上面的表格,CPU预分配给鼠标、键盘的中断请求分别时IRQ12和IRQ1,所以编写了中断处理程序(即发生中断时所要调用的程序,这边以键盘中断为例):

/* 键盘中断请求为IRQ1,且上文有IRQ0-7由INT20-27接收,所以这边是inthandler21 */
void inthandler21(int *esp) 
/* 来自键盘的中断 */
{
    struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO;
    boxfill8(binfo->vram, binfo->scrnx, COL8_000000, 0, 0, 32 * 8 - 1, 15);
    putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_FFFFFF, "INT 21 (IRQ1) : PS/2 keyboard");
    for (;;) {
        io_hlt();
    }
}

        该程序实现的是如果接收到键盘中断,那么就在屏幕上返回打印信息,并使程序保持待机

(2)汇编实现中断服务程序的返回:

        首先,把程序贴出来,在注释中一一说明:

_asm_inthandler21:
		PUSH	ES                ;ES寄存器入栈
		PUSH	DS                ;DS寄存器入栈
		PUSHAD                    ;基础寄存器入栈(包含EAX/ECX/EDX/EBX/ESP/EBP/ESI/EDI)
		MOV		EAX,ESP           
		PUSH	EAX               ;EAX寄存器入栈
		MOV		AX,SS
		MOV		DS,AX
		MOV		ES,AX             ;DS和ES调整到与SS相等
		CALL	_inthandler21     ;调用中断服务程序_inthandler21
		POP		EAX               ;EAX寄存器出栈
		POPAD                     ;基础寄存器出栈(包含EAX/ECX/EDX/EBX/ESP/EBP/ESI/EDI)
		POP		DS                ;DS寄存器出栈
		POP		ES                ;ES寄存器出栈
		IRETD                     ;中断服务程序返回

        其中,注意两点:

        第一点是,各个寄存器的出栈入栈的操作本质上就是在执行中断服务程序前后保存现场,使其原来的值不被破坏

        第二点是,中断服务程序的返回必须使用IRETD,这是INT的逆过程,而不能用子程序的返回指令RET

        所以上面的函数也可以总结归纳为以下几个步骤:

  1. 将寄存器的值保存到栈里;
  2. 然后将DS和ES调整到 与SS相等;
  3. 再调用_inthandler21;
  4. 返回以后,将所有寄存器的值再返回到原来的值,然后执行IRETD;

(3)将编写好的中断处理程序(包含中断服务程序和汇编处理两部分)注册到IDT中断记录表中

        GDT/IDT的初始化处理中加入键盘(IRQ1)、鼠标(IRQ12)、并行接口(IRQ7)三个中断处理程序的注册:

void init_gdtidt(void){
	struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR*) 0x00270000;
	struct GATE_DESCRIPTOR *idt = (struct GATE_DESCRIPTOR*) 0x0026f8000;
	int i;
	// GDT 初始化
	for (i = 0; i < 8192; i++) {
		set_segmdesc(gdt + i, 0, 0, 0);
	}
    // GDT 注册(注册了1段和2段)
	set_segmdesc(gdt + 1, 0xffffffff, 0x00000000, 0x4092);
	set_segmdesc(gdt + 2, 0x0007ffff, 0x00280000, 0x409a);
	load_dgtr(0xffff, 0x00270000);

	// IDT 初始化
	for (i = 0; i < 256; i++) {
		set_gatedesc(idt + i, 0, 0, 0);
	}
	load_idtr(0x07ff, 0x0026f800);
    // IDT 注册(注册了IRQ01、IRQ07、IRQ12三个中断处理程序)
    set_gatedesc(idt + 0x21, (int) asm_inthandler21, 2 * 8, AR_INTGATE32);
	set_gatedesc(idt + 0x27, (int) asm_inthandler27, 2 * 8, AR_INTGATE32);
	set_gatedesc(idt + 0x2c, (int) asm_inthandler2c, 2 * 8, AR_INTGATE32);
 
	return;
}

        其中,set_gatedesc的几个参数分别代表如下含义:

        (i)idt+0x21,在IDT的基址0x26f800上偏移0x21,即IRQ1的位置;

        (ii)将中断处理程序asm_inthandler21注册在idt中的第0x21位置上;

        (iii)2 * 8表示的是asm_inthandler21属于哪一个段,即段号是2。乘以8是因为低3位有着别的意思,这里低3位必须是0;

        (iv)AR_INTGATE32为0x008e,这边是将IDT的属性,设定为0x008e,它表示这是用于中断处理的有效设定;

        至此,经由中断服务程序制作---->中断处理程序制作(汇编调用中断服务程序并返回)---->中断处理程序写入注册表IDT,便成功将硬件鼠标键盘与CPU的中断关联起来

三 效果展示

        实际运行效果如下:

        按下键盘“A”键:

         操作鼠标还是没有反应,具体原因将在后面进行探究。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值