《操作系统真像还原》操作系统实现——从键盘获取输入和缓冲区

Posted on Jun 6, 2021

关于键盘输入我不想写,就是一个和硬件交互的过程,这里主要还是说一下输入输出缓冲区。

代码已经打好 tag,链接,比较重要的就是 ioqueue。

获取键盘输入后,可以直接打出到屏幕上,但是这样除了给用户看看之外没有任何的用处,如果想要让输入有效,就必然需要把输入暂存到一个地方,然后让需要从用户读取的线程读取输入,这就需要一个缓冲区来处理这个问题,Dijkstra 提出了一个生产者-消费者模型,基于该模型的思想可以有效地解决这个问题。

生产者-消费者模型

说到底来,其实这就是一个循环队列。以一个 shell 为例,shell 需要获取用户的输入,那么 shell 就是消费者,消费用户的输入,而用户的输入(通过键盘中断处理程序来输入)就是原料,键盘中断处理程序就是生产者。如果让生产者直接和消费者对接,由于两者是异步的,特别的,用户输入甚至是不确定的,就随时可能会出现供不应求或供过于求的问题。

于是我们就在两者之间放一个缓冲区,这个缓冲区应该可以做到:消费者随时可以从缓存区中取数据,取不到就阻塞自己,生产者随时可以向缓冲区中放数据,放不进了就阻塞自己;相应的,一旦消费者发现缓冲区里面可以放数据了,就唤醒被阻塞的生产者,生产者一旦发现缓冲区里面可以取数据了,就唤醒被阻塞的消费者。

为了实现这样的缓存区,我们需要维护一个循环队列(简单的,可以用数组模拟)和被阻塞的消费者和生产者指针,就是这样一个结构

struct ioqueue
{
    struct lock lock;

    PCB* sleeping_producer;
    PCB* sleeping_consumer;
    char buf[bufsize];
    size_t head;
    size_t tail;
};

这里的 head 和 tail 维护的是下标。

然后我们提供这样几个方法

/* get one byte from the buf */
char ioqueue_getchar(struct ioqueue* queue);

/* put one byte to the buf */
void ioqueue_putchar(struct ioqueue* queue, char byte);

void ioqueueInit(struct ioqueue* queue);

uint8_t ioqueueFull(struct ioqueue* queue);

uint8_t ioqueueEmpty(struct ioqueue* queue);

就可以实现这个缓冲区了。

getchar 就是一个消费过程

char ioqueue_getchar(struct ioqueue* queue)
{
    ASSERT(GetIntStatus() == INT_OFF);

    while (ioqueueEmpty(queue))
    {
        sys_lock_lock(&queue->lock);
        /* make current thread (consumer) blocked, and record it */
        ioqueueBlock(&queue->sleeping_consumer);
        sys_lock_unlock(&queue->lock);
    }
  
    char byte = queue->buf[queue->tail];
    queue->tail = ptr_next_pos(queue->tail);

    if (queue->sleeping_producer != NULL)
    {
        ioqueueWakeup(&queue->sleeping_producer);
    }

    return byte;
}

putchar 就是生产过程

void ioqueue_putchar(struct ioqueue* queue, char byte)
{
    ASSERT(GetIntStatus() == INT_OFF);

    while (ioqueueFull(queue))
    {
        sys_lock_lock(&queue->lock);
        /* make current thread (producer) blocked, and record it */
        ioqueueBlock(&queue->sleeping_producer);
        sys_lock_unlock(&queue->lock);
    }

    queue->buf[queue->head] = byte;
    queue->head = ptr_next_pos(queue->head);
  
    if (queue->sleeping_consumer != NULL)
    {
        ioqueueWakeup(&queue->sleeping_producer);
    }
    return;
}

修改 main.c 后就可以进行输入输出了

#include "print.h"
#include "init.h"
#include "debug.h"
#include "memory.h"
#include "thread.h"
#include "interrupt.h"
#include "console.h"
#include "keyboard.h"
#include "ioqueue.h"

void KThreadTest();

int _start()
{
    sys_putstr("this is kernel!\n");
    InitAll();

    /* this thread output the input from the keyboard */
    ThreadStart("KThreadTestA", 31, KThreadTest, "");

    EnableInt();
    while(1);
    return 0;
}

void KThreadTest()
{
    while(1)
    {
        enum int_status old_statu = DisableInt();
        if (!ioqueueEmpty(&keyboard_IO_buf))
        {
            console_putchar(ioqueue_getchar(&keyboard_IO_buf));   
        }
        SetIntStatus(old_statu);
    }
}

效果就是

这里总体比较简单,就不再多说了。