ucore实验七

同步互斥的设计与实现

实验执行流程概述

互斥是指某一资源同时只允许一个进程对其进行访问,具有唯一性和排它性,但互斥不用限制进程对资源的访问顺序,即访问可以是无序的。

同步是指在进程间的执行必须严格按照规定的某种先后次序来运行,即访问是有序的,这种先后次序取决于要系统完成的任务需求。在进程写资源情况下,进程间要求满足互斥条件。在进程读资源情况下,可允许多个进程同时访问资源。

在实验七中的ucore初始化过程,开始的执行流程都与实验六相同,直到执行到创建第二个内核线程init_main时,修改了init_main的具体执行内容,即增加了check_sync函数的调用,而位于lab7_figs/kern/sync/check_sync.c中的check_sync函数可以理解为是实验七的起始执行点,是实验七的总控函数。进一步分析此函数,可以看到这个函数主要分为了两个部分,第一部分是实现基于信号量的哲学家问题,第二部分是实现基于管程的哲学家问题。

# 该函数只是完成了初始化工作,并没有开始真正的往哲学家上调度。
229 void check_sync(void){                                                                                                                               
230 
231     int i;
232 
233     //check semaphore
        # 将mutex.value设置为1
        # 初始化作为临界区的信号量
234     sem_init(&mutex, 1);
        # N是5
        # 初始化N个哲学家
235     for(i=0;i<N;i++){
            # 将第i个哲学家的信号量value值初始化为0。
236         sem_init(&s[i], 0);
            # 每个哲学家对应一个内核线程(注意,此时还没开始调度到哲学家线程呢!!!)
237         int pid = kernel_thread(philosopher_using_semaphore, (void *)i, 0);
238         if (pid <= 0) {
239             panic("create No.%d philosopher_using_semaphore failed.\n");
240         }
            # 指向每个哲学家自己的PCB
241         philosopher_proc_sema[i] = find_proc(pid);
242         set_proc_name(philosopher_proc_sema[i], "philosopher_sema_proc");
243     }
244 
245     //check condition variable
246     monitor_init(&mt, N);
247     for(i=0;i<N;i++){
248         state_condvar[i]=THINKING;      
249         int pid = kernel_thread(philosopher_using_condvar, (void *)i, 0);
250         if (pid <= 0) {
251             panic("create No.%d philosopher_using_condvar failed.\n");
252         }
253         philosopher_proc_condvar[i] = find_proc(pid);
254         set_proc_name(philosopher_proc_condvar[i], "philosopher_condvar_proc");
255     }
256 }

相关数据结构与宏

  8 typedef struct {
  9     int value;
 10     wait_queue_t wait_queue;
 11 } semaphore_t;                                                                                                                                       
  # 计算左邻右邻
  7 #define N 5 /* 哲学家数目 */
  8 #define LEFT (i-1+N)%N /* i的左邻号码 */                                                                                                             
  9 #define RIGHT (i+1)%N /* i的右邻号码 */

 76 //---------- philosophers problem using semaphore ----------------------
 77 int state_sema[N]; /* 记录每个人状态的数组 */
 78 /* 信号量是一个特殊的整型变量 */
 79 semaphore_t mutex; /* 临界区互斥 */
 80 semaphore_t s[N]; /* 每个哲学家一个信号量 */
 81     
 82 struct proc_struct *philosopher_proc_sema[N];                                                                                                        

sem_init()

 10 void
 11 sem_init(semaphore_t *sem, int value) {                                                                                                              
 12     sem->value = value;
        # 初始化队列,其实就是初始化链表
 13     wait_queue_init(&(sem->wait_queue));
 14 }

philosopher_using_semaphore()

# 一共有五个线程(对应五个哲学家),每个线程的执行体都是这个
112 int philosopher_using_semaphore(void * arg) /* i:哲学家号码,从0到N-1 */                                                                            
113 {
114     int i, iter=0;
115     i=(int)arg;
116     cprintf("I am No.%d philosopher_sema\n",i);
117     while(iter++<TIMES)
118     { /* 无限循环 */
119         cprintf("Iter %d, No.%d philosopher_sema is thinking\n",iter,i); /* 哲学家正在思考 */
            # 正在“思考”
120         do_sleep(SLEEP_TIME);
            # 得到两只叉子,并进入eating状态,表示真正开吃
121         phi_take_forks_sema(i);
122         /* 需要两只叉子,或者阻塞 */
            # 能走到这里,就说明阻塞状态解除,而能够解除阻塞状态,就说明一定进入了“开吃”
            # 状态拿到了叉子。
123         cprintf("Iter %d, No.%d philosopher_sema is eating\n",iter,i); /* 进餐 */
            # 开吃SLEEP_TIME时间
124         do_sleep(SLEEP_TIME);
            # 把叉子放回去,同时唤醒旁边进入阻塞状态的哲学家,把叉子给他们(如果他们阻塞了的话)
125         phi_put_forks_sema(i);
126         /* 把两把叉子同时放回桌子 */
127     }
128     cprintf("No.%d philosopher_sema quit\n",i);
129     return 0;
130 }

do_sleep()

928 // do_sleep - set current process state to sleep and add timer with "time"
929 //          - then call scheduler. if process run again, delete timer first.
930 int 
931 do_sleep(unsigned int time) {                                                                                                                        
932     if (time == 0) {       
933         return 0;          
934     }
935     bool intr_flag;        
936     local_intr_save(intr_flag);
937     timer_t __timer, *timer = timer_init(&__timer, current, time);
        # 注意这里修改了线程状态,除非被唤醒,否则该状态的线程是不会被调度的!!!
938     current->state = PROC_SLEEPING; 
939     current->wait_state = WT_TIMER; 
940     add_timer(timer);      
941     local_intr_restore(intr_flag);  
942     
        # 触发调度,让出CPU,让别的线程去跑
        # 等定时器到点儿了还会被调度回来
943     schedule();
        # 能走到这里,说明当前线程已经被唤醒,并且定时器也到点儿了
944 
        # 这里重复删除定时器没问题,里面虽然没有检查,但是不影响。
945     del_timer(timer);      
946     return 0;              
947 } 

phi_take_forks_sema()

 94 void phi_take_forks_sema(int i) /* i:哲学家号码从0到N-1 */                                                                                          
 95 { 
 96         down(&mutex); /* 进入临界区 */  
 97         state_sema[i]=HUNGRY; /* 记录下哲学家i饥饿的事实 */

            # 如果资源充足,这里面会将当前哲学家的信号量加一,表示得到了叉子,
            # 哲学家可以开吃了;否则哲学家的信号历量就始终是0。
 98         phi_test_sema(i); /* 试图得到两只叉子 */
 99         up(&mutex); /* 离开临界区 */    
            # 如果当前哲学家已经拿到了叉子,即当前哲学家的信号量是1,那么这里会让当前哲学家
            # 自己的信号量减一,表示不用阻塞,可以往下进行;
            # 如果当前哲学家没有拿到叉子,即当前哲学家的信号量是0,那么这里会让当前哲学家线程
            # 阻塞,并将当前哲学家线程放到等待队列,调用schedule()开始调度,
            # 先让别的线程去运行。
100         down(&s[i]); /* 如果得不到叉子就阻塞 */
101 }

phi_put_forks_sema()

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

__down()

 32 static __noinline uint32_t __down(semaphore_t *sem, uint32_t wait_state) {                                                                           
 33     bool intr_flag;
 34     local_intr_save(intr_flag);
 35     if (sem->value > 0) {
 36         sem->value --;
 37         local_intr_restore(intr_flag);
 38         return 0;
 39     }
 40     wait_t __wait, *wait = &__wait;
 41     wait_current_set(&(sem->wait_queue), wait, wait_state);
 42     local_intr_restore(intr_flag);
 43     
        # 将本哲学家的线程放入等待队列,然后先让其他哲学家的线程被调度
 44     schedule();
        # 当本哲学家再次走到这里,说明本哲学家已经被再次调度了,而从整个代码来看,能走到这里
        # 只有一种情况,就是其他哲学家执行了phi_put_forks_sema()操作放下了叉子,并在这个
        # 操作里通过phi_test_sema(LEFT)或phi_test_sema(RIGHT)修改了当前哲学家线程的
        # 状态为eating继而通过up()操作将当前哲学家线程取出等待队列,并通过wakeup_wait()
        # 将当前哲学家线程唤醒,这样当前哲学家线程才能走到这里。注意,当当前哲学家线程走到
        # 这里的时候,尽管当前哲学家线程的信号量value还是0,但是当前哲学家的状态已经被更新
        # 为eating,整体结果就和当初没被阻塞一样。

        # 总的来说,即是如果当前哲学家线程因为拿不到叉子而陷入阻塞(自己让自己进入阻塞态),
        # 就只能等着别的拿到叉子的哲学家线程放下叉子,并由这些哲学家线程去修改当前哲学家
        # 线程的状态(开吃状态)继而唤醒当前哲学家线程(让别人唤醒自己,让别人把叉子给自己)。
 45     
 46     local_intr_save(intr_flag);
 47     wait_current_del(&(sem->wait_queue), wait);
 48     local_intr_restore(intr_flag);
 49 
 50     if (wait->wakeup_flags != wait_state) {
 51         return wait->wakeup_flags;
 52     }
 53     return 0;
 54 }

__up()

 16 static __noinline void __up(semaphore_t *sem, uint32_t wait_state) {                                                                                 
 17     bool intr_flag;
 18     local_intr_save(intr_flag);
 19     {   
 20         wait_t *wait;
 21         if ((wait = wait_queue_first(&(sem->wait_queue))) == NULL) {
 22             sem->value ++;
 23         }
 24         else {
 25             assert(wait->proc->wait_state == wait_state);
 26             wakeup_wait(&(sem->wait_queue), wait, wait_state, 1);
 27         }
 28     }
 29     local_intr_restore(intr_flag);
 30 }   

phi_test_sema()

# 从整体看,哲学家线程不仅通过该函数修改自己的状态和信号量value值,还通过该函数去修改自己临近
# 哲学家线程的状态和value值,从而使陷入阻塞的邻近哲学家线程被重新调度并有机会开吃。
 84 void phi_test_sema(i) /* i:哲学家号码从0到N-1 */                                                                                                    
 85 {   
        # 自己饥饿并且左邻和右邻都没在吃
 86     if(state_sema[i]==HUNGRY&&state_sema[LEFT]!=EATING
 87             &&state_sema[RIGHT]!=EATING)
 88     {   
            # 自己开吃
 89         state_sema[i]=EATING;
 90         up(&s[i]);
 91     }
 92 }   

计时器的原理和实现

在传统的操作系统中,计时器是其中一个基础而重要的功能,它提供了基于时间事件的调度机制。在ucore 中,timer 中断(irq0)给操作系统提供了有一定间隔的时间事件,操作系统将其作为基本的调度和计时单位(我们记两次时间中断之间的时间间隔为一个时间片,timer splice)。

基于此时间单位,操作系统得以向上提供基于时间点的事件,并实现基于时间长度的等待和唤醒机制。在每个时钟中断发生时,操作系统产生对应的时间事件。应用程序或者操作系统的其他组件可以以此来构建更复杂和高级的调度。

一个 timer_t 在系统中的存活周期可以被描述如下:

  1. timer_t 在某个位置被创建和初始化,并通过 add_timer加入系统管理列表中
  2. 系统时间被不断累加,直到 run_timer_list 发现该 timer_t到期。
  3. run_timer_list更改对应的进程状态,并从系统管理列表中移除该timer_t。

尽管本次实验并不需要填充计时器相关的代码,但是作为系统重要的组件(同时计时器也是调度器的一个部分),你应该了解其相关机制和在ucore中的实现方法。接下来的实验描述将会在一定程度上忽略计时器对调度带来的影响,即不考虑基于固定时间点的调度

sched.h, sched.c 定义了有关timer的各种相关接口来使用 timer 服务,其中主要包括如下内容

typedef struct {……} timer_t

定义了 timer_t 的基本结构,其可以用 sched.h 中的timer_init函数对其进行初始化。

 12 typedef struct {
 13     unsigned int expires;       //the expire time
 14     struct proc_struct *proc;   //the proc wait in this timer. If the expire time is end, then this proc will be scheduled
 15     list_entry_t timer_link;    //the timer list
 16 } timer_t;

void timer_init()

初始化某计时器,让它在 expires 时间片之后唤醒 proc 进程。

 21 // init a timer 
 22 static inline timer_t *
 23 timer_init(timer_t *timer, struct proc_struct *proc, int expires) {
 24     timer->expires = expires;
 25     timer->proc = proc;
 26     list_init(&(timer->timer_link));
 27     return timer;
 28 }

void add_timer()

向系统添加某个初始化过的timer_t,该计时器在指定时间后被激活,并将对应的进程唤醒至runnable(如果当前进程处在等待状态)。

102 // add timer to timer_list
103 void
104 add_timer(timer_t *timer) {
105     bool intr_flag;
106     local_intr_save(intr_flag);
107     {
108         assert(timer->expires > 0 && timer->proc != NULL);
109         assert(list_empty(&(timer->timer_link)));
110         list_entry_t *le = list_next(&timer_list);

            # 从该循环可以看出定时器链表的原理:
            # 整个定时器链表使用的“时间”是唯一的,定时器链表中每个节点上的时间都代表了一个
            # 单独的时间“跨度”,而从定时器链表头开始累加每个节点的expires直到加完当前节点
            # 的expires,表示从现在开始到当前节点的总定时时间!
            # 每当插入一个新的定时器的时候,要好好计算当前时间和这个新的定时器的时间,把新的
            # 定时器的时间从定时器链表里面的减掉才能把新的定时器加到定时器链表。
111         while (le != &timer_list) {
112             timer_t *next = le2timer(le, timer_link);
113             if (timer->expires < next->expires) {                                                                                                    
114                 next->expires -= timer->expires;
115                 break;
116             }
117             timer->expires -= next->expires;
118             le = list_next(le);
119         }
120         list_add_before(le, &(timer->timer_link));
121     }
122     local_intr_restore(intr_flag);
123 }

void del_timer()

向系统删除(或者说取消)某一个计时器。该计时器在取消后不会被系统激活并唤醒进程。

125 // del timer from timer_list
126 void
127 del_timer(timer_t *timer) {
128     bool intr_flag;
129     local_intr_save(intr_flag);
130     {
            # 根据定时器的原理,由于定时器链表中任何一个节点都代表了从当前开始到未来中间的某一
            # 个时间段,所以当删除一个没到时间的定时器时,必须把该定时器涵盖的那个时间段加到
            # 它后面的那个节点上,以保证整个定时器链表所涉及的时间是完整的。
131         if (!list_empty(&(timer->timer_link))) {
132             if (timer->expires != 0) {
133                 list_entry_t *le = list_next(&(timer->timer_link));
134                 if (le != &timer_list) {
135                     timer_t *next = le2timer(le, timer_link);
136                     next->expires += timer->expires;
137                 }
138             }
139             list_del_init(&(timer->timer_link));
140         }
141     }
142     local_intr_restore(intr_flag);
143 }

void run_timer_list()

更新当前系统时间点,遍历当前所有处在系统管理内的计时器,找出所有应该激活的计数器,并激活它们。该过程在且只在每次计时器中断时被调用。在ucore 中,其还会调用调度器事件处理程序。

145 // call scheduler to update tick related info, and check the timer is expired? If expired, then wakup proc
146 void
147 run_timer_list(void) {
148     bool intr_flag;
149     local_intr_save(intr_flag);
150     {
151         list_entry_t *le = list_next(&timer_list);
152         if (le != &timer_list) {
153             timer_t *timer = le2timer(le, timer_link);
154             assert(timer->expires != 0);
155             timer->expires --;
                # 注意这个循环很讲究,这里其实是将所有到点儿的定时器都触发。
                # 基于定时器链表的原理,某一时刻可能有多个定时器到点儿,这些链表节点一定都是
                # 相邻的,且expires肯定都是0,所以这里就是将所有到点儿的定时器全部触发。
156             while (timer->expires == 0) {
157                 le = list_next(le);
158                 struct proc_struct *proc = timer->proc;
159                 if (proc->wait_state != 0) {
160                     assert(proc->wait_state & WT_INTERRUPTED);
161                 }
162                 else {
163                     warn("process %d's wait_state == 0.\n", proc->pid);
164                 }
                    # 唤醒到点儿了的进程
165                 wakeup_proc(proc);
166                 del_timer(timer);
167                 if (le == &timer_list) {
168                     break;
169                 }
170                 timer = le2timer(le, timer_link);
171             }
172         }
173         sched_class_proc_tick(current);
174     }
175     local_intr_restore(intr_flag);
176 }

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值