IPC实现机制(四)---信号量(sem)

来源:互联网 发布:男士去黑头洗面奶 知乎 编辑:程序博客网 时间:2024/06/05 13:42

前言:学习信号量之前先弄清两个概念:

  • 什么是临界资源,什么是临界区
    (1)临界资源:一次仅允许一个进程使用的共享资源。
    (2)临界区:每个进程中访问临界资源的那段程序称为临界区

一、什么是信号量

信号量的本质是一种数据操作锁,它本身不具有数据交换的功能,而是通过控制其他的通信资源(文件,外部设备)来实现进程间通信的;它本身是一种外部资源的标识;信号量在这个过程中负责数据操作的互斥和同步功能

二、为什么要使用信号量来管理共享资源

为了防止出现因多个程序同时访问一个共享资源而引发的一系列问题,我们需要一种方法,它可以通过生成并使用令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区域。 临界区域是指执⾏数据更新的代码需要独占式地执⾏。⽽信号量就可以提供这样的⼀种访问机制,让⼀个临界区同⼀时间只有⼀个线程在访问它, 也就是说信号量是⽤来调协进程对共享资源的访问的。其中共享内存的使⽤就要⽤到信号量。(信号量用来保护临界资源,实际上是用来保护临界区)

三、信号量是怎么管理资源的

当请求⼀个使⽤信号量来表⽰的资源时,进程需要先读取信号量的值来判断资源是否可⽤*。⼤于0,资源可以请求,等于0,⽆资源可⽤,进程会进⼊睡眠状态直⾄资源可⽤*。

当进程不再使⽤⼀个信号量控制的共享资源时信号量的值+1,对信号量的值进⾏的增减操作均为原⼦操作,这是由于信号量主要的作⽤是维护资源的互斥或多进程的同步访问。⽽在信号量的创建及初始化上,不能保证操作均为原⼦性

四、信号量的工作原理
信号量进⾏两种操作,P(sv)和V(sv)
sv为一个共享资源的信号量;

  • p(sv):如果sv的值⼤于零,就给sv减1;如果sv的值为零,就挂起该进程的执⾏.
  • V(sv):如果有其他进程因等待sv⽽被挂起,就让它恢复运⾏,如果没有进程因等待sv⽽挂起,就给它加1;

举个例⼦,就是两个进程共享一个共享资源的信号量sv,⼀旦其中⼀个进程执⾏了P(sv)操作,它将得到信号量,并可以进⼊临界区,使sv减1。⽽当第⼆个进程试图执⾏P(sv)时,因为此时 sv为0,所以该进程被阻⽌进⼊临界区,而且被挂起以等待第⼀个进程离开临界区域并执⾏V(sv)释放信号量,这时第⼆个进程就可以恢复执⾏。

四、与信号量有关的函数

(1)创建/获取一个信号量集合:

       #include <sys/types.h>       #include <sys/ipc.h>       #include <sys/sem.h>       int semget(key_t key, int nsems, int semflg);

功能:创建/获取一个信号量集合

参数:

  • key:可以用函数key_t ftok(const char *pathname, int proj_id);来获取。
  • nsems:这个参数表示你要创建的信号量集合中的信号量的个数。sys v版本中信号量只能以集合的形式创建。
  • semflg:
    同时使用IPC_CREAT和IPC_EXCL则会创建一个新的信号量集合。若已经存在的话则返回-1。
    单独使用IPC_CREAT的话会返回一个新的或者已经存在的信号量集合。

返回值:

  • 成功返回信号量集合的semid,失败返回-1。

(2)控制信号量信息的函数

在这个函数中我们可以删除信号量初始化信号量。

#include<sys/sem.h>int  semctl(int _semid  ,int _semnum,int _cmd  ……);

功能:控制信号量的信息。

返回值:成功返回0,失败返回-1;

参数:

  • semid 信号量的标志码(ID),也就是semget()函数的返回值;

  • _semnum, 操作信号在信号集中的编号。从0开始。

  • _cmd 命令,表示要进行的操作。

  • 第四个参数:semctl()在semid标识的信号量集合上执行cmd指定的控制命令,或者在该信号量集合上第semnum个信号量上执行cmd指定的控制命令。
    根据cmd不同,这个函数有三个或四个参数,当有第四个参数时,第四个参数的类型是union。

union semun{int val;   //使用的值struct semid_ds *buf;  //IPC_STAT、IPC_SET使用缓存区unsigned short *array; //GETALL、SETALL使用的缓存区struct seminfo *__buf; //IPC_INFO(linux特有)使用缓存区};

该联合体没有定义在任何系统头⽂件中,因此得⽤户⾃⼰声明。
(Centos 是这样,但是UNIX下不需要⾃⼰定义声明)

  • 下面列出的这些命令来源于百度

参数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设置信号量集中的一个单独的信号量的值。

(3)信号量结合操作

       #include <sys/types.h>       #include <sys/ipc.h>       #include <sys/sem.h>       int semop(int semid, struct sembuf *sops, unsigned nsops);

功能:用户改变信号量的值。也就是使用资源还是释放资源 使用权。

返回值:成功返回0,失败返回-1;

参数:

  • _semid : 信号量的标识码。也就是semget()的返回值。

  • sops:是一个指向结构体数组的指针。

 struct   sembuf {     unsigned short  sem_num;//当前操作的信号在信号集中的编号,第一个信号量为0;     short  sem_op;//对该信号量的操作。     short _semflg;}; 

(1)sem_num:因为信号量是以集合的形式存在的,就相当于所有信号在一个数组里面,sem_num代表当前操作的信号在信号集中的编号; 第一个信号的编号为0;

(2)sem_op:
如果其值为正数,该值会加到现有的信号内含值中。通常用于释放所控资源的使用权(v操作);
如果sem_op的值为负数,而其绝对值又大于信号的现值,操作将会阻塞,直到信号值大于或等于sem_op的绝对值。通常用于获取资源的使用权;(p操作)
如果sem_op的值为0,则操作将暂时阻塞,直到信号的值变为0。

(3)sem_flg:信号操作标志,它的取值有两种

IPC_NOWAIT //对信号的操作不能满足时,semop()不会阻塞,并立即返回,同时设定错误信息。IPC_UNDO //程序结束时(不论正常或不正常),保证信号值会被重设为semop()调用前的值。这样做的目的在于避免程序在异常情况下结束时未将锁定的资源解锁,造成该资源永远锁定。
  • nsops:表示要操作信号量的个数。因为信号量是以集合的形式存在,所以第二个参数可以传一个数组,同时对一个集合中的多个信号量进行操作。 恒大于或等于1。

五、使用指令查看删除信号量

(1) ipcs -s //查看创建的信号量集合的个数

(2) ipcrm -s semid //删除一个信号量集合

这里写图片描述
这里写图片描述

六、一个简单的有关信号量的例子