Day07

今天继续完善了中断处理,还有如何获取中断的信息。

一些问题和感想

FIFO缓冲区的实现

因为中断请求(比如鼠标、键盘)可能短时间内会发送很多次,为了不要太影响当前正在执行的程序,我们可以对中断做一个缓冲区。当系统空闲,或者隔一段时间,再去看这个缓冲区里有没有东西。
作者最后用一个数组做的循环队列来存储这些缓冲。

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

//初始化FIFO缓冲区
void fifo8_init(struct FIFO8 *fifo, int size, unsigned char *buf)
{
    fifo->size = size;
    fifo->buf = buf;
    fifo->free = size; // 缓冲区大小
    fifo->flags = 0;
    fifo->p = 0; //下一个数据写入位置
    fifo->q = 0; //下一个数据读出位置
}

#define FLAGS_OVERRUN 0x0001

//向FIFO传送数据并保存
int fifo8_put(struct FIFO8 *fifo, unsigned char data)
{
    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;
}

//从FIFO取得一个数据
int fifo8_get(struct FIFO8 *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;
}

鼠标中断以及键盘中断接受数据

鼠标和键盘中断会从io接受数据,我们可以用对应的io_in8()函数来接收。
此外,如果想要计算机接受鼠标的中断,要先激活鼠标控制电路(控制电路——CPU),然后激活鼠标(鼠标——控制电路)。

各种参数的设置

接收各种中断,当然少不了各种IO地址。这些东西貌似作者直接给在代码里了。如果要真的自己做操作系统,恐怕要在各种参考资料里查询地址吧。。

进入正题

获取按键编码

harib04a

我们现在让中断程序接收键盘发来的信息,比如按键编码、以及按下/释放。
int.c

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);
    putfont8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s);
}

io_out一句,当中断结束后,我们需要重新让PC监视中断程序,不然执行一次中断后就不再执行了。

加快中断处理

harib04b

当然,每按一下键盘就中断、显示字符,也太频繁调用操作系统了(其实感觉鼠标会更麻烦)。所以我们可以设置一个缓冲区,用来暂时存储中断信息。

struct KEYBUF
{
    unsigned char data, flag;
};

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;
    }
}“
”

这个缓冲区还比较简陋,只能存储一个数据(还不能弹出),不过先这么用着吧。
Harimain部分

unsigned char i;
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);
        putfont8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s);
    }
}

cli先屏蔽中断,然后如果缓冲区没有东西,stihlt是恢复并暂停的意思。至于为什么要写一起,这样做是可以忽略sti和hlt之间的中断,否则突然有个数据进来就不好了。
其余的代码逻辑还是挺好懂的。

制作FIFO缓冲区

harib04c

一个很暴力的队列,弹出靠的是把数组里所有元素向前移。。这里就不放代码了,毕竟直接看最终版本就行了。

改善FIFO缓冲区

harib04d

用循环队列就好了。。很简单。。

整理FIFO缓冲区

harib04e

我们把队列的各种操作封装成方法,这样就可以比较方便、而且容易理解地进行各种队列操作了。
FIFO的各种方法写在了前面,现在看一下最终版本的中断程序。
int.c

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

这样就简洁多了,然后是HariMain函数

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

fifo8_init(&keyfifo, 32, keybuf);

unsigned char i;
int j;
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);
        putfont8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s);
    }
}

总算讲到鼠标了

harib04f

鼠标中断号为IRQ12,然后是鼠标的一系列激活操作:先让键盘控制电路准备好,然后激活鼠标

#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

void wait_KBC_sendready(void)
{
    //等待键盘控制电路准备完毕
    for(;;)
    {
        if((io_in8(PORT_KEYSTA) & KEYSTA_SEND_NOTREADY) == 0)
        {
            return;
        }
    }
    return;
}

void init_keyboard(void)
{
    //初始化键盘控制电路
    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

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

如果我们先io_out KEYCMD_SENDTO_MOUSE,接下来的io_out就会将数据写入鼠标。
这样我们就能接收到鼠标中断了。

从鼠标接受数据

harib04g

接下来看一下鼠标的中断程序,还是比较相似的。

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_in8(PORT_KEYDAT);
    fifo8_put(&mousefifo, data);
}

首先要通知从PIC受理完成,然后再通知主PIC受理完成,不然主PIC会忽视从PIC的下一个中断请求。
然后鼠标数据的获取方法:
bootpack.c

fifo8_init(&mousefifo, 256, mcursor);

enable_mouse();

unsigned char i;
int j;
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);
            putfont8_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);
            putfont8_asc(binfo->vram, binfo->scrnx, 32, 16, COL8_FFFFFF, s);
        }

    }
}

这样就可以获得来自鼠标的数据了。。。
明天继续。。