ucore学习笔记_LAB7
练习1: 理解内核级信号量的实现和基于内核级信号量的哲学家就餐问题(不需要编码)
信号量结构体:
typedef struct { int value;//资源数目 wait_queue_t wait_queue;//等待队列 } semaphore_t;
P,V操作:
static __noinline void __up(semaphore_t *sem, uint32_t wait_state) { bool intr_flag; local_intr_save(intr_flag); { wait_t *wait; //如果没有正在等待的进程,直接资源++ if ((wait = wait_queue_first(&(sem->wait_queue))) == NULL) { sem->value ++; } //否则唤醒位于等待队列中的进程 else { assert(wait->proc->wait_state == wait_state); wakeup_wait(&(sem->wait_queue), wait, wait_state, 1); } } local_intr_restore(intr_flag); } static __noinline uint32_t __down(semaphore_t *sem, uint32_t wait_state) { bool intr_flag; local_intr_save(intr_flag);//关中断 //如果资源数大于零直接资源--,返回 if (sem->value > 0) { sem->value --; local_intr_restore(intr_flag); return 0; } wait_t __wait, *wait = &__wait; wait_current_set(&(sem->wait_queue), wait, wait_state);//挂起 local_intr_restore(intr_flag);//开中断 schedule();//等待被唤醒 local_intr_save(intr_flag); //重新调度 wait_current_del(&(sem->wait_queue), wait); local_intr_restore(intr_flag); if (wait->wakeup_flags != wait_state) { return wait->wakeup_flags; } return 0; }
底层支持:
定时器、屏蔽/使能中断、等待队列wait_queue支持test_and_set_bit等原子操作机器指令
在ucore信号量中使用的是开关中断.
练习2: 完成内核级条件变量和基于内核级条件变量的哲学家就餐问题(需要编码)
管程由四部分组成:
管程内部的共享变量;
管程内部的条件变量;
管程内部并发执行的进程;
对局部于管程内部的共享数据设置初始值的语句。
条件变量(CV)和管程结构体:
typedef struct condvar{ semaphore_t sem; // the sem semaphore is used to down the waiting proc, and the signaling proc should up the waiting proc int count; // the number of waiters on condvar monitor_t * owner; // the owner(monitor) of this condvar } condvar_t; typedef struct monitor{ semaphore_t mutex; // the mutex lock for going into the routines in monitor, should be initialized to 1 semaphore_t next; // the next semaphore is used to down the signaling proc itself, and the other OR wakeuped waiting proc should wake up the sleeped signaling proc. int next_count; // the number of of sleeped signaling proc condvar_t *cv; // the condvars in monitor } monitor_t;
哎这个next一开始搞得我很迷,其实就是说由于唤醒别人而自己睡着的进程个数。他们被唤醒是无条件的,只要保证对管程的互斥访问即可。
每一个条件变量有它自己的等待队列,那么这个next是由于唤醒了别人,自己被挂在next上了,每个进程执行结束之后先查一查有没有挂在next上的进程,有的话先唤醒这个进程(Vnext),没有的话就放其他进程进来(Vmutex)。
条件等待和唤醒:
void cond_signal (condvar_t *cvp) { //LAB7 EXERCISE1: YOUR CODE cprintf("cond_signal begin: cvp %x, cvp->count %d, cvp->owner->next_count %d\n", cvp, cvp->count, cvp->owner->next_count); /* * cond_signal(cv) { * if(cv.count>0) { * mt.next_count ++; * signal(cv.sem); * wait(mt.next); * mt.next_count--; * } * } */ if(cvp->count>0) { cvp->owner->next_count ++; up(&(cvp->sem)); down(&(cvp->owner->next)); cvp->owner->next_count --; } cprintf("cond_signal end: cvp %x, cvp->count %d, cvp->owner->next_count %d\n", cvp, cvp->count, cvp->owner->next_count); } // Suspend calling thread on a condition variable waiting for condition Atomically unlocks // mutex and suspends calling thread on conditional variable after waking up locks mutex. Notice: mp is mutex semaphore for monitor's procedures void cond_wait (condvar_t *cvp) { //LAB7 EXERCISE1: YOUR CODE cprintf("cond_wait begin: cvp %x, cvp->count %d, cvp->owner->next_count %d\n", cvp, cvp->count, cvp->owner->next_count); /* * cv.count ++; * if(mt.next_count>0) * signal(mt.next) * else * signal(mt.mutex); * wait(cv.sem); * cv.count --; */ cvp->count++; if(cvp->owner->next_count > 0) up(&(cvp->owner->next)); else up(&(cvp->owner->mutex)); down(&(cvp->sem)); cvp->count --; cprintf("cond_wait end: cvp %x, cvp->count %d, cvp->owner->next_count %d\n", cvp, cvp->count, cvp->owner->next_count); }
管程中函数的入口出口设计
为了让整个管程正常运行,还需在管程中的每个函数的入口和出口增加相关操作,即:
function_in_monitor (…) { sem.wait(monitor.mutex); //----------------------------- the real body of function; //----------------------------- if(monitor.next_count > 0) sem_signal(monitor.next); else sem_signal(monitor.mutex); }
这样带来的作用有两个,(1)只有一个进程在执行管程中的函数。(2)避免由于执行了cond_signal函数而睡眠的进程无法被唤醒。对于第二点,如果进程A由于执行了cond_signal函数而睡眠(这会让monitor.next_count大于0,且执行sem_wait(monitor.next)),则其他进程在执行管程中的函数的出口,会判断monitor.next_count是否大于0,如果大于0,则执行sem_signal(monitor.next),从而执行了cond_signal函数而睡眠的进程被唤醒。上诉措施将使得管程正常执行。
吐槽
不得不说,MOOC上这里讲的不太清楚,那个next绕来绕去,其实是很容易能解释清楚的,我们这里实现的管程属于Hoare语义,即
- 线程B执行signal之后,迅速唤醒等待中的线程A,自己进入signal队列中(这个队列是此语义特有的)
- 每次有线程退出时,先到signal队列中调度线程,如果signal队列空,才到入口等待队列调度线程
- 实际实现中一般不采用,因为需要多一个队列,代价增大了
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步