操作系统真相还原——输入输出系统

文章详细阐述了信号量和锁在操作系统中实现线程同步的原理,以及如何通过它们解决生产者消费者问题。同时,分析了GP异常产生的原因,主要涉及显示内存操作的不完整性。此外,还介绍了键盘输入的基本原理,包括8042芯片的角色和环形缓冲区的概念。
摘要由CSDN通过智能技术生成

显示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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值