锁
信号量
线程的阻塞与唤醒
/* 当前线程将自己阻塞,标志其状态为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位的寄存器。