进程间的通信-信号量
- 信号量就类似与马路上的红绿灯,来控制人们在各个路口朝各个方向上的行进,从而更好地有规划的使用这条道路。
- 在程序中,信号则对进程们的执行进行控制。
什么是信号量
问题:
- 在程序中,有时会存在一种特殊代码,同一时间只允许一个进程执行该部分代码。这部分区域,被称为
"临界区"
。- 然后在多进程并发执行中,当一个进程进入临界区,因某种原因被挂起时,其他进程就有可能也进入该区域。
- 解决办法:——使用信号量。
- 什么是信号量?
- 信号量是一种特殊的变量。
我们只能对信号量执行P操作和V操作。
P操作
:申请资源。
- 如果信号量的值>0,则把该信号量-1。
- 如果信号量的值=0,则挂起该进程。
V操作
:释放资源。
- 如果有进程因该信号量而被挂起,则恢复当前进程运行。
- 如果没有进程因该信号量而被挂起,则把该信号量+1。
注意:
- P操作、V操作都是
原子操作
,即,其在执行期间,不会被中断
。- 这里指的信号量是指
System V IPC
的信号量,与线程所使用的信号量不同。该信号量用于进程间通信
。
信号量的使用
信号量的获取
- semget
- 函数原型:int semget(key_t key, int nsems, int semflg);
- 功能:获取一个已存在的、或创建一个新的信号量,并返回该信号量的标识符。
- 参数:
key:键值,该键值对应一个唯一的信号量。类似于共享内存的键值。
- 不同的可通过该键值和semget获取唯一的信号量。
- 特殊键值——IPC_PRIVAT,该信号量只允许创建者进程才可以访问,可用于父子进程间通信。
- nsems:需要的信号量数目,一般为1。
semflag:访问权限。
- 若设置为IPC_CREAT,则如果该信号量未存在,则创建该信号量,如果该信号量已经存在,也不会发生错误。
- 返回值:
- 成功:返回一个正整数。
- 失败:返回-1。
信号量的操作
- semop
- 函数原型:int semop(int semid, struct sembuf *sops, unsigned nsops);
- 功能:改变信号量的值,即对信号量执行P操作、V操作。
参数:
- semid:信号量标识符,即semget函数的返回值。
- sops:是一个数组,元素类型为struct sembuf。
struct sembuf { short sem_num; //信号量组中的编号(即指定对哪个信号量操作) //semget实际是获取一组信号量 //信号量组中的编号从0开始 short sem_op; //操作类型:-1表示P操作; 1表示V操作 short sem_flg; // 通常为SEM_UNDO,使操作系统跟踪信号, // 并在进程没有释放该信号量而终止时,操作系统释放信号量 }
- nsops:表示第二个参数sops所表示的数组大小,即有几个struct sembuf。
返回值:
- 成功:返回0。
- 失败:返回-1。
- 相关参考与补充:Linux进程间通信(五):信号量 semget()、semop()、semctl()
信号量的控制
semctl
- 函数原型:int semctl(int semid, int sem_num, int cmd, ...);
- 功能:对信号量进行控制。
参数:
- semid:信号量标识符。
- sem_num:信号量组中的编号,如果只有一个信号量,则取0。
cmd:通常是下面两个值的其中一个。
- SETVAL:用来把信号量初始化为一个已知的值。p 这个值通过
union semun
中的val成员设置,其作用是在信号量第一次使用前对它进行设置。- IPC_RMID:用于删除一个已经无需继续使用的信号量标识符。
参数四类型为:
union semun
。
union semun { int val; // SETVAL命令要设置的值 struct semid_ds *buf; unsigned short *array; }
- union semun有些Linux发行版在sys/sem.h中定义,有些则没有定义,可自行定义:
#if defined(__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED) #else union semun { int val; struct semid_ds *buf; unsigned short int *array; struct seminfo *__buf; }; #endif
- 返回值:略,详见-semctl(2) — Linux manual page
- 相关参考与补充:Linux进程间通信(五):信号量 semget()、semop()、semctl()
示例
- 示例1:不使用信号量,并发执行多个程序,观察对临界区的访问。
#include <stdlib.h>
#include <stdio.h>
int main(void) {
int i;
pid_t pd = fork();
for (i=0; i<5; i++) {
/* 模拟临界区----begin */
printf("Process(%d) In\n", getpid());
sleep(1);
printf("Process(%d) Out\n", getpid());
/* 模拟临界区----end */
sleep(1);
}
return 0;
}
可见并不是我们想要的效果,我们想要的是一个进去了,另外一个就不可以进去了,出去一个,另外一个才可以进去。
- 示例2:使用信号量,并发指定多个进程,观察对临界区的访问。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdlib.h>
#include <stdio.h>
#if defined(__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED)
#else
union semun {
int val;
struct semid_ds *buf;
unsigned short int *array;
struct seminfo *__buf;
};
#endif
//信号的初始化
static sem_initial(int semid){
int ret;
union semun semun;
semun.val = 1;
ret = semctl(semid,0,SETVAL,semun);
if(ret == -1){
fprintf(stderr, "semctl failed!\n");
}
return ret;
}
//将p v操作封装成函数
//p操作
static int sem_p(int semid){
int ret;
struct sembuf sembuf;
sembuf.sem_op = -1;//操作类型,设置为-1即p操作。
sembuf.sem_num = 0;//指定信号量在信号量组中的编号
sembuf.sem_flg = SEM_UNDO;//让操作系统跟踪信号,如果进程忘记释放,则操作系统进行负责释放。
ret = semop(semid,&sembuf,1);//根据设置对信号量进行操作
if (ret == -1) {
fprintf(stderr, "sem_p failed!\n");
}
return ret;
}
//v操作
static int sem_v(int semid){
int ret;
struct sembuf sembuf;
sembuf.sem_op = 1;//操作类型,设置为1即v操作。
sembuf.sem_num = 0;//指定信号量在信号量组中的编号
sembuf.sem_flg = SEM_UNDO;//让操作系统跟踪信号,如果进程忘记释放,则操作系统进行负责释放。
ret = semop(semid,&sembuf,1);//根据设置对信号量进行操作
if (ret == -1) {
fprintf(stderr, "sem_v failed!\n");
}
return ret;
}
int main(int argc, char* argv[]) {
int semid;
//获取信号
semid =semget((key_t)1234,1,0666 | IPC_CREAT);
if(semid == -1){
printf("semget failed!\n");
exit(1);
}
//信号量的初始化
if (argc > 1) {
int ret = sem_initial(semid);
if (ret == -1) {
exit(1);
}
}
for(;;){
if(sem_p(semid) == -1){//p操作,申请,若无可用资源,则挂起等待。
exit(1);
}
/* 模拟临界区----begin */
printf("Process(%d) In\n", getpid());
sleep(3);
printf("Process(%d) Out\n", getpid());
/* 模拟临界区----end */
if(sem_v(semid) == -1){//v操作,释放。
exit(1);
}
}
return 0;
}
可以看到,一个出来,另一个才可以进去。