信号量 消息队列 共享内存

/****************************************************SEMGET函数*******************************************************************************/

可以使用系统调用semget()创建一个新的信号量集,或者存取一个已经存在的信号量集:

系统调用:semget();
原型:intsemget(key_t key,int nsems,int semflg);
返回值:如果成功,则返回信号量集的IPC标识符。如果失败,则返回-1:errno=EACCESS(没有权限)
EEXIST(信号量集已经存在,无法创建)
EIDRM(信号量集已经删除)
ENOENT(信号量集不存在,同时没有使用IPC_CREAT)
ENOMEM(没有足够的内存创建新的信号量集)
ENOSPC(超出限制)
    系统调用semget()的第一个参数是关键字值(一般是由系统调用ftok()返回的)。系统内核将此值和系统中存在的其他的信号量集的关键字值进行比 较。打开和存取操作与参数semflg中的内容相关。IPC_CREAT如果信号量集在系统内核中不存在,则创建信号量集。IPC_EXCL当和 IPC_CREAT一同使用时,如果信号量集已经存在,则调用失败。如果单独使用IPC_CREAT,则semget()要么返回新创建的信号量集的标识 符,要么返回系统中已经存在的同样的关键字值的信号量的标识符。如果IPC_EXCL和IPC_CREAT一同使用,则要么返回新创建的信号量集的标识 符,要么返回-1。IPC_EXCL单独使用没有意义。参数nsems指出了一个新的信号量集中应该创建的信号量的个数。信号量集中最多的信号量的个数是 在linux/sem.h中定义的:

#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(信号量值超出范围)

    系统调用semctl用来执行在信号量集上的控制操作。这和在消息队列中的系统调用msgctl是十分相似的。但这两个系统调用的参数略有不同。因为信号 量一般是作为一个信号量集使用的,而不是一个单独的信号量。所以在信号量集的操作中,不但要知道IPC关键字值,也要知道信号量集中的具体的信号量。这两 个系统调用都使用了参数cmd,它用来指出要操作的具体命令。两个系统调用中的最后一个参数也不一样。在系统调用msgctl中,最后一个参数是指向内核 中使用的数据结构的指针。我们使用此数据结构来取得有关消息队列的一些信息,以及设置或者改变队列的存取权限和使用者。但在信号量中支持额外的可选的命 令,这样就要求有一个更为复杂的数据结构。
系统调用semctl()的第一个参数是关键字值。第二个参数是信号量数目。
    参数cmd中可以使用的命令如下:
    ·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设置信号量集中的一个单独的信号量的值。
参数arg代表一个semun的实例。semun是在linux/sem.h中定义的:
但是有的linux发行版系统在该头文件中没有定义这个结构
这样你可以根据这个函数中的说明在自己的程序中自己定义这个结构
/******************************************************************************************************************/
This  function  has three or four arguments,
       depending on cmd.  When there are four,  the
       fourth  has the type union semun.  The call‐
       ing program must define this union  as  fol‐
       lows:

           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
                                           (Linux-specific) */
           };
/**************************************************************************************************************************/

/*arg for semctl systemcalls.*/
unionsemun{
intval;/*value for SETVAL*/
structsemid_ds*buf;/*buffer for IPC_STAT&IPC_SET*/
ushort*array;/*array for GETALL&SETALL*/
structseminfo*__buf;/*buffer for IPC_INFO*/
void*__pad;     val当执行SETVAL命令时使用。buf在IPC_STAT/IPC_SET命令中使用。代表了内核中使用的信号量的数据结构。array在使用GETALL/SETALL命令时使用的指针。
唉本来想自己写完这篇文章,但上网搜,高手如云
本着资源最有配置,
引用啦

以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);

 
    信号量
    优点: 比文件锁有优势,效率不是高那么一点,起码不用打开文件关闭文件这些耗时间的工作。
    缺点: 一旦锁定,若在解锁之前出现程序崩溃等segment fault问题,将直接导致锁定的信号量无法恢复,形成永久占用。 文件锁则没有这个问题,进程的退出将导致文件描述符关闭,在该描述符上进行的锁定操作就自行解除了。
 
    办法解决: 在加锁的时候会有一个UNDO的设置,也就是在调用semop的时候指定操作结构体当中可以放置一个UNDO参数,
 
    通常都是这样去调用的:
        semop(iSemID, &stLocksem, 1);


    其中stLocksem就是定义的一个操作结构体,原型为:
        struct sembuf{
            unsighed  short  sem_num;
            short            sem_op;
            short            sem_flg;
        };

        一般定义为:
        struct sembuf stLocksem={0, -1, SEM_UNDO} , stUnlocksem={0, 1, SEM_UNDO};

 
    这样的UNDO选项会让内核记录一个与调用进程相关的UNDO记录,如果该进程崩溃,则根据这个进程的UNDO记录自动恢复相应信号量的计数值。
 
    不过有个问题:
    对于SEM_UNDO来说,内核记录的信息是跟进程相关的。一个进程在lock的时候设置一个UNDO,那么对应该进程的UNDO计数就多一个,unlock的时候
    设置一个UNDO,那么计数就减一个。对于临界区互斥的应用而言,lock和unlock都是在一个进程当中完成,于是UNDO可以切实发挥作用。
    然而,如果是一个进程lock,而另一个进程unlock,那么使用UNDO就不起作用了,而且由于都是单边操作,导致UNDO计数对单一进程而言,
    只朝一个方向发展,最后必定是超过内核限制值,这时会出现ERANGE的错误

你可能感兴趣的:(数据结构,struct,Semaphore,cmd,buffer,linux内核)