《30天自制操作系统》 day7 小结

FIFO与鼠标控制

1. 获取按键编码

实现功能:让程序在按一个键后不结束,然后在屏幕上显示出信息。这样就可以切实完成中断处理程序了。

修改int.c程序中的inthandler21函数:

#define PORT_KEYDAT     0x0060

void inthandler21(int *esp)
/*来自PS/2键盘的中断*/
{
    struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO;
    unsigned char data, s[4];
    io_out8(PIC0_OCW2, 0x61);       /*通知PIC*IRQ-01已经受理完毕*/
    data = io_in8(PORT_KEYDAT);

    sprintf(s, "%02X", data);
    boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 16, 15, 31);
    putfonts8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s);

    return;
}

io_out8(PIC0_OCW2, 0x61); 这句话用来通知PIC“已经知道发生了IRQ1中断。如果是IRQ3,则写成0x63.也就是说,将”0x60+IRQ号码”输出给OCW2就可以。执行这句话之后,PIC继续时刻监视IRQ1中断是否发生。反过来,如果忘记了执行这句话,PIC就不在监视IRQ1中断,不管下次由键盘输入什么信息,系统都感知不到了。

详情参阅:http://community.osdev,info/?(PIC)8259A

从编号为0x0060的设备输入的8位信息是按键编码。编号为0x0060的设备就是键盘。

2. 加快中断处理

实现功能:将处理字符显示内容的功能从中断处理中提出来,将按键编码接收下来保存到变量中,然后由HariMain偶尔去查看这个变量。发现有数据就显示出来。

原因:中断处理是打断CPU本来的工作,加塞要求进行处理。在处理进行期间不再接受别的中断。若中断处理速度太慢会影响CPU的的工作。

Int.c节选:

struct KEYBUF {
    unsigned char data, flag;
};

#define PORT_KEYDAT     0x0060

struct KEYBUF keybuf;

void inthandler21(int *esp)
/*来自PS/2键盘的中断*/
{
    unsigned char data;
    io_out8(PIC0_OCW2, 0x61);       /*通知PIC*IRQ-01已经受理完毕*/
    data = io_in8(PORT_KEYDAT);
    if (keybuf.flag == 0)
    {
        keybuf.data = data;
        keybuf.flag = 1;
    }

    return;
}

在函数中用到了两个变量:data和flag,所以可以建一个结构体把两个变量集中起来。

将bootpack.c中MariMain函数中的io_halt()函数更改如下:

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);
        }
    }

即将最初的inthandler21()函数拆解成了inthandler21()和for循环函数。

开始先用io_cli指令屏蔽中断(是为了防止被另外的指令中断),然后去看一看keybuf.flag的值是什么。

如果flag是0说明键还没有按下,就去执行io_hlt指令。但是由于已经执行io_cli屏蔽了中断,所以就这样去之心HLT指令的话,即使有键按下,程序也不会有任何反应。所以STI和HLT两个指令都要执行,而执行这两个指令的函数就是io_stihlt。执行HLT指令后,如果收到PIC的通知,CPU就会被唤醒。这样,CPU首先回去执行中断程序。中断处理程序执行完之后又回到for语句的开头,再执行io_cli函数。

若执行到else语句,说明在keybuf.data里存入了按键编码。先将这个键码(keybuf.data)值保存到变量i中,然后将flag置为0表示把键码值清为空,再通过io_sti语句开放中断。

这样一来,在屏蔽中断期间所做的处理非常少,且会加快操作系统的中断处理过程。

最后莫忘了在bootpack中添加这一句:

extern struct KEYBUF keybuf;

以及早for循环中使用的i的声明,还需要将

struct KEYBUF {
    unsigned char data, flag;
};

添加到.h文件中。

右ctrl键的键码值比较特殊,按下会产生两个字节的键码值”E0 1D”,而松开这个键之后会产生两个字节的键码值”E0 9D”。在一次产生两个字节键码值的情况下,因为键盘内部电路一次只能发送一个字节,所以一次按键就会产生两次中断,第一次中断时发送E0,第二次中断时发送1D。

3. 制作FIFO缓冲区

解决问题:上一步在按右ctrl键时存入参数应该有两个字节,但只显示了一个字节舍弃了最开始那个。所以设计一个能够存储多字节的缓冲区,就不会马上存满并舍弃较早存入的字节了。

在结构体KEYBUF里增加变量,可以定义一个数组:

struct KEYBUF {
    unsigned char data[4];
};

数组data[4]就是存入字节的缓冲区,这里可以使用先前讲到的FIFO、FILO这种栈,这里需要的是FIFO型,也就是先接收到的字节先显示出来。于是代码可以修改成这样:

struct KEYBUF {
    unsigned char data[32];
    int next;
};

这一段代码还是放在.h文件中,然后在int.c中修改代码如下:

void inthandler21(int *esp)
/*来自PS/2键盘的中断*/
{
    unsigned char data;
    io_out8(PIC0_OCW2, 0x61);       /*通知PIC*IRQ-01已经受理完毕*/
    data = io_in8(PORT_KEYDAT);
    if (keybuf.flag < 32)
    {
        keybuf.data[keybuf.next] = data;
        keybuf.next++;
    }

    return;
}

keybuf的起始点是“0”,所以最初存储的数据是keybuf.data[0]。下一个数据是keybuf.data[1],接着是[2],一次类推,一共是32个存储位置。下一个存储位置用变量next来管理,这样就能记住32个数据而不会溢出。修改HariMain中代码:

for (;;) {
    io_cli();
    if (keybuf.next == 0) {
        io_stihlt();
    } else {
        i = keybuf.data[0];
        keybuf.next--;
        for (j = 0; j < keybuf.next; j++) {
            keybuf.data[j] = keybuf.data[j + 1];
        }
        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);
    }
}

注意里面用到的j还是要先声明。如果next不是0,则说明至少有一个数据。最开始的一个数据肯定是放在data[0]中的,将这个数存入到变量i中去。这样,数就减少了一个,所以讲next减去1。for语句的工作如下:

7-3-1

4. 改善FIFO缓冲区

解决问题:开发一个不需要数据移送操作的FIFO型缓冲区(因为如果在数据移送处理前就允许中断的话,会搞乱要处理的数据)

笔者讲了很大一段内容,实际上就是在讲类似循环队列的缓冲区,数据读出位置追着数据写入位置,且可以在写入位置到达尽头即缓冲区为空时重新回到0的位置,循环使用缓冲区空间。

7-4-1

Bootpack.h的结构体修改:

struct KEYBUF {
    unsigned char data[32];
    int next_r, next_w, len;
};

Int.c中inthandler21函数修改如下:

void inthandler21(int *esp)
/*来自PS/2键盘的中断*/
{
    unsigned char data;
    io_out8(PIC0_OCW2, 0x61);       /*通知PIC*IRQ-01已经受理完毕*/
    data = io_in8(PORT_KEYDAT);
    if (keybuf.next < 32)
    {
        keybuf.data[keybuf.next_w] = data;
        keybuf.len++;
        keybuf.next_w++;
        if (keybuf.next_w == 32)
        {
            keybuf.next_w = 0;
        }
    }
    return;
}

HariMain函数读出数据代码如下:

for (;;) {
    io_cli();
    if (keybuf.len == 0) {
        io_stihlt();
    } else {
        i = keybuf.data[keybuf.next_r];
        keybuf.len--;
        keybuf.next_r++;
        if (keybuf.next_r == 32) {
            keybuf.next_r = 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);
    }
}

这样修改之后没有任何数据移送操作,这个缓冲区可以记录大量数据,执行速度又快。

但是读出来的按键码字母部分有问题,运行了笔者的程序也是这样,不知道别人是不是这样。

5. 整理FIFO

缓冲区固定为32字节以后改起来就不方便,所以把它定义成可变的。所以采用了指针的方法*buf(data[32]),将缓冲区的总字节保存在变量size(len)中。变量free用于保存缓冲区里没有数据的字节数。缓冲区的地址保存在变量buf里。p代表下一个数据写入地址next_w,q代表写一个数据读出地址next_r

结构体修改之后如下:

struct KEYBUF {
    unsigned char *buf;
    int p, q, size , free, flags;
};

为了初始化缓冲区以及实现对缓冲区的相关操作,新建了一个fifo.c的文件。

/* FIFO */

#include "bootpack.h"

#define FLAGS_OVERRUN       0x0001

//fifo_init是结构的初始化函数,用来设定各种初始值,也就是设定FIFO8结构的地址以及与结构有关
void fifo8_init(struct FIFO8 *fifo, int size, unsigned char *buf)
/*初始化FIFO缓冲区*/
{
    fifo->size = size;
    fifo->buf = buf;
    fifo->free = size;      /*缓冲区的大小*/
    fifo->flags = 0;
    fifo->p = 0;            /*下一个数据写入位置*/
    fifo->q = 0;            /*下一个数据读出位置*/
    return;
}

//fifo8_put是往FIFO缓冲区存储1字节信息的函数。如果一出返回-1,没有溢出就返回0
int fifo8_put(struct FIFO8 *fifo, unsigned char  data)
/*想FIFO传递数据并保存*/
{
    if (fifo->free == 0) /*空余没有了,溢出了*/
    {
        fifo->flags |= FLAGS_OVERRUN;
        return -1;
    }
    fifo->buf[fifo->p] = data;
    fifo->p++;
    if (fifo->p == fifo->size)
    {
        fifo->p = 0;
    }
    fifo->free--;
    return 0;
}

//fifo8_get函数是从FIFO缓冲区取出1字节的函数
int fifo8_get(struct FIFO8 *fifo)
/*从FIFO取得一个数据*/
{
    int data;
    if (fifo->free == fifo->size)
    {
        //如果缓冲区为空,则返回-1
        return -1;
    }
    data = fifo->buf(fifo->q);
    fifo->q++;
    if (fifo->q == fifo->size)
    {
        fifo->q = 0;
    }
    fifo->free++;
    return data;
}

//报告一下到底积攒了多少数据
int fifo8_status(struct FIFO8 *fifo)
{
    return fifo->size - fifo->free;
}
然后根据fifo.c中的函数在int.c中修改 inthandler21函数如下
struct FIFO8 keyfifo;

void inthandler21(int *esp)
/*来自PS/2键盘的中断*/
{
    unsigned char data;
    io_out8(PIC0_OCW2, 0x61);       /*通知PIC*IRQ-01已经受理完毕*/
    data = io_in8(PORT_KEYDAT);
    fifo8_put(&keyfifo, data);
    return;
}

该函数中的&是取地址运算符,用它可以取得结构或变量的地址值。Fifo8_put接受的第一个参数是内存地址,与之匹配,这里调用时传递的第一个参数也要是内存地址。

HariMain函数内容如下:

    char s[40], mcursor[256], keybuf[32];

    for (;;) {
        io_cli();
        if (fifo8_status(&keyfifo) == 0) {
            io_stihlt();
        } else {
            i = fifo8_get(&keyfifo);
            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);
        }
    }

6. 鼠标

分配给鼠标的中断号码是IRQ12,与键盘的IRQ1比起来差了好多代。要让鼠标信号得到处理,要让下面两个装置有效,一个是鼠标控制电路,一个是鼠标本身。

关于控制电路的设定:鼠标控制电路包含在键盘控制电路里,如果键盘控制电路的初始化正常完成,鼠标电路控制器的激活也就完成了。

在bootpack.c后添加如下代码:


#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                0x47

//让键盘控制电路(KBC)做好准备动作,等待控制指令的到来。
void wait_KBC_sendready(void)  //等待键盘控制电路准备完毕
{
    for (;;) {
        if ((io_in8(PORT_KEYSTA) & KEYSTA_SEND_NOTREADY) == 0) {
            break;   /*break语句是从for循环中强制退出*/
        }
    }
    return;
}

void init_keyboard(void)  //初始化键盘控制电路,然后在HariMain函数调用init_keyboard函数,鼠标控制电路的准备就完成了
{
    wait_KBC_sendready();
    io_out8(PORT_KEYCMD, KEYCMD_WRITE_MODE);
    wait_KBC_sendready();
    io_out8(PORT_KEYDAT, KBC_MODE);
    return;
}

//然后开始发送激活鼠标的指令。归根结底还是要向键盘控制器发送指令
#define KEYCMD_SENDTO_MOUSE     0xd4
#define MOUSECMD_ENABLE         0xf4

//这个函数与init_keyboard函数非常相似,不同点在于写入的数据不同。如果往键盘控制电路发送指令0xd4,下一个数据就会自动发送给鼠标
void enable_mouse(void) /*激活鼠标*/
{
    wait_KBC_sendready();
    io_out8(PORT_KEYCMD, KEYCMD_SENDTO_MOUSE);
    wait_KBC_sendready();
    io_out8(PORT_KEYDAT, MOUSECMD_ENABLE);
    return;  /*顺利的话,键盘控制器会返送回ACK(0xfa)*/
}

运行结果如下:

7-6-1

7. 从鼠标接收数据

鼠标中断现在已经有了,接下来是取出中断数据,int.c中添加代码:

struct FIFO8 mousefifo;

void inthandler2c(int *esp)
/* 来自PS/2鼠标的中断 */
{
    unsigned char data;
    io_out8(PIC1_OCW2, 0x64);           /*通知PIC1 IRQ-12的受理已经完成*/
    io_out8(PIC0_OCW2, 0x62);           /*通知PIC0 IRQ-02的受理已经完成*/
    data = io_out8(PORT_KEYDAT);
    fifo8_put(&mousefifo, data);
    return;
}

鼠标和键盘的原理几乎相同,所以程序也就非常相似。不同之处只有送给PIC的中断受理通知。IRQ-12时从PIC的第4号(从PIC相当于IRQ-08~IRQ-15),首先要通知IRQ-12受理已完成,然后再通知主PIC。这是因为主/从PIC的协调不能够自动完成,如果程序不交给主PIC该怎么做,它就会忽视从PIC的下一个中断请求。

鼠标数据取得方法如下:

ifo8_init(&mousefifo, 128, mousebuf);

for (;;) {
        io_cli();
        if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) == 0) {
            io_stihlt();
        } else {
            if (fifo8_status(&keyfifo) != 0) {
                i = fifo8_get(&keyfifo);
                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);
            } else if (fifo8_status(&mousefifo) != 0) {
                i = fifo8_get(&mousefifo);
                io_sti();
                sprintf(s, "%02X", i);
                boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 32, 16, 47, 31);
                putfonts8_asc(binfo->vram, binfo->scrnx, 32, 16, COL8_FFFFFF, s);
            }
        }
    }

因为鼠标往往会比键盘更快地送出大量数据,所以我们将它的FIFO缓冲区增加到了128字节,避免溢出。取得数据的程序中,如果键盘和鼠标的FIFO缓冲区都为空了,就执行HLT。如果不是两个都空,就先检查keyinfo,如果有数据就取出一个显示出来。如果keyinfo是空,就再去检查mouseinfo,如果有数据,就取出一个显示出来。

7-7-1

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值