semaphore

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

信号量是一个特殊的变量,程序对其访问都是原子操作,且只允许对它进行等待(即P(信号变量))和发送(即V(信号变量))信息操作。最简单的信号量是只能取0和1的变量,这也是信号量最常见的一种形式,叫做二进制信号量。而可以取多个正整数的信号量被称为通用信号量。这里主要讨论二进制信号量。

信号量的工作原理
由于信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv),他们的行为是这样的:
P(sv):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行
V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1.

P V操作实际上是对信号量集合的操作,它是批量申请或批量释放资源的操作。一个信号量对应一个资源。(个人理解,可能不对)

sys/sem.h

信号号相关的两个结构体
内核为每个信号量集合设置了一个semid_ds结构
struct semid_ds {
    struct ipc_perm sem_perm ;
    structsem* sem_base ; //信号数组指针
    ushort sem_nsem ; //此集中信号个数
    time_t sem_otime ; //最后一次semop时间
    time_t sem_ctime ; //最后一次创建时间
} ;

每个信号量由一个无名结构表示,它至少包含下列成员: (这个是啥?)    
struct {
    ushort_t semval ; //信号量的值
    short sempid ; //最后一个调用semop的进程ID
    ushort semncnt ; //等待该信号量值大于当前值的进程数(一有进程释放资源 就被唤醒)
    ushort semzcnt ; //等待该信号量值等于0的进程数
} ;

信号量的使用
1、创建信号量
semget函数创建一个信号量集或访问一个已存在的信号量集。
#include <sys/sem.h>
int semget (key_t key, int nsem, int oflag) ;
返回值是一个称为信号量标识符的整数,semop和semctl函数将使用它。
参数nsem指定集合中的信号量数。(若用于访问一个已存在的集合,那就可以把该参数指定为0)
参数oflag可以是SEM_R(read)和SEM_A(alter)常值的组合。(打开时用到),也可以是IPC_CREAT或IPC_EXCL ;
 
2、打开信号量
使用semget打开一个信号量集后,对其中一个或多个信号量的操作就使用semop(op--operate)函数来执行。
#include <sys/sem.h>
int semop (int semid, struct sembuf * opsptr, size_t nops) ;

参数opsptr是一个指针,它指向一个信号量操作数组,信号量操作由sembuf结构表示:
 
struct sembuf{
    short sem_num; // 除非使用一组信号量,否则它为0
    short sem_op; // 信号量在一次操作中需要改变的数据,通常是两个数,
    // 一个是-1,即P(等待)操作,一个是+1,即V(发送信号)操作
    short sem_flg; // 通常为SEM_UNDO,使操作系统跟踪信号,并在进程没有释放该信号量而终止时,
    // 操作系统释放信号量
};
参数nops规定opsptr数组中元素个数。
sem_op值:
(1)若sem_op为正,这对应于进程释放占用的资源数。sem_op值加到信号量的值上。(V操作)
(2)若sem_op为负,这表示要获取该信号量控制的资源数。信号量值减去sem_op的绝对值。(P操作)
(3)若sem_op为0,这表示调用进程希望等待到该信号量值变成0
如果信号量值小于sem_op的绝对值(资源不能满足要求),则:
(1)若指定了IPC_NOWAIT,则semop()出错返回EAGAIN。
(2)若未指定IPC_NOWAIT,则信号量的semncnt值加1(因为调用进程将进 入休眠状态),然后调用进程被挂起直至:①此信号量变成大于或等于sem_op的绝对值;②从系统中删除了此信号量,返回EIDRM;③进程捕捉到一个信 号,并从信号处理程序返回,返回EINTR。(与消息队列的阻塞处理方式 很相似)
 3、信号量是操作
semctl函数对一个信号量执行各种控制操作。
#include <sys/sem.h>
int semctl (int semid, int semnum, int cmd, /*可选参数*/ ) ;
第四个参数是可选的,取决于第三个参数cmd。
参数semnum指定信号集中的哪个信号(操作对象)
参数cmd指定以下10种命令中的一种,在semid指定的信号量集合上执行此命令。
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      设置信号量集中的一个单独的信号量的值。

信号量值的初始化
semget并不初始化各个信号量的值,这个初始化必须通过以SETVAL命令(设置集合中的一个值)或SETALL命令(设置集合中的所有值) 调用semctl来完成。
SystemV信号量的设计中,创建一个信号量集并将它初始化需两次函数调用是一个致命的缺陷。一个不完备的解决方案是:在调用semget时指定IPC_CREAT | IPC_EXCL标志,这样只有一个进程(首先调用semget的那个进程)创建所需信号量,该进程随后初始化该信号量。

// testsema.c
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
#include<errno.h>
#include<string.h>
#include<stdlib.h>
#include<assert.h>
#include<time.h>
#include<unistd.h>
#include<sys/wait.h>
#define MAX_SEMAPHORE 10
#define FILE_NAME "testsema.c"
 
union semun{
    int val ;
    struct semid_ds *buf ;
    unsigned short *array ;
    struct seminfo *_buf ;
}arg;

int main() { key_t key ; int semid ,ret,i; unsigned short buf[MAX_SEMAPHORE] ; struct sembuf sb[MAX_SEMAPHORE] ; pid_t pid ; pid = fork() ; if(pid < 0) { /* Create process Error! */ fprintf(stderr,"Create Process Error!:%s\n",strerror(errno)); exit(1) ; } if(pid > 0) { /* in parent process !*/ key = ftok(FILE_NAME,'a') ; if(key == -1) { /* in parent process*/ fprintf(stderr,"Error in ftok:%s!\n",strerror(errno)); exit(1) ; } semid = semget(key,MAX_SEMAPHORE,IPC_CREAT|0666); //创建信号量集合 if(semid == -1) { fprintf(stderr,"Error in semget:%s\n",strerror(errno)); exit(1) ; } printf("Semaphore have been initialed successfully in parent process,ID is :%d\n",semid); sleep(2) ; printf("parent wake up....\n"); /* 父进程在子进程得到semaphore的时候请求semaphore,此时父进程将阻塞直至子进程释放掉semaphore*/ /* 此时父进程的阻塞是因为semaphore 1 不能申请,因而导致的进程阻塞*/ for(i=0;i<MAX_SEMAPHORE;++i) { sb[i].sem_num = i ; sb[i].sem_op = -1 ; /*表示申请semaphore*/ sb[i].sem_flg = 0 ; } printf("parent is asking for resource...\n"); ret = semop(semid , sb ,10); //p() if(ret == 0) { printf("parent got the resource!\n"); } /* 父进程等待子进程退出 */ waitpid(pid,NULL,0); printf("parent exiting .. \n"); exit(0) ; } else { key = ftok(FILE_NAME,'a'); //如果不同进程操作同一个信号量,那么应该返回同一个key if(key == -1) { fprintf(stderr,"Error in ftok:%s!\n",strerror(errno)); exit(1) ; } semid = semget(key,MAX_SEMAPHORE,IPC_CREAT|0666);   //返回信号量标识符,进程通过这个标识符做对应的P、V等操作 if(semid == -1) { fprintf(stderr,"Error in semget:%s\n",strerror(errno)); exit(1) ; } printf("Semaphore have been initialed successfully in child process,ID is:%d\n",semid); for(i=0;i<MAX_SEMAPHORE;++i) { /* Initial semaphore */ buf[i] = i + 1; } arg.array = buf; ret = semctl(semid , 0, SETALL,arg); if(ret == -1) { fprintf(stderr,"Error in semctl in child:%s!\n",strerror(errno)); exit(1) ; } printf("In child , Semaphore Initailed!\n"); /* 子进程在初始化了semaphore之后,就申请获得semaphore*/ for(i=0;i<MAX_SEMAPHORE;++i) { sb[i].sem_num = i ; sb[i].sem_op = -1 ; sb[i].sem_flg = 0 ; } ret = semop(semid , sb , 10);//信号量0被阻塞 if( ret == -1 ) { fprintf(stderr,"子进程申请semaphore失败:%s\n",strerror(errno)); exit(1) ; } printf("child got semaphore,and start to sleep 3 seconds!\n"); sleep(3) ; printf("child wake up .\n"); for(i=0;i < MAX_SEMAPHORE;++i) { sb[i].sem_num = i ; sb[i].sem_op = +1 ; sb[i].sem_flg = 0 ; } printf("child start to release the resource...\n"); ret = semop(semid, sb ,10) ; if(ret == -1) { fprintf(stderr,"子进程释放semaphore失败:%s\n",strerror(errno)); exit(1) ; } ret = semctl(semid ,0 ,IPC_RMID); if(ret == -1) { fprintf(stderr,"semaphore删除失败:%s!\n",strerror(errno)); exit(1) ; } printf("child exiting successfully!\n"); exit(0) ; } return 0; }

 :~/c/semaphore$ ./testsema.o
Semaphore have been initialed successfully in parent process,ID is :0
Semaphore have been initialed successfully in child process,ID is:0
In child , Semaphore Initailed!
child got semaphore,and start to sleep 3 seconds!
parent wake up....
parent is asking for resource...
child wake up .
child start to release the resource...

child exiting successfully!
parent got the resource!
parent exiting ..

---------------------------------------------------------------------------------------

Summary:信号量与其它进程间通信方式有所不同,它主要用于进程间同步。通常所说的系统V信号量实际上是一个信号量的集合,可用于多种共享资源的进程间同步。每个信号量都有一个值,可以用来表示当前该信号量代表的共享资源可用(available)数量,如果一个进程要申请共享资源,那么就从信号量值中减去要申请的数目,如果当前没有足够的可用资源,进程可以睡眠等待,也可以立即返回。当进程要申请多种共享资源时,linux可以保证操作的原子性,即要么申请到所有的共享资源,要么放弃所有资源,这样能够保证多个进程不会造成互锁。Linux对信号量有各种各样的限制,程序中给出了输出结果。另外,如果读者想对信号量作进一步的理解,建议阅读sem.h源代码,该文件不长,但给出了信号量相关的重要数据结构。

其它资料

附录1: struct sem_array如下
/*系统中的每个信号量集对应一个sem_array 结构 */
struct sem_array {
struct kern_ipc_perm sem_perm;  /* permissions .. see ipc.h */
time_t   sem_otime;   /* last semop time */
time_t   sem_ctime;   /* last change time */
struct sem  *sem_base;   /* ptr to first semaphore in array */
struct sem_queue *sem_pending;  /* pending operations to be processed */
struct sem_queue **sem_pending_last;  /* last pending operation */
struct sem_undo  *undo;   /* undo requests on this array */
unsigned long  sem_nsems;  /* no. of semaphores in array */
};


其中,sem_queue结构如下:
/* 系统中每个因为信号量而睡眠的进程,都对应一个sem_queue结构*/
struct sem_queue {
struct sem_queue * next;   /* next entry in the queue */
struct sem_queue ** prev;   /* previous entry in the queue, *(q->prev) == q */
struct task_struct* sleeper;  /* this process */
struct sem_undo * undo;   /* undo structure */
int   pid;       /* process id of requesting process */
int   status;      /* completion status of operation */
struct sem_array * sma;    /* semaphore array for operations */
int id;        /* internal sem id */
struct sembuf * sops;    /* array of pending operations */
int nsops;       /* number of operations */
int alter;       /* operation will alter semaphore */
};


附录2:union semun是系统调用semctl中的重要参数:
union semun {
int val;     /* value for SETVAL */
struct semid_ds *buf;  /* buffer for IPC_STAT & IPC_SET */
unsigned short *array;  /* array for GETALL & SETALL */
struct seminfo *__buf;  /* buffer for IPC_INFO */   //test!!
void *__pad;
};
struct  seminfo {
int semmap;
int semmni;
int semmns;
int semmnu;
int semmsl;
int semopm;
int semume;
int semusz;
int semvmx;
int semaem;
};

 

posted @ 2015-04-30 17:04  牧 天  阅读(368)  评论(0)    收藏  举报