Linux进程间通信(六)---信号量通信之semget()、semctl()、semop()及其基础实验

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/mybelief321/article/details/9086151

    这个信号量理解起来是有点不容易啊,我看书看了好几遍才知道怎么回事。在讲这一节信号量之前,我还是想先说几个小知识点,这也是我在学习完后最终理解的“精华”,哈哈!

信号量是干啥的?

   信号量就是用来解决进程间的同步与互斥问题的一种进程间通信机制。

同步与互斥的通俗理解

   这两个名词咱们从字面上就能理解。举个例子吧,在创建子进程时,你是怎么保证父子进程执行的先后顺序呢?我在以前的时候是通过sleep()函数来实现的,比如我想让子进程先运行再让父进程运行,那么我就在父进程的程序中加一个sleep()函数,让父进程先睡眠,这样子就能先执行子进程了。有的时候咱们事先无法知道父进程和子进程哪一个先执行,但是要向我那样使用sleep()函数,只能保证先执行子进程,但是不能保证子进程执行完后再执行父进程,这样说能理解吧。所以如果我们想要子进程完全执行完后再执行父进程,就可以利用信号量来解决它们之间的同步问题。还不理解也没关系,我会在后面的实验中再结合实际讲。再举个更通俗的例子,一条食品生产线上,假设A、B共同完成一个食品的包装任务,A负责将食品放到盒子里,B和C负责将盒子打包。必须得是A先装食品B再打包吧,要是B不按规则先打包,那A还装啥,所以就需要一种机制方法保证A先进行B再进行,“信号量”就是这种机制方法,AB之间的关系就是同步关系;再假设打包要用到刀子,而车间就有一把刀子,这时候B和C就构成了互斥关系。

信号量与信号的区别

   不瞒你说,我刚开始学的时候所理解的就是“信号量是很多个信号”,当然这是错误的哈!光从英语上看就不一样:信号量是 Semaphore,而信号是 Signal 。其实,信号和信号量是不同的。它们虽然都可以实现同步和互斥,但是前者是使用信号处理器来进行的,而后者是使用P,V操作来实现的。(PV操作后边有讲)

   好了,进入正题。

信号量概述

   在多任务操作系统环境下,多个进程会同时运行,并且一些进程间可能会存在一定的关联。多个进程可能为了完成同一个任务相互协作,这就形成了进程间的同步关系。而且在不同进程间,为了争夺有限的系统资源(硬件或软件资源)会进入竞争状态,这就是进程间的互斥关系。

   进程间的互斥关系与同步关系存在的根源在于临界资源。临界资源是在同一时刻只允许有限个(通常只有一个)进程可以访问(读)或修改(写)的资源,通常包括硬件资源(处理器、内存、存储器及其它外围设备等)和软件资源(共享代码段、共享结构和变量等)。访问临界资源的代码叫做临界区,临界区本身也会称为临界资源。

   信号量是用来解决进程间的同步与互斥问题的一种进程间通信机制,包括一个称为信号量的变量和在该信号量下等待资源的进程等待队列,以及对信号量进行的两个原子操作(P/V操作)。其中,信号量对应于某一种资源,取一个非负的整形值。信号量值(常用sem_id表示)指的是当前可用的该资源的数量,若等于0则意味着目前没有可用的资源。

PV原子操作(很重要的)

   PV原子操作的具体定义如下:(好好理解,很重要的啊)

   ●  P操作:如果有可用的资源(信号量值>0),则此操作所在的进程占用一个资源(此时信号量值减1,进入临界区代码);如果没有可用的资源(信号量值=0),则此操作所在的进程被阻塞直到系统将资源分配给该进程(进入等待队列,一直等到资源轮到该进程)。

   ●  V操作:如果在该信号量的等待队列中有进程在等待资源,则唤醒一个阻塞进程;如果没有进程等待它,则释放一个资源(即信号量值加1)。

   常见的使用信号量访问临界区的伪代码如下图1:

   

  图1中的非临界区和临界区在咱们这里就是代码,具体这张图的理解还要结合着后面的实验才能理解。

  最简单的信号量只能取0和1值,这种信号量叫做二维信号量,在本节中,主要讨论二维信号量。二维信号量学好了,比较容易扩展到使用多维信号量的情况。

信号量编程

函数说明

   在Linux系统中,使用信号量通常分为以下4个步骤:

   ①  创建信号量或获得在系统中已存在的信号量,此时需要调用 semget() 函数。不同进程通过使用同一个信号量键值来获得同一个信号量

   ②  初始化信号量,此时使用 semctl() 函数的SETVAL操作。当使用二维信号量时,通常将信号量初始化为1。

   ③  进行信号量的PV操作,此时,调用 semop()函数。这一步是实现进程间的同步和互斥的核心工作部分。

   ④  如果不需要信号量,则从系统中删除它,此时使用semctl()函数的 IPC_RMID操作。需要注意的是,在程序中不应该出现对已经被删除的信号量的操作。

函数格式

   

   

                                                                    表2   semctl()函数

           

基础实验1

   这两个实验主要是练习熟悉一下信号量的概念和基本用法,首先,我先在实验1的代码中不添加与信号量相关的代码,观察运行结果,实验代码如下

    simple_fork.c文件点此下载

    

   编译运行结果如下

   

   这个运行结果是有点意思哈,由结果可以看到父进程先结束,然后子进程结束,但是我本意不是这样啊,我想让子进程先执行,父进程再执行,也就是父进程等待子进程结束。下面咱们就用信号量来实现它。

基础实验2

   本实验使用信号量来解决上面实验1的多进程间存在的同步问题,完成的功能是使父进程等待子进程结束。因为信号量相关的函数调用接口比较复杂,咱们将它们封装成二维单个信号量的基本函数,分别为信号量初始化函数(或者信号量赋值函数)init_sem()、P操作函数sem_p()、V操作函数sem_v()及删除信号量函数 del_sem()等,具体实验代码如下

   sem_fork.c文件点此下载


   



    编译运行结果如下

   

这样通过信号量就保证了它们之间的同步问题

实验分析

   下面再进一步结合实验结果重点理解一下 P/V操作,下面再贴出来图1

    

   由实验2的第47~49行可以看出,47行的 P操作和49行的 V操作决定了48行的代码为图1中的临界区(资源R)。

   程序执行流程就是,先将信号量值初始化为0(第31行),假设程序先执行父进程,通过前边讲的P操作的特点可知,由于信号量值为0,该进程被阻塞,也就是转而去执行子进程,由V操作可知,执行完子进程后,由于信号量的等待队列中有进程(这里是父进程)在等待资源,则唤醒一个阻塞进程(这里是父进程)。

   如需转载:请注明出处:http://blog.csdn.net/mybelief321/article/details/9086151

/************************************************************************************************

*欢迎关注本人公众号:BigBearIT,一起分享交流知识,更多精彩等着你!

*************************************************************************************************/


展开阅读全文

没有更多推荐了,返回首页