• ucore lab7 同步互斥机制 学习笔记


    管程的设计实在是精妙,初看的时候觉得非常奇怪,这混乱的进程切换怎么能保证同一时刻只有一个进程访问管程?理清之后大为赞叹,函数中途把前一个进程唤醒后立刻把自己挂起,完美切换.后一个进程又在巧妙的时机将自己唤醒,同时让后一个挂起.看似松散的跳转背后竟然是无比严丝合缝的逻辑,真的就滴水不漏.

    等待状态
    在proc.h中又增加了等待定时器和等待内核信号量的宏供本节使用

    #define WT_INTERRUPTED // the wait state could be interrupted
    #define WT_CHILD       // wait child process
    #define WT_KSEM        // wait kernel semaphore
    #define WT_TIMER       // wait timer
    

    定时器timer

    static list_entry_t timer_list; //定时器链表
    typedef struct {
        unsigned int expires;       //与前一个timer的时间差
        struct proc_struct *proc;   //关联的进程
        list_entry_t timer_link;    //定时器链表,管理所有定时器
    } timer_t;
    

    timer_init: 设置指定timer的proc和expires,并timer_link的next和prev指向自身
    add_timer: 把timer插入到timer_list的适应位置,并调整自身和后一项的expires
    del_timer: 调整后一项的expires,把自身从timer_list里删除
    run_timer_list: timer_list头的时差-1,减到0时唤醒它和它之后的时差为0进程,并从timer_list中删除.返回前调用进程调度框架的proc_tick()
    值得注意在其他项目中我们令一个链表项为空时都是让它的next指向NULL,而在UCORE中则是让next指向自身.

    等待队列

    typedef struct {
        list_entry_t wait_head;
    } wait_queue_t;
    
    typedef struct {
        struct proc_struct *proc;	//关联的进程
        uint32_t wakeup_flags;	//标志
        wait_queue_t *wait_queue;	//所属的等待队列
        list_entry_t wait_link;	//等待队列的链表,决定了自己的位置
    } wait_t;
    

    与之对应的定义了各种增删查找初始化的函数

    信号量semaphore

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

    信号量的本质可以想象成红绿灯,大于0时可以通行,否则就按顺序排队等待.
    value>0: 信号量可用
    value=0: 信号量被占用
    value<0: 等待信号量的进程数

    主要操作为一下四个:
    sem_init(sem,value): 初始化
    down(sem): 申请占用信号量
    up(sem): 释放信号量
    try_down(sem): 检查信号量value>0?,为真的时候令value--并返回1

    down间接调用了__down

    void
    down(semaphore_t *sem) {
        uint32_t flags = __down(sem, WT_KSEM);
        assert(flags == 0); //返回非零,状态异常
    }
    
    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;
        //把当前进程变为SLEEPING态并插入等待队列
        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;
    }
    

    up间接调用了__up

    void
    up(semaphore_t *sem) {
        __up(sem, WT_KSEM);
    }
    
    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);
    }
    

    这时候我们来分析一下基于信号量的哲学家就餐问题

    int state_sema[N]; /* 记录每个人状态的数组 */
    /* 信号量是一个特殊的整型变量 */
    semaphore_t mutex; /* 临界区互斥 */
    semaphore_t s[N]; /* 每个哲学家一个信号量 */
    
    struct proc_struct *philosopher_proc_sema[N];
    
    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]);
        }
    }
    
    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); /* 离开临界区 */
    }
    
    int philosopher_using_semaphore(void * arg) /* i:哲学家号码,从0到N-1 */
    {
        int i, iter=0;
        i=(int)arg;
        cprintf("I am No.%d philosopher_sema
    ",i);
        while(iter++<TIMES)
        { /* 无限循环 */
            cprintf("Iter %d, No.%d philosopher_sema is thinking
    ",iter,i); /* 哲学家正在思考 */
            do_sleep(SLEEP_TIME);
            phi_take_forks_sema(i); 
            /* 需要两只叉子,或者阻塞 */
            cprintf("Iter %d, No.%d philosopher_sema is eating
    ",iter,i); /* 进餐 */
            do_sleep(SLEEP_TIME);
            phi_put_forks_sema(i); 
            /* 把两把叉子同时放回桌子 */
        }
        cprintf("No.%d philosopher_sema quit
    ",i);
        return 0;    
    }
    
    void check_sync(void){
    
        int i;
    
        //check semaphore
        sem_init(&mutex, 1);
        for(i=0;i<N;i++){
            sem_init(&s[i], 0);
            int pid = kernel_thread(philosopher_using_semaphore, (void *)i, 0);
            if (pid <= 0) {
                panic("create No.%d philosopher_using_semaphore failed.
    ");
            }
            philosopher_proc_sema[i] = find_proc(pid);
            set_proc_name(philosopher_proc_sema[i], "philosopher_sema_proc");
        }
    
    	//.......
    
    }
    
    

    注意mutex初始值为1,可以被占用.而s数组值为0.
    假设0号哲学家第一个行动,执行它的take_fork函数.先占用mutex,保证这一时刻只有他能行动.他HUNGRY了,一看左右都没有在EATING,于是把自己设为EATING态,并把信号量s0释放,证明自己现在可以吃了.再把mutex释放,允许别人行动.此时再把s0占用掉,证明自己已经开始吃了.最后返回到using_semaphore里,让自己睡眠一会儿.
    在此期间1号哲学家被调度,不过要等到0号哲学家释放mutex后才能执行take_fork.1号占用到mutex后感觉HUNGRY了,一看左边0号还拿着叉子不放手.没办法就先释放了mutex,等在了自己的s1上.
    风水轮流转,等着0号执行到put_fork,把叉子放下,才能唤醒1号

    管程monitor

    //条件变量结构体
    typedef struct condvar{
        semaphore_t sem;        // 关联的信号量
        int count;              // 等待进程个数
        monitor_t * owner;      // 所属的管程
    } condvar_t;
    
    //管程结构体
    typedef struct monitor{
        semaphore_t mutex;      // 初始值为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;          // 条件变量数组
    } monitor_t;
    
    // Initialize monitor.
    void     
    monitor_init (monitor_t * mtp, size_t num_cv) {
        int i;
        assert(num_cv>0);
        mtp->next_count = 0;
        mtp->cv = NULL;
        sem_init(&(mtp->mutex), 1); //unlocked
        sem_init(&(mtp->next), 0);
        mtp->cv =(condvar_t *) kmalloc(sizeof(condvar_t)*num_cv);
        assert(mtp->cv!=NULL);
        for(i=0; i<num_cv; i++){
            mtp->cv[i].count=0;
            sem_init(&(mtp->cv[i].sem),0);
            mtp->cv[i].owner=mtp;
        }
    }
    
    // Unlock one of threads waiting on the condition variable. 
    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--;
       }
    }
    
    // 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) {
        cvp->count++;
    	if(cvp->owner->next_count>0){
    		up(&(cvp->owner->next));
    	}
    	else{
    		up(&(cvp->owner->mutex));
    	}
    	down(&(cvp->sem));
    	cvp->count--;
    }
    

    基于管程的哲学家就餐问题

    struct proc_struct *philosopher_proc_condvar[N]; // N philosopher
    int state_condvar[N];                            // the philosopher's state: EATING, HUNGARY, THINKING  
    monitor_t mt, *mtp=&mt;                          // monitor
    
    void phi_test_condvar (i) { 
        if(state_condvar[i]==HUNGRY&&state_condvar[LEFT]!=EATING
                &&state_condvar[RIGHT]!=EATING) {
            cprintf("phi_test_condvar: state_condvar[%d] will eating
    ",i);
            state_condvar[i] = EATING ;
            cprintf("phi_test_condvar: signal self_cv[%d] 
    ",i);
            cond_signal(&mtp->cv[i]) ;
        }
    }
    
    
    void phi_take_forks_condvar(int i) {
        down(&(mtp->mutex));
        state_condvar[i]=HUNGRY;
        phi_test_condvar(i);
        if(state_condvar[i]!=EATING){
            cond_wait(&(mtp->cv[i]));
        }
        if(mtp->next_count>0)
            up(&(mtp->next));
        else
            up(&(mtp->mutex));
    }
    
    void phi_put_forks_condvar(int i) {
        down(&(mtp->mutex));
        state_condvar[i]=THINKING;
        phi_test_condvar((i+1)%5);
        phi_test_condvar((i+4)%5);
        if(mtp->next_count>0)
            up(&(mtp->next));
        else
            up(&(mtp->mutex));
    }
    
    //---------- philosophers using monitor (condition variable) ----------------------
    int philosopher_using_condvar(void * arg) { /* arg is the No. of philosopher 0~N-1*/
      
        int i, iter=0;
        i=(int)arg;
        cprintf("I am No.%d philosopher_condvar
    ",i);
        while(iter++<TIMES)
        { /* iterate*/
            cprintf("Iter %d, No.%d philosopher_condvar is thinking
    ",iter,i); /* thinking*/
            do_sleep(SLEEP_TIME);
            phi_take_forks_condvar(i); 
            /* need two forks, maybe blocked */
            cprintf("Iter %d, No.%d philosopher_condvar is eating
    ",iter,i); /* eating*/
            do_sleep(SLEEP_TIME);
            phi_put_forks_condvar(i); 
            /* return two forks back*/
        }
        cprintf("No.%d philosopher_condvar quit
    ",i);
        return 0;    
    }
    
    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.
    ");
            }
            philosopher_proc_condvar[i] = find_proc(pid);
            set_proc_name(philosopher_proc_condvar[i], "philosopher_condvar_proc");
        }
    }
    
    

    monitor_init中会将除mutex以外的所有成员置零
    0号先行动,调用take_forks,占用mutex,检查合法后将自己设为EATING.调用cond_signal,因为cv[0]->count初始值为0,所以直接跳过.然后mtp->next_count为初始值0,释放mutex,返回.
    1号再行动,占用mutex,检查后发现自己不满足EATING条件,调用cond_wait.mtp->next_count为0,释放mutex.cv[1]->count变成1.申请信号量,因为cv[1]->sem->value为0,进入等待状态.
    等到轮完一遍再到0号时,执行put_fork,占用mutex,放下叉子后检查左右的状态,执行1号的cond_signal.此时cv[1]->count为1,满足条件,将monitor的next_count增加1,解除cv[1]->sem的占用,1号进入RUNNABLE态.因为管程中同一时刻只能有一个进程访问,故马上申请占用monitor的next信号量,让自己进入等待状态.
    此时1号得以继续在cont_wait的down中执行.因为是从一个占用管程的进程切换到了另一个占用管程的进程,保证了管程中同一时刻只能有一个进程访问,所以mutex也无须变更.将cv[1]->count变回0,返回.
    再往后,随便哪个n号进程运行到cond_wait时,都会因为next_count>0而唤醒等待next信号量的0号进程.又因为n号进程一定是因为没有进入EATING才执行的cond_wait,所以n号进程一定会在down(cv[n]->sem)处卡住,切换到0号进程,有一次保证了保证了管程中同一时刻只能有一个进程访问.
    0号进程切回后next_count--,直接返回.

  • 相关阅读:
    pt-align的用法简要记录
    percona-toolkit 3.0.13 简单安装记录
    mysql8.0.17复制搭建及其gtid的1062和1032异常
    percona-xtrabackup-8.0.7简单快捷使用
    8.0.17 MySQL Community Server 二进制手工安装
    mysql参数sql_log_bin配置
    ERROR 1040 (08004): Too many connections
    MongoDB 副本集+分片 认证方式搭建
    属性配置文件详解(2)(十七)
    属性配置文件详解(1)(十六)
  • 原文地址:https://www.cnblogs.com/kangyupl/p/12870352.html
Copyright © 2020-2023  润新知