通信资源(文件,外部设备)来实现进程间通信,它本身只是一种外部资源的标识。信号
量在此过程中负责数据操作的互斥、同步等功能。
当请求一个使用信号量来表示的资源时,进程需要先读取信号量的值来判断资源是否可
用。大于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中。
信号量的意图在于进程间同步,互斥锁和条件变量的意图则在于线程间同步。但是信号
量也可用于线程间,互斥锁和条件变量也可用于进程间。我们应该使用适合具体应用的那
组原语。
简易的信号量实现:
comm.h
#ifndef _COMM_H_
#define _COMM_H_
#include<sys/sem.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#define PATHNAME "."
#define PROJ_ID "0x6666"
typedef struct mysembuf
{
unsigned short int sem_num; /* semaphore number */
short int sem_op; /* semaphore operation */
short int sem_flg; /* operation flag */
}mysembuf;
typedef union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo*_buf;
}_semnu;
int creat_sems(int nums);
int get_sems();
int init_sems(int semid,int which);
int destory_sem(int semid);
int P(int semid,int which);
int V(int semid,int which);
#endif
comm.c
#include"comm.h"
static int comm_sems(int nums,int flags)
{
key_t k = ftok(PATHNAME,PROJ_ID);
int semid=semget(k,nums,flags);
if(semid<0)
{
perror("semget");
}
return semid;
}
int creat_sems(int nums)
{
return comm_sems(nums,IPC_CREAT|IPC_EXCL|0666);
}
int get_sems()
{
return comm_sems(0,IPC_CREAT);
}
int init_sems(int semid,int which)
{
_semnu __semun;
__semun.val=1;
if(semctl(semid,which,SETVAL,__semun)<0)
{
perror("semctl");
return -1;
}
return 0;
}
int destory_sem(int semid)
{
if(semctl(semid,0,IPC_RMID,NULL)<0)
{
return -1;
}
return 0;
}
static int comm_op(int semid,int which,int op)
{
struct mysembuf sbuf;
// memset();
sbuf.sem_op=op;
sbuf.sem_flg=0;
sbuf.sem_num=which;
return semop(semid,&sbuf,1);
}
int P(int semid,int which)
{
return comm_op(semid,which,-1);
}
int V(int semid,int which)
{
return comm_op(semid,which,1);
}
#include"comm.h"
int main()
{
int semid=creat_sems(1);
init_sems(semid,0);
pid_t id=fork();
if(id<0)
{
perror("fork");
}
if(id==0){//child
while(1)
{
int _semid=get_sems();
P(_semid,0);
printf("A");
usleep(12345);
fflush(stdout);
printf("A");
usleep(35412);
fflush(stdout);
V(_semid,0);
}
}else{//father
while(1)
{
P(semid,0);
printf("B");
usleep(32345);
fflush(stdout);
printf("B");
usleep(28712);
fflush(stdout);
V(semid,0);
}
wait(NULL);
desrory_sems(semid);
}
return 0;
}
当操作信号量(semop)时,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。