实验七:同步互斥
练习0:填写已有实验
使用meld可以简单地将前几个lab的代码填入lab7中,但是要注意在这次实验中,部分代码需要做出修改,如下,主要是trap_dispatch
这一个函数
kern/trap/trap.c
中lab6的部分代码
...
ticks++;
assert(current != NULL);
run_timer_list(); //lab6中的处理方式是临时的,lab7开始启动计时器机制,具体实现在练习1中解释
break;
...
练习1:理解内核级信号量的实现和基于内核级信号量的哲学家就餐问题
1、同步互斥机制的底层实现
计时器
计时器通过定义在kern/schedule/sched.[ch]
中的函数完成,计时器提供了基于时间事件的调节机制,在ucore中利用计时器可以实现基于时间长度的睡眠等待和唤醒机制,每当时钟中断发生时,ucore就可以产生相应的时间事件。计时器相关的数据结构和基本操作如下:
sched.h
中定义了timer_t
的基本数据结构
typedef struct {
unsigned int expires; //the expire time 计时长度
struct proc_struct *proc; //the proc wait in this timer. If the expire time is end, then this proc will be scheduled 该计时器对应的进程
list_entry_t timer_link; //the timer list 计时器链表
} timer_t;
timer_init
对计时器初始化
static inline timer_t *
timer_init(timer_t *timer, struct proc_struct *proc, int expires) {
timer->expires = expires; //初始化计时长度
timer->proc = proc; //初始化计时器绑定的进程
list_init(&(timer->timer_link)); //初始化计时器链表
return timer;
}
add_timer
向系统添加已初始化的新计时器
void
add_timer(timer_t *timer) {
bool intr_flag;
local_intr_save(intr_flag);
{
assert(timer->expires > 0 && timer->proc != NULL);
assert(list_empty(&(timer->timer_link)));
list_entry_t *le = list_next(&timer_list);
while (le != &timer_list) { //while循环的作用在于将一个计时器放入到合适的位置,结合del_timer可以看出,每个计时器实际计时的值为计时队列在这个计时器之前的计时值之和
timer_t *next = le2timer(le, timer_link);
if (timer->expires < next->expires) { //例如新计时器值为5,已有的计时队列为2->3->6->7
next->expires -= timer->expires; //则5-2=3,3-3=0,最终新队列为2->3->0->6->7,显然当第1、2个计时器都走完时第3个计时器走0步就走完,符合初始值2+3=5
break; //因此调用run_timer_list时每次只需要减少第一个计时器的值
}
timer->expires -= next->expires;
le = list_next(le);
}
list_add_before(le, &(timer->timer_link));
}
local_intr_restore(intr_flag);
}
del_timer
取消一个计时器
void
del_timer(timer_t *timer) {
bool intr_flag;
local_intr_save(intr_flag);
{
if (!list_empty(&(timer->timer_link))) {
if (timer->expires != 0) {
list_entry_t *le = list_next(&(timer->timer_link));
if (le != &timer_list) {
timer_t *next = le2timer(le, timer_link);
next->expires += timer->expires; //结合add_timer的机制可以看出,取消后只需要在下一个计时器上加上取消的计时器的当前计时值就可以保证后续每一个计时器的实际计时值都与设定值一致
}
}
list_del_init(&(timer->timer_link));
}
}
local_intr_restore(intr_flag);
}
run_timer_list
更新系统计时并唤醒计时器归零可以被激活的进程
void
run_timer_list(void) {
bool intr_flag;
local_intr_save(intr_flag);
{
list_entry_t *le = list_next(&timer_list);
if (le != &timer_list) {
timer_t *timer = le2timer(le, timer_link);
assert(timer->expires != 0);
timer->expires --; //只需要在第一个计时器上减1即可,由于进程加入计时的频率应远远小于时钟中断的频率,这样设计计时队列计时值的更新,可以减小开销避免每次时钟中断都要遍历整个计时器队列
while (timer->expires == 0) { //当归零时执行唤醒,由于可能存在后续也为0例如add_timer中注释举得例子,用while循环将所有归零的计时器对应的进程激活
le = list_next(le);
struct proc_struct *proc = timer->proc;
if (proc->wait_state != 0) {
assert(proc->wait_state & WT_INTERRUPTED);
}
else {
warn("process %d's wait_state == 0.\n", proc->pid);
}
wakeup_proc(proc);
del_timer(timer);
if (le == &timer_list) {
break;
}
timer = le2timer(le, timer_link);
}
}
sched_class_proc_tick(current); //执行调度算法
}
local_intr_restore(intr_flag);
}
屏蔽与使能中断
中断的屏蔽与使能通过定义在kern/sync/sync.h
中的函数完成,源码较为简单,基本调用关系如下:
关中断:local_intr_save -> __intr_save -> intr_disable -> cli
开中断:local_intr_restore -> __intr_restore -> intr_enable -> sti
需要用到中断相关的操作时按如下格式即可:
...
bool intr_flag;
local_intr_save(intr_flag);
{
critical code...
}
local_intr_restore(intr_flag);
...
等待队列
等待队列通过定义在kern/sync/wait.[ch]
中的数据结构和函数完成
wait.h
中定义了等待队列的基本数据结构
typedef struct {
list_entry_t wait_head;
} wait_queue_t; //wait_queue的头节点
typedef struct {
struct proc_struct *proc; //与该wait节点绑定的进程指针
uint32_t wakeup_flags; //等待原因标志
wait_queue_t *wait_queue; //指向此wait节点所属的wait_queue头节点