您的位置:首页 > 其它

进程间通信----信号量

2017-06-07 19:45 239 查看
信号量的本质是一种数据操作锁或叫它计数器,它本身不具有数据交换的功能,而是通过控制其他通信资源(文件,外设)来实现进程间通信的。它本身也是一种临界资源,,能被多个进程同时看到与访问,但是为了访问的正确性,必须保证访问的元子性。

补充知识:

(1)临界资源: 不同进程能够看到的一份公共的资源,且一次仅允许一个进程使用的资源称为临界资源。,比如两个进程都要访问硬盘,那么硬盘就是临界资源。

(2)临界区:临界区是一段代码,在这段代码中进程将访问临界资源(例如:公用的设备或是存储器),当有进程进入临界区时,其他进程必须等待,有一些同步的机制必须在临界区段的进入点和离开点实现,确保这些共用资源被互斥所获得。

(3)同步:在访问公共资源的时候,以某种特定顺序的方式去访问资源

(4)互斥:一个资源每次只能被一个进程所访问。

(5)访问的元子性:一个公共资源(临界资源),一次只能让一个进程访问

(一)为什么要使用信号量

为什么不使用一个全局变量来做计数器?

原因①:各个进程之间是独立的,一个进程中的全局变量,另一个进程根本不会看见,(即使是父子进程)。
原因②:全局变量的加1或减一操作不是原子操作。


信号量的用途:统计临界资源的数目

为了防止因多个进程同时访问一个共享资源而引发的问题。信号量提供了一种访问机制,让一个临界资源同一时间只有一个线程去访问它,也就是说信号量是用来调协进程对临界资源访问的。

(二)信号量的工作原理

1.信号量本来是为了保护临界资源而产生的,而这里的信号量本身又是一个临界资源。

所以操作系统就保证了信号量的PV操作是元子的。

2.信号量只能进行两种操作等待和发送信号,即P操作和V操作。

P(sv):如果sv的值大于0,给它减一,如果值为0,则挂起该进程的执行。

V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加一。

举例说明:

有两个进程共享信号量sv,一旦其中一个进程执行了P操作,它将得到信号量,并可以进入临界区(访问临界资源的代码),使信号量减一。

这时第二个进程想要执行P操作时,信号量为0,将被阻止进入临界区,

它会被挂起以等待第一个进程离开临界区并执行V操作释放信号量。

当第一个进程释放信号量时,信号量执行加1操作,这时第二个进程就可以恢复执行。

3.信号量(一般为二元信号量)—->>所以信号量从1开始。

信号量的申请单元:信号量集—>>本质上是一个数组,数组的下标标记具体的信号量。

(三)linux的信号量机制

(一)主意要点

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

②创建信号量(semget)和对信号量赋初值(semctl)分开进行,这是信号量的一个缺点,因为创建和初始化信号量并不是原子操作。

③即使没有进程在使用I P C资源,它们仍然是存在的,这称之为信号量的生命周期是随内核,要时刻防止资源被锁定,避免程序在异常情况下结束时没有解锁资源,可以使用关键字(SEM_UNDO )在退出时恢复信号量值为初始值。

(二)接口函数

1.ftok函数

#include <sys/ipc.h>
#include <sys/types.h>
key_t ftok(const char* path, int id);


参数说明:

* ftok 函数把一个已存在的路径名和一个整数标识转换成一个key_t值,即IPC关键字

* path 参数就是一个指定的文件名(已经存在的文件名),一般使用当前目录。当产生键时,只使用id参数的低8位。

* id 是子序号, 只使用8bit (1-255)

* 返回值:若成功返回键值,若出错返回-1

在一般的UNIX实现中,是将文件的索引节点号取出(inode),前面加上子序号的到key_t的返回值。

2.semget函数: 用来创建一个信号集,或者获取已存在的信号集。

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


key: 所创建或打开信号量集的键值(ftok函数执行的返回值)。

nsems:创建的信号量集 中的信号量个数,该参数只在创建信号量时有效。

semflg :调用函数的操作类型,也可用于设置信号量集的访问权限,通过或运算使用

semflg

* IPC_CREAT |
IPC _EXCL
| 0666 :一般用于创建,可保证返回一个新的ID,同时制定权限为666

* IPC_CREAT : 用于获取一个已经存在的ID

* 返回值:成功返回信号量集的标识符,失败返回-1,errno被设置成以下的某个值:

* EACESS : 没有访问该信号量集的权限。

* EEXIST:信号量集已经存在,无法创建。

* EINVAL:参数nsems的值小于0,或者大于该信号量集的限制,或者是该key关联的信号量以存在,并且nsems的值大于该信号量集的信号量数。

* ENOENT:信号量集不存在,同时没有使用,IPC_CREAT。

* ENOMEM:没有足够的内存创建新的信号量集。

3.semctl函数:用来初始化信号集(带有四个参数),或者删除信号集(关注两个参数)。

#include <sys/types.h
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semun, int cmd, ...);


semid:信号量集I P C 标识符。

semun:信号集实质上是一个数组,那么该参数则是操作信号在信号量数组中的下标。

cmd:在semid指定的信号量集合上执行此命令。

第三个参数cmd常用命令:

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

* IPC_RMID:从系统中删除该信号量集合。

* SETVAL:设置信号量集中的一个单独的信号量的值,此时需要传入第四个参数,,当初始化信号量时就需要设置该值。

返回值:成功返回一个正数,失败返回-1。

第四个参数是可选的,当要初始化信号量数组中的某个信号量时,就需要初始化该结构体中的val成员。

如果使用该参数,则其类型是semun,它是多个特定命令参数的联合(union):



4.se
4000
mop函数::操作一个或一组信号。也可以叫PV操作

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


参数说明:

* semid:信号集的ID,可以通过semget获取。

* sops:是一个指针,指向一个信号量操作数组。信号量操作由结构体sembuf 结构表示如下:

struct sembuf
{
unsigned short sem_num; // 在信号集中的编码 0 , 1, ...
nsems-1 short sem_op; //操作 负值或正值
short sem_flg; // IPC_NOWAIT, SEM_UNDO
};


sembuf结构体参数说明:

sem_num:操作信号在信号集中的编号,第一个信号的编号是0,最后一个信号的编号是nsems-1。

sem_op:操作信号量

若sem_op 为负(P操作), 其绝对值又大于信号的现有值,操作将会阻塞,直到信号值大于或等于sem_op的绝对值。通常用于获取资源的使用权。

若sem_op 为正(V操作), 该值会加到现有的信号内值上。通常用于释放所控制资源的使用权。

sem_op的值为0:如果没有设置IPC_NOWAIT,则调用该操作的进程或线程将暂时睡眠,直到信号量的值为0;否则进程或线程会返回错误EAGAIN。

sem_flg: 信号操作标识,有如下两种选择:

IPC_NOWAIT:对信号的操作不能满足时,semop()不会阻塞,并立即返回,同时设定错误信息。

SEM_UNDO:程序结束时(正常退出或异常终止),保证信号值会被重设为semop()调用前的值。

nsops:信号操作结构的数量,恒大于或等于1.

返回值:成功执行时,都会回0,失败返回-1,并设置errno错误信息。

代码验证:

场景:父子进程都向显示器(文件设备)上面打印信息,因为父子进程的执行顺序并不一定,所以父子进程进程向显示器上面打印信息这个操作并不是原子的操作。

sem.h文件



sem.c文件





test.c文件



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  信号量