信号量
信号量主要用于同步和互斥
进程互斥
- 由于有些资源是进程共享的,而且这些资源不可以同时被多个进程使用,所以这些进程竞争使用这些资源,这种进程间的关系叫做进程互斥。
- 系统中某些资源一次只允许一个进程使用,称这样的资源为临界资源或互斥资源
- 涉及到临界资源的程序段为临界区
进程同步:指的是多个进程需要相互配合共同完成一项任务
P、V原语
P :申请信号量
V :释放信号量
信号量就类似于一个计数器,并不是让进程间能够直接发送字符串数据,而是通过自身计数器的性质,来完成进程之间的同步与互斥。
查看系统中的信号量集
ipcs -s
删除一个已经定义了的信号量集
ipcrm -s +semid
信号量集的结构
struct semid_ds
{
struct ipc_perm sem_perm; //包含最主要的元素是 key 和 mode 权限
time_t sem_otime; //
time_t sem_ctime;
unsigned short sem_nsems;
};
信号量集函数
- semget函数
功能 :用来创建和访问一个信号量集
原型 :int semget(key_t key, int nsems, int semflg);
参数 :
key :信号集的名字
nsems :信号集中信号集的个数
semflg :标志位
返回值 :
成功返回一个非负整数,即该信号集的标识码,失败返回-1 - semctl函数
功能 :用来控制信号量集
原型 :int semctl(int semid, int semnum, int cmd, ···);
参数 :
semid :由semget返回的信号集标码
semnum :信号集中信号集的序号
cmd :将要采取的动作(SETVAL、GETVAL、IPC_STAT、IPC_SET、IPC_RMID)
最后一个参数根据cmd的选项而不同
返回值 :
成功返回0, 失败返回 -1。
cmd :
SETVAL :设置信号量集中的信号量的计数值
GETVAL :获取信号量集中的信号量的计数值
IPC_STAT :把semid_ds结构中的数据设置为信号集的当前关联值
IPC_SET :在进程有足够权限下,把信号集的当前关联值设置为semid_ds数据结构中给出的值
IPC_RMID :删除信号集
若cmd为IPC_RMID,最后一个参数不需要使用 semop函数
原型 :int semop(int semid, struct sembuf *sops, unsigned nsops);
功能 :用于实现PV操作,是原子操作
参数 :
semid :信号量集的标识码
sops :指向一个结构数值的指针
nsops :信号量的个数
返回值 :
成功返回0,失败返回 -1
struct sembuf :
struct sembuf
{
short sem_num;
short sem_op;
short sem_flg;
};
sem_num : 信号量的编号
sem_op :信号量一次PV操作时加减的数值,一般只会用到两个值:
一个是 -1 ,就是P操作,等待信号量变的可用
另一个是 +1 ,就是V操作,发出信号量已经可用
ser_flag :两个取值IPC_NOWAIT(P操作不成功,不等待)、SEM_UNDO(进程结束后,申请的信号量还原)
实现简单的PV操作
common.h
#pragma once
#include <stdio.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/types.h>
#define PATH_NAME "."
#define PROJ_ID 1
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) */
};
int CreateSem(int n);
int GetSem();
int DestroySem(int semid);
int SetSemValue(int semid, int index, int value);
int GetSemValue(int semid, int index, int *value);
int P(int semid, int index);
int V(int semid, int index);
common.c
#include "common.h"
//n为信号集中信号量的个数
int CommonSem(int n, int flags)
{
key_t key = ftok(PATH_NAME, PROJ_ID);
if(key < 0)
{
perror("ftok");
return -1;
}
int semid = semget(key, n,flags);
if(semid < 0)
{
perror("semget");
return -1;
}
return semid;
}
int CreateSem(int n)
{
return CommonSem(n, IPC_CREAT | IPC_EXCL | 0666);
}
int GetSem()
{
return CommonSem(0, IPC_CREAT);
}
int DestroySem(int semid)
{
int ret = semctl(semid, 0, IPC_RMID);
if(ret < 0)
{
perror("semctl");
return -1;
}
return 0;
}
int SetSemValue(int semid, int index, int value)
{
union semun _semun;
_semun.val = value;
int ret = semctl(semid, index, SETVAL, _semun);
if(ret < 0)
{
perror("semctl");
return -1;
}
return 0;
}
int GetSemValue(int semid, int index, int *value)
{
//semctl返回值就是当前value
int ret = semctl(semid, index, GETVAL);
if(ret < 0)
{
perror("semctl");
return -1;
}
*value = ret;
return 0;
}
int Commonop(int semid, int index, int n)
{
//不能直接将信号量减一再设置回去,这样的操作不是原子操作
//P、V操作应该都是原子操作。
struct sembuf _sembuf;
_sembuf.sem_num = index;
_sembuf.sem_op = n;
_sembuf.sem_flg = 0;
int ret = semop(semid, &_sembuf, 1);
if(ret < 0)
{
perror("semop");
return -1;
}
return 0;
}
int P(int semid, int index)
{
return Commonop(semid, index, -1);
}
int V(int semid, int index)
{
return Commonop(semid, index, 1);
}
test.c
#include "common.h"
int main()
{
//int semid = GetSem();
int semid = CreateSem(10);
if(semid < 0)
{
printf("CreateSem fail");
return 1;
}
printf("semid = %d\n",semid);
int ret = SetSemValue(semid, 0, 1);
if(ret < 0)
{
printf("SetSemValue failed");
return 1;
}
int value = 0;
GetSemValue(semid, 0, &value);
printf("after value expect 1, actual %d\n", value);
P(semid, 0);
GetSemValue(semid, 0, &value);
printf("after value expect 1, actual %d\n", value);
V(semid, 0);
printf("---------释放了一个资源后--------\n");
P(semid, 0);
GetSemValue(semid, 0, &value);
printf("-------haha--------\n");
DestroySem(semid);
return 0;
}