进程间通信方式(四)----------------信号量

信号量是3个XSI IPC机制中的最后一个个。如想了解XSI IPC可点击XSI IPC

信号量与已经介绍过的IPC机构(无名管道、有名管道、消息队列、共享内存)不同。它是一个计数器,用于多进程对共享数据对象的访问。

为了获取共享资源,进程需要进行下列操作:

(1)测试控制该资源的信号量。

(2)若此信号量的值为正,则进程可以使用该资源。进程将信号量的值减1,表示它使用了一个资源单位。

(3)若此信号量的值为0,则进程进入休眠状态,直至信号量的值大于0.进程被唤醒后,他返回至第(1)步。

当进程不再使用由一个信号量控制的共享资源时,该信号量的值增1.如果有进程正在休眠等待此信号量,则唤醒他们。

为了正确地实现信号量,信号量值的测试及减1操作应当是原子操作。为此,信号量通常是在内核中实现的。

常用信号量被称为二元信号量或双态信号量。它控制单个资源,初始值为1.但是一般而言,信号量的初值可以是任一正值,该值说明有多少个共享资源单位可供使用。

但是,XSI的信号量与此相比要复杂的多。三种特性造成了这种并非必要的复杂性:

(1)信号量并非是单个非负值,而必须将此信号量定义为含有一个或多个信号量值的集合。当创建一个信号量时,要指定该集合中信号量值的数量。

(2)创建信号量(semget)与对其赋初值(semctl)分开。这是一个致命的弱点,因为不能原子地创建一个信号量集合,并且对该集合中的各个信号量值赋初值。

(3)即使没有进程正在使用各种形式的XSI IPC,它们仍然是存在的。有些程序在终止时并没有释放已经分配给它的信号量,所以我们不得不为这种程序担心。

内核为每个信号量集合设置了一个semid_ds结构:

struct semid_ds{
struct ipc_perm     sem_perm;
unsigned short      sem_nsems;
time_t              sem_otime;
time_t              sem_ctime;
.
.
.};

每个信号量由一个无名结构表示,它至少包含下列成员:

struct{
unsigned short semval;
pid_t          sempid;
unsigned short semncnt;
unsigned short semzcnt;
.
.
.};

下表列出了影响信号量集的系统限制:
进程间通信方式(四)----------------信号量_第1张图片

创建或获取信号量

#include 
#include 
#include 

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

key:和信号灯集关联的key值,可以用宏定义IPC_PRIVATE,也可以用ftok()函数

nsems:  信号灯集中包含的信号灯数目

flag:信号灯集的访问权限

函数返回值

          成功:     信号量ID
          出错:     -1

当创建一个新的信号量集合时,对semid_ds结构的下列成员赋初值:

按XSI IPC的规则对ipc_perm结构赋初值。该结构中的mode被设置为flag中的相应权限位。

sem_otime被设置为0

sem_ctime设置为当前时间

sem_nsems设置为nsems

nsems是该集合中的信号量数。如果是创建新集合(一般是在服务器进程中),则必须指定nsems。如果引用一个现存的集合(一个客户进程),则将nsems指定为0。

操作信号量(赋初值、销毁)

#include 
#include 
#include 

int semctl ( int semid, int semnum,  int cmd,/*…union semun arg(不是地址) */);

semid:    信号量ID

semnum: 要修改的信号量编号

cmd:
           GETVAL:获取信号灯的值

           SETVAL:设置信号灯的值

           IPC_RMID:从系统中删除信号灯集合

函数返回值
              成功:0
              出错:-1

依赖于所请求的命令,第四个参数是可选的,如果使用该参数,则其类型是semun,它是多个特定命令参数的联合(union)

union{
int                    val;
struct    semid_ds    *buf;
unsigned  short       *array;
 };

注意,这是一个联合,而非指向联合的指针。

cmd参数指定下列10种命令中的一种,在semid指定的信号量集合上执行此命令。其中有5条命令是针对一个特定的信号量值的,它们用semnum指定该型号量集合中的一个成员。semnum值在0~nsems-1之间(包括0和nsems-1)。

IPC_STAT    对此集合取semid_ds结构,并存放在arg.buf指向的结构中。

IPC_SET      按由arg.buf指向结构中的值设置与此集合相关结构中的下列三个字段值:

                     sem_perm.uid、sem_perm.gid、sem_perm.mode。

                     此命令只能由下列两种进程执行:

                     一种是其有效用户ID等于sem_perm.cuid或sem_perm.uid的进程

                      另一种是具有超级用户权限的进程

IPC_RMID    从系统中删除该信号量集合。这种删除是立即发生的。仍然在使用此信号量集合的其他进程在它们下次试图对此信号量集合进行操作时,将出错返回EIDRM。此命令只能由下列两种进程执行:

                                                                    一种是其有效用户ID等于sem_perm.cuid或sem_perm.uid的进程

                                                                     另一种是具有超级用户权限的进程

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命令,semctl函数都返回相应的值。其他命令的返回值为0。

信号量的操作

#include 
#include 
#include 

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

semid:         信号灯量ID

 参数semoparray是一个指针,指向一个信号量操作函数,信号量操作由sembuf结构表示:

struct sembuf {

   unsigned short  sem_num;  // 要操作的信号灯的编号

   short           sem_op;   //  0 :  等待,直到信号灯的值变成0
 
                             // 1 :  释放资源,V操作

                             //   -1 :  分配资源,P操作                    

   short  sem_flg;    // 0,  IPC_NOWAIT,  SEM_UNDO
};

nops:  要操作的信号量的个数

返回值:                       成功返回   0
                              出错返回   -1

对集合中每个成员的操作由相应的sem_op值规定。此值可以为负值、0或正值。

下面将讲述信号量的undo标志。此标志对应于相应sem_flg成员的SEM_UNDO位。

(1)最容易处理的情况是sem_op为正。这对应于进程释放占用的资源数。sem_op值加到信号量的值上。如果指定了undo标志,则也从该进程的此信号量调整值中减去sem_op。

(2)若sem_op为负,则表示要获取由该信号量控制的资源。

如果该信号量的值大于或等于sem_op的绝对值(具有所需的资源),则从信号量值中减去sem_op的绝对值。这保证信号量的结果值大于或等于0。如果指定了undo标志,则sem_op的绝对值也加到该进程的此信号量调整值上。

如果信号量值小于sem_op的绝对值(资源不能满足要求),则:

                    (a)若指定了IPC_NOWAIT,则semop出错返回EAGAIN。

                    (b)若未指定IPC_NOWAIT,则该信号量的semncnt值增1(因为调用进程将进入休眠状态),然后调用进程被挂起直至下列事件之一发生:

                    (i)此信号量变成大于或等于sem_op的绝对(即某个进程已经释放了某些资源)。此信号量的semncnt值减1(因为等待结束),并且从信号量值中减去sem_op的绝对值。如果指定了undo标志,则sem_op的绝对值也加到该进程的此信号量的调整值上。

                   (ii)从系统中删除此信号量。在这种情况下,函数出错则返回EIDRM。

                   (iii)进程捕捉到一个信号,并从信号处理程序返回。在这种情况下,此信号量的semncnt值减1(因为调用进程不再等待),并且函数出错返回EINTR。

(3)若sem_op为0,这表示调用进程希望等待到该信号量值变为0。

如果信号量值当前为0,则函数立即返回。

如果信号量值非0,则:

        (a)若指定了IPC_NOWAIT,则出错返回EAGAIN。

        (b)若未指定IPC_NOWAIT,则该信号量的semzcnt值增1(因为调用进程将进入休眠状态),然后调用进程被挂起直至下列事件之一发生:

                   (i)此信号量值变为0.此信号量的semzcnt值减1(因为调用进程已结束等待)

                   (ii)从系统中删除了此信号量。在此情况下,函数出错返回EIDRM。

                   (iii)进程捕捉到了一个信号,并且从信号处理程序返回。在此情况下此信号量的semzcnt值减1(因为调用进程1不再等待),并且函数出错返回EINTR。

semop函数具有原子性,它或者执行数组中的所有操作,或者什么也不做。

exit 时信号量调整

正如前面提到的,如果在进程终止时,它占用了经由信号量分配的资源,那么就会成为一个问题。无论如何,只要为信号量操作指定了SEM_UNDO标志,然后分配资源(sem_op值小于0),那么内核就会记住对于该信号量,分配给调用进程多少资源(sem_op的绝对值)。当该进程终止时,无论自愿或者不自愿,内核都将检验该进程是否还有尚未处理的信号量调整值,如果有,则按调整值对相应量值进行处理。

如果用带SETVAL或SETALL命令的semctl设置一信号量的值,则在所有进程中,对于该信号的调整值都设置为0。

你可能感兴趣的:(Linux,信号量,XSI,IPC,进程间通信)