APUE2中信号灯(semaphores)一节

来源:互联网 发布:三国志11日本武将数据 编辑:程序博客网 时间:2024/04/27 20:45

信号灯(semaphores):

信号灯和我们前面说的pipes, FIFOs, and message queues这些IPC 不一样,信号灯是多进程访问共享资源的一个计数器。

为了取得共享的资源,需要做下面几步:

1.       检查控制资源的信号灯。

2.       如果信号灯的值是正的,可以用这个资源,相应的信号灯的值会减一。

3.       如果信号灯的值是0,进程会阻塞至到信号灯的值大于0,当进程被唤醒,应该返回第一步操作。

当一个进程操作一个被信号灯控制的资源,信号灯被减1 ,其它进程将在信号灯的值增加后被唤醒。

为了正确的执行信号灯,检测信号灯值和减少信号灯的值都要在原子操作。由于这个原因,信号灯的操作都在操作系统内核级完成的。

一般的信号灯被叫做二进制的信号灯,它控制一个独立的资源,一开始被初始化为1 。然而有时候,信号灯被初始化为一个正值,这个值可以表示,有多少个资源被这个信号灯控制。

XSI semaphores 是一种更复杂的信号灯,三个特点造成了它的复杂性。

1.  它不是一个简单的非负的数值,而是一个或多个信号灯值的组合。当我们创建一个信号灯的时候,我们要指定信号灯值的个数。

2.  创建信号灯(semget)和初始化信号灯(semctl)是分开进行的。因为我们不能用原子操作来完成创建和初始化这两个步骤。

3.  所有的XSI  IPC 在没有进程调用的时候,也会存在,我们可能会担心,如果一个进程分配了信号灯,但还没有释放,进程序就退出了,怎么办?我们下面说的,UNDO 特点就是解决这个问题。

内核为每个信号灯集合(semaphore set)维护一个semid_ds结构。

struct semid_ds {
     struct ipc_perm  sem_perm;  /* see Section 15.6.2 */
     unsigned short   sem_nsems; /* # of semaphores in set */
     time_t           sem_otime; /* last-semop() time */
     time_t           sem_ctime; /* last-change time */
     .
     .
     .
   };

每个信号灯被一个结构表示(也可以说对应的有一个结构,译者注),这个结构至少包括下面几个项。

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 */
     .
     .
     .
   };

(下面原文列了一些限制,此处不译,可以查看原文)

第一个函数是获得信号灯的ID semget

#include <sys/sem.h>

 

int semget(key_t key, int nsems, int flag);

 

Returns: semaphore ID if OK, 1 on error

 

Section 15.6.1(其它章节),我们讨论转换一个KEY 到一个标识符(应该是指ID 吧,译者注)。被调用的集合可能是新建的也可能是已经存在的。当一个新的集合被创建,下面的semid_ds 成员被初始化。

ipc_perm 被初始化

·        sem_otime 初始化为 0.

sem_ctime 初始化为当前时间。

·        sem_nsems 初始化为nsems.

集合中信号灯的元素个数就是nsems,创建新集合时(在server上),nsems 必须赋值,调用集合时(在client 上),nsems 可以指定为0

 

函数semctl 可以进行大多数信号灯的操作。

#include <sys/sem.h>

 

int semctl(int semid, int semnum, int  cmd,

           ... /* union semun arg */);

 

Returns: (see following)

第四个参数是可变的,具体视CMD 参数而定,semun 是一个命令参数的组合。

union semun {
     int              val;    /* for SETVAL */
     struct semid_ds *buf;    /* for IPC_STAT and IPC_SET */
     unsigned short  *array;  /* for GETALL and SETALL */
   };

注意:第四个参数是union , 不是union 的指针。

CMD 可以用下面十条命令,来处理 semid 指定的集合。有五条命令涉及到了信号灯集合中的某个元素,通过semnum来指定,semnum的值在0—nsems1 之间。

IPC_STAT  取得集合的semid_ds结构体,存放到arg.buf指针所指的空间。

IPC_SET  设置sem_perm.uid, sem_perm.gid, and sem_perm.mode值,这条命令的只能在effective user ID 等于 sem_perm.cuid or sem_perm.uid 或者有超级用户权限的情况下,才可以执行

IPC_RMID 删除信号灯集合。删除是即时起作用的,任何调用该集合的进程会得到一个错误值EIDRM,这条命令需要的执行权限和上一条相同。

GETVAL  返回集合中semnum成员的值semval

SETVAL 设置集合中semnum成员的值semval,,值在arg.val中指定。

GETPID 返回集合中semnum成员sempid

GETNCNT  返回集合中semnum成员semncnt

GETZCNT   返回集合中semnum成员semzcnt

GETALL  得到信号灯中所有的值,存放在arg.array

SETALL  得到信号灯中所有的值,存放在arg.array

除了GETALL的所有GET命令,返回相应的值,其余的命令返回0

Semop 对信号灯集合进行一组原子操作。

#include <sys/sem.h>

 

int semop(int semid, struct sembuf semoparray[],

 size_t nops);

 

Returns: 0 if OK, 1 on error

Semoparray 这个参数是一组操作命令的指针,nops 是这组命令的个数。

Semoparray 是一个结构体,声明如下:

struct sembuf {
   unsigned short  sem_num;  /* member # in set (0, 1, ..., nsems-1) */
   short           sem_op;   /* operation (negative, 0, or positive) */
   short           sem_flg;  /* IPC_NOWAIT, SEM_UNDO */
 };

sem_op 的值指定集合中的某个元素,可以为负,0,正。

1.    最简单的情况就是sem_op为正,直接为进程返回相应的资源,sem_op的值被加到信号灯值。Undo的标志被指定,就把信号灯的值减去sem_op的值。

2.    如果sem_op 是负的,我们想要获得相应的资源。如果信号灯的值大于或等于sem_op的绝对值,那么信号灯的值减去sem_op的绝对值。这保证了信号灯的值能大于等于0。如果undo 标志被指定,就进行相应的加操作。

如果信号灯的值小于sem_op,将会发生下面的几种情况。

a.    如果IPC_NOWAIT被指定,semop函数将返回EAGAIN的错误值。

b.    如果IPC_NOWAIT没有指定,信号灯的semncnt值被加1,调用者被阻塞,直到下面的情况发生。

信号灯的值大于或等于sem_op的绝对值;

信号灯被删除

调用者进程处理信号,并且退出信号处理。

4.如果sem_op 0,这意味着调用者进程将等待信号灯的值变为0

如果信号灯的值为0,函数会马上返回。

如果信号灯的值不为0,下面几种情况会发生。

 如果IPC_NOWAIT的值被指定,函数返回错误值EAGAIN;

如果IPC_NOWAIT 的值没指定,函数将等待。

Semop的操作是原子性的,要么完成一系列的操作,要么一点都不做。

 

原创粉丝点击