一、信号量概述
信号量(Semaphore)与已经学过的的IPC结构不同,它是一个计数器,信号量用于实现进程间的互斥与同步,而不是用于存储进程间的通信数据。举例子来理解这句话:
把一个临界资源(也可以认为是共享内存)比作一个上了锁的房间,某人比作一个进程,而开锁的钥匙就是信号量。某人有钥匙的话,就能开锁进入房间里,做它需要做的事情,如果这时候有第二个人想进入房间里的话,是进不去的,因为它没有钥匙(信号量)。直到进去的那个人除了房间把钥匙放在某个地方,然后第二人拿到了钥匙,才能渠道房间里去。这钥匙起到的作用就是信号量。
二、相关API
全都放在了<sys/sem.h> 头文件中
1.int semget(key_t key,int num_sems,int sem_flags)
创建或获取一个信号量组
参数说明:
1. key 索引值,可使用ftok函数来获取;
2. num_sems 信号量的个数;
3. sem_flags 传递IPC_CREAT 加上权限可以创建信号量集;
返回值: 成功返回信号集的ID;失败返回 -1 ;
用法:
key_t key;
key=ftok(".",1);
int semid=semget(key,1,IPC_CREAT|0666);//创建一个信号量
2.int semop(int semid,struct sembuf semoparray[],size_t semops)
操作信号量组函数。有两种操作,p操作:即上面例子提到的拿钥匙,v操作:上面例子提到的放钥匙。
参数说明:
1. semid 被操作目标信号量的ID号;
2. semoparray[] 结构体数组,它有三个成员:
(1)sem_num:需要操作第几个信号量;
(2)sem_op:信号量的值,-1为减1
(3)sem_flg:一般赋值SEM_UNDO;
3. semops :semoparray[] 数组的个数。
返回值: 成功返回0;失败返回 -1 ;
p操作用法:
struct sembuf *semoparry;
semoparry=(struct sembuf *)malloc(sizeof(struct sembuf));
semoparry->sem_num=0;
semoparry->sem_op=-1;
semoparry->sem_flg=SEM_UNDO;
semop(semid,semoparry,1);
v操作用法:
struct sembuf *semoparry;
semoparry=(struct sembuf *)malloc(sizeof(struct sembuf));
semoparry->sem_num=0;
semoparry->sem_op=1;
semoparry->sem_flg=SEM_UNDO;
semop(semid,semoparry,1);
2.int semctl(int semid,int sem_num,int cmd,…)
控制信号量函数。用来控制信号,比如初始化,移除信号量等。
参数说明:
1. semid 被操作目标信号量的ID号;
2. sem_num 信号量的个数,即需要操作第几个信号量;
3. cmd 操作指令,它有几个宏,我们这里用SETVAL(操作信号量的值)
4. 其他 这个参数,是配合SETVAL指令来使用,它是个联合体而且需要定义这个联合体:
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 */
};
返回值: 成功返回0;失败返回 -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*/
};
union semun value;
value.val=1;
semctl(semid,0,SETVAL,value);
三、小实验
认识了这三个API之后,我们可以做个小实验,来看看信号量是怎么实现管控进程。
代码:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
//int semget(key_t key, int nsems, int semflg);
//定义联合体
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) */
};
//pGetKey p操作
void pGetKey(int semid)
{
int semop_s;
struct sembuf *sops;
sops=(struct sembuf *)malloc(sizeof(struct sembuf));
sops->sem_num=0;
sops->sem_op=-1;
sops->sem_flg=SEM_UNDO;
semop_s=semop(semid,sops,1);
if(semop_s==0){
printf("I get key!\n");
}else{
printf("I no key!\n");
}
}
//vGetKey v操作
void vGetKey(int semid)
{
int semop_s;
struct sembuf *sops;
sops=(struct sembuf *)malloc(sizeof(struct sembuf));
sops->sem_num=0;
sops->sem_op=1;
sops->sem_flg=SEM_UNDO;
semop_s=semop(semid,sops,1);
if(semop_s==0){
printf("I no key!\n");
}else{
printf("I have key!\n");
}
}
int main(int argc,char **argv)
{
key_t key;
key=ftok(".",12);
int semid;
int pid;
semid=semget(key,1,IPC_CREAT|0666);//创建信号量
union semun Value;
Value.val=0;
semctl(semid,0,SETVAL,Value);//初始化信号量
pid=fork();//创建子进程
if(pid>0){
pGetKey(semid);//父进程p操作
printf("This is father!\n");
vGetKey(semid);//父进程v操作
}else if(pid==0){
printf("This is child!\n");
vGetKey(semid);//子进程v操作
}else{
printf("fork error!\n");
}
return 0;
}
因为 semctl 时,把信号量的值设置为 0;所以父进程p操作(拿钥匙)时会阻塞,当子进程v操作后,重新放出了钥匙。父进程才能执行打印。所以这个运行结果就是子进程先执行,然后父进程才执行。
运行结果:
四、进程间通信总结
进程间通信(IPC)学到现在,就可结束了了。回顾一下进程间通信都学了写啥:
一、通道和命名通道FIFO: 是个半双工的通信方式,读写不可以同时进行,必须读完后关闭通道,然后才可以写入。而FIFO相当于一个文件,可以用普通的文件操作来操作FIFO。
二、消息队列: 消息队列是个链表,发消息就是往消息链表里添加一个节点(消息的结构体),而且两个不相关的进程都可以操作消息队列。
三、共享内存: 区别于两个进程之间的另外一块内存空间,而且可以连接到进程的空间当中,可以使用普通的指针操作来访问共享内存。
四、信号: Linux操作系统中已经定义了64个信号,我们可以通过这些信号来控制进程(入门版)或传递消息(高级版);
五:信号量: 区别于前四个IPC结构,不是一种进程通信方式,我们可以理解为它起到一个进程调度的作用。