EXER1: 理解内核级信号量的实现和基于内核级信号量的哲学家就餐问题*
Q1: 给出内核级信号量的设计描述,并说明其大致执行流程*
内核级信号量的相关数据结构:
// 等待队列
typedef struct {
list_entry_t wait_head;
} wait_queue_t;
// 等待项
typedef struct {
struct proc_struct *proc; // 等待的进程 proc
uint32_t wakeup_flags; // 进程进入等待的原因标记
wait_queue_t *wait_queue; // 等待项所在的队列
list_entry_t wait_link; // 组织等待队列的连接
} wait_t;
// 信号量
typedef struct {
int value; // 计数值
wait_queue_t wait_queue; // 等待队列
} semaphore_t;
信号量最主要的还是 PV 操作:
// up 相当于信号量的 V 操作
static __noinline void __up(semaphore_t *sem, uint32_t wait_state) {
bool intr_flag;
local_intr_save(intr_flag); // 中断保证原子性
{
wait_t *wait;
// wait_queue 为 FIFO,在信号量对应的等待队列中获取等待项
if ((wait = wait_queue_first(&(sem->wait_queue))) == NULL) {
sem->value ++; // 资源没人要,
}
else {
// 如果有等待信号量的进程,则唤醒进程 wakeup_wait -> wakeup_proc 添加进程到就绪队列
assert(wait->proc->wait_state == wait_state);
wakeup_wait(&(sem->wait_queue), wait, wait_state, 1);
}
}
local_intr_restore(intr_flag);
}
// down 相当于信号量的 P 操作
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);
// 当前进程进入 PROC_SLEEPING 状态,让出 cpu 控制权
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;
}
up 操作在有等待进程时唤醒等待队列头的进程,若无,资源+1;down 操作在资源计数器=0 时阻塞并把自身加入等待队列队尾,否则资源-1。
哲学家就餐问题的流程:
int state_sema[N]; // 记录每个人状态的数组
semaphore_t mutex; // 锁
semaphore_t s[N]; // 每个哲学家一个信号量
struct proc_struct *philosopher_proc_sema[N];
void phi_test_sema(i)
{
// 想吃且两边没人吃
if(state_sema[i] == HUNGRY && state_sema[LEFT] != EATING
&& state_sema[RIGHT] != EATING)
{
state_sema[i]=EATING;
up(&s[i]); // 第 i 个信号量资源已顺备好
}
}
void phi_take_forks_sema(int i)
{
down(&mutex);
state_sema[i] = HUNGRY;
phi_test_sema(i);
up(&mutex);
down(&s[i]); // 使用资源,无法使用则阻塞
void phi_put_forks_sema(int i)
{
down(&mutex);
state_sema[i]=THINKING;
phi_test_sema(LEFT); // 释放左资源
phi_test_sema(RIGHT); // 释放右资源
up(&mutex);
}
int philosopher_using_semaphore(void * arg)
{
int i, iter=0;
i=(int)arg;
while(iter++ < TIMES) {
do_sleep(SLEEP_TIME); // 思考
phi_take_forks_sema(i); // 获取资源或阻塞
do_sleep(SLEEP_TIME); // 吃
phi_put_forks_sema(i); // 释放资源
}
return 0;
}
简单描述流程就是:哲学家需要筷子时,先上锁,在临界区检测左右状态,如果自己可以就餐就 V 一次自己的信号量,在临界区外,down 一次自己的信号量。刚才的一次可能的 V,或者其他哲学家放下叉子的 V,为这次 down 提供所需的资源。哲学家需要放下筷子时,先上锁,在临界区内检测左右状态,如果左右邻居可以就餐,则 V 对应的资源。
Q2: 给出给用户态进程/线程提供信号量机制的设计方案,并比较说明给内核级提供信号量机制的异同。*
内核态信号量封装成系统调用供用户进程使用。
futex,用户态和内核态混合的同步机制。在用户态检查,如果有竞争才执行系统调用。
最后更新: November 26, 2020