深入学习信号量
一.什么资源是临界资源?
临界资源是一次仅允许一个进程使用的共享资源
二.临界区域是什么意思?什么是临界代码?
每个进程中访问临界资源的那段程序称为临界区,或者叫临界代码。
三.临界区或者临界代码有什么特点:
每次只准许一个进程进入临界区,进入后不允许其他进程进入。
四.信号量是解决是什么问题的?
解决在任一时刻只有一个执行线程访问的临界区的代码(即临界代码),防止多个线程或者多个程序同时访问一个共享资源而引发的问题。
因为:程序中存在着一部分临界代码,我们需要确保只有一个进程(或者一个执行线程),可以进入这个临界代码并拥有对该资源的独占式的访问权。
五.信号量的实质:
用于管理对资源的访问;
解决程序对某个特定的资源具有独占式的访问权。
六.能否举个例子,说明哪些代码属于临界代码?
我们用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.信号量也是一种有限资源,大家需要节约使用。