九、进程间通信-信号量
一、概述
1.信号量
信号量本质上是一个计数器(不设置全局变量是因为进程间是相互独立的,而这不一定能看到,看到也不能保证++引用计数为原子操作),用于多进程对共享数据对象的读取,它和管道有所不同,它不以传送数据为主要目的,它主要是用来保护共享资源(信号量也属于临界资源),使得资源在一个时刻只有一个进程独享。
2.信号量的工作原理
由于信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv),他们的行为是这样的:
(1)P(sv)
如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行
(2)V(sv)
如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1.
在信号量进行PV操作时都为原子操作(因为它需要保护临界资源)
注:原子操作:单指令的操作称为原子的,单条指令的执行是不会被打断的
3、作用
- 互斥
- 同步
- 若要在进程间传递数据需要结合共享内存
4、二元信号量
二元信号量(Binary Semaphore)是最简单的一种锁(互斥锁),它只用两种状态:占用与非占用。所以它的引用计数为1。
5.进程如何获得共享资源
(1)测试控制该资源的信号量
(2)信号量的值为正,进程获得该资源的使用权,进程将信号量减1,表示它使用了一个资源单位
(3)若此时信号量的值为0,则进程进入挂起状态(进程状态改变),直到信号量的值大于0,若进程被唤醒则返回至第一步。
注:信号量通过同步与互斥保证访问资源的一致性。
二、信号量的用法
1、定义一个唯一key(ftok)
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
2、构造一个信号量(semget)
int semget(key_t key,int nsems,int semflg) //获取信号量ID
(1)参数:
- key:信号量键值
- nsems:信号量数量
- semflg:
- IPC_CREATE:信号量不存在则创建
- mode:信号量的权限
(2)返回值:
-
成功:信号量ID.
-
失败:-1
3、初始化信号量(semctl SETVA)
int semctl(int semid,int semnum,int cmd,union semun arg)
(1)参数
-
semid:信号量ID
-
semnum:信号量编号
-
cmd:
-
IPC_STAT:获取信号量的属性信息
-
IPC_SET:设置信号量的属性
-
IPC_RMID:删除信号量
-
IPC_SETVAL:设置信号量的值
-
- arg:
union semun
{
int val;
struct semid_ds *buf;
}
(2)返回值
-
成功:由cmd类型决定。
-
失败:-1
4、对信号量进行P/V操作(semop)
int semop(int semid,struct sembuf *sops,size_t nsops)
(1)参数
-
semid :信号量ID
- sops:信号量操作结构体数组
struct sembuf
{
short sem_num; //信号量编号
short sem_op; //信号量P/V操作
short sem_flg; //信号量行为,SEM_UNDO
}
-
nsops:信号量数量
(2)返回值
-
成功:0
-
失败: -1
5、删除信号量(semctl RMID)
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
//信号量控制操作
int semctl(int semid, int semnum, int cmd, ...);
(1)参数
-
semid:信号量ID.
-
semnum:信号量数量
-
cmd:
-
IPC_STAT : 读取一个信号量集的数据结构semid_ds,并将其存储在semun中的buf参数中。
-
IPC_SET : 设置信号量集的数据结构semid_ds中的元素ipc_perm,其值取自semun中的buf参数。
-
IPC_RMID : 将信号量集从内存中删除。
-
IPC_INFO : 返回有关系统范围的信号量限制和参数的信息
-
GETALL :用于读取信号量集中的所有信号量的值
-
GETNCNT :返回正在等待资源的进程数目。
-
GETPID : 返回最后一个执行semop操作的进程的PID。
-
GETVAL : 返回信号量集中的一个单个的信号量的值。
-
GETZCNT : 返回正在等待完全空闲的资源的进程数目。
-
SETALL : 设置信号量集中的所有的信号量的值。
-
SETVAL : 设置信号量集中的一个单独的信号量的值。
-
(2)返回值:
- 失败:返回-1
- 成功:非负值,返回值由cmd决定
三、实例
信号量用于子进程和父进程的同步
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
#define DELAY_TIME 3
union semun{
int val;
struct seimd_ds *buf;
};
/*初始化信号量*/
int init_sem(int sem_id,int init_value)
{
union semun sem_union;
sem_union.val = init_value;
/*设置信号量集中的一个单独的信号量的值*/
if(semctl(sem_id,0,SETVAL,sem_union) == -1)
{
printf("Initialize semaphore failed!\n");
return -1;
}
return 0;
}
/*删除信号量*/
int del_sem(int sem_id)
{
union semun sem_union;
if(semctl(sem_id, 0, IPC_RMID, sem_union) == -1)
{
perror("Delete semaohore fail!\n");
return -1;
}
return 0;
}
/*P 操作:执行P操作时,信号量-1*/
int sem_p(int sem_id)
{
struct sembuf sops;
sops.sem_num = 0;
sops.sem_op = -1;
sops.sem_flg = SEM_UNDO;/*系统自动释放将会在系统中残留的信号量*/
if(semop(sem_id,&sops,1) == -1)
{
perror("P operation failed\n");
return -1;
}
return 0;
}
/*V 操作:执行P操作时,信号量+1*/
int sem_v(int sem_id)
{
struct sembuf sops;
sops.sem_num = 0; /*单个信号量的编号应该为 0*/
sops.sem_op = 1; /*表示V操作*/
sops.sem_flg = SEM_UNDO; /*系统自动释放将会在系统中残留的信号量*/
if(semop(sem_id,&sops,1) == -1)
{
perror("V operation failed\n");
return -1;
}
return 0;
}
void main(void)
{
pid_t result;
int sem_id;
sem_id = semget((key_t)6666,1,0666|IPC_CREAT);/*创建一个信号量*/
init_sem(sem_id,0); //初始化信号量的值为0,只能先执行V操作+1,然后才能执行P操作-1
/*调用fork()函数*/
result = fork();
if(result == -1)
{
perror("Fork\n");
}
else if(result == 0)
{
printf("Child proess will wait for some seconds...\n");
sleep(DELAY_TIME);
printf("the child process is running...\r\n");
sem_v(sem_id); //如果有其他进程因等待信号量而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1.
}
else /*返回值大于0代表父进程*/
{
sem_p(sem_id); //如果信号量等于0,则进程挂起,直到信号量大于0,执行V操作
printf("the father process is running...\r\n");
del_sem(sem_id);
}
exit(0);
}
执行结果:
由于信号量的值初始化为0,所以父进程一致挂起不能进行P操作,等待子进程执行完毕V操作后,信号量+1,此时信号量大于0,可以进行P操作执行父进程。执行完父进程后删除信号量。