Advertisement

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

阅读量:

FIFO与鼠标控制

基于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;
    }

这条指令用于向PIC发送中断状态指示信息。具体来说,在处理IRQ中断事件时,默认情况下会将 IRQ1 的中断状态通过 I/O 输出送到 OCW2 端口的地址为 0x60 的位置(即 0x60 + IRQ 码)。如果遇到 IRQ3 中断,则需要将数据设置为 0x63 并发送给 OCW2 端口的位置 0x61 处(即 0x61)。换句话说,在运行这条指令后,PIC 将持续监控 IRQ1 中断的发生情况以及时处理相应的事件响应。如果未执行该指令,则 PIC 将无法持续关注 IRQ1 中断的状态变化;即使后续从键盘输入了中断信息也无法被系统感知到处理

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

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

2. 加快中断处理

功能模块化实现:从中断处理流程中提取字符显示功能,并捕获按键编码后存储于变量中。随后由HariMain定期检查该变量。若检测到数据则进行显示操作。

根本原因:中断处理会干扰正常工作流程并被强行中断以完成其他任务。在执行中不再接收其他中断请求。如果无法及时完成这些任务可能会导致系统性能下降。

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中。接下来的操作是将该键码值赋值给变量i以便后续处理;随后将其置零以表示已清空键码;最后通过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;
    }

在程序设计中,默认情况下数组从索引0开始访问元素。因此,在本例中初始数据将被存放在内存地址1的位置上。紧接着的数据存放在内存地址2的位置上(即紧随其后的一个单元),以此类推;总共有连续的32个内存单元被分配用于存储相关数据以避免溢出问题。为了方便程序流程的控制,在处理每一个新数据时都会使用一个计数器变量next来进行跟踪定位

复制代码
    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。这样一来,总的数据数量减少了1个;因此应将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字节后操作变得不便时

其中

因此

结构体修改之后如下:

复制代码
    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;
    }

鼠标和键盘的工作原理极为相似,因此程序也会极为相似。主要区别在于它们向目标处理器发送中断响应通知的方式不同.当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)

还没有任何评论哟~