《操作系统真像还原》操作系统实现——从键盘获取输入和缓冲区
关于键盘输入我不想写,就是一个和硬件交互的过程,这里主要还是说一下输入输出缓冲区。
代码已经打好 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);
}
}
效果就是

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