信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。
特点:
1. 信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。
2. 信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。
3. 每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。
4. 支持信号量组。
原型:
最简单的信号量是只能取 0 和 1 的变量,这也是信号量最常见的一种形式,叫做二值信号量(Binary Semaphore)。而可以取多个正整数的信号量被称为通用信号量。
Linux 下的信号量函数都是在通用的信号量数组上进行操作,而不是在一个单一的二值信号量上进行操作。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
// 创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
1. int semget(key_t key, int num_sems, int sem_flags);
// 对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
2. int semop(int semid, struct sembuf semoparray[], size_t numops);
// 控制信号量的相关信息
3. int semctl(int semid, int sem_num, int cmd, ...);
当semget
创建新的信号量集合时,必须指定集合中信号量的个数(即num_sems
),通常为1; 如果是引用一个现有的集合,则将num_sems
指定为 0 。
在semop
函数中,sembuf
结构的定义如下:
1 struct sembuf
2 {
3 short sem_num; // 信号量组中对应的序号,0~sem_nums-1
4 short sem_op; // 信号量值在一次操作中的改变量
5 short sem_flg; // IPC_NOWAIT, SEM_UNDO
6 }
其中 sem_op 是一次操作中的信号量的改变量:
-
若
sem_op > 0
,表示进程释放相应的资源数,将 sem_op 的值加到信号量的值上。如果有进程正在休眠等待此信号量,则换行它们。 -
若
sem_op < 0
,请求 sem_op 的绝对值的资源。- 如果相应的资源数可以满足请求,则将该信号量的值减去sem_op的绝对值,函数成功返回。
- 当相应的资源数不能满足请求时,这个操作与
sem_flg
有关。- sem_flg 指定
IPC_NOWAIT
,则semop函数出错返回EAGAIN
。 - sem_flg 没有指定
IPC_NOWAIT
,则将该信号量的semncnt值加1,然后进程挂起直到下述情况发生:- 当相应的资源数可以满足请求,此信号量的semncnt值减1,该信号量的值减去sem_op的绝对值。成功返回;
- 此信号量被删除,函数smeop出错返回EIDRM;
- 进程捕捉到信号,并从信号处理函数返回,此情况下将此信号量的semncnt值减1,函数semop出错返回EINTR
- sem_flg 指定
-
若
sem_op == 0
,进程阻塞直到信号量的相应值为0:- 当信号量已经为0,函数立即返回。
- 如果信号量的值不为0,则依据
sem_flg
决定函数动作:- sem_flg指定
IPC_NOWAIT
,则出错返回EAGAIN
。 - sem_flg没有指定
IPC_NOWAIT
,则将该信号量的semncnt值加1,然后进程挂起直到下述情况发生:- 信号量值为0,将信号量的semzcnt的值减1,函数semop成功返回;
- 此信号量被删除,函数smeop出错返回EIDRM;
- 进程捕捉到信号,并从信号处理函数返回,在此情况将此信号量的semncnt值减1,函数semop出错返回EINTR
- sem_flg指定
在semctl
函数中的命令有多种,这里就说两个常用的:
.SETVAL
:用于初始化信号量为一个已知的值。所需要的值作为联合semun的val成员来传递。在信号量第一次使用之前需要设置信号量。
.IPC_RMID
:删除一个信号量集合。如果不删除信号量,它将继续在系统中存在,即使程序已经退出,它可能在你下次运行此程序时引发问题,而且信号量是一种有限的资源。
代码示例:
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdlib.h>
#include <unistd.h>
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
void sem_p(int id)
{
struct sembuf set;
set.sem_num = 0; /*序号*/
set.sem_op = -1; /*P操作*/
set.sem_flg = SEM_UNDO;
if(semop(id, &set, 1) == -1)
{
perror("P operation Error");
exit(-1);
}
}
void sem_v(int id)
{
struct sembuf set;
set.sem_num = 0; /*序号*/
set.sem_op = 1; /*V操作*/
set.sem_flg = SEM_UNDO;
if(semop(id, &set, 1) == -1)
{
perror("P operation Error");
exit(-1);
}
}
}
void sem_v(int id)
{
struct sembuf set;
set.sem_num = 0; /*序号*/
set.sem_op = 1; /*V操作*/
set.sem_flg = SEM_UNDO;
if(semop(id, &set, 1) == -1)
{
perror("P operation Error");
exit(-1);
}
}
int main()
{
key_t key;
int semid;
if((key = ftok(".",1)) < 0)
{
perror("ftok error");
exit(-1);
}
semid = semget(key,1,IPC_CREAT|0666);//获取创建信号量
if(semid == -1)
{
perror("semget error");
exit(-1);
}
union semun initsem;
initsem.val = 0;
semctl(semid,0,SETVAL,initsem);//初始化信号量
int pid = fork();
if(pid < 0)
{
perror("fork error");
exit(-1);
}
else if(pid > 0)
{
sem_p(semid);
printf("this is father\n");
sem_v(semid);
}else
{
printf("this is child\n");
sem_v(semid);
}
return 0;
}