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

等待状态

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

  1. #define WT_INTERRUPTED // the wait state could be interrupted
  2. #define WT_CHILD // wait child process
  3. #define WT_KSEM // wait kernel semaphore
  4. #define WT_TIMER // wait timer

定时器timer

  1. static list_entry_t timer_list; //定时器链表
  2. typedef struct {
  3. unsigned int expires; //与前一个timer的时间差
  4. struct proc_struct *proc; //关联的进程
  5. list_entry_t timer_link; //定时器链表,管理所有定时器
  6. } 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指向自身.

等待队列

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

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

信号量semaphore

  1. typedef struct {
  2. int value;
  3. wait_queue_t wait_queue;
  4. } 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

  1. void
  2. down(semaphore_t *sem) {
  3. uint32_t flags = __down(sem, WT_KSEM);
  4. assert(flags == 0); //返回非零,状态异常
  5. }
  6. static __noinline uint32_t __down(semaphore_t *sem, uint32_t wait_state) {
  7. bool intr_flag;
  8. local_intr_save(intr_flag);
  9. if (sem->value > 0) {//有剩余直接占用并返回
  10. sem->value --;
  11. local_intr_restore(intr_flag);
  12. return 0;
  13. }
  14. //没有剩余的时候继续执行
  15. wait_t __wait, *wait = &__wait;
  16. //把当前进程变为SLEEPING态并插入等待队列
  17. wait_current_set(&(sem->wait_queue), wait, wait_state);
  18. local_intr_restore(intr_flag);
  19. //使能中断,切换进程
  20. schedule();
  21. //返回时自己已被唤醒,即等到了申请的信号量
  22. local_intr_save(intr_flag);
  23. wait_current_del(&(sem->wait_queue), wait);//从等待队列中删除
  24. local_intr_restore(intr_flag);
  25. if (wait->wakeup_flags != wait_state) {
  26. return wait->wakeup_flags;//等待状态与唤醒状态不符,返回异常
  27. }
  28. return 0;
  29. }

up间接调用了__up

  1. void
  2. up(semaphore_t *sem) {
  3. __up(sem, WT_KSEM);
  4. }
  5. static __noinline void __up(semaphore_t *sem, uint32_t wait_state) {
  6. bool intr_flag;
  7. local_intr_save(intr_flag);
  8. {
  9. wait_t *wait;
  10. //等待队列为空,解除信号量占用
  11. if ((wait = wait_queue_first(&(sem->wait_queue))) == NULL) {
  12. sem->value ++;
  13. }
  14. else {
  15. //唤醒等待队列的首项
  16. assert(wait->proc->wait_state == wait_state);
  17. wakeup_wait(&(sem->wait_queue), wait, wait_state, 1);
  18. }
  19. }
  20. local_intr_restore(intr_flag);
  21. }

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

  1. int state_sema[N]; /* 记录每个人状态的数组 */
  2. /* 信号量是一个特殊的整型变量 */
  3. semaphore_t mutex; /* 临界区互斥 */
  4. semaphore_t s[N]; /* 每个哲学家一个信号量 */
  5. struct proc_struct *philosopher_proc_sema[N];
  6. void phi_test_sema(i) /* i:哲学家号码从0到N-1 */
  7. {
  8. if(state_sema[i]==HUNGRY&&state_sema[LEFT]!=EATING
  9. &&state_sema[RIGHT]!=EATING)
  10. {
  11. state_sema[i]=EATING;
  12. up(&s[i]);
  13. }
  14. }
  15. void phi_take_forks_sema(int i) /* i:哲学家号码从0到N-1 */
  16. {
  17. down(&mutex); /* 进入临界区 */
  18. state_sema[i]=HUNGRY; /* 记录下哲学家i饥饿的事实 */
  19. phi_test_sema(i); /* 试图得到两只叉子 */
  20. up(&mutex); /* 离开临界区 */
  21. down(&s[i]); /* 如果得不到叉子就阻塞 */
  22. }
  23. void phi_put_forks_sema(int i) /* i:哲学家号码从0到N-1 */
  24. {
  25. down(&mutex); /* 进入临界区 */
  26. state_sema[i]=THINKING; /* 哲学家进餐结束 */
  27. phi_test_sema(LEFT); /* 看一下左邻居现在是否能进餐 */
  28. phi_test_sema(RIGHT); /* 看一下右邻居现在是否能进餐 */
  29. up(&mutex); /* 离开临界区 */
  30. }
  31. int philosopher_using_semaphore(void * arg) /* i:哲学家号码,从0到N-1 */
  32. {
  33. int i, iter=0;
  34. i=(int)arg;
  35. cprintf("I am No.%d philosopher_sema\n",i);
  36. while(iter++<TIMES)
  37. { /* 无限循环 */
  38. cprintf("Iter %d, No.%d philosopher_sema is thinking\n",iter,i); /* 哲学家正在思考 */
  39. do_sleep(SLEEP_TIME);
  40. phi_take_forks_sema(i);
  41. /* 需要两只叉子,或者阻塞 */
  42. cprintf("Iter %d, No.%d philosopher_sema is eating\n",iter,i); /* 进餐 */
  43. do_sleep(SLEEP_TIME);
  44. phi_put_forks_sema(i);
  45. /* 把两把叉子同时放回桌子 */
  46. }
  47. cprintf("No.%d philosopher_sema quit\n",i);
  48. return 0;
  49. }
  50. void check_sync(void){
  51. int i;
  52. //check semaphore
  53. sem_init(&mutex, 1);
  54. for(i=0;i<N;i++){
  55. sem_init(&s[i], 0);
  56. int pid = kernel_thread(philosopher_using_semaphore, (void *)i, 0);
  57. if (pid <= 0) {
  58. panic("create No.%d philosopher_using_semaphore failed.\n");
  59. }
  60. philosopher_proc_sema[i] = find_proc(pid);
  61. set_proc_name(philosopher_proc_sema[i], "philosopher_sema_proc");
  62. }
  63. //.......
  64. }

注意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

  1. //条件变量结构体
  2. typedef struct condvar{
  3. semaphore_t sem; // 关联的信号量
  4. int count; // 等待进程个数
  5. monitor_t * owner; // 所属的管程
  6. } condvar_t;
  7. //管程结构体
  8. typedef struct monitor{
  9. semaphore_t mutex; // 初始值为1,保证每次只有一个进程进入管程
  10. 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.
  11. int next_count; // the number of of sleeped signaling proc
  12. condvar_t *cv; // 条件变量数组
  13. } monitor_t;
  14. // Initialize monitor.
  15. void
  16. monitor_init (monitor_t * mtp, size_t num_cv) {
  17. int i;
  18. assert(num_cv>0);
  19. mtp->next_count = 0;
  20. mtp->cv = NULL;
  21. sem_init(&(mtp->mutex), 1); //unlocked
  22. sem_init(&(mtp->next), 0);
  23. mtp->cv =(condvar_t *) kmalloc(sizeof(condvar_t)*num_cv);
  24. assert(mtp->cv!=NULL);
  25. for(i=0; i<num_cv; i++){
  26. mtp->cv[i].count=0;
  27. sem_init(&(mtp->cv[i].sem),0);
  28. mtp->cv[i].owner=mtp;
  29. }
  30. }
  31. // Unlock one of threads waiting on the condition variable.
  32. void
  33. cond_signal (condvar_t *cvp) {
  34. if(cvp->count>0){
  35. cvp->owner->next_count++;
  36. up(&(cvp->sem));
  37. down(&(cvp->owner->next));
  38. cvp->owner->next_count--;
  39. }
  40. }
  41. // Suspend calling thread on a condition variable waiting for condition Atomically unlocks
  42. // mutex and suspends calling thread on conditional variable after waking up locks mutex. Notice: mp is mutex semaphore for monitor's procedures
  43. void
  44. cond_wait (condvar_t *cvp) {
  45. cvp->count++;
  46. if(cvp->owner->next_count>0){
  47. up(&(cvp->owner->next));
  48. }
  49. else{
  50. up(&(cvp->owner->mutex));
  51. }
  52. down(&(cvp->sem));
  53. cvp->count--;
  54. }

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

  1. struct proc_struct *philosopher_proc_condvar[N]; // N philosopher
  2. int state_condvar[N]; // the philosopher's state: EATING, HUNGARY, THINKING
  3. monitor_t mt, *mtp=&mt; // monitor
  4. void phi_test_condvar (i) {
  5. if(state_condvar[i]==HUNGRY&&state_condvar[LEFT]!=EATING
  6. &&state_condvar[RIGHT]!=EATING) {
  7. cprintf("phi_test_condvar: state_condvar[%d] will eating\n",i);
  8. state_condvar[i] = EATING ;
  9. cprintf("phi_test_condvar: signal self_cv[%d] \n",i);
  10. cond_signal(&mtp->cv[i]) ;
  11. }
  12. }
  13. void phi_take_forks_condvar(int i) {
  14. down(&(mtp->mutex));
  15. state_condvar[i]=HUNGRY;
  16. phi_test_condvar(i);
  17. if(state_condvar[i]!=EATING){
  18. cond_wait(&(mtp->cv[i]));
  19. }
  20. if(mtp->next_count>0)
  21. up(&(mtp->next));
  22. else
  23. up(&(mtp->mutex));
  24. }
  25. void phi_put_forks_condvar(int i) {
  26. down(&(mtp->mutex));
  27. state_condvar[i]=THINKING;
  28. phi_test_condvar((i+1)%5);
  29. phi_test_condvar((i+4)%5);
  30. if(mtp->next_count>0)
  31. up(&(mtp->next));
  32. else
  33. up(&(mtp->mutex));
  34. }
  35. //---------- philosophers using monitor (condition variable) ----------------------
  36. int philosopher_using_condvar(void * arg) { /* arg is the No. of philosopher 0~N-1*/
  37. int i, iter=0;
  38. i=(int)arg;
  39. cprintf("I am No.%d philosopher_condvar\n",i);
  40. while(iter++<TIMES)
  41. { /* iterate*/
  42. cprintf("Iter %d, No.%d philosopher_condvar is thinking\n",iter,i); /* thinking*/
  43. do_sleep(SLEEP_TIME);
  44. phi_take_forks_condvar(i);
  45. /* need two forks, maybe blocked */
  46. cprintf("Iter %d, No.%d philosopher_condvar is eating\n",iter,i); /* eating*/
  47. do_sleep(SLEEP_TIME);
  48. phi_put_forks_condvar(i);
  49. /* return two forks back*/
  50. }
  51. cprintf("No.%d philosopher_condvar quit\n",i);
  52. return 0;
  53. }
  54. void check_sync(void){
  55. int i;
  56. //......
  57. //check condition variable
  58. monitor_init(&mt, N);
  59. for(i=0;i<N;i++){
  60. state_condvar[i]=THINKING;
  61. int pid = kernel_thread(philosopher_using_condvar, (void *)i, 0);
  62. if (pid <= 0) {
  63. panic("create No.%d philosopher_using_condvar failed.\n");
  64. }
  65. philosopher_proc_condvar[i] = find_proc(pid);
  66. set_proc_name(philosopher_proc_condvar[i], "philosopher_condvar_proc");
  67. }
  68. }

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--,直接返回.

ucore lab7 同步互斥机制 学习笔记的更多相关文章

  1. ucore操作系统学习(七) ucore lab7同步互斥

    1. ucore lab7介绍 ucore在前面的实验中实现了进程/线程机制,并在lab6中实现了抢占式的线程调度机制.基于中断的抢占式线程调度机制使得线程在执行的过程中随时可能被操作系统打断,被阻塞 ...

  2. ucore lab6 调度管理机制 学习笔记

    这节虽叫调度管理机制,整篇下来主要就讲了几个调度算法.兴许是考虑到LAB5难,LAB6就仁慈了一把,难度大跳水.平常讲两节原理做一个实验,这次就上了一节原理.权当大战后的小憩吧. schedule函数 ...

  3. 浏览器中js执行机制学习笔记

    浏览器中js执行机制学习笔记 RiverSouthMan关注 0.0772019.05.15 20:56:37字数 872阅读 291 同步任务 当一个脚本第一次执行的时候,js引擎会解析这段代码,并 ...

  4. JUC.Lock(锁机制)学习笔记[附详细源码解析]

    锁机制学习笔记 目录: CAS的意义 锁的一些基本原理 ReentrantLock的相关代码结构 两个重要的状态 I.AQS的state(int类型,32位) II.Node的waitStatus 获 ...

  5. JAVA的反射机制学习笔记(二)

    上次写JAVA的反射机制学习笔记(一)的时候,还是7月22号,这些天就瞎忙活了.自己的步伐全然被打乱了~不能继续被动下去.得又一次找到自己的节奏. 4.获取类的Constructor 通过反射机制得到 ...

  6. Linux中同步互斥机制研究之原子操作

    操作系统中,对共享资源的访问需要有同步互斥机制来保证其逻辑的正确性,而这一切的基础便是原子操作. | 原子操作(Atomic Operations):    原子操作从定义上理解,应当是类似原子的,不 ...

  7. 【原创】xenomai内核解析--同步互斥机制(一)--优先级倒置

    版权声明:本文为本文为博主原创文章,转载请注明出处.如有错误,欢迎指正.博客地址:https://www.cnblogs.com/wsg1100/ 目录 一.xenomai 资源管理简要 二.优先级倒 ...

  8. .NET GC机制学习笔记

    学习笔记内容来自网络资料摘录http://www.cnblogs.com/springyangwc/archive/2011/06/13/2080149.html 1.GC介绍 Garbage Col ...

  9. Java 基础 类加载器和双亲委派机制 学习笔记

    转自博客:https://blog.csdn.net/weixin_38118016/article/details/79579657 文章不是我写的,但是感觉写的挺通俗易懂的,然后防止以后丢失,就转 ...

随机推荐

  1. 解释AOP?

    面向切面的编程,或AOP, 是一种编程技术,允许程序模块化横向切割关注点,或横切典型的责任划分,如日志和事务管理.

  2. SQL 语言包括哪几部分?每部分都有哪些操作关键字?

    SQL 语言包括数据定义(DDL).数据操纵(DML),数据控制(DCL)和数据查询(DQL) 四个部分. 数据定义:Create Table,Alter Table,Drop Table, Crae ...

  3. Springmvc入门基础(六) ---拦截器应用demo

    1.拦截器定义 Spring Web MVC 的处理器拦截器类似于Servlet 开发中的过滤器Filter,用于对处理器进行预处理和后处理. 2.拦截器demo demo需求: 拦截用户请求,判断用 ...

  4. kafka 的高可用机制是什么?

    这个问题比较系统,回答出 kafka 的系统特点,leader 和 follower 的关系,消息 读写的顺序即可.

  5. Redis 是单进程单线程的?

    Redis 是单进程单线程的,redis 利用队列技术将并发访问变为串行访问,消 除了传统数据库串行控制的开销.

  6. elasticsearch 的倒排索引是什么 ?

    解答:通俗解释一下就可以. 传统的我们的检索是通过文章,逐个遍历找到对应关键词的位置. 而倒排索引,是通过分词策略,形成了词和文章的映射关系表,这种词典+映射表 即为倒排索引. 有了倒排索引,就能实现 ...

  7. resin服务之三---独立resin的配置

    独立resin的配置 关掉httpd服务: [root@data-1-1 ~]# killall httpd [root@data-1-1 ~]# lsof -i :80    ------>h ...

  8. ctfhub 过滤运算符 综合过滤练习 读取源代码 远程包含 eval执行 文件包含 php://input

    过滤运算符 过滤了\和&那么尝试; 成功那么将flag cat出来 127.0.0.1;cat flag_27249269530391.php 得到flag 综合过滤练习 这次过滤有点多过滤了 ...

  9. Rust 中的数据布局-repr

    repr(Rust) 首先,所有类型都有一个以字节为单位的对齐方式,一个类型的对齐方式指定了哪些地址可以用来存储该值.一个具有对齐方式n的值只能存储在n的倍数的地址上.所以对齐方式 2 意味着你必须存 ...

  10. pandas学习总结

    什么是pandas pandas数据读取 03. Pandas数据结构 Pandas查询数据的几种方法