七、输入输出系统:用锁实现输出、编写键盘驱动程序、环形输入缓冲区
来源:互联网 发布:大数据 算法 编辑:程序博客网 时间:2024/06/05 03:39
锁
信号量
线程的阻塞与唤醒
/* 当前线程将自己阻塞,标志其状态为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 */ 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);}
锁的实现
信号量的结构
/* 信号量结构 */struct semaphore { uint8_t value; struct list waiters;};
锁结构:这个锁目前的持有者、信号量、等待序列、持有者重复申请锁的次数
struct lock { struct task_struct* holder; // 锁的持有者 struct semaphore semaphore; // 用二元信号量实现锁 uint32_t holder_repeat_nr; // 锁的持有者重复申请锁的次数};
锁 down 操作:如果信号量为1,线程获得锁,信号量减1。如果信号量为0,线程阻塞自己,加入该锁的阻塞队列,调用调度函数换上下一个线程,等待唤醒。
同理,也要关中断,如果不关中断,中断继续,时间片减1,当还没有获得锁时候就被中断换下处理器,下一个线程也去执行临界区,因为上个线程没有down锁,此时信号量还为1,就可以获得锁,继续访问公共资源。换下处理器后,上个线程开始继续执行,它已经判断过信号量了,但是不知道公共资源被抢占了,继续运行,引起了竞争。所以要关中断。
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)); /* 当前线程不应该已在信号量的waiters队列中 */ 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); // 阻塞线程,直到被唤醒 }/* 若value为1或被唤醒后,会执行下面的代码,也就是获得了锁。*/ psema->value--; ASSERT(psema->value == 0); /* 恢复之前的中断状态 */ intr_set_status(old_status);}
锁 up 操作:将信号量加1,并换下该锁的阻塞列表中一个线程。
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);}
获取锁:线程对 锁 进行 down 操作
void lock_acquire(struct lock* plock){/* 排除曾经自己已经持有锁但还未将其释放的情况*/if (plock->holder != running_thread()){sema_down(&plock->semaphore); // 对信号量P操作,原子操作plock->holder = running_thread();ASSERT(plock->holder_repeat_nr == 0);plock->holder_repeat_nr = 1;}else{plock->holder_repeat_nr++;}}
释放锁:
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操作,也是原子操作}
用锁实现终端输出
我们通过给临界区加锁,可以实现互斥。临界区就是 put_char、put_str、put_int,这几个程序。
/* 终端中输出字符 */void console_put_char(uint8_t char_asci) { lock_acquire(); put_char(char_asci); lock_release();}
从键盘获取输入
8048必然要和8042达成一个协议:键盘上的每个物理按键都是唯一数值。因此所有的按键对应的数值便组成了一张 “ 按键-数值 ” 编码映射表--键盘扫描码。它不仅记录按键被按下时对应的编码,也记录按键被松开时的编码。 因此一个按键即有两个码:按下去时的编码叫做通码--makecode ,按键按住不动时候会持续产生相同的码,直到按键被松开时终止产生。按键被松开时产生的编码叫 断码 --breakcode。一个键的扫描码是由通码和断码组成。
无论是按下键还是松开键,当键的状态改变时,键盘的8048芯片把按键对应的扫描码发送到主板上的8042芯片,8042芯片处理后保存到自己的寄存器中,然后向8259A发送中断信号,处理器去执行键盘的中断处理程序。
扫描码
完整的击键操作完整过程:按下、按下保持、弹起三个阶段。这三个阶段8048向8042发扫描码,8042都会向8259A发中断,即按下按键发中断、持续按着不松手持续发中断、按键弹起发中断。击键产生的扫描码是由键盘中的8048传给主板上的8042的,8042将扫描码转码处理后存入自己的 输出缓冲区寄存器(8位宽度) 中,然后向8059A发中断信号,我们的键盘中断程序读取8042的输出缓冲区寄存器,会获得键盘扫描码。
8042
很多外部设备都有自己的处理器,来分担CPU的工作,比如显卡的处理器是GPU,系欸你拍的处理器就是 8048 和 8042,都有自己的寄存器和内存。8048位于键盘中,负责监控按键扫描码和对键盘设置。8042位于主板的南桥芯片中,是键盘的IO接口,8042有4个8位的寄存器。
键盘中断处理程序
环形输入缓冲区
生产者与消费者问题
环形缓冲区的实现
- 七、输入输出系统:用锁实现输出、编写键盘驱动程序、环形输入缓冲区
- 七、输入、输出系统
- C++ 无锁环形缓冲区实现
- 一个免锁环形缓冲区的实现
- 环形缓冲区的实现
- 环形缓冲区的实现
- 环形缓冲区的实现
- 环形缓冲区的实现
- STM32F10x 利用环形缓冲区的串口驱动程序
- 七、输入/输出流--streambuffer类介绍--缓冲区迭代器
- 七、输入/输出流--streambuffer类介绍--自定义缓冲区
- 环形缓冲区的实现原理
- 环形缓冲区的实现原理
- 环形缓冲区的c实现
- 环形缓冲区的实现原理
- android如何实现环形缓冲区
- 环形缓冲区的实现原理
- FIFO环形缓冲区的实现
- TCP/IP报文头部结构整理
- centos7安装redis
- 反射基础之方法调用
- day_12选择排序 Date等常用类
- 使用mongodb+pytest+allure+jenkins构建api接口自动化测试
- 七、输入输出系统:用锁实现输出、编写键盘驱动程序、环形输入缓冲区
- 让你轻松处理java异常
- 实现APP中轮播图的展示
- Microsoft Office
- MyBatis 通用 Mapper 实现原理
- evenbus的简单使用方法
- kubernetes-dashboard
- getDeclaredMethod和getMethod的区别
- mariadb事务级别