进程间同步之信号量(semaphore)
1. 信号量的创建与使用
linux下使用semget创建或打开信号量集,
int semget(key_t key, int nsems, int semflg);
该函数执行成功则返回一个信号量集的标识符,失败返回-1。返回的参数key是由ftok得到的键值;
第二个参数nsems指明要创建的信号量集包含的信号量个数。如果只是打开信号量,把nsems设置为0即可。该参数只在创建信号量集时有效。
第三个参数semflg为操作标识,可取如下值:
0:取信号量集标识符,若不存在则函数会报错
IPC_CREAT:当semflg&IPC_CREAT为真时,如果内核中不存在键值与key相等的信号量集,则新建一个信号量集;如果存在这样的信号量集,返回此信号量集的标识符
IPC_CREAT|IPC_EXCL:如果内核中不存在键值与key相等的信号量集,则新建一个消息队列;如果存在这样的信号量集则报错
上述semflg参数为模式标志参数,使用时需要与IPC对象存取权限(如0600)进行|运算来确定信号量集的存取权限
2.信号量的操作
信号量的值与相应资源的使用情况有关,当它的值大于0时,表示当前可用资源的数量,当他的值小于0时,其绝对值表示等待
使用这个资源的进程个数。信号量的值仅能由PV操作来改变。
在linux下,pv操作通过调用函数semop实现。该函数定义在头文件sys/sem.h中,原型:
int semop(int semid, struct sembuf *sops, unsigned nsops);
对信号量集标识符为semid中的一个或多个信号量进行P操作或V操作
semid:信号量集标识符
sops:指向进行操作的信号量集结构体数组的首地址,此结构的具体说明如下:
struct sembuf {
short semnum; /*信号量集合中的信号量编号,0代表第1个信号量*/
short val;/*若val>0进行V操作信号量值加val,表示进程释放控制的资源 */
/*若val<0进行P操作信号量值减val,若(semval-val)<0(semval为该信号量值),则调用进程阻塞,直到资源可用;若设置IPC_NOWAIT不会睡眠,进程直接返回EAGAIN错误*/
/*若val==0时阻塞等待信号量为0,调用进程进入睡眠状态,直到信号值为0;若设置IPC_NOWAIT,进程不会睡眠,直接返回EAGAIN错误*/
short flag; /*0 设置信号量的默认操作*/
/*IPC_NOWAIT设置信号量操作不等待*/
/*SEM_UNDO 选项会让内核记录一个与调用进程相关的UNDO记录,如果该进程崩溃,则根据这个进程的UNDO记录自动恢复相应信号量的计数值*/
};
nsops:进行操作信号量的个数,即sops结构变量的个数,需大于或等于1。最常见设置此值等于1,只完成对一个信号量的操作
错误代码:
E2BIG:一次对信号量个数的操作超过了系统限制
EACCESS:权限不够
EAGAIN:使用了IPC_NOWAIT,但操作不能继续进行
EFAULT:sops指向的地址无效
EIDRM:信号量集已经删除
sops为指向sembuf数组,定义所要进行的操作序列。下面是信号量操作举例。
struct sembuf sem_get={0,-1,IPC_NOWAIT}; /*将信号量对象中序号为0的信号量减1*/
struct sembuf sem_get={0,1,IPC_NOWAIT}; /*将信号量对象中序号为0的信号量加1*/
struct sembuf sem_get={0,0,0}; /*进程被阻塞,直到对应的信号量值为0*/
flag一般为0,若flag包含IPC_NOWAIT,则该操作为非阻塞操作。若flag包含SEM_UNDO,则当进程退出的时候会还原该进程的信号量操作,这个标志在某些情况下是很有用的,
比如某进程做了P操作得到资源,但还没来得及做V操作时就异常退出了,此时,其他进程就只能都阻塞在P操作上,于是造成了死锁。若采取SEM_UNDO标志,就可以避免因为进程异常退出而造成的死锁。
3. semctl (得到一个信号量集标识符或创建一个信号量集对象)
int semctl(int semid, int semnum, int cmd, union semun arg)
semid:信号量集标识符
semnum:信号量集数组上的下标,表示某一个信号量
下面截个图:
下面举例说明:
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>
#include <string.h>
#define NUMBER 5
#define KEY 1111
union semun{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};
int main()
{
int sem_id = 0;
union semun semattr;
struct sembuf sbuf_p = {0, -1, 0};
struct sembuf sbuf_v = {0, 1, 0};
pid_t pid;
sem_id = semget((key_t)KEY, 1,IPC_CREAT | 0666);
semattr.val = NUMBER;
semctl(sem_id, 0, SETVAL, semattr);
pid = fork();
if(pid == 0)
{
int i = 10;
printf("enter child process\n");
while (i > 0)
{
semop(sem_id, &sbuf_p, 1);
printf("the child process get semaphore!\n");
}
return 0;
}else
{
char buf[20]="";
printf("enter parent process\n");
while(fgets(buf, 20, stdin) > 0)
{
if(strncmp(buf,"over", 4) == 0)
{
break;
}
printf("parent process set semaphore\n");
semop(sem_id, &sbuf_v, 1);
}
}
return 0;
}