龙空技术网

30天自制操作系统day06:控制鼠标

老师明明可以靠颜值 228

前言:

此刻看官们对“c语言的中断函数怎么写”都比较珍视,咱们都想要分析一些“c语言的中断函数怎么写”的相关内容。那么小编同时在网络上网罗了一些有关“c语言的中断函数怎么写””的相关内容,希望我们能喜欢,我们一起来学习一下吧!

第5天,我们尝试在屏幕上显示了字符串,并且显示了鼠标。

第5天的教程链接:

第5天的显示程序,让当前这个界面,有点像操作系统的样子了

但是,这个鼠标是静止的,所以。我们下一步就是先让鼠标动起来,让屏幕对鼠标的按键和移动操作有响应。

能对鼠标响应之后,再对键盘有响应。

好了,那我们的目标就确定了:让当前的屏幕对鼠标有响应,分别对鼠标的按键和移动有响应。

要做到对鼠标有响应,需要先设置寄存器GDTR, 并生成一个表GDT,然后设置IDTR,并生成一个表IDT。

这个咱们前一天的教程中已经完成了。

具体的,我们可以把获取鼠标按键,移动等代码放在IDT定义的中断函数中去实现,然后再想办法把移动后的鼠标显示到界面上来。

实际上这个IDT和GDT我们在第5天的代码中已经实现了,所以今天完成鼠标的˙中断函数,在中断函数里控制,响应鼠标动作。

只要能够响应鼠标的动作,就可以用鼠标的动作去改变操作系统显示的内容。比如让鼠标指针随着鼠标的动作而改变。

今天的内容会按照以下顺序展开:

初始化中断相关硬件:中断控制器鼠标的中断函数实现分析C语言写的中断函数对中断函数进行优化:将比较耗时的操作移出中断函数使用中断功能:需要先初始化中断控制硬件

中断控制有专门的硬件:可编程中断控制器,programmable interrupt controller,简称PIC.

下面咱们直接看程序:

void init_pic(void)/* PIC初始化设定 */{	io_out8(PIC0_IMR,  0xff  ); /* 禁止所有中断 */	io_out8(PIC1_IMR,  0xff  ); /* 禁止所有中断 */	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  ); /* 无缓冲区模式 */	io_out8(PIC1_ICW1, 0x11  ); /* 边沿触发 */	io_out8(PIC1_ICW2, 0x28  ); /* IRQ8-15由INT28-接受 */	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;}

程序解读:

PIC0_IMR, 第0个中断控制器的屏蔽寄存器。8位对应8路中断信号。如果某一位为1,那么该位所对应的信号就被屏蔽了,也就是说中断信号产生以后,CPU不会感知到。

这里我们要对中断控制器进行初始化,当然要把中断屏蔽了。所以,我们将PIC0_IMR设置位0xff,将PIC1_IMR设置位0xff.

就是将中断控制器0,中断控制器1的中断信号全部屏蔽了

下面的程序都是有关ICW的设定了。ICW,initial control word,初始化控制数据。也就是把数据写在ICW里,就是对中断控制器初始化。

每个PIC都有4个ICW,ICW1,ICW2,ICW3,ICW4,

ICW1,ICW4主要是设定配线方式。

ICW3设定主从连接方式的,也是8位,如果将其第2位设置位1,那么就是将其第二位作为主从连接用,来接受从中断控制器的中断信号。

ICW2设定中断号,如果设定为0x20,那么当前控制器产生的中断号为:0x20+0,0x20+1,0x20+...,0x20+7,即:0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27.

所以整个init_pic函数主要设定了两个中断控制器PIC0,PIC1,其中,PIC0是主,PIC1是从。

另外,这里使用了OUT指令对PIC的控制字进行操作,因为PIC是CPU的外部设别,CPU只要按照端口去对应写入到控制字就行了。

分别设定了这两个中断控制器的电器连接属性,中断号,以及主从连接属性。

鼠标的中断函数

完成PIC的初始化设定后,硬件上,CPU就可以感知到鼠标的动作了,我们编写中断函数,在感知到鼠标动作后,打印出一些内容就行了。

一般来说,鼠标的中断函数只能在汇编中写,但是这里我们既然用了C语言来写操作系统,自然就想用C语言来写中断函数。

要用C语言写中断函数,就必须用汇编来做桥梁,也就是说在汇编写的中断函数中调用C语言写的中断函数。

汇编写的中断函数只是做一些数据的传递工作,然后调用C语言写的中断函数,不做跟鼠标相关的内容。

我们先来看看汇编写的中断函数,如下:

以下代码摘自:

EXTERN	 _inthandler2c   ; 引入C语言写的中断函数_asm_inthandler2c:		PUSH	ES		PUSH	DS		PUSHAD		MOV		EAX,ESP		PUSH	EAX		MOV		AX,SS		MOV		DS,AX ; make DS=SS		MOV		ES,AX ; make ES=SS		CALL	_inthandler2c		POP		EAX		POPAD		POP		DS		POP		ES		IRETD       

可以看到,中间有一句:CALL _inthandler2c

这句代码调用了C语言写的中断函数。

这句代码之外的其他代码,基本上都是push,pop,栈的存入和取出。

也就是说,在这个汇编写的中断函数里,跟鼠标相关的操作,啥都没有做。

这个函数就是调用了C语言中写的中断函数。

如何把汇编中的中断函数放在C语言中实现

如上一段所示,用汇编写一个中断函数,在这个汇编写的中断函数里,调用C写的中断函数就行了。

汇编写的中断函数可以响应系统中断。

因为汇编写的中断函数调用了C语言写的中断函数,所以在它响应系统中断时,C语言写的中断函数就运行起来了。

我们使用C语言写的中断函数来完成对鼠标的响应。

那么C语言写的中断函数如下:

void inthandler2c(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 2C (IRQ-12) : PS/2 mouse");	for (;;) {		io_hlt();	}}

这个函数内部先是用boxfill8绘制了长方形,然后又在长方形上写了一串字符串:"INT 2C (IRQ-12) : PS/2 mouse"

那么,如果一切正常,将会显示如下画面:

但是,当我们编译完成后,发现并没有出现如上画面。无论是我们滑动鼠标也好,还是点击按钮,都没有反应。

这是因为,鼠标作为计算机的外部设备,也是需要先发送一些控制码,使与鼠标相关的硬件有效。

与鼠标有关的硬件其实就是键盘控制器和鼠标电路了,所以这里要分别给键盘控制器和鼠标发送控制码,如下:

#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				0x47void wait_KBC_sendready(void){	/* 等待键盘控制器准备好 */	for (;;) {		if ((io_in8(PORT_KEYSTA) & KEYSTA_SEND_NOTREADY) == 0) {			break;		}	}	return;}#define KEYCMD_SENDTO_MOUSE		0xd4#define MOUSECMD_ENABLE			0xf4void enable_mouse(void){	// 等待键盘控制器准备好	wait_KBC_sendready();  // 给键盘控制器发送命令	io_out8(PORT_KEYCMD, KEYCMD_SENDTO_MOUSE);  // 等待键盘控制器准备好	wait_KBC_sendready();  // 给鼠标发送命令	io_out8(PORT_KEYDAT, MOUSECMD_ENABLE);	return; }

将这些代码放在C语言中一起编译,然后运行,就会成功的到如下画面:

到现在为止,系统对鼠标可以响应了:显示字符:PS/2 mouse.

这个正是在我们用C写的中断函数里实现的。

提升中断函数的执行效率:

所谓中断处理,就是打断CPU本来的工作,加塞要求进行处理。

所以必须完成得干净利索。而且进行中断处理期间,不再接受新的中端。

所以如果中断处理程序运行的太慢,它会占用CPU,CPU就无法及时响应新产生的中断。

无法产生新的中断,鼠标如果有新的动作,CPU就无法捕捉到

这将会使CPU无法及时响应用户对鼠标,键盘的操作。

这种现象是我们要极力去避免的。

那么如何做呢?

原则就是中断程序内,只做最简单的事情。

显示一般比较耗时的,

所以,把最耗时的显示程序,放在中断程序外面。

中断程序内,就只是把鼠标的数据保存在某个变量里。

这样,中断程序只完成一个鼠标动作数据的传递功能,对数据的解读,显示等功能,交给main函数就行了。

这样,中断程序就会非常快速地完成,干净利索,cpu就可以更高的频率去捕捉鼠标的动作。

中断程序改成如下形式:

#define PORT_KEYDAT 0x0060// 定义一个结构体// 这个结构体用来存放鼠标数据struct KEYBUF {    unsigned char data, flag;};struct KEYBUF keybuf;void inthandler2c(int *esp){    unsigned char data;    io_out8(PIC1_OCW2, 0x64); /* 通知PIC1已经处理完成了IRQ-12的中断 */    io_out8(PIC0_OCW2, 0x62); /* 通知PIC1已经处理完成了IRQ-02的中断 */    data = io_in8(PORT_KEYDAT);    if (keybuf.flag == 0) {        keybuf.data = data;        keybuf.flag = 1;    }    return;}

注意到,我们定义了一个结构体KEYBUF,专门用来存放鼠标的端口PORT_KEYDAT传过来的数据。

然后我们再在主程序中,把KEYBUF里的内容显示出来,代码如下:

// bootpack.cvoid HariMain(void){    ...    ...    ...    // 前面是其他带代码    for (;;) {        io_cli();        if (keybuf.flag == 0) {            io_stihlt();         } else {             i = keybuf.data;             keybuf.flag = 0;             io_sti();             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);          }      }}

以上代码就实现了把原本在中断函数inthandler2c中的显示部分代码,利用keybuf,转移到了HariMain函数中。

不仅实现了对鼠标动作的响应,还实现了中断函数内部的精简。

编译运行成功后,我们发现,我们对鼠标进行各种操作,屏幕上都会成功打印出一个字符出来。

移动鼠标,屏幕出现字符。

点击鼠标按键,屏幕出现字符。

鼠标的响应非常迅速。

但是,屏幕上的鼠标并没有移动。

要想让鼠标移动,就必须在鼠标移动后的位置,去重新绘制一鼠标,

也就是说,我们要完成两个步骤:

1. 获取鼠标的位置

2. 重新绘制鼠标。

那么,如何获取鼠标的位置?

要从keybuf中的data去找。

怎么找?

没办法找,因为data只有一个数值。

鼠标的位置坐标有连个数值:x和y.

所以,data如果是个数组的话,我们就可以把鼠标坐标相关的数据一次性的放入data了。

我们就把data改为数组。然后去按照这个数组来重新绘制鼠标就行了。

那么鼠标返回的数据,难道就只有x,y坐标么?还有其他值么?

有其他值的,比如鼠标按下的是左键,还是右键?鼠标移动时,是每隔一个很短的时候就发送一组x,y的,所以, 鼠标会在很短的时间发送大量的数据。

那么如此大量的数据,显然,如果data这个数组是需要把他们都存储下来的,所以我们需要把data数组优化为一个FIFO的缓冲区

FIFO缓冲区正式为应对鼠标这种短时间大量数据,来不及处理的情况而设计的数据结构,

今天的内容已经很多了,由于FIFO需要讲的比较多,所以我们放在下一天的教程中讲

总结:

今天我们开始对鼠标产生响应了,我们写了对鼠标硬件的控制程序,然后程序能够成功的响应鼠标的移动和按键。我们还为了提高中断函数的效率,把鼠标相关的显示程序放在了HariMain函数中。

但是我们还没有实现把鼠标的移动过程画出来。

因为要把鼠标移动的过程画出来,就要一次性接收大量的数据。我们需要把接收数据的变量data改造成FIFO数据缓冲区,然后再对数据逐一解读,解读出鼠标的坐标变化,然后才能依据解读出来的坐标,把鼠标指针画在屏幕上。

下一天的教程我们将会完成这个内容。

标签: #c语言的中断函数怎么写