system v信号量又被称为system v信号量集。它的本质就是一个计数器,用来为多个进程共享的数据结构提供受控访问。
信号量支持的操作有:
使用最广泛的信号量为二元信号量,它控制单个资源,对于这种信号量而言,它只有两种合法值: 0 和 1 ,对应一个可用的资源。若当前有资源可用,则与之对应的二值信号量的值为 1 ;若资源已被占用,则与之对应的二值信号量的值为 0 。当进程申请资源时,如果当前信号量的值为 0 ,那么进程会陷入阻塞,直到有其他进程释放资源,将信号量的值加 1 才能被唤醒。
内核为每个信号量集维护者一个semid_ds结构,该结构定义了此信号量的权限、指针、最近修改时间和队列中信号量队列信息:/usr /src/kernels/2.6.32-431.el6.i686/include/Linux/sem.h
——sem_perm权限;
——sem_otime最近semop时间;
——sem_ctime最近修改时间;
——sem_base队列第一个信号量;
——sem_pending阻塞信号量;
——sem_pending_last最后一个阻塞信号量;
——undo表示undo队列;
——sem_nsems表示信号量个数;
每一个信号量还有一个结构:
——semval表示信号量值;
——sempid表示最近一个操作的进程号pid;
当然信号量仍然存在系统限制:
——SEMMNI系统容许的信号量集的上限;
——SEMMSL 单个信号量集中信号量的上限;
——SEMMNS 系统容许的信号量的上限
——SEMOPM 单次 semop 调用能够操作的信号量的最大值;
——SEMVMX 信号量值的上限;
semget
功能:用来创建或打开一个信号量集;
原型:int semget(key_t key, int nsems, int semflg);
参数:key表示信号集的名字,一般由ftok函数产生;
nsems表示信号集中信号量的个数;
semflg用来标识信号量集合的权限,和创建文件类似,由九个权限标志为构成如0644,他还可以和以下参数一起使用:
——IPC_CREAT表示如果key不存在就创建;
——IPC_EXCL表示如果key存在就返回失败;
——IPC_NOWAIT表示如果需要等待,则直接返回错误;
返回值:成功返回一个非负整数即该信号量的标识码;失败返回-1;
semctl
功能:用来控制信号量集;
原型:int semctl(int semid, int semnum, int cmd, ...);
参数:semid由semget返回的信号量标识;
semnum信号集中信号量的序号;
cmd表示将要采取的操作;
最后一个参数根据命令不同而不同,他是一个类型为senum的联合(下文会有介绍);
cmd可采取的操作有:IPC_RMID、IPC_SET、IPC_STAT和IPC_INFO,定义在:/usr/include/linux/ipc.h中
IPC_RMID、IPC_SET、IPC_STAT和IPC_INFO这四个参数都是system v的通用参数;
——IPC_RMID表示删除信号集
——IPC_STAT表示获取ipc_perm的参数
——IPC_INFO表示获取系统信息
——IPC_SET表示设置ipc_prem的参数,对于这个参数,semctl有单独的规定参数:
——GETPID获取信号量拥有者的pid的值;
——GETVAL获取信号量的值;
——GETALL获取所有信号量的值;
——GETNCNT获取等待信号量的值递增的进程数;
——GETZCNT获取等待信号量的值递减的进程数;
——SETVAL设置信号量的值;
——SETALL设置所有信号的值;
第四个参数的senum的联合体定义如下:
接下来介绍几个常用的操作:
|cmd操作为GETVAL
semctl第二个参数为信号量编号,若执行成功,semctl返回当前信号量的值,失败返回-1;
|cmd操作为SETVAL
semctl第二个参数为信号量编号,第四个参数为要设置的val
返回值:成功返回0;失败返回-1;
semop
功能:修改集合中一个或多个信号量值;
原型:int semop(int semid, struct sembuf *sops, unsigned nsops);
参数:semid信号领=量标识码;
sops是一个sembuf类型的指针;
nsops标识信号量个数;
sembuf结构体定义如下:
——sem_num标识信号量编号;
——sem_op信号量一次pv操作时加减的数值,一般会用到两个值:
-1,p操作,等待信号量变得可用
+1,v操作,发出的信号量变得可用
——sem_flg操作标识,有以下值: IPC_NOWAIT和SEM_UNDO
IPC_NOWAIT对某一信号量操作,即使其中一个操作失败,也不会导致修改其他信号量的值;
SEM_UNDO当进程退出后,该进程对sem进行的操作将被撤销;
返回值:成功返回0;失败返回-1;
测试案例一:哲学家就餐问题
问题描述:
(由Dijkstra首先提出并解决)5个哲学家围绕一张圆桌而坐,桌子上放着5支筷子,每两个哲学家之间放一支;哲学家的动作包括思考和进餐,进餐时需要同时拿起他左边和右边的两支筷子,思考时则同时将两支筷子放回原处。如何保证哲学家们的动作有序进行?如:不出现相邻者同时要求进餐;不出现有人永远拿不到筷子;
解决思路:五只筷子相当于资源,保证每次会有两位哲学家先进餐,进餐完后在换其他哲学家,能够保证五位哲学家都能进餐
#include
#include
#include
#include
#include
#include
int semid;
union semun{
int value;
};
//获得资源
void get_sour(int num)
{
struct sembuf sb[2]={
{num,-1,0},
{(num+1)%5,-1,0}
};
semop(semid,sb,2);
}
//释放资源
void free_sour(int num)
{
struct sembuf sb[2]={
{num,1,0},
{(num+1)%5,1,0}
};
semop(semid,sb,2);
}
void phil(int num)
{
while(1){
printf("%d is thinking...\n",num);
sleep(rand()%5);
get_sour(num);
printf("%d start eating...\n",num);
sleep(rand()%3);
printf("%d end eating...\n",num);
free_sour(num);
}
}
int main()
{
srand(getpid());
semid=semget(1234,5,IPC_CREAT|0600);//创建信号量
if(semid == -1){
perror("semget");
exit(1);
}
union semun s;
s.value=1;
//五个信号量分别设初值
int i=0;
for(i=0;i<5;i++){
semctl(semid,i,SETVAL,s);
}
//创建五个进程——一个父进程+4个子进程
int num=0;//为每个进程编号,父进程初始化编号为0
for(i=1;i<5;i++){
pid_t pid=fork();
if(pid == 0){
num=i;
break;
}
}
phil(num);
}
运行结果:
本文主要介绍了system v信号量的一些基本概念,目的是能够对信号量有一个基本的初步认识,文中如有不当之处,欢迎大家指正。