ucore lab7 同步互斥

练习0:填写已有实验

对kern/trap/trap.c中的lab6 code中的sched_class_proc_tick(current)改成run_timer_list()

case IRQ_OFFSET + IRQ_TIMER:
        ticks ++;
        assert(current != NULL);
        run_timer_list(); //这里 
        break;

练习1: 理解内核级信号量的实现和基于内核级信号量的哲学家就餐问题(不需要编码)

请在实验报告中给出内核级信号量的设计描述,并说明其大致执行流程。

可以看出内核信号量结构体中就是个值,和队列而已

typedef struct {
    int value;
    wait_queue_t wait_queue;
} semaphore_t;

void sem_init(semaphore_t *sem, int value);
void up(semaphore_t *sem);
void down(semaphore_t *sem);
bool try_down(semaphore_t *sem);

首先分析 up函数(完成V操作),发现只是调用了_up把sem传入了

void
up(semaphore_t *sem) {
    __up(sem, WT_KSEM);//WT_KSEM:  0x00000100  //等待内核信号量
}

然后来看一下_up

大致手段就是禁用中断的方式来处理(在单核有效,多核无效)

禁用中断后,首先判断是否有等待的,没有就把值++

有的话直接唤醒他就行(就是简化了一下++之后在–,所以就直接唤醒就行,方便写)

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)
}

然后看一下down操作(P操作)类似调用了_down

void
down(semaphore_t *sem) {
    uint32_t flags = __down(sem, WT_KSEM);
    assert(flags == 0);
}

首先关中断

然后查看value,如果有的话就–然后恢复中断了

没有value的话,把当前放到等待队列里,然后执行调度(让别人去占用cpu)

然后等别人有value了把自己叫醒

​ 关中断,把等待链表上的自己删掉,恢复中断

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;
}

value>0,表示共享资源的空闲数
vlaue<0,表示该信号量的等待队列里的进程数
value=0,表示等待队列为空

哲学家就餐问题
//---------- philosophers problem using semaphore ----------------------
int state_sema[N]; /* 记录每个人状态的数组 */
/* 信号量是一个特殊的整型变量 */
semaphore_t mutex; /* 临界区互斥 */
semaphore_t s[N];  /* 每个哲学家一个信号量 */

说白了就是 先弄个互斥锁,然后看看自己能抢不,不能让别人到时候调用我的phi_test_sema去抢

void phi_take_forks_sema(int i) /* i:哲学家号码从0到N-1 */
{
    down(&mutex);           /* 进入临界区 */
    state_sema[i] = HUNGRY; /* 记录下哲学家i饥饿的事实 */
    phi_test_sema(i);       /* 试图得到两只叉子 */
    up(&mutex);             /* 离开临界区 */
    down(&s[i]);            /* 如果得不到叉子就阻塞 */
}

这个就是 吃饭的人不吃了,然后看看左边的人想吃不,和右边的人想吃不

void phi_put_forks_sema(int i) /* i:哲学家号码从0到N-1 */
{
    down(&mutex);             /* 进入临界区 */
    state_sema[i] = THINKING; /* 哲学家进餐结束 */
    phi_test_sema(LEFT);      /* 看一下左邻居现在是否能进餐 */
    phi_test_sema(RIGHT);     /* 看一下右邻居现在是否能进餐 */
    up(&mutex);               /* 离开临界区 */
}

整体就是,一个信号量表示公共的互斥操作,五个信号量(也算是条件变量了)表示状态(阻塞,非阻塞)

自己阻塞的时候,通过别人调用下面的函数来唤醒

void phi_test_sema(i) /* i:哲学家号码从0到N-1 */
{
    if (state_sema[i] == HUNGRY && state_sema[LEFT] != EATING && state_sema[RIGHT] != EATING)
    {
        state_sema[i] = EATING;
        up(&s[i]);
    }
}

每个线程的操作函数

int philosopher_using_semaphore(void *arg) /* i:哲学家号码,从0到N-1 */
{
    int i, iter = 0;
    i = (int)arg;
    cprintf("I am No.%d philosopher_sema\n", i);
    while (iter++ < TIMES)
    {                                                                      /* 无限循环 */
        cprintf("Iter %d, No.%d philosopher_sema is thinking\n", iter, i); /* 哲学家正在思考 */
        do_sleep(SLEEP_TIME);
        phi_take_forks_sema(i);
        /* 需要两只叉子,或者阻塞 */
        cprintf("Iter %d, No.%d philosopher_sema is eating\n", iter, i); /* 进餐 */
        do_sleep(SLEEP_TIME);
        phi_put_forks_sema(i);
        /* 把两把叉子同时放回桌子 */
    }
    cprintf("No.%d philosopher_sema quit\n", i);
    return 0;
}

练习2: 完成内核级条件变量和基于内核级条件变量的哲学家就餐问题(需要编码)

内核级条件变量的哲学家就餐问题在check_sync处实现。同信号量的测试相似,这里也是创建了5个内核进程表示5个哲学家的行为。

void check_sync(void){
    int i;
    //check condition variable
    monitor_init(&mt, N);
    for(i=0;i<N;i++){
        state_condvar[i]=THINKING;
        int pid = kernel_thread(philosopher_using_condvar, (void *)i, 0);
        if (pid <= 0) {
            panic("create No.%d philosopher_using_condvar failed.\n");
        }
        philosopher_proc_condvar[i] = find_proc(pid);
        set_proc_name(philosopher_proc_condvar[i], "philosopher_condvar_proc");
    }

每个线程也是调用phi_test_condvar(i)尝试自己获取到叉子,自己获取不到,那就让别人帮自己调用phi_test_condvar这个函数来获取

同时在尝试获取不到叉子时候调用 cond_wait(&mtp->cv[i]);阻塞自己,等待别人唤醒,因为有可能唤醒之后,自己还是没有EATING所以要用循环判断,直到自己获取到了

void phi_take_forks_condvar(int i)
{
    down(&(mtp->mutex));
    //--------into routine in monitor--------------
    // LAB7 EXERCISE1: YOUR CODE
    // I am hungry
    state_condvar[i] = HUNGRY; /* 记录下哲学家i饥饿的事实 */
                            // try to get fork
    phi_test_condvar(i);
    while (state_condvar[i] != EATING)
    {
        cprintf("phi_take_forks_condvar: %d didn't get fork and will wait\n", i);
        cond_wait(&mtp->cv[i]);
    }
    //--------leave routine in monitor--------------
    if (mtp->next_count > 0)
        up(&(mtp->next));
    else
        up(&(mtp->mutex));
}

其中下面是管程的操作

     down(&(mtp->mutex));
    	。。。。
    //--------leave routine in monitor--------------
    if (mtp->next_count > 0)
        up(&(mtp->next));
    else
        up(&(mtp->mutex));
}

一个管程定义了一个数据结构和能为并发进程所执行(在该数据结构上)的一组操作,这组操作能同步进程和改变管程中的数据。管程由以下四部分组成:
①管程内部的共享变量;
②管程内部的条件变量;
③管程内部并发执行的进程;
④对局部于管程内部的共享数据设置初始值的语句。

即一个管程由一个锁和多个条件变量组成。由此可见,管程相当于一个隔离区,它把共享变量和对它进行操作的若干个过程围了起来,所有进程要访问临界资源时,都必须经过管程才能进入,而管程每次只允许一个进程进入管程,从而需要确保进程之间互斥。

typedef struct monitor monitor_t;

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;

管程中的成员变量mutex是一个二值信号量,是实现每次只允许一个进程进入管程的关键元素,确保了互斥访问性质。管程中的条件变量cv通过执行wait_cv,会使得等待某个条件C为真的进程能够离开管程并睡眠,且让其他进程进入管程继续执行;而进入管程的某进程设置条件C为真并执行signal_cv时,能够让等待某个条件C为真的睡眠进程被唤醒,从而继续进入管程中执行。管程中的成员变量信号量next和整形变量next_count是配合进程对条件变量cv的操作而设置的,这是由于发出signal_cv的进程A会唤醒睡眠进程B,进程B执行会导致进程A睡眠,直到进程B离开管程,进程A才能继续执行,这个同步过程是通过信号量next完成的;而next_count表示了由于发出singal_cv而睡眠的进程个数。
然后下面就是实现管程中条件变量(信号量模拟)的函数

void cond_signal(condvar_t *cvp)
{
    if (cvp->count > 0) //确保有人在阻塞
    {
        cvp->owner->next_count++;//管程只能同时有一个线程在运行,所以要把自己挂起来
        up(&cvp->sem);//唤醒别人
        down(&cvp->owner->next);//阻塞自己
        cvp->owner->next_count--;//自己阻塞没了就--
    }
}

void cond_wait(condvar_t *cvp)
{
    cvp->count++;//等待数++
    if(cvp->owner->next_count > 0){ // 如果说别人是因为条件变量阻塞的
        up(&cvp->owner->next);
    }else{//是不是条件变量阻塞的就是互斥锁阻塞
        up(&cvp->owner->mutex);
    }
    down(&cvp->sem);//阻塞自己
    cvp->count--;//接触阻塞
}

下面是哲学家操作

void phi_take_forks_condvar(int i)
{
    down(&(mtp->mutex));
    //--------into routine in monitor--------------
    // LAB7 EXERCISE1: YOUR CODE
    // I am hungry
    state_condvar[i] = HUNGRY; /* 记录下哲学家i饥饿的事实 */                     
    phi_test_condvar(i);  // try to get fork
    while (state_condvar[i] != EATING)
    {
        cprintf("phi_take_forks_condvar: %d didn't get fork and will wait\n", i);
        cond_wait(&mtp->cv[i]);
    }
    //--------leave routine in monitor--------------
    if (mtp->next_count > 0)
        up(&(mtp->next));
    else
        up(&(mtp->mutex));
}

void phi_put_forks_condvar(int i)
{
    down(&(mtp->mutex));

    //--------into routine in monitor--------------
    // LAB7 EXERCISE1: YOUR CODE
    // I ate over
    state_condvar[i] = THINKING;
    // test left and right neighbors
    phi_test_condvar(LEFT);
    phi_test_condvar(RIGHT);
    //--------leave routine in monitor--------------
    if (mtp->next_count > 0)
        up(&(mtp->next));
    else
        up(&(mtp->mutex));
}

以上就是代码操作

总体来说,我有点菜,这节的管程还是没太理解,没办法用简单的话语描述,下去在仔细了解一下把

你可能感兴趣的:(操作系统,c语言)