信号量
这篇文章主要来说说进程通信(IPC)的又一大主题—————信号量;
在说信号量之前,我们来说一下几个概念吧!!!
############################################################################################
大家对于【临界资源】【临界区】这两个概念了解有多少呢???
临界资源:就是在多个进程可以同时看到的一份资源;例如我们之前说的管道、消息队列,这类的资源都是属于的是临界资源,可以说是临界资源就是两个进程通信过程中要同时访问的那块空间;
临界区:说的临界区就容易理解了,临界区就是我们访问临界资源的那一份代码,叫做的是临界区;
############################################################################################
1、信号量的初步了解
有了上面的两个概念的基础,我们了解 信号量这个感念起来就会相对的容易些;
简单的来说的话,信号量就是用来保护临界资源的一个变量;
信号量的本质是一种数据操作锁,它本身不具有数据交换的功能,而是通过控制其他的通信资源(文件,外部设备)来实现进程间通信,它本身只是一种外部资源的标识。信号量在此过程中负责数据操作的互斥、同步等功能。
2、信号量的使用介绍
当请求一个使用信号量来表示的资源时,进程需要先读取信号量的值来判断资源是否可用。大于0,资源可以请求,等于0,无资源可用,进程会进入睡眠状态直至资源可用。当进程不再使用一个信号量控制的共享资源时,信号量的值+1,对信号量的值进行的增减操作均为原子操作,这是由于信号量主要的作用是维护资源的互斥或多进程的同步访问。
而在信号量的创建及初始化上,不能保证操作均为原子性。
而在信号量的创建及初始化上,不能保证操作均为原子性。
3、为什么要是使用信号量
为了防止出现因多个程序同时访问一个共享资源而引发的一系列问题,我们需要一种方法,它可以通过生成并使用令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区域。临界区域是指执行数据更新的代码需要独占式地执行。而信号量就可以提供这样的一种访问机制,让一个临界区同一时间只有一个线程在访问它, 也就是说信号量是用来调协进程对共享资源的访问的。其中共享内存的使用就要用到信号量。
4、信号量的工作原理
由于信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv),他们的行为是这样的:
P(sv):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行。
V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1。
举个例子,
就是两个进程共享信号量sv,一旦其中一个进程执行了P(sv)操作,它将得到信号量,并可以进入临界区,使sv减1。
而第二个进程将被阻止进入临界区,因为当它试图执行
P(sv)时,sv为0,它会被挂起以等待第一个进程离开临界区域并执行V(sv)释放信号量,这时
第二个进程就可以恢复执行。
5、Linux下的信号量机制
Linux操作系统下的信号量不是以单个的形式出现的,它是一信号量机的形式出现的。
那要怎么来证明呢???
使用 命令
ipcs -s
我们可以看到这么一幅图:
Linux提供了一组精心设计的信号量接口来对信号量进行操作,它们不只是针对二进制信号量,下面将会对这些函数进行介绍,但请注意,这些函数都是用来对成组的信号量值进行操作的。它们声明在头文件sys/sem.h中。
1】、信号量集的创建
系统为我们提供的信号量集创建的接口函数 :
2】、信号量集的销毁
系统为我们提供的信号量集创建的接口函数 :
3】、信号量集的初始化
系统为我们提供的信号量集创建的接口函数 :
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 下确实是这样,但是UNIX下不同,不需要自己定义声明>
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下不同,不需要自己定义声明>
4】、信号量集的P、V操作
系统为我们提供的信号量集创建的接口函数 :
信号量的应用代码:
实现的函数的头文件comm.h:
函数的实现comm.c:
#include"comm.h"
static int commSem(int flag,int nsems)
{
key_t key = ftok(PATHNAME,PROJ_ID);
if(key < 0)
{
perror("ftok");
return -1;
}
int semid = semget(key,nsems,flag);
if(semid < 0 )
{
perror("semget");
return -2;
}
return semid;
}
int creatSem(int nsems)
{
return commSem(IPC_CREAT|IPC_EXCL|0666,nsems);
}
int getSem(int nsems)
{
return commSem(IPC_CREAT,nsems);
}
int destroySem(int semid)
{
if(semctl(semid,0,IPC_RMID) < 0 )
{
perror("semctl");
return -1;
}
return 0;
}
int initSem(int semid ,int which)
{
union semun _semun;
_semun.val = 1;
if(semctl(semid,which,SETVAL,_semun) < 0 )
{
perror("semctl");
return -1;
}
return 0;
}
int Semop(int semid,int which,int op)
{
struct sembuf _sembuf;
_sembuf.sem_num = which;
_sembuf.sem_op = op;
_sembuf.sem_flg = 0;
return semop(semid,&_sembuf,1);
}
int P(int semid,int which)
{
if(Semop(semid,which ,-1) ==0 )
return 0;
perror("P_sem");
return -1;
}
int V(int semid,int which)
{
if(Semop(semid,which ,1) == 0 )
return 0;
perror("V_sem");
return -1;
}
测试代码:
#include"comm.h"
int main()
{
int semid = creatSem(1);
if(semid < 0 )
{
printf("Creat failure!\n");
}
sleep(4);
initSem(semid,0);
pid_t id = fork();
if(id > 0 )
{
//parent;
int count = 0;
while(count ++ <20)
{
P(semid,0);
printf("A");
fflush(stdout);
printf("A");
fflush(stdout);
sleep(1);
V(semid,0);
}
sleep(10);
}
else
{
//child
int count = 0 ;
while(count++ < 20)
{
P(semid,0);
printf("B");
fflush(stdout);
printf("B");
fflush(stdout);
sleep(1);
V(semid,0);
}
return 0;
}
destroySem(semid);
return 0;
}