Linux下IPC主题二-------------信号量

时间:2022-02-12 05:05:09

一、什么是信号量

信号量的本质是一种数据操作锁,它本身不具有数据交换的功能,而是通过控制其他的通信资源(文件,外部设备)来实现进程间通信,它本身只是一种外部资源的标识。信号量在此过程中负责数据操作的互斥、同步等功能,保护临界资源。

二  为什么使用信号量

为了防止出现因多个程序同时访问一个共享资源而引发的一系列问题,我们需要一种方法,它可以通过生成并使用令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区域。临界区域是指执行数据更新的代码需要独占式地执行。而信号量就可以提供这样的一种访问机制,让一个临界区同一时间只有一个线程在访问它,也就是说信号量是用来调协进程对共享资源的访问的。

三 信号量的原理

由于信号量只能进行两种操作等待和发送信号,即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 num_sems, int sem_flags);


函数参数:第一个参数key是整数值(唯一非零),不相关的进程可以通过它访问一个信号量,它代表程序可能要使用的某个资源,程序对所有信号量的访问都是间接的,程序先通过调用semget函数并提供一个键,再由系统生成一个相应的信号标识符(semget函数的返回值),只有semget函数才直接使用信号量键,所有其他的信号量函数使用由semget函数返回的信号量标识符。如果多个程序使用相同的key值,key将负责协调工作。
 
第二个参数num_sems指定需要的信号量数目,它的值几乎总是1。
 
第三个参数sem_flags是一组标志,当想要当信号量不存在时创建一个新的信号量,可以和值IPC_CREAT做按位或操作。设置了IPC_CREAT标志后,即使给出的键是一个已有信号量的键,也不会产生错误。而IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误。
 
函数返回值:semget函数成功返回一个相应信号标识符(非零),失败返回-1.


2、semop函数 :它的作用是改变信号量的值,

原型为:int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);

函数参数:第一个参数,sem_id,是由semget函数所返回的信号量标识符。

 第二个参数,sem_ops,是一个指向结构数组的指针,其中的每一个结构至少包含下列成员:

struct sembuf{
short sem_num;//除非使用一组信号量,否则它为0,一般从0,1,...num_secs-1
short sem_op;//信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作,
//一个是+1,即V(释放信号)操作。
short sem_flg;//通常为SEM_UNDO,使操作系统跟踪信号,
//并在进程没有释放该信号量而终止时,操作系统释放信号量
};

  这里说下第三个成员 sem_flg:通常设置为SEM_UNDO,这会使得操作系统跟踪当前进程对信号量所做的改变,而且如果进程终止而没有释放这个信号量, 如果信号量为这个进程所占有,这个标记可以使得操作系统自动释放这个信号量。

第三个参数,表示进行操作信号量的个数,即sops结构变量的个数,需大于或等于1。最常见设置此值等于1,只完成对一个信号量的操作。

函数返回值:成功:返回信号量集的标识符,错误,返回-1

3semctl函数,该函数用来直接控制信号量信息

函数原型:int semctl(int sem_id, int sem_num, int command, ...);

函数参数:第二个参数:信号量集数组上的下标,表示某一个信号量

 第三个参数:表示执行的命令,IPC_RMID 表示删除信号集合,SETVAL表示设置联合体val值

第四个参数(可以没有):如果有第四个参数,是一个联合体


union semun {

short val; /*SETVAL用的值*/

struct semid_ds* buf; /*IPC_STAT、IPC_SET用的semid_ds结构*/

unsigned short* array; /*SETALL、GETALL用的数组值*/

struct seminfo *buf; /*为控制IPC_INFO提供的缓存*/

}


下面来使用信号量

 com.h

#ifndef _COMM_H_
#define _COMM_H_

#include<stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include<stdio.h>
#include<unistd.h>
#include<errno.h>

#define PATHNAME "."
#define PROJ_ID 0x666
typedef union mysemun {
int val; /* Value forSETVAL */
struct semid_ds *buf; /* Bufferfor IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO */
}my_semun;

int create_sems(int which);
int get_sem();
int init_sem(int semid,int which);
int destory_sem(int semid);
int P(int semid);
int V(int semid);
#endif

comm.c

/*************************************************************************
> File Name: comm.c
> Author: WenQiang
> Mail: wenqiangw1208@gmail.com
> Created Time: Fri 10 Mar 2017 11:25:30 AM PST
************************************************************************/

#include "comm.h"

static int comm_sems(int which,int flags)
{
key_t key = ftok(PATHNAME,PROJ_ID);
int semid = semget(key,which,flags);
if(semid < 0)
{
perror("semget");
}
return semid;
}
int create_sems(int which)
{
return comm_sems(which,IPC_CREAT|IPC_EXCL|0666);
}
int get_sem()
{
return comm_sems(0,IPC_CREAT);
}
int init_sem(int semid,int which)
{
my_semun semun;
semun.val = 1;
if(semctl(semid,which,SETVAL,semun)< 0)
{
perror("Init_sem");
return -1;
}
return 0;
}
int destory_sem(int semid)
{
if(semctl(semid,0,IPC_RMID,NULL) < 0)
{
perror("destory");
return -1;
}
return 0;
}
static int comm_op(int semid,int which,int _op)
{
struct sembuf mysembuf;
mysembuf.sem_num = which;
mysembuf.sem_op = _op;
mysembuf.sem_flg = 0;
return semop(semid,&mysembuf,1);
}
int P(int semid)
{
return comm_op(semid,0,-1);
}
int V(int semid)
{
return comm_op(semid,0,1);
}

测试函数,使用信号量

#include"comm.h"
#include<stdlib.h>
int main()
{
int semid = create_sems(1);
init_sem(semid,0);
pid_t id = fork();
if(id < 0)
{
perror("fork");
return -1;
}
else if(id == 0)
{
while(1)
{
int child_id = get_sem();
P(child_id);
printf("A");
usleep(12345);
fflush(stdout);
printf("A");
usleep(35412);
fflush(stdout);
V(child_id);
}
exit(1);
}
else
{
while(1)
{
P(semid);
printf("B");
usleep(32345);
fflush(stdout);
printf("B");
usleep(28712);
fflush(stdout);
V(semid);
}
wait(NULL);
destory_sem(semid);
}
return 0;
}