一、什么是信号量
信号量的本质是一种数据操作锁,它本⾝身不具有数据交换的功能,而是通过控制其他的通信资源(文件,外部设备)来实现进程间通信,它本身只是一种外部资源的标识。信号量在此过程中负责数据操作的互斥、同步等功能。
当请求一个使用信号量来表示的资源时,进程需要先读取信号量的值来判断资源是否可用。⼤于0,资源可以请求,等于0,无资源可⽤,进程会进入睡眠状态直⾄资源可⽤。当进程不再使⽤一个信号量控制的共享资源时,信号量的值+1,对信号量的值进行的增减操作均为原子操作,这是由于信号量主要的作用是维护资源的互斥或多进程的同步访问。而在信号量的创建及初始化上,不能保证操作均为原⼦性。
二、为什么要使用信号量
为了防⽌出现因多个程序同时访问一个共享资源而引发的一系列问题,我们需要一种方法,它可以通过生成并使用令牌来授权,在任一时刻只能有一个执⾏行线程访问代码的临界区域。
临界区域是指执⾏行数据更新的代码需要独占式地执行。⽽信号量就可以提供这样的⼀种访问机制,让一个临界区同一时间只有一个线程在访问它, 也就是说信号量是用来调协进程对共享资源的访问的。其中共享内存的使用就要用到信号量。
三、信号量的工作原理
由于信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv),他们的行为是这样的:P(sv):如果sv的值⼤于零,就给它减1;如果它的值为零,就挂起该进程的执行。V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv⽽挂起,就给它加1.
举个例子,就是两个进程共享信号量sv,一旦其中一个进程执行了P(sv)操作,它将得到信号量,并可以进⼊临界区,使sv减1。而第二个进程将被阻止进入临界区,因为当它试图执⾏行P(sv)时,sv为0,它会被挂起以等待第一个进程离开临界区域并执⾏行V(sv)释放信号量,这时第二个进程就可以恢复执行。
四、Linux的信号量机制
Linux提供了一组精心设计的信号量接口来对信号量进⾏行操作,它们不只是针对二进制信号量,下⾯将会对这些函数进⾏介绍,但请注意,这些函数都是⽤来对成组的信号量值进⾏行操作的。它们声明在头文件sys/sem.h中。
下面我们先来介绍一下这几个函数:
1、semget()
函数原型:int semget(key_t key,int nsems,int semflg)
这个函数是对信号量的创建,第一个参数为一个整数值(唯一非零),不相关的进程可以通过他来访问一个信号量,他代表程序需要使用的某个资源,程序对所有信号量的访问都是间接的,程序先通过调用semget()函数并提供一个键,再由系统生成一个相应的信号标识符(也就是semget函数的返回值)。只有semget函数才能直接使用信号量键,所有其他的信号量函数使用由semget函数返回的信号量标识符。如果多个程序使用相同的key值,key将负责协调工作。
第二个参数num_sems是指定需要的信号量数目,他的值几乎总是1.
第三个参数sem_flags是一组标志,用IPC_CREAT时表示若申请的信号量存在则返回,若不存在则创建之。IPC_CREAT||IPCEXCL时表示如果申请的信号量存在则返回错误,若没有则创建之。
函数的返回值:成功返回一个相应信号标识符(非0),失败返回-1。
2、semctl()
函数原型:int semctl(int sem_id,int sem_num,int command,...);
第二个参数:信号量集数组上的下标,表示某一信号量。
第三个参数:表示执行的命令,IPC_RMID表示删除信号集合,SETVAL表示设置联合体val的值。
第四个参数(可以没有):如果有第四个参数,是一个联合体
union semun {
int val; // 使⽤用的值
struct semid_ds *buf; // IPC_STAT、IPC_SET 使⽤用缓存区
unsigned short *array; // GETALL,、SETALL 使⽤用的数组
struct seminfo *__buf; // IPC_INFO(Linux特有) 使⽤用缓存区
};
注意:该联合体没有定义在任何系统头⽂文件中,因此得⽤用户⾃自⼰己声明。 <Centos 下确实是这样,但是UNIX下不同,不需要⾃己定义声明>。
3、semop()改变信号量的值
函数原型:int semop(int sem_id,struct sembuf *sem_opa,size_t num_sem_ops)
第二个参数,sem_ops是一个指向结构数组的指针,其中的每一个结构至少包含下列成员:
这里说下第三个成员 sem_flg:通常设置为SEM_UNDO,这会使得操作系统跟踪当前进程对信号量所做的改变,而且如果进程终止而没有释放这个信号量, 如果信号量为这个进程所占有,这个标记可以使得操作系统自动释放这个信号量。
第三个参数,表示进行操作信号量的个数,即sops结构变量的个数,需大于或等于1。最常见设置此值等于1,只完成对一个信号量的操作。
函数返回值:成功:返回信号量集的标识符,错误,返回-1
信号量的意图在于进程间同步,互斥锁和条件变量的意图则在于线程间同步。但是信号量也可⽤于线程间,互斥锁和条件变量也可用于进程间。我们应该使⽤用适合具体应⽤的那组原语。
四、源码验证:
Comm.h:
#ifndef _COMM_H_
#define _COMM_H_
#define PATHNAME "."
#define PROJ_ID 0x666
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
typedef 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 */
void *_pad;
}mysemun;
int create_sem(int nums);
int destroy_sems(int semid);
int init_sem(int semid,int which,int _val);
int P(int semid);
int V(int semid);
int get_sems();
int comm_sems(int nums,int flags);
int comm_semop(int semid,int which,int op);
#endif
Comm.c:
#include"comm.h"
int comm_sems(int nums,int flags)
{
key_t k=ftok(PATHNAME,PROJ_ID);
if(k<0)
{
perror("fork");
return -1;
}
int semid=semget(k,nums,flags);
if(semid<0)
{
perror("semget");
return -1;
}
return semid;
}
//创建
int create_sems(int nums)
{
return comm_sems(nums,IPC_CREAT | IPC_EXCL | 0666);
}
//获取
int get_sems()
{
return comm_sems(0,IPC_CREAT);
}
//销毁
int destroy_sems(int semid)
{
if(semctl(semid,0,IPC_RMID)<0)
{
perror("semctl");
return -1;
}
return 0;
}
//操作
int comm_semop(int semid,int which ,int _op)
{
struct sembuf mysembuf;
mysembuf.sem_num=which;
mysembuf.sem_op=_op;
mysembuf.sem_flg=0;
if(semop(semid,&mysembuf,1)<0)
{
perror("semop");
return -1;
}
return 0;
}
//初始化
int init_sem(int semid,int which,int _val)
{
mysemun un;
un.val=_val;
if(semctl(semid,which,SETVAL,un)<0)
{
perror("semctl");
return -1;
}
return 0;
}
int P(int semid)
{
return comm_semop(semid,0,-1);
}
int V(int semid)
{
return comm_semop(semid,0,1);
}
Sem.c:
#include"comm.h"
int main()
{
int semid=create_sems(1);
init_sem(semid,0,1);
pid_t id=fork();
if(id<0)
{
perror("fork");
return -1;
}
if(id == 0)
{
//child
while(1)
{
int child_id=get_sems();
//P(child_id);
printf("A");
usleep(12345);
fflush(stdout);
printf("A");
usleep(22345);
fflush(stdout);
//V(child_id);
}
exit(1);
}
else
{
//father
while(1)
{
//P(semid);
printf("B");
usleep(12345);
fflush(stdout);
printf("B");
usleep(22345);
fflush(stdout);
//V(semid);
}
wait(NULL);
destory_sems(semid);
}
return 0;
}
结果:
没有用信号量时:
乱序的,两个进程只要临界资源空闲就可以进去.
有信号量时:
成对出现,因为只有一个线程执行完毕,才允许第二个进程进去执行。