目录
system-V IPC信号量
semget():创建或获取信号量
semop():PV操作
semctl():信号量集的一系列控制操作
sem.h文件
sem.c文件
main.c文件
Makefile文件
执行过程
system-V IPC信号量
本质上是一个计数器,用于协调多进程间对共享数据对象的读取,主要用来保护共享资源,使得该共享资源在一个时刻只有一个进程独享。
信号量只能进行两种操作:P(锁,申请资源)、V(解锁,释放资源)。
P操作:
如果有可用的资源(信号量值大于0),则申请一个资源(信号量值减1,进入临界区代码);
如果没有可用的资源(信号量值等于0),则阻塞,直到系统将资源分配给该进程(进入等待队列,一直等到资源轮到该进程)。
V操作:
如果在该信号量的等待队列中有进程在等待资源,则唤醒一个阻塞的进程。
如果在该信号量的等待队列中没有进程在等待资源,则释放一个资源(信号量值加1)。
在信号量进行PV操作时都为原子操作(因为它需要保护临界资源)。
原子操作:单指令的操作称为原子的,单条指令的执行是不会被打断的。
semget():创建或获取信号量
semget()函数是创建或获取一个已经创建的信号量。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
/*
key:表示系统内的信号量。如果key已存在,nsems参数指定为0,semflg参数也指定为0。可以使用IPC_PRIVATE创建一个没有key的信号量
nsems:用于创建信号量时,表示可用的信号量数目。
semflg:指定标志位和mode。
IPC_CREAT:创建新的信号量,即使信号量已经存在也不会出错。
IPC_CREAT | IPC_EXCL:创建一个新的唯一的信号量,如果已经存在会返回报错
返回值:
执行成功:信号量标识符
执行失败:-1
*/
创建信号量时,受到一下系统信息的影响:
SEMMNI:系统中信号量总数的最大值。
SEMMSL:每个信号量中信号量元素个数的最大值。
SEMMNS:系统中所有信号量中的信号量元素总数的最大值。
在Linux系统中,以下信息可通过命令ipcs -l查看。
semop():PV操作
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, size_t nsops);
/*
semid:system-v IPC信号量的标识符
sops:指向一个struct sembuf结构体数组的指针,该数组是一个信号量操作数组,如下文
nsops:表示sops参数数组的数量
*/
struct sembuf{
unsigned short int sem_num; // 信号量的序号:0~nsems-1
short int sem_op; // 对信号量的操作:>0、0、<0
short int sem_flg; // 操作标识:0、IPC_WAIT、SEM_UNDO
};
sem_num标识信号量的第几个信号量,0标识第一个,nsems-1标识最后一个。
sem_op标识对信号量所进行的操作类型。
>0:表示进程对资源使用完毕,交回该资源,即对该信号量执行V操作,交回的资源数由sem_op决定,系统会把sem_op的值加到该信号量的信号量当前值semval上。如果sem_flg指定了SEM_UNDO(还原)标志,则从该进程的信号量调整值中减去sem_op。
<0:表示进程希望使用资源,对该信号量执行P操作,当信号量当前值semval大于或等于-sem_op时,semval减去sem_op的绝对值,为该进程分配对应数目的资源。如果sem_flg指定了SEM_UNDO(还原)标志,则sem_op的绝对值加到该进程的此信号量调整值上。当semval小于-sem_op时,相应信号量的等待进程数量就加1,调用进程被阻塞,直到semval大于或等于-sem_op时,调用进程被唤醒,执行相应的P操作。
0:表示进程要阻塞等待,直到信号量当前值semval变为0。
sem_flg表示信号量操作的属性标志。
0:正常操作
SEM_UNDO:维护进程对信号量的调整值,进程退出时会自动还原它对信号量的操作。
IPC_WAIT:调用进程在信号量的值不满足条件的情况下不会被阻塞,直接返回-1,并将errno设置为EAGAIN。
信号量调整值:指定信号量针对某个特定进程的调整值。只有sembuf结构的sem_flg指定为SEM_UNDO后,信号量调整值才会随着sem_op而更新。
对某个进程,在指定了SEM_UNDO后,对信号量的当前值的修改都会反应到信号量调整值上,当该进程终止时,内核会根据信号量调整值重新恢复信号量之前的值,SEM_UNDO操作可以防止进程退出时没有释放信号量导致的死锁。
semctl():信号量集的一系列控制操作
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ...);
/*
semid:system-v IPC信号量的标识符
semnum:表示信号量集的第几个信号量,0表示第一个,nsems-1表示最后一个
cmd:操作命令
IPC_STAT:获取此信号量集合的 semid_ds 结构,存放在第四个参数的 buf 中。
IPC_SET:通过第四个参数的 buf 来设定信号量集相关联的 semid_ds 中信号量集合权
限为 sem_perm 中的 uid, gid, mode。
IPC_RMID:从系统中删除该信号量集合。
GETVAL:返回第 semnum 个信号量的值。
SETVAL:设置第 semnum 个信号量的值,该值由第四个参数中的 val 指定。
GETPID:返回第 semnum 个信号量的 sempid,最后一个操作的 pid。
GETNCNT:返回第 semnum 个信号量的 semncnt。等待 semval 变为大于当前值的线程
数。
GETZCNT:返回第 semnum 个信号量的 semzcnt。等待 semval 变为 0 的线程数。
GETALL:去信号量集合中所有信号量的值,将结果存放到的 array 所指向的数组。
SETALL:按 arg.array 所指向的数组中的值,设置集合中所有信号量的值。
第四个参数:可选,参数类型为union semun。
union semun{
int val; // setval的值
struct semid_ds *buf; // IPC_STAT、IPC_SET的buffer
unsigned short *array; // GETALL、SETALL的数组
struct seminfo *__buf; // IPC_INFO的buffer
}
*/
sem.h文件
#ifndef __SEM_H_
#define __SEM_H_
int init_sem(int sem_id, int init_value);
int del_sem(int sem_id);
int sem_p(int sem_id);
int sem_v(int sem_id);
#endif
sem.c文件
#include <sys/sem.h>
#include <sys/ipc.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include "sem.h"
union semun{
int val;
struct semid_ds *buf;
};
int init_sem(int sem_id, int init_value)
{
union semun sem_union;
// setval的值
sem_union.val = init_value;
// 设置第一个信号量的值
if(semctl(sem_id, 0, SETVAL, sem_union) == -1){
perror("sem init");
return -1;
}
return 0;
}
int del_sem(int sem_id)
{
union semun sem_union;
// 删除第一个信号量
if(semctl(sem_id, 0, IPC_RMID, sem_union) == -1){
perror("sem del");
return -1;
}
return 0;
}
int sem_p(int sem_id)
{
struct sembuf sops;
sops.sem_num = 0; // 单个信号量的编号应该为0
sops.sem_op = -1; // P操作
sops.sem_flg = SEM_UNDO; // 若进程退出,系统将还原信号量
if(semop(sem_id, &sops, 1) == -1){
perror("P");
return -1;
}else{
printf("P successful!\n");
}
return 0;
}
int sem_v(int sem_id)
{
struct sembuf sops;
sops.sem_num = 0; // 单个信号量的编号应该为0
sops.sem_op = 1; // V操作
sops.sem_flg = SEM_UNDO; // 若进程退出,系统将还原信号量
if(semop(sem_id, &sops, 1) == -1){
perror("V");
return -1;
}else{
printf("V successful!\n");
}
return 0;
}
main.c文件
#include <sys/types.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include "sem.h"
int main(void)
{
pid_t pid;
int sem_id;
sem_id = semget((key_t)0x1111, 1, 0666 | IPC_CREAT);
init_sem(sem_id, 0);
pid = fork();
if(pid == -1){
perror("fork");
}else if(pid == 0){
// 子进程
sleep(3);
printf("the child process pid:%d\n", getpid());
sem_v(sem_id);
}else{
// 父进程
sem_p(sem_id);
printf("the father process pid:%d\n", getpid());
sem_v(sem_id);
del_sem(sem_id);
}
exit(0);
}
Makefile文件
照旧