Chinaunix首页 | 论坛 | 博客
  • 博客访问: 216650
  • 博文数量: 30
  • 博客积分: 1308
  • 博客等级: 中尉
  • 技术积分: 279
  • 用 户 组: 普通用户
  • 注册时间: 2006-07-20 22:12
文章分类

全部博文(30)

文章存档

2010年(1)

2008年(2)

2007年(8)

2006年(19)

分类: LINUX

2007-01-09 16:51:37

structunion为线索来观察信号量


第一部分 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_permXSI 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

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

int semctl (int semid, int semnum, int cmd, … /* union semun arg */);

返回值: 有点复杂, 下面详细说明

 

先说semctlsemun的关系.

semctl的第四个参数为可选参数, 如果使用, 则应该为semun类型, 要注意的是semun必须显式的定义在用户的程序中. 具体用法和cmd有关系.

再说semctl和无名结构体的关系. 具体用法还是和cmd有关系.

 

所以, 我们必须介绍一下cmd的用法.

cmd包括十种命令, 而针对的信号量用semnum指定. 范围为[0, nsems-1]

IPC_STAT

读取semid_dsarg.buf指向的struct

IPC_SET

arg.buf指向的struct设置到sem_perm.uid, sem_perm.gidsem_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 valSETVAL使用, semctl将会把arg.val设置到信号量的semval.

2.       struct semid_ds *bufIPC_STATIPC_SET使用, 读取时将信号量集合的semid_ds读取到arg.buf. 设置时使用arg.buf的三项内容.

3.       unsigned short *arrayGETALLSETALL使用, 将读取和设置集合中的所有信号量

围绕无名结构体来说:

1.       semvalR/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

int semop (int semid, struct sembuf semoparray[], size_t nops);

返回值: 若成功返回0, 若出错则返回

参数nops规定该数组中操作的数量(元素数)

 

先说明一下, 第二个参数之所以定义为当前形式, 而没有定义成struct sembuf *semoparray. 是因为semop可以执行数组中的nops个操作.

 

整个semop围绕sembuf来进行不同的操作.

成员sem_num指定了信号量

成员sem_opsem_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)
进程捕获到一个信号, 并从信号处理程序返回.semncnt1, semop返回EINTR.

3.       sem_op0的情况
semval
0, 正常返回.
semval
0, :

  (a) 指定了IPC_NOWAIT, sem_op返回EAGAIN
  (b)
未指定IPC_NOWAIT, semzcnt1, 进程挂起, 直到下列事件之一发生

    (i)  semval
变为0, semzcnt1
    (ii) 
信号量被删除, semop返回EIDRM

(iii) 进程捕获到一个信号, 并从信号处理程序返回,semzcnt1,semop返回EINTR

4.       如果设置了SEM_UNDO, 则在调用semop, semval的操作将会记录到信号量调整值上, 当前进程退出后, 内核将按调整值对信号量进行处理. 但是如果使用带有SETVALSETALLsemctl设置某一信号量, 则在所有进程中, 该信号量的调整值都设置为0.

 

书上说的很严谨, 但是带来的问题就是罗嗦. 其实我们简单的理解, 就是, semop用来获得和释放资源, 释放资源时比较简单, 获得资源时, 如果资源不足, 则根据IPC_NOWAIT标志, 挂起或者直接返回. 取消挂起有三个条件, 有足够的资源了, 信号量被删除了, 捕获并处理完了一个信号. 进程醒来后semop返回相应的值.

一句话概括, semop根据semoparray进行nops个获取和释放资源的动作.

 

第四部分 信号量和记录锁

 

书中最后对比了信号量和记录锁. 给出的结论为如果只需锁一个资源, 并且不需要使用XSI信号量的所有花哨(fancy)的功能, 则宁可使用记录锁, 尽管记录锁比信号量耗时, 但是记录锁使用简单, 且进程终止时系统会处理任何遗留下来的锁.

 

第五部分 参考书籍和联系方式

参考书籍

UNIX环境高级编程(2)      APUE中文版

联系方式

misc.receiver at gmail.com

 

阅读(3397) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~