深入学习信号量
一.什么资源是临界资源?
临界资源是一次仅允许一个进程使用的共享资源
二.临界区域是什么意思?什么是临界代码?
每个进程中访问临界资源的那段程序称为临界区,或者叫临界代码。
三.临界区或者临界代码有什么特点:
每次只准许一个进程进入临界区,进入后不允许其他进程进入。
四.信号量是解决是什么问题的?
解决在任一时刻只有一个执行线程访问的临界区的代码(即临界代码),防止多个线程或者多个程序同时访问一个共享资源而引发的问题。
因为:程序中存在着一部分临界代码,我们需要确保只有一个进程(或者一个执行线程),可以进入这个临界代码并拥有对该资源的独占式的访问权。
五.信号量的实质:
用于管理对资源的访问;
解决程序对某个特定的资源具有独占式的访问权。
六.能否举个例子,说明哪些代码属于临界代码?
我们用
dbm
来访问数据库。如果有多个程序试图在同一时间更新这个数据库,哪么这个数据可能会遭到破坏。
两个不同的程序要求不同的用户向数据库输入数据,这本身并没有错,但是问题可能出现在对数据库更新的哪部分代码上。因为这部分真正执行数据更新的代码需要独占式执行。
我们称这段代码是:临界代码,或者是临界区。
七.进程进入临界区的调度原则是:
①如果有若干进程要求进入空闲的临界区,一次仅允许一个进程进入;
②任何时候,处于临界区内的进程不可多于一个。如已有进程进入自己的临界区,则其它所有试图进入临界区的进程必须等待;
③进入临界区的进程要在有限时间内退出,以便其它进程能及时进入自己的临界区;
④如果进程不能进入自己的临界区,则应让出
CPU
,避免进程出现“忙等”现象。
七.信号量的分类:
|
|
|
|
|
|
八.信号量是什么特殊的变量
她是只能取整数值的变量。
九.问什么一个普通变量进行类似的加减法不行?
传统的编程语言如
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
.信号量也是一种有限资源,大家需要节约使用。