信号量是一个计数器,常用于处理进程或线程的同步问题,特别是对临界资源的同步访问。
1.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.信号量的操作
原型:int semop(int semid, struct sembuf *sops, unsigned nsops);
信号量的值与相应资源的使用情况有关,当它的值大于0时,表示当前可用资源的数量,当他的值小于0时,其绝对值表示等待
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,只完成对一个信号量的操作
3.semctl (得到一个信号量集标识符或创建一个信号量集对象)
原型:int semctl(int semid, int semnum, int cmd, union semun arg)
semid:信号量集标识符
semnum:信号量集数组上的下标,表示某一个信号量
cmd:
PC_STAT:从信号量集上检索semid_ds结构,并存到semun联合体参数的成员buf的地址中
IPC_SET:设置一个信号量集合的semid_ds结构中ipc_perm域的值,并从semun的buf中取出值
IPC_RMID:从内核中删除信号量集合
GETALL:从信号量集合中获得所有信号量的值,并把其整数值存到semun联合体成员的一个指针数组中
GETNCNT:返回当前等待资源的进程个数
GETPID:返回最后一个执行系统调用semop()进程的PID
GETVAL:返回信号量集合内单个信号量的值
GETZCNT:返回当前等待100%资源利用的进程个数
SETALL与GETALL正好相反
SETVAL:用联合体中val成员的值设置信号量集合中单个信号量的值
arg:
union semun {
short val; /SETVAL用的值/
struct semid_ds* buf; /IPC_STAT、IPC_SET用的semid_ds结构/
unsigned short* array; /SETALL、GETALL用的数组值/
struct seminfo *buf; /为控制IPC_INFO提供的缓存/
} arg;
编程案例:
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
union semun{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *_buf;
};
void pGetKey(int id)
{
struct sembuf set;
set.sem_num = 0; //0代表第1个信号量
set.sem_op = -1; //对应上面的val ,-1表示减少一个信号量(拿锁)
set.sem_flg=SEM_UNDO;
semop(id,&set,1);
printf("getKey\n");
}
void vPutBackKey(int id)
{
struct sembuf set;
set.sem_num = 0; //0代表第1个信号量
set.sem_op = 1; //对应上面的val ,1表示增加一个信号量(放锁)
set.sem_flg=SEM_UNDO;
semop(id,&set,1);
printf("getKey\n");
}
int main(int argc, char const *argv[])
{
key_t key;
key = ftok(".",2);
int semid = semget(key,1,IPC_CREAT|0666); //创建获取信号量
union semun initsem;
initsem.val = 0; //初始化信号量个数为0
semctl(semid,0,SETVAL,initsem);//初始化信号量,设置SETVAL的值为inistem
int pid = fork();
if(pid>0){
pGetKey(semid); //获取信号量(拿锁)
printf("this is fsther\n");
vPutBackKey(semid);//放回信号量(放锁)
semctl(semid,0,IPC_RMID);
}else if(pid == 0){
printf("this is child\n");
vPutBackKey(semid); //放回信号量(放锁)
}else{
printf("fork fail\n");
}
return 0;
}
运行后因为初始化信号量为0 (锁为0),父进程开始得不到信号量(拿不到锁)阻塞,等子进程运行放信号量(放锁)后,父进程才能获得信号量(拿锁),此时父进程才能运行
运行结果为: