深入学习信号量

  • Post author:
  • Post category:其他




深入学习信号量




一.什么资源是临界资源?




临界资源是一次仅允许一个进程使用的共享资源



二.临界区域是什么意思?什么是临界代码?




每个进程中访问临界资源的那段程序称为临界区,或者叫临界代码。



三.临界区或者临界代码有什么特点:




每次只准许一个进程进入临界区,进入后不允许其他进程进入。



四.信号量是解决是什么问题的?




解决在任一时刻只有一个执行线程访问的临界区的代码(即临界代码),防止多个线程或者多个程序同时访问一个共享资源而引发的问题。



因为:程序中存在着一部分临界代码,我们需要确保只有一个进程(或者一个执行线程),可以进入这个临界代码并拥有对该资源的独占式的访问权。



五.信号量的实质:




用于管理对资源的访问;



解决程序对某个特定的资源具有独占式的访问权。



六.能否举个例子,说明哪些代码属于临界代码?




我们用



dbm



来访问数据库。如果有多个程序试图在同一时间更新这个数据库,哪么这个数据可能会遭到破坏。









两个不同的程序要求不同的用户向数据库输入数据,这本身并没有错,但是问题可能出现在对数据库更新的哪部分代码上。因为这部分真正执行数据更新的代码需要独占式执行。



我们称这段代码是:临界代码,或者是临界区。



七.进程进入临界区的调度原则是:




①如果有若干进程要求进入空闲的临界区,一次仅允许一个进程进入;



②任何时候,处于临界区内的进程不可多于一个。如已有进程进入自己的临界区,则其它所有试图进入临界区的进程必须等待;



③进入临界区的进程要在有限时间内退出,以便其它进程能及时进入自己的临界区;



④如果进程不能进入自己的临界区,则应让出



CPU



,避免进程出现“忙等”现象。



七.信号量的分类:





类型







区别






二进制信号量





1.








只能取值



0







1



的变量;





2.








最常见的一种信号量形式;





3.








目前,我们集中讨论的就是:二进制信号量;



通用信号量




1



.可以取多个正整数值的变量



八.信号量是什么特殊的变量






她是只能取整数值的变量。



九.问什么一个普通变量进行类似的加减法不行?




传统的编程语言如



C







C++



等语言,都没有一个原子操作,就是检测:检测一个变量是否为



true



,如果是



true



的话,再将其变量设置为



false







十.原子操作如何理解:





1



.原子操作就是不可拆分的操作



例如,



mv



就是原子操作,



cp



就不是原子操作。




2



.在多进程(线程)的操作系统中不能被其它进程(线程)打断的操作就叫原子操作;




3



.文件的原子操作是指操作文件时的不能被打断的操作。




4



.原子还有一层意思,在该次操作不能完成的时候,必须回到操作之前的状态,原子不可分嘛!



如果你删除文件,只删了一半发生错误,你不能只留下另一半吧,必须恢复整个文件。





5.








所有原子操作是同步的,而且不可被外部中断(内中断可以,不过一般是致命错误处理)。





6.








即不可中断的一个或一列系操作





7.








信号量和锁的



PV



操作可以用



CPU







XCHG



指令来实现,





8.








原子操作可以用信号量来实现





9.








任何



CPU



的中断都只能发生在指令边界上




10



.(即指令在流水线的执行单元上跑到一半的时候中断



/



异常是不会发生的)




11



.所以原子操作的前提是



test







change



操作必须在一个指令中完成。




12



.原子操作的要求是不能在执行的过程中被中断抢占(要知道操作系统的进程调度也是通过时钟中断引发的)



十一.信号量有什么特点:




只能对该变量,该值进行



2



种操作,分别是:



P



操作和



V



操作。




P



操作:用于等待,就是进入临界区域之前的检查点;




V



操作:用于信号,就是放弃对临界区的控制权,或者释放资源;



十二.



P,V



原语理论是谁提出的,该科学家在计算机领域还有什么建树?




赫赫有名的荷兰科学家



E.W.Dijkstra





P,V



原语的概念以及



P,V



操作当中需要使用到的信号量的概念都是由他在



1965



年提出的。



他提出了图论中最短路径问题的



Dijkstra



算法。



十三.信号量的历史,最开始是解决什么问题的?




信号量是最早出现的用来解决进程同步与互斥问题的机制,包括一个称为信号量的变量及对它进行的两个原语操作。信号量为一个整数,我们设这个信号量为:



sem



。很显然,我们规定在



sem



大于等于零的时候代表可供并发进程使用的资源实体数,



sem



小于零的时候,表示正在等待使用临界区的进程的个数。根据这个原则,在给信号量附初值的时候,我们显然就要设初值大于零。



十四.



PV



操作的特点:




P


操作和

V

操作是不可中断的程序段,称为原语。





P,V


原语中

P

是荷兰语的

Passeren

,相当于英文的

pass,




V


是荷兰语的

Verhoog,

相当于英文中的

incremnet







十五.



PV



操作到底实现了什么操作,能够详细解释下吗?





1







P



原语操作的动作是:




信号量为一个整数,我们设这个信号量为:



sem








1







sem







1















2












sem







1



后仍大于或等于零,则进程继续执行;







3












sem







1



后小于零,则该进程被阻塞后(也就是挂起该进程的执行),进入与该信号相对应的队列中,然后转进程调度。






2







V



原语操作的动作是:












1







sem







1















2








若相加结果大于零,则进程继续执行;











3








若相加结果小于或等于零,则从该信号的等待队列中唤醒一等待进程,然后再返回原进程继续执行或转进程调度。



十六.



P,V



操作有什么注意点?






1.







P,V





操作对于每一个进程来说,都只能进行一次;





2.








而且必须成对使用;





3.








且在



P,V



原语执行期间不允许有中断的发生;



十七.信号量的缺点是什么?





1



.信号量机制必须有公共内存;




2



.不能用于分布式操作系统;



十八.如何实现



P,V



操作



?







1.








可以用硬件实现;





2.








也可以用软件实现;



实现的参考代码如下:



procedure p(var s:samephore);



{



s.value=s.value-1;



if (s.value<0) asleep(s.queue);



}



procedure v(var s:samephore);



{



s.value=s.value+1;



if (s.value<=0) wakeup(s.queue);



}



其中用到两个标准过程:








asleep(s.queue);



执行此操作的进程控制块进入



s.queue



尾部,进程变成等待状态








wakeup(s.queue);







s.queue



头进程唤醒插入就绪队列







对于这个过程,



s.value



初值为



1



时,用来实现进程的互斥。



十九.信号量的初始值是什么?为什么是这样?










信号量的初始值是



1













信号量为一个整数,我们设这个信号量为:



sem





1



.我们规定在



sem



大于等于零的时候,代表可供并发进程使用的资源实体数;




2







sem



小于零的时候,表示正在等待使用临界区的进程的个数。



根据这个原则,在给信号量附初值的时候,我们显然就要设初值大于零。






二十.如何用



P V



原语实现进程互斥?








把临界区置于



P(sem)







V(sem)



之间。当一个进程想要进入临界区时,它必须先执行



P



原语操作以将信号量



sem







1







在进程完成对临界区的操作后,它必须执行



V



原语操作以释放它所占用的临界区。从而就实现了进程的互斥:










具体的过程我们可以简单的描述如下:










PA:



P(sem)



<S>;



V(sem)



PB:



P(sem)



<S>;



V(sem)






二十一.用



P V



原语如何实现进程通信








我们以邮箱通信为例说明问题:





邮箱通信满足的条件是:







<1>;



发送进程发送消息的时候,邮箱中至少要有一个空格能存放该消息。






<2>;



接收进程接收消息时,邮箱中至少要有一个消息存在。





发送进程和接收进程我们可以进行如下的描述:




Deposit(m)



为发送进程,接收进程是



remove(m).





Fromnum



为发送进程的私用信号量,信箱空格数



n







mesnum



为接收进程的私用信号量,初值为



0.





2



个信号量,分别是:发送进程



Fromnum



和接收进程



mesnum





1



.发送进程的实现:




Deposit(m)






Begin local x




P(fromnum) //



发送进程的



P



操作






选择空格



x //



邮箱中至少要有一个空格能存放该消息



将消息



m



放入空格



x







置格



x



的标志为满







V(mesnum) //



接受进程的



v



操作



end




2



.接受进程的实现:




Remove(m)






Begin local x







P(mesnum) //



接受进程的



P



操作






选择满格



x //



邮箱中至少要有一个消息存在



把满格



x



中的消息取出放



m







置格



x



标志为空







V(fromnum) //



发送进程的



v



操作



End



二十二.如何使用信号量的几个步骤:





1



.创建一个新的信号量;




2



.初始化信号量;




3



.在程序进入临界区的时候,调用



P



操作,设置信号量以等待进入;




4



.离开临界区的时候,调用



V



操作,设置信号量,来表示可以再次访问和操作;




5



.删除信号量。



二十三.使用信号量的源码:





1



.创建一个信号量:




static int sem_id;



sem_id = semget((key_t)1234, 1, 0666 | IPC_CREAT);




2



.初始化信号量:




static int set_semvalue(void)



{







union semun sem_union;









sem_union.val = 1;







//semctl



函数允许我们直接控制信号量信息






//SETVAL



用来把信号量初始化为一个已知的值,其作用是在信号量第一次使用之前,



//



对它进行设置;返回



-1:



表示失败






if (semctl(sem_id, 0, SETVAL, sem_union) == -1)//semctl



函数允许我们直接控制信号量信息










return(0);
















return(1);




}




3



.删除信号量:





//



删除信号量



ID,



是如何删除的呢?通过将



semctl



函数的



command



参数设置为



IPC_RMID



,然后调用该函数,来进行删除



static void del_semvalue(void)



{







union semun sem_union;





//IPC_RMID



用于删除一个已经无需继续使用的信号量标识符






if (semctl(sem_id, 0, IPC_RMID, sem_union) == -1)//semctl



函数允许我们直接控制信号



//



量信息






fprintf(stderr, “Failed to delete semaphore/n”);




}




4







P



操作:





//



信号量的



P



操作



static int semaphore_p(void)



{







struct sembuf sem_b;









sem_b.sem_num = 0;//



表示信号量的编号,如果一个信号量,其取值为



0



,如果是一组信号量,则是非



0







sem_b.sem_op = -1; /* P() *///-1



表示



P



操作,她等待一个信号量变成可用,也就是说,此时可以进入临界区,进行工作






sem_b.sem_flg = SEM_UNDO;//



她将使得操作系统跟踪当前进程对这个信号量的修改情况






//



进程退出时,清除其信号量






if (semop(sem_id, &sem_b, 1) == -1) //semop



函数用于改变信号量的值






{








fprintf(stderr, “semaphore_p failed/n”);







return(0);







}







return(1);




}




5







V



操作:





//



信号量的



V



操作



static int semaphore_v(void)



{







struct sembuf sem_b;












sem_b.sem_num = 0;







sem_b.sem_op = 1; /* V() */







sem_b.sem_flg = SEM_UNDO;







if (semop(sem_id, &sem_b, 1) == -1) //







{








fprintf(stderr, “semaphore_v failed/n”);







return(0);







}







return(1);




}



二十四.信号量的补充说明:





1



.信号量对进程而言是共享的的,也就是说对系统来说,必须要有共享内存;




2



.如果不删除信号量,它将继续在系统中存在,即使无程序在使用她也是粗次。




3



.信号量也是一种有限资源,大家需要节约使用。



版权声明:本文为littletigerat原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。