信号量的基本介绍:
信号量的本质是⼀种数据操作锁,它本⾝不具有数据交换的功能,⽽是通过控制其他的通信资源(⽂件,外部设备)来实现进程间通信, 它本只是⼀种外部资源的标识。信号量在此过程中负责数据操作的互斥、同步等功能。
当请求⼀个使⽤信号量来表⽰的资源时,进程需要先读取信号量的值来判断资源是否可⽤。⼤于0,资源可以请求,等于0,⽆资源可⽤,进程会进⼊睡眠状态直⾄资源可⽤。
当进程不再使⽤⼀个信号量控制的共享资源时,信号量的值+1,对信号量的值进⾏的增减操作均为原⼦操作,这是由于信号量主要的作⽤是维护资源的互斥或多进程的同步访问。⽽在信号量的创建及初始化上,不能保证操作均为原⼦性。
⼀ 为什么要使⽤信号量
为了防⽌出现因多个程序同时访问⼀个共享资源⽽引发的⼀系列问题,我们需要⼀种⽅法,它可以通过⽣成并使⽤令牌来授权,在任⼀时刻只能有⼀个执⾏线程访问代码的临界区域。
临界区域是指执⾏数据更新的代码需要独占式地执⾏。⽽信号量就可以提供这样的⼀种访问机制,让⼀个临界区同⼀时间只有⼀个线程在访问它, 也就是说信号量是⽤来调协进程对共享资源的访问的。其中共享内存的使⽤就要⽤到信号量。
⼆ 信号量的⼯作原理
由于信号量只能进⾏两种操作等待和发送信号,即P(sv)和V(sv),他们的⾏为是这样的:
P(sv):如果sv的值⼤于零,就给它减1;如果它的值为零,就挂起该进程的执⾏
V(sv):如果有其他进程因等待sv⽽被挂起,就让它恢复运⾏,如果没有进程因等待sv⽽挂起,就给它加1.
Linux提供了一些关与信号量的操作的接口用于进行信号量的创建、销毁、P、V操作、初始化等操作 以下予以模拟实现信号量的操作:
sem.h
#ifndef _TEM_H_
#define _TEM_H_
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/sem.h>
#include<sys/ipc.h>
#define PATH "."
#define PID 0x66
int create_sem(int _semset_num);
int init_sem(int _sem_id);
int sem_P(int _sem_id,int _which);
int sem_V(int _sem_id,int _which);
int destroy_sem(int _sem_id);
int getsem();
#endif
sem.c
#include"tem.h"
static int comm_sem(int _semset_num,int flag)
{
key_t key = ftok(PATH,PID);
if(key<0)
{
perror("ftok");
return -1;
}
int semid = semget(key,_semset_num,flag);
if(semid<0)
{
perror("semid");
return -2;
}
return semid;
}
int create_sem(int _semset_num)
{
return comm_sem(_semset_num,IPC_CREAT|IPC_EXCL|0x666);
}
int init_sem(int _sem_id)
{
union semun //mctl的第四个参数是可以选择的,是共用体semun.
{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *_buf;
};
union semun _semun;
_semun.val = 1;
int ret = semctl(_sem_id,0,SETVAL,_semun);
if (ret < 0)
{
perror("semctl");
return -1;
}
return ret;
}
int destroy_sem(int _sem_id)
{
int ret = semctl(_sem_id,0,IPC_RMID);
if(ret<0)
{
perror("semctl");
return -1;
}
return ret;
}
int getsem()
{
return comm_sem(0,IPC_CREAT);
}
static int comm_op(int _sem_id,int which,int flag)
{
struct sembuf sembuf;
sembuf.sem_num = which;
sembuf.sem_op = flag;
sembuf.sem_flg = 0;
int ret = semop(_sem_id,&sembuf,1);
if(ret<0)
{
perror("semop");
return -1;
}
return ret;
}
int sem_P(int sem_id,int which)
{
return comm_op(sem_id,which,-1);
}
int sem_V(int sem_id,int which)
{
return comm_op(sem_id,which,1);
}
下面对Linux提供的信号量接口进行简单介绍
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
semget
功能:创建一个新的信号量或获取一个已经存在的信号量的键值。
返回值:成功返回信号量的标识码ID。失败返回-1;
参数:
key 为整型值,用户可以自己设定。有两种情况:
1. 键值是IPC_PRIVATE,该值通常为0,意思就是创建一个仅能被进程进程给我的信号量。
2. 键值不是IPC_PRIVATE,我们可以指定键值,例如1234;也可以一个ftok()函数来取得一个唯一的键值。
_nsems 表示初始化信号量的个数。比如我们要创建一个信号量,则该值为1.,创建2个就是2。
_semflg :信号量的创建方式或权限。有IPC_CREAT,IPC_EXCL。
IPC_CREAT如果信号量不存在,则创建一个信号量,否则获取。
IPC_EXCL只有信号量不存在的时候,新的信号量才建立,否则就产生错误。
semctl(int semid, int semnum, int cmd, …);
semctl() 在 semid 标识的信号量集上,或者该集合的第 semnum 个信号量上执⾏ cmd 指定的控制命令。 (信号量集合索引起始于零。 )根据 cmd 不同,这个函数有三个或四个参数。当有四个参数时,第四个参数的类型是 union
。调⽤程序必须按照下⾯⽅式定义这个联合体:
union semun {
int val; // 使⽤的值
struct semid_ds *buf; //IPC_STAT、 IPC_SET 使⽤缓存区
unsigned short *array; //GETALL,、 SETALL 使⽤的数组
struct seminfo *__buf; // IPC_INFO(Linux特有) 使⽤缓存区
};
注意:该联合体没有定义在任何系统头⽂件中,因此得⽤户⾃⼰声明。(centos下)
在这个函数中我们可以删除信号量或初始化信号量。
功能:控制信号量的信息。
返回值:成功返回0,失败返回-1;
参数:
_semid 信号量的标志码(ID),也就是semget()函数的返回值;
_semnum, 操作信号在信号集中的编号。从0开始。
_cmd 命令,表示要进行的操作。
参数cmd中可以使用的命令如下:
IPC_STAT读取一个信号量集的数据结构semid_ds,并将其存储在semun中的buf参数中。
IPC_SET设置信号量集的数据结构semid_ds中的元素ipc_perm,其值取自semun中的buf参数。
IPC_RMID将信号量集从内存中删除。
GETALL用于读取信号量集中的所有信号量的值。
GETNCNT返回正在等待资源的进程数目。
GETPID返回最后一个执行semop操作的进程的PID。
GETVAL返回信号量集中的一个单个的信号量的值。
GETZCNT返回这在等待完全空闲的资源的进程数目。
SETALL设置信号量集中的所有的信号量的值。
SETVAL设置信号量集中的一个单独的信号量的值。
int semop(int semid, struct sembuf *sops, unsigned nsops);
功能:用户改变信号量的值。也就是使用资源还是释放资源使用权。
返回值:成功返回0,失败返回-1;
参数:
_semid : 信号量的标识码。也就是semget()的返回值。
_sops是一个指向结构体数组的指针。
struct sembuf{
unsigned short sem_num;//第几个信号量,第一个信号量为0;
short sem_op;//对该信号量的操作。
short _semflg;
};
nsops:操作结构的数量,恒大于或等于1。
_semflg
sem_flg可以设置为SEM UNDO标识;SEM_UNDO用于将修改的信号量值在进程正常退出(调用exit退出或main执行完)或异常退出(如段异常、除0异常、收到KILL信号等)时归还给信号量。
如信号量初始值是20,进程以SEM_UNDO方式操作信号量减2,减5,加1;在进程未退出时,信号量变成20-2-5+1=14;在进程退出时,将修改的值归还给信号量,信号量变成14+2+5-1=20。