一 临界区问题
1 do{ 2 进入区 3 临界区 4 退出区 5 剩余区 6 }while(TRUE);
临界区:每个进程有一个代码段称为临界区,该区中进程可能改变共同变量、更新一个表、写一个文件等。
进入区:请求允许进入临界区。
退出区:
剩余区:剩余代码
必须满足:互斥:若进程Pi在其临界区内执行,则其他进程不可在其临界区内执行
前进:如果没有进程在其临界区内执行且有进程需进入临界区,那么只有那些不在剩余区内执行的进程可参加选择来确定谁能下一 个进入临界区,且这种选择不能无限推迟
有限等待:从一个进程做出进入临界区的请求,直到该请求允许为止,其他进程允许进入其临界区的次数有上限
抢占内核允许处于内核模式的进程被抢占,非抢占内核不允许。
二 Peterson算法
1 do{ 2 flag[i]=TRUE; //数组flag表示哪个进程想要进入临界区 3 turn=j; //turn表示哪个进程可以进入临界区 4 while(flag[j]&&turn==j); //j==1-i 5 ------------------------------- 6 临界区 7 ------------------------------- 8 flag[i]=FALSE; 9 ------------------------------- 10 剩余区 11 }while(TRUE);
Peterson算法适用于两个进程在临界区与剩余区交替执行。
三 硬件同步----锁
1 do{ 2 进入锁 3 临界区 4 释放锁 5 剩余区 6 }while(TRUE); 7 8 9 boolean TestAndSet(boolean *target){ 10 boolean *rv = *target; 11 *target = TRUE; 12 return rv; 13 }//TestAndSet指令定义 该指令可以原子地(不可中断地)执行 14 15 do{ 16 while(TestAndSet(&lock)) //lock初始化为false 17 ;//do nothing 18 //critical section 19 lock=FALSE; 20 //remainder section 21 }while(TRUE); //使用TestAndSet的互斥实现 22 23 void Swap(boolean *a, boolean *b){ 24 boolean temp=*a; 25 *a=*b; 26 *b=temp; 27 } //Swap指令的定义,原子地执行 28 29 do{ 30 key=TRUE; 31 while(key==TRUE) 32 Swap(&lock,&key); //lock初始化为false 33 //critical section 34 lock=FALSE; 35 //remainder section 36 }while(TRUE);//使用Swap的互斥实现
四 信号量
1 wait(S){ 2 while(S<=0) 3 ;//no-op 4 S--; 5 } //加锁 6 7 signal(S){ 8 S++; 9 } //解锁
4.1 用法
通常操作系统区分计数信号量与二进制信号量。计数信号量的值域不受限制,而二进制信号量的值只能为0或1。有的系统,将二进制信号量称为互斥锁,因为他们可以提供互斥。
1 do{ 2 waiting(mutex); //使用二进制信号量处理多进程临界问题,n个进程共享一个信号量mutex,并初始化为1 3 //critical section 4 signal(mutex); 5 //remainder section 6 }while(TRUE);
4.2 实现
问题:上述信号量主要缺点是忙等待。当一个进程位于其临界区内时,任何其他试图进入其临界区的进程都必须在其进入代码中连续循环,浪费了CPU时钟,这本来可有效的为其他进程所使用。这种类型信号量也称为自旋锁,因为进程在等待琐时还在运行。
解决办法:带一个进程执行wait()操作时,发现信号量不为正,则它必须等待(阻塞自己)。阻塞操作将一个进程放入到与信号量相关的等待队列中,并将该进程的状态切换为等待状态。接着,控制转到CPU调度程序,以选择另外一个进程来执行。
1 typedef struct{ 2 int value; 3 struct process *list; //当一个进程必须等待信号量时,就将其加入到进程链表上 4 }semaphore; 5 6 wait(semaphore *S){ 7 S->value--; 8 if(S->value < 0){ 9 add this process to S->list; 10 block();//该函数作用为挂起调用它的进程 11 } 12 } 13 14 signal(semaphore *S){ 15 S->value++; 16 if(S->value <= 0){ 17 remove a process P from S->list; 18 wakeup(P); //该函数作用为重新启动阻塞进程P的执行 19 } 20 }
注:在信号量的经典定义下,其值不可能为负。但是本实现中可能产生负的信号量值,且如果其值为负的,则其绝对值为等待该信号量的进程的个数。
信号量关键之处在于它们原子地执行,必须确保没有两个进程能够同时对同一信号量执行wait()和signal()。
无限期阻塞/饥饿:进程在信号量内无限等待。
五 经典同步问题
5.1 有限缓冲问题
5.2 读者-写者问题
读者:只读数据库 写者:读和写数据库
第一读者--写者问题:当写者写时,所有读者等待; 当一个读者读时,其他读者也可以读,但写者必须等待。(可能导致写者饥饿)
第二读者--写者问题:当写者等时,不允许有新的读者开始读操作。(可能导致读者饥饿)
部分代码解释:
1 if(readcount==1) wait(wrt); //如果该读者为第一个读者,那么他将对wrt上锁,防止写者写 2 if(readcount==1) signal(wrt); //如果该读者为最后一个读者,那么他将对wrt解锁,表面现在没人在读,写者可以写了 3 4 wait(mutex).....signal(mutex) //由于读者间共享readcount,故每次对其操作时,也应对readcount上锁
6.管程(太难了,理解了再补这方面笔记)