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

来源:互联网 发布:淘宝买家用户数据出售 编辑:程序博客网 时间:2024/06/05 19:05

进程间通信(IPC)

Linux中的内存空间分为系统空间和用户空间。
在系统空间中,由于各个线程的地址空间都是共享的,即一个线程能够随意访问kernel中的任意地址,所以无需进程通信机制的保护。
而在用户空间中,每个进程都有自己的地址空间,一个进程为了与其他进程通信,必须进入到有足够权限访问其他进程空间的kernel中,从而与其他进程进行通信。

在Linux中支持System V 进程通信的手段有三种:消息队列(Message queue)、信号量(Semaphore)、共享内存(Shared memory)。

进程通信对象标示符和键:
在kernel中,对每一类I P C 对象,都由一个非负整数来索引。为了识别并唯一标识各个进程通信的对象,需要一个标识符(即IPC标示符)来标识各个通信对象。而为了获取一个独一无二的通信对象,必须使用键(可使用ftok( )函数生成,返回值key)。这里的键是用来定位I P C 对象的标识符的。


信号量—–>实质是计数器

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

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

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

-注解:
-临界资源: 不同进程能够看到的一份公共的资源(如:打印机,磁带机等),且一次仅允许一个进程使用的资源称为临界资源。

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

信号量的本质是一种数据操作锁。
信号量在此过程中负责数据操作的互斥、同步等功能。

说明:
当请求一个使用信号量来表示的资源时,进程需要先读取信号量的值来判断资源是否可用。
信号量>0 ,资源可以使用请求。
信号量=0,无资源可用,进程会进入睡眠状态直至资源可用。

当进程不再使用一个信号量控制的共享资源时,信号量的值+1(即V操作)。

操作系统保证对信号量的加减操作均为原子操作。这是由于信号量主要的作用是维护资源的互斥或多进程的同步访问。
信号量的创建及初始化上,不能保证操作均为原子性。
这也是信号量的一大缺点:信号量的创建和初始化是分离开的。

注解:
同步与互斥:
同步:在访问公共资源的时候,以某种特定顺序的方式去访问资源
互斥:一个资源每次只能被一个进程所访问。

同步与互斥是保证在高效率运行的同时,可以正确运行。大部分情况下同步是在互斥的基础上进行的。

为什么要有信号量?

为了防止出现多个进程同时访问一个共享资源而引发的一系列问题。

信号量可以提供这样的一个机制:让一个临界区在同一时间只有一个线程在访问它。
也就是信号量是用来调协进程对共享资源的访问的。


信号量的工作原理:

**1.信号量本来是为了保护临界资源而产生的,而这里的信号量本身又是一个临界资源。
所以操作系统就保证了信号量的PV操作是原子的。

注解:
原子操作:意为不可被中断的一个或一系列操作,也可以理解为就是一件事情要么做了,要么没做。而原子操作的实现,一般是依靠硬件来实现的。

2.信号量只能进行两种操作:等待和发送信号。(P(sv)和V(sv)操作)。
保证同步和互斥操作:(sv指代信号量)。
P(sv) —–>如果sv的值大于0,信号量执行减1操作;如果等于0,则挂起该进程的执行。
V(sv) ——>如果有其他进程因等待sv而被挂起,就让它恢复运行,
否则,信号量执行加1操作。

举例说明:
有两个进程共享信号量sv,一旦其中一个进程执行了P操作,它将得到信号量,并可以进入临界区(访问临界资源的代码),使信号量减一。
这时第二个进程想要执行P操作时,信号量为0,将被阻止进入临界区,
它会被挂起以等待第一个进程离开临界区并执行V操作释放信号量。
当第一个进程释放信号量时,信号量执行加1操作,这时第二个进程就可以恢复执行。

3.信号量(一般为二元信号量)—-》所以信号量从1开始。
信号量的申请单元:信号量集—》本质上是一个数组,数组的下标标记具体的信号量。

4..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 :调用函数的操作类型,也可用于设置信号量集的访问权限,通过or运算使用。

    * 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、 semop函数::操作一个或一组信号。也可以叫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; // 在信号集中的编码 01...     nsems-1 short sem_op; //操作 负值或正值    short sem_flg; // IPC_NOWAIT, SEM_UNDO  };
* sembuf结构体参数说明:    1. sem_num:操作信号在信号集中的编号,第一个信号的编号是0,最后一个信号的编号是nsems-1。    2. sem_op:操作信号量    * 若sem_op 为负(P操作), 其绝对值又大于信号的现有值,操作将会阻塞,直到信号值大于或等于sem_op的绝对值。通常用于获取资源的使用权。    * 若sem_op 为正(V操作), 该值会加到现有的信号内值上。通常用于释放所控制资源的使用权。    * sem_op的值为0:如果没有设置IPC_NOWAIT,则调用该操作的进程或线程将暂时睡眠,直到信号量的值为0;否则进程或线程会返回错误EAGAIN。    3. sem_flg: 信号操作标识,有如下两种选择:        * IPC_NOWAIT:对信号的操作不能满足时,semop()不会阻塞,并立即返回,同时设定错误信息。      * SEM_UNDO:程序结束时(正常退出或异常终止),保证信号值会被重设为semop()调用前的值。避免程序在异常情况下结束时未解锁锁定的资源,早成资源被永远锁定。造成死锁。

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

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


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

1.没有使用信号量前:

/ 未加信号量的测试代码#include"comm.h"int main(){    pid_t id = fork(); // 创建子进程    if( id < 0)    {        perror("fork");        return -1;    }    else if (0 == id)    {// child        int sem_id = get_sems();        while(1)        {            printf("A");            fflush(stdout);            usleep(500000);            printf("A");            fflush(stdout);            sleep(500000);        }    }    else    {// father        while(1)        {            usleep(200000);            printf("B");            fflush(stdout);            usleep(500000);            printf("B");            fflush(stdout);            usleep(500000);        }       pid_t ret = waitpid(id,NULL,0);       if(ret > 0)     {          printf("proc is done!\n");     }    }    return 0;}

执行的结果如下:
从下图打印的AB的顺序可得,这时父子进程向显示器打印信息这个操作并不是原子的。
这里写图片描述

当加了信号量之后:

comm.h
这里写图片描述

comm.c

这里写图片描述

test_sem.c
这里写图片描述

Makefile
这里写图片描述

结果如下:
这里写图片描述


指令:
①查看系统中的信号量:
ipcs -q
②删除系统中的信号量:
ipcrm -q semid

原创粉丝点击