信号量(semaphore)与前面的IPC结构不同,它是一个计数器。数据量用于实现进程间的互斥与同步,而不是用于存储进程间通信的数据。
特点
信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。
信号量基于操作系统的PV操作,程序对信号量的操作都是原子操作
每次对信号量的PV操作不仅限于对信号量值加1减1,而且可以加减任意正整数。
支持信号量组
原型
最简单的信号量是只能取0和1的变量,这也是信号量最常见的一种形式,也叫二值信号量。
而可以取多个正整数的信号量被称为通用信号量。
Linux下的信号量函数都是在通用信号量数组上进行操作,而不是在一个单一的二值信号量上进行操作。
#include <sys/sem.h>
#include <sys/types.h>
#include <sys/ipc.h>
// 创建或获取一个信号量;若成功返回信号量集ID,失败返回 -1
int semget(key_t key, int nsems, int semflg);
key: 整数值,不相关的进程可以通过它访问同一个信号量。
nsems: 创建信号量的数目,一般取值为1
semflg: 标志位,如果未创建时IPC_CREAT;如果为全新创建,不知道是否有人创建过,则为IPC_CREAT|IPC_EXCL。
// 对信号量组进行操作,改变信号量的值;成功返回0,失败返回-1
int semop(int semid, struct sembuf *sops, unsigned nsops);
semid: 信号量标识符。
sops: 结构体
struct sembuf{
short sem_num;
short sem_op; // 表明是p操作还是v操作,p:-1,v:+1。
short sem_flg; // 一般等于SEM_UNDO, 目的是:不论当前进程是否正常退出,
// 都将还原此操作的 sem_op 值。用于以防万一异常结束的进程。
}
// 控制信号量的相关信息;成功返回0,失败返回-1。
int semctl(int semid, int semnum, int cmd, ...);
semid: 信号量标识符。
semnum: 操作第几个信号量,因为是数组,所以从0开始
cmd: 将要采取的动作
SETVAL:初始化信号量,该值通过下面union semun中的val成员设置,其作用是再信号量第一次使用之前对它进行设置。
IPC_RMID:用于删除一个已经无需继续使用的信号量标识符。
如果有第四个参数,它会是一个union semun的联合体:
union semun{
int val;
struct semid_ds *buf;
unsigned short * array;
}
P:用于等待--获取资源 (-1)
V:用于操作--释放资源(1)
目的
保证同一时刻只能有一个进程对某个资源进行访问,下面是一个让子进程先执行去访问资源的例子
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
// int semget(key_t key, int nsems, int semflg);
// int semctl(int semid, int semnum, int cmd, ...);
// int semop(int semid, struct sembuf *sops, unsigned nsops);
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 pCetKey(int id)
{
struct sembuf set;
set.sem_num = 0;
set.sem_op = -1; // 获取资源
set.sem_flg = SEM_UNDO;
semop(id, &set, 1);
printf("getkey\n");
}
void vPutBackKey(int id)
{
struct sembuf set;
set.sem_num = 0;
set.sem_op = 1; // 释放资源
set.sem_flg = SEM_UNDO;
semop(id, &set, 1);
printf("put back key\n");
}
int main()
{
key_t key;
int semid;
key = ftok(".", 2);
// 获取创建信号量,结果是数组, 1代表有一个信号量
semid = semget(key, 1, IPC_CREAT|0666);
union semun initsem;
initsem.val = 0;
// 0 代表操作的是数组中的第一个信号量
// 初始化信号量
semctl(semid, 0, SETVAL, initsem);
int pid = fork();
if(pid > 0){
// 拿锁
pCetKey(semid);
printf("this is father\n");
// 把锁返回去
semctl(semid, 0, IPC_RMID);
}
else if(pid == 0){
printf("this is child\n");
vPutBackKey(semid);
}else{
printf("fork error\n");
}
return 0;
}