/****************************************************SEMGET函数*******************************************************************************/
可以使用系统调用semget()创建一个新的信号量集,或者存取一个已经存在的信号量集:
#defineSEMMSL32/*<=512maxnumofsemaphoresperid*/
/***************************************SEMGET函数***********************************************************************************************/
下面是这个函数的第一个参数和第三个参数
¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥
三种系统V IPC-消息队列、信号量以及共享存储器之间有很多相似之处
每个在核中的IPC结构(消息队列、信号量或共享存储段)都用一个非负整数的标
识符加以引用。例如,为了对一个消息队列发送或取消息,我们只需知道其队列标
识符。与文件描述符不同,IPC标识符不是小的整数。当一个IPC结构被创建,以后
又被删除时,与这种结构相关的标识符连续加1,直至达到一个整型数的最大正值
,然后又回转到0。(即使在IPC结构被删除后也记住该值,每次使用此结构时则增
1,该值被称为"槽使用顺序号"。它在ipc_perm结构中,我们将在下一节说明此结
构。)
无论何时在创建IPC结构时(调用msgget、semget或shmget),都应指定一个key(
关键字),关键字的数据类型由系统规定为key_t,通常在头文件中
有多种方法使客户和服务者在同一IPC结构上会合。
1. 服务者可以指定关键字IPC_PRIVATE创建一个新IPC结构,将返回的标识符存放
在某处(例如一个文件)以便客户取用。关键字IPC_PRIVATE保证服务者创建一个
新IPC结构。这种技术的缺点是;服务者要将整型标识符写到文件中,然后客户在
此后又要读文件取得此标识符。
IPC_PRIVATE关键字也可用于父、子关系进程。父进程指定IPC_PRIVATE创建一个新
IPC结构,所返回的标识符在fork后可由子进程使用。子进程可将此标识符作为ex
ec函数的一个参数传给一个新程序。
2. 在一个公用头文件中定义一个客户和服务者都认可的关键字。然后服务者指定
此关键字创建一个新的IPC结构。这种方法的问题是该关键字可能已与一个IPC结构
相结合,在此情况下,get函数(msgget,semget或shmget)出错返回。服务者必须
处理这一错误,删除已存在的IPC结构,然后试着再创建它。
3. 客户和服务者认同一个路径名和课题ID(课题ID是在0~255之间的字符值),然
后调用函数ftok将这两个值变换为一个关键字。(函数ftok在手册页stdipc(3)
中说明。)然后在方法2中使用此关键字。ftok提供的唯一服务就是由一个路径名
和课题ID产生一个关键字。因为客户和服务者典型地至少共享一个头文件,所以一
个比较简写的方法是避免使用ftok,而只是在该头文件中存放一个大家都知道的关
键字。这样做还避免了使用另一个函数。
三个get函数(msgget,semget和shmget)都有两个类似的参数key和一个整型的fl
ag。如若满足下列条件,则创建一个新的IPC结构(通常由服务者创建);
1. key是IPC_PRIVATE,或
2. key当前末与特定类型的IPC结构相结合,flag中指定了IPC_CREAT位。为访问现
存的队列(通常由客户进行),key必须等于创建该队列时所指定的关键字,并且
不应指定IPC_CREAT。
注意,为了访问一个现存队列,决不能指定IPC_PRIVATE作为关键字因为这是
一个特殊的键值,它总是用于创建一个新队列。为了访问一个用IPC_PRIVATE关键
字创建的现存队列,一定要知道与该队列相结合的标识符,然后在其它IPC调用中
(例如msgsnd、msgrcv)使用该标识符。
如果我们希望创建一个新IPC结构,保证不是引用具有同一标识符的一个现行IPC结构。那么必须在FLAG中间同时制定IPC——EXCL和IPC——CREAT
¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥
第二个参数NSEMS是该信号集中的信号量数,如果是创建新集和,则必须指定NSEMS。如果引用一个现存的集合,则NSEMS指定为0.
/************************************************SEMCTL函数*******************************************************************************/
系统调用:semctl();
原型:int semctl(int semid,int semnum,int cmd,union semunarg);
返回值:如果成功,则为一个正数。
如果失败,则为-1:errno=EACCESS(权限不够)
EFAULT(arg指向的地址无效)
EIDRM(信号量集已经删除)
EINVAL(信号量集不存在,或者semid无效)
EPERM(EUID没有cmd的权利)
ERANGE(信号量值超出范围)
以struct和union为线索来观察信号量
第一部分 semid_ds ipc_perm
内核为每个信号量集合维护一个结构体semid_ds:
struct semid_ds {
struct ipc_perm sem_perm;
unsigned short sem_nsems; /* # of semaphore in set */
time_t sem_otime; /* last-semop() time */
time_t sem_ctime; /* last-change time */
…
};
semid_ds的一个结构体成员ipc_perm是XSI IPC为每一个IPC结构设置的, 定义如下:
struct ipc_perm {
uid_t uid; /* owner’s effective user id */
gid_t gui; /* owner’s effective group id */
uid_t cuid; /* creator’s effective user id */
gid_t cgid; /* creator’s effective group id */
mode_t mode; /* access mode */
…..
};
好, 有了以上两个结构体, 我们先认识semget
#include <sys/sem.h>
int semget (key_t key; int nsems, int flag);
返回值: 若成功则返回信号量ID, 若出错则返回-1
先说semget和这两个struct的关系:
如果是创建一个新的信号量集合, 内核为这个信号量集合维护一个结构体semid_ds, 同时对semid_ds的成员进行初始化:
ipc_perm结构赋初值, ipc_perm中的mode被设置为flag中的相应权限位.
sem_nsems设置为nsems
sem_otime设置为0.
sem_ctime设置为当前时间.
另外, 注意如果创建新集合, 则必须指定nsems, 如果引用一个集合, 则nsems设定为0;
一句话概括, 就是semget创建信号量集合的时候初始化了semid_ds.
第二部分 semun和一个无名结构体
semctl参数中使用了一个union, 定义如下
union semun {
int val; /* for SETVAL */
struct semid_ds *buf; /* for IPC_STAT and IPC_SET */
unsigned short *array; /* for GETALL and SETALL */
};
还要认识一个无名结构体. (我总觉得无名结构体听起来就很cool). 每个信号量由这样一个结构体表示. 定义如下:
struct {
unsigned short semval; /* semaphore value, always >= 0 */
pid_t sempid; /* pid for last operation */
unsigned short semncnt; /* # processes awaiting semval>curval */
unsigned short semzcnt; /* # processes awaiting semval == 0 */
….
};
有必要说明一下, semget创建的是一个信号量集合, 所以semid_ds是针对这个信号量集合的. 而上面这个无名结构体是针对的一个信号量.
好, 有了以上的一个union和一个struct, 我们引出semctl的定义.
#include <sys/sem.h>
int semctl (int semid, int semnum, int cmd, … /* union semun arg */);
返回值: 有点复杂, 下面详细说明
先说semctl和semun的关系.
semctl的第四个参数为可选参数, 如果使用, 则应该为semun类型, 要注意的是semun必须显式的定义在用户的程序中. 具体用法和cmd有关系.
再说semctl和无名结构体的关系. 具体用法还是和cmd有关系.
所以, 我们必须介绍一下cmd的用法.
cmd包括十种命令, 而针对的信号量用semnum指定. 范围为[0, nsems-1]
IPC_STAT
读取semid_ds到arg.buf指向的struct中
IPC_SET
将arg.buf指向的struct设置到sem_perm.uid, sem_perm.gid和sem_perm.mode
IPC_RMID
删除信号量集合
GETVAL
返回semnum信号量的无名结构体的成员semval值
SETVAL
arg.val设置到由semnum信号量的无名结构体成员semval中
GETPID
返回无名结构体成员sempid
GETNCNT
返回无名结构体成员semncnt
GETZCNT
返回无名结构体成员semzcnt
GETALL
取该集合中所有信号量(无名结构体)的值, 存放在arg.array指向的数组中
SETALL
将arg.array指向的数组的值设置该集合所有信号量的值(无名结构体)
总结一下.
围绕union semun来说:
1. int val由SETVAL使用, semctl将会把arg.val设置到信号量的semval中.
2. struct semid_ds *buf由IPC_STAT和IPC_SET使用, 读取时将信号量集合的semid_ds读取到arg.buf中. 设置时使用arg.buf的三项内容.
3. unsigned short *array由GETALL和SETALL使用, 将读取和设置集合中的所有信号量
围绕无名结构体来说:
1. semval为R/W, 读取时直接为semop的返回值, 设置时将arg.val设置给semval
2. sempid, semncnt, semzcnt为只读, 读取时职位为semop的返回值.
一句话概括. semctl读取和设置整个信号量集合, 读取和设置信号量集合中的每个信号量, 删除信号量集合
注意. semget只是创建了信号量集合, 在使用之前必须使用semctl设置你要使用的信号量
第三部分 sembuf
semop的一个参数为struct sembuf类型, 定义如下:
struct sembuf {
unsigned short sem_num; /* member # in set [0, nsems-1] */
short sem_op; /* operation (negative, 0, or positive) */
short sem_flg; /* IPC_NOWAIT, SEM_UNDO */
};
#include <sys/sem.h>
int semop (int semid, struct sembuf semoparray[], size_t nops);
返回值: 若成功返回0, 若出错则返回
参数nops规定该数组中操作的数量(元素数)
先说明一下, 第二个参数之所以定义为当前形式, 而没有定义成struct sembuf *semoparray. 是因为semop可以执行数组中的nops个操作.
整个semop围绕sembuf来进行不同的操作.
成员sem_num指定了信号量
成员sem_op和sem_flg联合指定了semop的行为. 根据书上的描述, 如下:
1. sem_op为正, 则将sem_op加到semval上.
2. sem_op为负,
semval大于或等于sem_op的绝对值, 则从semval减去sem_op的绝对值.
semval小于sem_op的绝对值, 因为信号量为非负值, 则根据sem_flg有如下行为:
(a) 设定了IPC_NOWAIT, 则semop返回EAGAIN.
(b) 没设定IPC_NOWAIT, 则semncnt值加1, 然后进程挂起直到下列时间之一发生.
(i) semval变成大于或等于sem_op的绝对值. semncnt值减一.
(ii) 信号量被删除, semop返回EIDRM
(iii) 进程捕获到一个信号, 并从信号处理程序返回.semncnt减1, semop返回EINTR.
3. sem_op为0的情况
semval为0, 正常返回.
semval非0, 则:
(a) 指定了IPC_NOWAIT, sem_op返回EAGAIN
(b) 未指定IPC_NOWAIT, semzcnt加1, 进程挂起, 直到下列事件之一发生
(i) semval变为0, semzcnt减1
(ii) 信号量被删除, semop返回EIDRM
(iii) 进程捕获到一个信号, 并从信号处理程序返回,semzcnt减1,semop返回EINTR
4. 如果设置了SEM_UNDO, 则在调用semop时, 对semval的操作将会记录到信号量调整值上, 当前进程退出后, 内核将按调整值对信号量进行处理. 但是如果使用带有SETVAL或SETALL的semctl设置某一信号量, 则在所有进程中, 该信号量的调整值都设置为0.
书上说的很严谨, 但是带来的问题就是罗嗦. 其实我们简单的理解, 就是, semop用来获得和释放资源, 释放资源时比较简单, 获得资源时, 如果资源不足, 则根据IPC_NOWAIT标志, 挂起或者直接返回. 取消挂起有三个条件, 有足够的资源了, 信号量被删除了, 捕获并处理完了一个信号. 进程醒来后semop返回相应的值.
一句话概括, semop根据semoparray进行nops个获取和释放资源的动作.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
这个可以说是对这三个信号量函数最NB的解释
但在读的时候还会发现问题
1 int val; /* for SETVAL */
这个SETVAL 是个什么东西
2SEM——UNDO这个很重要
参看APUE关于信号两的讲解
下面的两篇能解释这两个问题
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////】
信号灯也可以说是一种锁,但它可以用来控制除了文件以外的更多资源。信号灯的初始值一般为一个正数,决定了可以分配的资源数,为进程分配一个资源后自减,减到0后被锁住。SysV IPC要求信号灯必须定义为一个集合。创建信号量时则指定此集合中的值。
双态信号灯是最简单的一种,0表示锁定,无资源;1表示解锁,有一个可用资源。
2.内核中与信号量有关的数据结构
内核中维持一份全局的struct semid_ds数组,semid_ds是信号量集合的结构。semget函数返回这个信号量集合在数组中的小标。
struct semid_ds
{
struct ipc_perm sem_perm; //该信号量集合的操作许可权限
struct sem *sem_base; //该数组的元素是:该集合包含的信号量的结构。
ushort sem_nsems; //sem_base数组的个数
time_t sem_otime; //最后一次成功修改该信号量数组的时间。
time_t sem_ctime; //成功创建的时间
};
struct sem
{
ushort semval; //信号量的当前值
short sempid; //最后一次返回该信号量的进程的id号
ushort semncnt; //等待semval大于当前值的进程的个数
ushort semzcnt; //等待semval变成0的进程的个数
};
3.信号灯操作
A.创建新信号量
int shmget(key_t key, int nsems, int flag); 返回值是信号量标示符。
key: key_t是int型的,这个是一个整数,要保证内核唯一性。
nsems:该集合包含的信号量的个数。
flag:创建的权限, 可以使一些读写权限与:IPC_CREAT ( | IPC_EXCL )的按位或。
当该函数成功时,linux内核中的semval值为0,但是该值的初始化没有可移植性(就是说不能保证所有系统都能初始化该值)
这个函数的作用:创建或者打开信号量集。
B.对一个已打开的信号量集的若干信号量操作控制
int semop(int semid, struct sembuf *opsptr, size_t nops);
semid:是semget返回的semid号。
nops:是数组opsptr的个数。
opsptr是操作结构的数组
struct sembuf
{
short sem_num; //信号量在semid集合中的序号:0到nsems - 1;
short sem_op; //操作
short sem_flag; //0, IPC_NOWAIT, SEM_UNDO
};
其中,sem_op值如下:semval为信号量当前值
a、如果sem_op大于0,表示sem_num信号量所代表的资源的释放,semval += sem_op;如果sem_flag指定了SEM_UNDO标志,则信号量的调整值减去sem_op;
b、如果sem_op小于0,表示sem_num信号量所代表资源的分配,具体是:如果semval 大于等于 sem_op的绝对值,则semval -= sem_op绝对值,成为新值;否则,阻塞知道条件满足,挂起(设IPC_NOWAIT会返回)
c、如果sem_op等于0,表示直到semval 等于0时才返回,否则一直挂起。
C.对信号量集实行控制操作
int semctl(int semid, int semnum, int cmd, ../* union semun arg */);
其中semid是信号量集合,semnum是信号在集合中的序号,
union semun
{
int val; /* cmd == SETVAL */
struct semid_ds *buf /* cmd == IPC_SET或者 cmd == IPC_STAT */
ushort *array; /* cmd == SETALL, 或 cmd = GETALL */
};
cmd是控制命令,参数可选
cmd取值如下:
GETVAL, SETVAL : semid集合中semnum信号量当前的semval值
GETALL,SETALL :semid集合中所有信号量的值。
IPC_RMID:删除semid信号量集
GETPID:返回最后成功操作该信号的进程号。
IPC_STAT:返回semid集合中的struct semid_ds结构。
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
函数:int semop(int id , struct sembuf array[], nops);