介绍
信号量准确的来说不算是一种ipc通信机制,更像是一种ipc通信的辅助机制,在信号量的约束下,不同进程访问共享资源或者临界资源的时候,会判断当前信号量是否满足访问条件。信号量是一个特殊的变量,程序对其访问都是原子操作。
原理
信号量只能进行两种操作 wait 和 signal 信号, 即P(sv)和V(sv) :
P(sv):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行
V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1.
举例说明:就是两个进程共享信号量sv,一旦A进程执行了P(sv)操作,它将得到信号量,并可以进入临界区,使sv减1,而因为sv为0,B进程将被阻止进入临界区,得不到P(sv)操作,它会被挂起以等待A进程离开临界区域并执行V(sv)释放信号量,之后B进程可以得到P(sv)操作,进入临界区进行操作。
函数
头文件 include<sys/sem.h>
int semget(key_t key, int num_sems, int sem_flags);
作用:创建一个新信号量或取得一个已有信号量。
参数1: key是整数值(非0值),两个进程之间可以通过它访问一个信号量,它代表程序可能要使用的某个资源,程序对所有信号量的访问都是间接的,程序先通过调用semget()函数并提供一个键再由系统生成一个相应的信号标识符(semget()函数的返回值),只有semget()函数才直接使用信号量键,所有其他的信号量函数使用由semget()函数返回的信号量标识符。如果多个程序使用相同的key值,key将负责协调工作。
参数2:num_sems指定需要的信号量数目。比如我们要创建一个信号量,则该值为1.,创建2个就是2
参数3:sem_flags信号量的创建方式或权限。有IPC_CREAT,IPC_EXCL。
IPC_CREAT:如果信号量存在,则直接获取,否则申请一个。
IPC_EXCL: 只有信号量不存在的时候,新的信号量才会创建,否则就产生错误。
返回值:semget()函数成功返回一个相应信号标识符(非零),失败返回-1.
int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);
作用:改变信号量的值。
参数1:semid信号集的识别码,可通过semget获取。
参数2:sops指向存储信号操作结构的数组指针,信号操作结构的原型如下
struct sembuf{
short sem_num; // 除非使用一组信号量,否则它为0
short sem_op; // 信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作,
// 一个是+1,即V(发送信号)操作。
short sem_flg; // 通常为SEM_UNDO,使操作系统跟踪信号,
// 并在进程没有释放该信号量而终止时,操作系统释放信号量
};
参数3:num_sem_ops信号操作结构的数量,恒大于或等于1
int semctl(int sem_id, int sem_num, int command, ...);
作用:用来直接控制信号量信息
参数1:信号集的标识符,即是信号表的索引。
参数2:信号集的索引,用来存取信号集内的某个信号。
cmd:需要执行的命令,有效值有
范例
// semget的调用者可以给其key传递一个特殊的键值IPC_PRIVATE(值为0)
// 这样无论信号量是否存在,semget都将创建一个新的信号量。使用该键值
// 创建的信号量
// 并非像它的名字那样是进程私有的。其他进程,尤其子进程,也有办法来访问这个信号量。
// 下面的例子就是在父子进程间使用一个IPC_PRIVATE信号量来进行同步。
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
union semun {
int val; // 用于setvalue;
struct semid_ds* buf; // 用于ipc_stat 和ipc_set 命令
unsigned short* array; // 用于getall 和setall 命令
struct seminfo* __buf; // 用于ipc_info命令
};
// op为-1时执行p操作,op为1执行v操作。
// semop操作
void pv( int sem_id, int op) {
struct sembuf sem_b;
sem_b.sem_num = 0; // 信号集中编号为1的信号量
sem_b.sem_op = op; // 执行op操作, 大于0,执行+1操作, 小于0 执行-1 操作
sem_b.sem_flg = SEM_UNDO; // 信号量操作进程退出时取消正在进行op操作。
semop(sem_id, &sem_b, 1); // 操作信号量的个数只有一个
}
int main(int argc, char* argv[]) {
// 创建一个信号量集,它创建一个新的信号集, 权限为读写
int sem_id = semget(IPC_PRIVATE, 1, 0666);
// semctl中第四个参数的格式
union semun sem_un;
sem_un.val = 1;
// SETVAL社会semun.buf的数据成员复制到信号量集关联的内核数据结构中,
// 同时内核数据中的semid_ds。sem_ctime被更新。
semctl(sem_id, 0, SETVAL, sem_un);
// 复制一个新进程
pid_t pid = fork();
if (pid < 0) {
return -1;
} else if (pid == 0) {
printf("child try to get binary sem\n");
// 在父子进程共享IPC_PRIVATE 信号量的关键在于二者都可以操作该信号量的标识符
// sem_id;
pv (sem_id, -1);
printf("child get the sem and would releases it after 20 seconds\n");
sleep(20);
pv (sem_id, 1);
exit(0);
} else {
printf("parents try to get binary sem\n");
pv (sem_id, -1);
printf("parent get the sem and would releases it after 20 seconds\n");
sleep(20);
pv (sem_id, 1);
}
waitpid(pid, NULL, 0);
// 立即移除信号集,唤醒所有等待该信号集的进程
semctl(sem_id, 0, IPC_RMID, sem_un);
return 0;
}
注意
1、显示所有的IPC设施
ipcs -a
2、显示所有的消息队列Message Queue
ipcs -q
3、显示所有的信号量
ipcs -s
4、显示所有的共享内存
ipcs -m
5、显示IPC设施的详细信息
ipcs -q -i id
id 对应shmid、semid、msgid等。-q对应设施的类型(队列),查看信号量详细情况使用-s,查看共享内存使用-m。
6、显示IPC设施的限制大小
ipcs -m -l
-m对应设施类型,可选参数包括-q、-m、-s。
7、显示IPC设施的权限关系
ipcs -c
ipcs -m -c
ipcs -q -c
ipcs -s -c
8、显示最近访问过IPC设施的进程ID。
ipcs -p
ipcs -m -p
ipcs -q -p
9、显示IPC设施的最后操作时间
ipcs -t
ipcs -q -t
ipcs -m -t
ipcs -s -t
10、显示IPC设施的当前状态
ipcs -u