锁
显示GP异常的原因
显示GP异常,代表着写入的显存超出范围,既然是显存出现问题,我们系统什么时候进行显存操作?
显然put_str(),put_char,put_int()是现在唯一与显存相关函数,其中put_int我们没有使用,同时put_str可以看做多个put_char()组合成的函数,所以问题大概出现在put_char()之中。
我们回忆一下put_char()工作原理,
1.获取光标值,
2.将光标值转换为字节的地址,在该地址写入字符
3.更新光标值
按理来说,这三个步骤必须一气呵成,如何没有,会怎么样呢?
假设线程k_thread_a正通过put_char打印字符,读取了字符打印光标的位置,光标值为C1,于是它就兴高采烈地进行步骤2 ,也就是“打
算”将光标值转换成地址后,再往该地址处写入字符的ASCII 码及属性。由于步骤2中的微步骤很多,所以步骤2的完成需要一步一步来,不管步骤2是否执行彻底,总之尚未进行步骤3。此时中断发生了,k_thread_a被调度器换下处理器,k_thread_b换上处理器,开始执行步骤1,线程k_thread_a还没执行步骤3,因此光标值还未更新,因此k_thread_b获取的光标值也会是C1,因此会在相同地方打印字符,就会发生字符覆盖。
GP异常又是如何来的呢?
我们的代码中,光标设置分为4个微操作:
1.通知光标寄存器设置高8位
2.输入光标值的高8位
3.通知光标寄存器设置低8位
4.输入光标值的低8位
假设k_thread_a在处理器上运行,此时k_thread_a要设置的光标值为0x7cf,执行完步骤3,且步骤4还未执行,中断发生,k_thread_b被换上处理器,目前光标处理器中,仅更新了高8位,第八位还未设置彻底,光标寄存器的值仍为0x7ce,当下次运行时,就是把0xcf写入光标寄存器的低8位
k_thread_b运行后,他先获取光标值0x7ce,然后将光标转换成显存的偏移地址,随后在该地址处
输出字符。当输出一个字符后,光标值被更新为0x7cf,这样在输出下一个字符时,获取到的光标值便为
0x7cf,接着在此光标值对应的显存地址处写入字符。当它开始进行更新时,完成步骤1,且步骤2未进行,中断又发生了。此时通知了要设置高八位,但传入了k_thread_a低八位的值,导致光标值变成0xcfcf。
信号量
我们的锁是通过信号量来实现的,信号量是种同步机制,实质上就是个计数器,用来记录积累信号的数量。
其关键操作如下
1.增加操作 up
(1)将信号量的值加1
(2)唤醒在此信号量上等待的线程
2.减少操作 down
(1)判断信号量是否大于0
(2)若信号量大于0,则将信号量减1
(3)若信号量等于0,当前线程阻塞自己,以便在此信号量上等待
在二元信号量中,down操作就是获取锁,up操作就是释放锁,我们可以让线程通过锁进入临界区,可以保证只有一个线程可以进入临界区,从而做到互斥。大致流程位:
(1)线程A进入临界区前先通过down操作获取锁,此时信号量便为0
(2)后续线程B再进入临界区时也通过down操作获取锁,由于信号量为0,线程B便在此信号量上等待,也就是相当于线程B进入了睡眠态
(3)当线程A从临界区出来后执行up操作释放锁,此时信号量的值重新变成1,之后线程A将线程B唤醒。
(4)线程B醒来后,进入临界区
线程阻塞和唤醒
阻塞即让其不能运行,我们系统就是让线程在就绪队列中出现就行。
调度器的功能只是去挑选哪个线程运行,但是它并不决定线程是否可以运行,只是决定了运行的时机。线程可否运行是由线程自己把控的,当线程被换上处理器运行后,在其时间片内,线程将主宰自己的命运,阻塞是线程自己发起的。
阻塞操作:
因为是原子操作首先要关闭中断,获取现在的线程,并且将其状态设置为阻塞态,通知调度器更换线程,最后打开中断。
唤醒操作:
原子操作,关闭中断,被阻塞的线程,其状态肯定不是TASK_READY ,为保险起见,我们还是判了一下,也就是if (pthread->status != TASK_READY)仅仅是让我们更放心。list_push将阻塞的线程重新加入就绪队列的队首一边被优先调度。
信号量的down和up操作实现
down操作:
原子操作,关闭中断。
信号量为0,将线程放入信号量的等待队列并将线程阻塞
信号量为1,信号量减1,开启中断
up操作:
原子操作,关闭中断
list_pop()出队首的第一个线程,并且通过宏转换为PCB,存储到thread_blocked中,然后通过thread_unblock()将线程唤醒。信号量加1,打开中断。
锁的获取和释放
键盘获取输入
键盘输入原理
键盘是一个独立的设备,在他内部有个Intel 8084或兼容芯片, 每当键盘敲下,他就向键盘控制器报告哪个键被按下,按键是否弹起。
一个键的状态要么是按下,要么是弹起,因此一个键有两个编码,按键按下时的编码叫通码,按键被持续按住时会持续产生相同的码,直到按键被松开时才终止。按键松开时产生的码叫做断码。
一个键的扫描码是由通码和断码组成。
无论是按下键,或是松开键,当键的状态改变后,键盘中的8048 芯片把按键对应的扫描码(通码或断码)发送到主板上的8042 芯片,由8042 处理后保存在自己的寄存器中,然后向8259A 发送中断信号,这样处理器便去执行键盘中断处理程序,将8042 处理过的扫描码从它的寄存器中读取出来,继续进行下一步处理。
8042芯片
8042 是8048 的IO 接口,对8048 的编程也是通过8042 完成的,所以只要学习8042 足矣
四个寄存器共用两个端口,说明在不同场合下同一端口由不同作用。
8042 是连接8048 和处理器的桥梁, 8042 存在的目的是:为了处理器可以通过它控制8048 的工作方
式,然后让8048 的工作成果通过8042 回传给处理器。此时8042 就相当于数据的缓冲区、中转站,根据数据被发送的方向, 8042 的作用分别是输入和输出。
环形输入缓冲区
生产者与消费者问题
有一个或多个生产者、一个或多个消费者和一个固定大小的缓冲区,所有生产者和消费者共享这同一个缓冲区。生产者生产某种类型的数据,每次放一个到缓冲区中,消费者消费这种数据,每次从缓冲区中消费一个。同一时刻,缓冲区只能被A 图10 23 生产者与消费者一个生产者或消费者使用。当缓冲区已满时,生产者不能继续往缓冲区中添加数据,当缓冲区为空时,消费者不能在缓冲区中消费数据。
环形缓冲区
缓冲区是多个线程共同使用的共享内存,对于缓冲区的访问我们提供两个指针(头指针和尾指针),头指针用于往缓冲区写数据,尾指针用于往缓冲区读数据。
内存一般是线性的,如何变为逻辑上的环形呢?
取模
代码
syn.c
#include "sync.h"
/*初始化信号量*/
void sema_init(struct semaphore* psema,uint8_t value){
psema->value = value ; // 为信号量赋初值
list_init(&psema->waiters) ; // 初始化信号量的等待队列
}
/*初始化锁plock*/
void lock_init(struct lock* plock){
plock->holder = NULL;
plock->holder_repeat_nr = 0;
sema_init(&plock->semaphore,1); // 信号量初值为1
}
/*信号量down操作*/
void sema_down(struct semaphore* psema){
/*关中断来保证原子操作*/
enum intr_status old_status = intr_disable();
while(psema->value == 0){ // 若value为0,表示已经被别人持有
ASSERT(!elem_find(&psema->waiters, \
&running_thread()->general_tag));
if(elem_find(&psema->waiters,&running_thread()->general_tag)){
PANIC("sema_down:thread blocked has been in waiters_list \n");
}
/*若信号量的值等于0,则当前线程把自己加入该锁的等待队列,然后阻塞自己*/
list_append(&psema->waiters,&running_thread()->general_tag);
thread_block(TASK_BLOCKED);
//线程阻塞直到唤醒
}
psema->value--; //value为1或被唤醒后,会执行下面的代码,也就是获得了锁
ASSERT(psema->value == 0);
//恢复之前的中断状态
intr_set_status(old_status);
}
/*信号量的up操作*/
void sema_up(struct semaphore* psema){
/*关中断,保证原子操作*/
enum intr_status old_status = intr_disable();
ASSERT(psema->value == 0);
if(!list_empty(&psema->waiters)){
struct task_struct* thread_blocked = elem2entry(struct task_struct, \
general_tag,list_pop(&psema->waiters));
thread_unblock(thread_blocked);
}
psema->value++;
ASSERT(psema->value == 1);
/*恢复之前的中断状态*/
intr_set_status(old_status);
}
/*获取锁plock*/
void lock_acquire(struct lock* plock){
/*排除曾经自己已经拥有锁但是还未释放的情况*/
if(plock->holder != running_thread()){
sema_down(&plock->semaphore);
plock->holder = running_thread();
ASSERT(plock->holder_repeat_nr == 0);
plock->holder_repeat_nr = 1;
}else{
plock->holder_repeat_nr++;
}
}
/*释放锁plock*/
void lock_release(struct lock* plock){
ASSERT(plock->holder == running_thread());
if(plock->holder_repeat_nr > 1){
plock->holder_repeat_nr--;
return ;
}
ASSERT(plock->holder_repeat_nr == 1);
plock->holder = NULL ; //把锁的持有者空放到V操作之前
plock->holder_repeat_nr = 0;
sema_up(&plock->semaphore); //信号量的V操作,也就是原子操作
}
sync.h
#ifndef __THEARD_SYNC_H
#define __THEARD_SYNC_H
#include "list.h"
#include "stdint.h"
#include "thread.h"
#include "string.h"
#include "interrupt.h"
/*信号量结构 */
struct semaphore{
uint8_t value;
struct list waiters;
};
struct lock{
struct task_struct* holder; //锁的持有者
struct semaphore semaphore; //用二元信号量实现锁
uint32_t holder_repeat_nr; //锁的持有者重复申请锁的次数
};
void sema_init(struct semaphore* psema, uint8_t value);
void sema_down(struct semaphore* psema);
void sema_up(struct semaphore* psema);
void lock_init(struct lock* plock);
void lock_acquire(struct lock* plock);
void lock_release(struct lock* plock);
#endif
thread.c
#include "thread.h"
#include "stdint.h"
#include "string.h"
#include "global.h"
#include "memory.h"
#include "interrupt.h"
#include "switch.h"
#include "process.h"
#define PG_SIZE 4096
struct task_struct* main_thread; //主线程PCB
struct list thread_ready_list; //就绪队列
struct list thread_all_list; //所有任务队列
static struct list_elem* thread_tag; //用于保存队列中的线程节点
extern void switch_to(struct task_struct* cur,struct task_struct* next);
/*获取当前线程的pcb指针*/
struct task_struct* running_thread(){
uint32_t esp;
asm("mov %%esp,%0" : "=g"(esp));
/*取esp整数部分,即pcb起始地址*/
return (struct task_struct*)(esp & 0xfffff000);
}
/*由kernel_thread去执行function(func_arg)*/
static void kernel_thread(thread_func* function,void* func_arg)
/*执行前要开中断,避免后面的时钟中断被屏蔽,而无法调度其他线程*/
{
intr_enable();
function(func_arg);
}
/*初始化线程栈thread_stack,将待执行的函数和参数放到thread_stack中对应位置*/
void thread_create(struct task_struct* pthread,thread_func function,void* func_arg){
/*先预留中断使用栈空间,可见thread.h定义的结构*/
pthread->self_kstack -= sizeof(struct intr_stack);
pthread->self_kstack -= sizeof(struct thread_stack);
struct thread_stack* kthread_stack = (struct thread_stack*)pthread->self_kstack;
kthread_stack->eip = kernel_thread;
kthread_stack->function = function;
kthread_stack->func_arg = func_arg;
kthread_stack->ebp = kthread_stack->ebx = \
kthread_stack->esi = kthread_stack->edi = 0;
}
/*初始化线程基本信息*/
void init_thread(struct task_struct* pthread,char* name,int prio){
memset(pthread,0,sizeof(*pthread));
strcpy(pthread->name,name);
if(pthread == main_thread){
//由于把main函数也封装成一个线程,并且它一直是执行的,故将其直接设为TASK_RUNNING
pthread->status = TASK_RUNNING;
}else
{
pthread->status = TASK_READY;
}
/*先预留中断使用栈空间,可见thread.h定义的结构*/
pthread->self_kstack = (uint32_t*)((uint32_t)pthread + PG_SIZE);
pthread->priority = prio ;
pthread->ticks = prio;
pthread->elapsed_ticks = 0;
pthread->pgdir = NULL ;
pthread->stack_magic = 0x19870916;
}
/*创建一优先级为prio的线程,线程名为name,线程名为name,
线程所执行的函数是function(func_arg)*/
struct task_struct* thread_start(char* name,\
int prio, \
thread_func function, \
void *func_arg){
/*pcb都位于内核空间,包括用户进程的pcb也是在内核空间*/
struct task_struct* thread = get_kernel_pages(1);
init_thread(thread,name,prio);
thread_create(thread,function,func_arg);
ASSERT(!elem_find(&thread_ready_list,&thread->general_tag));
list_append(&thread_ready_list,&thread->general_tag);
ASSERT(!elem_find(&thread_all_list,&thread->all_list_tag));
list_append(&thread_all_list,&thread->all_list_tag);
return thread;
}
static void make_main_thread(void){
main_thread = running_thread();
init_thread(main_thread,"main",31);
ASSERT(!elem_find(&thread_all_list,&main_thread->all_list_tag));
list_append(&thread_all_list,&main_thread->all_list_tag);
}
void schedule(){
ASSERT(intr_get_status()==INTR_OFF);
struct task_struct* cur = running_thread();
if(cur->status == TASK_RUNNING){
//此线程只是cpu时间到了,将其加入就绪队尾
ASSERT(!elem_find(&thread_ready_list,&cur->general_tag));
list_append(&thread_ready_list,&cur->general_tag);
cur->ticks = cur->priority;
cur->status = TASK_READY;
}else{
/*此线程需要某事件发生后才能继续上CPU运行
不需要将其加入队列,因为当前线程不在就绪队列中*/
}
ASSERT(!list_empty(&thread_ready_list));
thread_tag = NULL; //thread_tag清空
/*将thread_ready_list队列中的第一个就绪线程弹出,准备将其调度上CPU*/
thread_tag = list_pop(&thread_ready_list);
struct task_struct* next = elem2entry(struct task_struct,\
general_tag, thread_tag);
next->status = TASK_RUNNING;
process_activate(next);
switch_to(cur,next);
}
void thread_init(void){
put_str("thread_init start \n");
list_init(&thread_ready_list);
list_init(&thread_all_list);
/*当前main函数创建为线程*/
make_main_thread();
put_str("thread_init done\n");
}
/*当前线程将自己堵塞,标志为stat*/
void thread_block(enum task_status stat){
/*stat取值为TASK_BLOCKED,TASK_WAITING,TASK_HANGING只有这三种状态才不会被调度*/
ASSERT(((stat==TASK_BLOCKED) || (stat == TASK_WAITING) || (stat == TASK_HANGING)));
enum intr_status old_status = intr_disable();
struct task_struct* cur_thread = running_thread();
cur_thread->status = stat ; //置其状态为stat
schedule();//当前线程换下处理器
intr_set_status(old_status);
}
/*将线程pthread解除阻塞*/
void thread_unblock(struct task_struct* pthread){
enum intr_status old_status = intr_disable();
ASSERT(((pthread->status == TASK_BLOCKED) || (pthread->status == TASK_WAITING) || \
pthread->status == TASK_HANGING));
if(pthread->status != TASK_READY){
ASSERT(!elem_find(&thread_ready_list,&pthread->general_tag));
if(elem_find(&thread_ready_list,&pthread->general_tag)){
PANIC("thread_unblock:blocked thread in ready_list\n");
}
list_push(&thread_ready_list,&pthread->general_tag);
//放到队列最前面,使其尽快得到调度
pthread->status = TASK_READY;
intr_set_status(old_status);
}
}
keyboard.c
#include "keyboard.h"
#include "print.h"
#include "interrupt.h"
#include "io.h"
#include "global.h"
#include "ioqueue.h"
#define KBD_BUF_PORT 0x60 //键盘buffer寄存器端口号为0x60
#define esc '\033'
#define backspace '\b'
#define tab '\t'
#define enter '\r'
#define delete '\177'
#define char_invisible 0
#define ctrl_l_char char_invisible
#define ctrl_r_char char_invisible
#define shift_l_char char_invisible
#define shift_r_char char_invisible
#define alt_l_char char_invisible
#define alt_r_char char_invisible
#define caps_lock_char char_invisible
/*定义控制字符的通码和断码*/
#define shift_l_make 0x2a
#define shift_r_make 0x36
#define alt_l_make 0x38
#define alt_r_make 0xe038
#define alt_r_break 0xe0b8
#define ctrl_l_make 0x1d
#define ctrl_r_make 0xe01d
#define ctrl_r_break 0xe09d
#define caps_lock_make 0x3a
/*定义以下变量记录相应键是否按下的状态,
ext_scancode用于记录makecode是否以0xe0开头*/
static bool ctrl_status,shift_status,alt_status,caps_lock_status,ext_scancode;
struct ioqueue kbd_buf; //定义键盘缓冲区
static char keymap[][2]={
{0,0},
{esc,esc},
{'1','!'},
{'2','@'},
{'3','#'},
{'4','$'},
{'5','%'},
{'6','^'},
{'7','&'},
{'8','*'},
{'9','('},
{'0',')'},
{'-','_'},
{'=','+'},
{backspace,backspace},
{tab,tab},
{'q','Q'},
{'w','W'},
{'e','E'},
{'r','R'},
{'t','T'},
{'y','Y'},
{'u','U'},
{'i','I'},
{'o','O'},
{'p','P'},
{'[','{'},
{']','}'},
{enter,enter},
{ctrl_l_char,ctrl_l_char},
{'a','A'},
{'s','S'},
{'d','D'},
{'f','F'},
{'g','G'},
{'h','H'},
{'j','J'},
{'k','K'},
{'l','L'},
{';',':'},
{'\'','"'},
{'`','~'},
{shift_l_char,shift_l_char},
{'\\','|'},
{'z','Z'},
{'x','X'},
{'c','C'},
{'v','V'},
{'b','B'},
{'n','N'},
{'m','M'},
{',','<'},
{'.','>'},
{'/','?'},
{shift_r_char,shift_r_char},
{'*','*'},
{alt_l_char,alt_l_char},
{' ',' '},
{caps_lock_char,caps_lock_char}
};
/*键盘中断处理程序*/
static void intr_keyboard_handler(void){
bool ctrl_down_last = ctrl_status;
bool shift_down_last = shift_status;
bool caps_lock_last = caps_lock_status;
bool break_code ;
uint16_t scancode = inb(KBD_BUF_PORT);
/*若扫描码scancode是e0开头的,表示此键的按下将产生多个扫描码,所以马上结束此次中断处理函数,等待下一个扫描码进来*/
if(scancode == 0xe0){
ext_scancode = true ;
return;
}
/*如果上次是以0xe0开头的,将扫描码合并*/
if(ext_scancode){
scancode = ((0xe000) | scancode);
ext_scancode = false ; //关闭e0标志
}
break_code = ((scancode & 0x0080) != 0);//获取break_code
if(break_code){
//若是段码break_code(按键弹起来时产生的扫描码)
/*由于ctrl_r和alt_r的make_code 和 break_code都是两字节*/
uint16_t make_code = (scancode &= 0xff7f);
/*若是以下三个键弹起来,将状态置为false*/
if(make_code == ctrl_l_make || make_code == ctrl_r_make){
ctrl_status =false;
}else if(make_code == shift_l_make || make_code == shift_r_make){
shift_status = false;
}else if(make_code == alt_l_make || make_code == alt_r_make){
alt_status = false;
}//caps_lock不是弾起来后关闭的,所以要单独处理
return ;
}
/*若是通码,只处理数组中定义的键以及alt_right和ctrl键,全是make_code*/
else if((scancode > 0x00 && scancode < 0x3b) || scancode == alt_r_make || scancode == ctrl_r_make){
bool shift = false;
if((scancode < 0x0e) || (scancode == 0x29) || (scancode == 0x1a) || (scancode == 0x1b) || (scancode==0x2b) || (scancode==0x27) \
|| (scancode==0x28) || (scancode==0x33) || (scancode==0x34) || (scancode==0x35)){
if(shift_down_last){//如果同时按下shift键
shift=true;
}
}
else{ //默认为字母键
if(shift_down_last && caps_lock_last){
//shift和capslock同时按下
shift = false;
}else if (shift_down_last || caps_lock_last){
//如果ishift和capslock任意一个被按下
shift=true;
}else {
shift =false;
}
}
uint8_t index = (scancode &= 0x00ff);
//将扫描码的高字节置0,主要针对高字节是e0的扫描码
char cur_char = keymap[index][shift];
//只处理ASCII码不为0的键
if(cur_char){
if(!ioq_full(&kbd_buf)){
//put_char(cur_char);
ioq_putchar(&kbd_buf,cur_char);
}
return;
}
/*记录本次是否按下了下面几类控制键之一,供下次键入时判断组合键*/
if(scancode == ctrl_l_make || scancode == ctrl_r_make){
ctrl_status= true;
}else if(scancode == shift_l_make || scancode == shift_r_make)
{
shift_status =true;
}else if(scancode == alt_l_make || scancode == alt_r_make){
alt_status = true;
}else if(scancode == caps_lock_make){
//不管之前是否有按下caps_lock键,当再次按下时则状态取反,即已经开启时,再按下同样的键是关闭的,关闭时,按下表示开启
caps_lock_status =! caps_lock_status;
}
}else {
put_str("unknow key\n");
}
}
/*键盘初始化*/
void keyboard_init(){
put_str("keyboard init start \n");
register_handler(0x21,intr_keyboard_handler);
put_str("keyboard init done\n");
}
ioqueue.c
#include "ioqueue.h"
#include "interrupt.h"
#include "global.h"
#include "debug.h"
#include "thread.h"
/*初始化io队列ioq*/
void ioqueue_init(struct ioqueue* ioq){
lock_init(&ioq->lock); //
ioq->producer = ioq->consumer = NULL;
ioq->head = ioq->tail = 0;
}
/*返回pos在缓冲区中的下一个位置值*/
static int32_t next_pos(int32_t pos){
return (pos + 1) % bufsize;
}
/*判断队列是否已满*/
bool ioq_full(struct ioqueue* ioq){
ASSERT(intr_get_status() == INTR_OFF);
return next_pos(ioq->head) == ioq->tail;
}
/*判断队列是否已经空*/
bool ioq_empty(struct ioqueue* ioq){
ASSERT(intr_get_status() == INTR_OFF);
return ioq->head == ioq->tail;
}
/*使当前生产者或消费者在此缓冲区上等待*/
static void ioq_wait(struct task_struct** waiter){
ASSERT(*waiter == NULL && waiter != NULL);
*waiter = running_thread();
thread_block(TASK_BLOCKED);
}
/*唤醒waiter*/
static void wakeup(struct task_struct** waiter){
ASSERT(*waiter != NULL);
thread_unblock(*waiter);
*waiter = NULL;
}
/*消费者从ioq队列中获取一个字符*/
char ioq_getchar(struct ioqueue* ioq){
ASSERT(intr_get_status() == INTR_OFF);
/*若缓冲区为空,把消费者ioq->consumer记录为当前线程自己,
目的是将来生产者往缓冲区里面装商品后,生产者知道唤醒哪个消费者
也就是唤醒当前线程自己*/
while(ioq_empty(ioq)){
lock_acquire(&ioq->lock);
ioq_wait(&ioq->consumer);
lock_release(&ioq->lock);
}
char byte = ioq->buf[ioq->tail]; //从缓冲区中读出
ioq->tail = next_pos(ioq->tail);//把读游标移到下一个位置
if(ioq->producer != NULL){
wakeup(&ioq->producer); //唤醒生产者
}
return byte;
}
/*生产者往ioq队列中写入一个字符byte*/
void ioq_putchar(struct ioqueue* ioq,char byte){
ASSERT(intr_get_status()==INTR_OFF);
/*若缓冲区已经满了,把生产者ioq->producer记为自己,
为的就是当缓冲区里的东西被消费者取完后,让消费者知道唤醒哪个生产者*/
//也就是唤醒当前线程自己
while(ioq_full(ioq)){
lock_acquire(&ioq->lock);
ioq_wait(&ioq->producer);
lock_release(&ioq->lock);
}
ioq->buf[ioq->head]=byte;
ioq->head = next_pos(ioq->head); //把字节放入缓冲区中
if(ioq->consumer != NULL){ //把写游标移动到下一个位置
wakeup(&ioq->consumer); //唤醒消费者
}
}
ioqueue.h
#ifndef __DEVICE_IOQUEUE_H
#define __DEVICE_IOQUEUE_H
#include "stdint.h"
#include "thread.h"
#include "sync.h"
#define bufsize 64
struct ioqueue{
/*生产者消费者问题 */
struct lock lock;
/*生产者,缓冲区不满时就继续往里面放数据
否则就休眠,此项记录哪个生产者在此缓冲区上睡眠*/
struct task_struct* producer;
/*消费者,缓冲区不空时就继续从里面拿数据
否则就休眠,此项记录哪个消费者在此缓冲区休眠*/
struct task_struct* consumer;
char buf[bufsize]; //缓冲区大小
int32_t head; //队首,数据往队首处写入
int32_t tail; //队尾,数据从队尾处读出
};
void ioqueue_init(struct ioqueue* ioq);
static int32_t next_pos(int32_t pos);
bool ioq_full(struct ioqueue* ioq);
bool ioq_empty(struct ioqueue* ioq);
static void ioq_wait(struct task_struct** waiter);
static void wakeup(struct task_struct** waiter);
char ioq_getchar(struct ioqueue* ioq);
void ioq_putchar(struct ioqueue* ioq,char byte);
#endif