十六、进程间通讯--信号量

本文详细介绍了进程间通信中的信号量机制,包括基础概念如进程同步与互斥、临界资源、原子操作和PV操作。还阐述了信号量的定义、使用和特点,并列举了创建、获取、设置和操作信号量的系统调用函数。最后通过实例展示了如何使用信号量解决进程间的打印机访问冲突问题。
摘要由CSDN通过智能技术生成

一、相关基础概念

(一)进程关系

1.进程同步:进程之间相互合作,协同工作的关系称为进程的同步。简单来说就是多个相关进程在执行次序上的协调,谁先执行后执行都有顺序。是一种直接制约关系
2.进程互斥:多个进程因为争夺临界资源而互斥执行称为进程的互斥。是一种间接制约关系。

如多个人打篮球,篮球是临界资源,形成互斥,对他们形成了一个间接制约关系。工厂上流水线工作,一道工序接着下一道,有明确的次序,形成同步,上一道的工序对下一道工序有直接影响,是一种直接制约关系。

(二)临界资源&临界区

1.临界资源:在操作系统中,把那些可以被进程共享的资源(文件,打印机等),但是在同一时刻只允许一个进程/线程访问的资源统称为临界资源或共享变量。
2.临界区:访问临界资源的那段代码。

(三)原子操作

原子操作是指不会被进程/线程调度机制打断的操作,这种操作一旦开始,就一直运行到结束,中间不会有任何进程切换。故原子操作要不然不做,要不然一直做到结束。

(四)PV操作

这两个字母来源于荷兰语单词:passeren传递,就好像进入临界区;vrijgeven释放,退出临界区。假设信号量为SV,那么P、V操作含义如下:

  • P(SV):如果SV的值大于1,进行减一操作,表示获得资源;如果SV的值为0,则挂起进程的执行,即阻塞,因为目前已经没有资源了。
  • V(SV):如果当前有挂起的进程在等待资源,那么执行V就会将它唤醒;如果没有,SV加一,即释放资源。

P操作会出现阻塞,V操作永远不会阻塞。

二、信号量概念

(一)定义

信号量和前面介绍的IPC(管道,消息队列)不同,它是一个计数器,用于多线程对共享数据对象的访问。和前面的有本质的不同,前面学的管道,消息队列是为了传送数据,而信号量是以保护共享资源(临界资源)或保证进程同步为目标的,不存储进程间的通信数据。故信号量机制一般用在进程或线程之间的同步与互斥。

信号量是一个特殊的计数器,一般取正数值。它的值代表允许访问的资源数目。一般有两种常用取值:

  • 信号量的值如果只取0,1,将其称为二元信号量,控制单个资源,初始值为1。
  • 如果信号量的值大于1,则称之为计数信号量,说明有多个临界资源单位可共享。

(二)使用

使用信号量获得共享资源,进程需要执行下列操作:

  1. 测试控制临界资源的信号量。
  2. 若此信号量的值为正,则进程可以使用该资源,进程P操作将信号量减一,表示它使用了一个资源单位。
  3. 若此信号量的值为0,则进程进入休眠状态,因为此时没有资源可以获取。如果进程被唤醒后,它返回至第(1)步。
  4. 当进程不再使用由一个信号量控制的临界资源时,V操作将信号量值加1。如果有进程正在阻塞等待此信号量,则唤醒它。

上述操作均为原子操作,所以信号量通常在内核中实现。

(三)特点


  • 本质是一个计数器,内存中有多少个临界资源,信号量的数字就是多少。
  • 信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作
  • 信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。
  • 信号量不能传递复杂消息,只能用来同步。

三、信号量函数

内核为每个信号量集合维护了一个semid_ds结构体,具体成员如下:

strcut semid_ds{
    struct ipc_perm sem_perm;  //权限结构体
    time_t          sem_otime; //最后一次操作时间
    time_t          sem_ctime; //最后一次改变时间
    unsigned long   sem_nsems; //在集合中信号量
};

我们要操作的信号量就包含在这个信号量集合中,信号量的操作大多数也是通过这个信号量集合来操作的。

(一)创建/获取信号量集合semget函数

对信号量进行操作,我们先创建或获得一个信号量ID,那么调用semget函数,函数原型如下:

#include<sys/sem.h>
int semget(key_t key,int nsems,int flag);
               成功返回信号量ID,失败出错返回-1

参数:

  • key: 有两种办法可以获得,在消息队列讲解中我们已详细写出两种办法,两个进程使用相同key值,就可以使用同一个信号量。
  • nsems:如果是创建新信号量集合,那么nsems代表新信号量集合中的信号量的数目。如果是获取当前存在的信号量集合,那么此参数为0。
  • flag: 和消息队列的设置一样,标识函数的行为信号量集合的权限,取值如下:
取值 含义
IPC_CREATE 创建信号量集合
IPC_EXCL 检测信号量集合是否存在
位或权限位 可以设置信号量集合的访问权限,和其他两个参数可以或表示,取值和open函数的open_t一样,一般为0664

如果semget函数用来创建一个新集合,那么内核会自动把新信号量集合的strut semid_ds结构体做以下初始化:

  • 初始化ipc_perm结构体,该结构体中的mode成员按semget函数的flag参数中的相应权限位设置。
  • sem_otime设置为0。
  • sem_ctime设置为当前时间。
  • sem_qnsems设置为semget函数的nsems参数的值。

(二)设置信号量集合semctl函数

semctl函数可以对信号量集合/信号量进行不同的操作,如初始化,删除等,函数原型为:

# include<sys/sem.h>
int semctl(int semid,int semnum,int cmd,……/*union semun arg*/);
                cmd参数为SETAVL,IPC_RMID参数,函数返回值成功为0,失败返回-1

参数:

  • semid: 信号量集合ID

  • semunm: 用来指定该信号量集合中的某一特定信号量成员,也就是信号量对应的ID。

  • cmd参数: 用来指定对信号量集合的操作,通常用下面两个值:
    (1) SETVAL: 在信号量第一次使用之前,把信号量初始一个已知的值,通过自定义union semun中的val成员设置。
    (2)IPC_RMID: 删除信号量集合,立即发生。

  • arg参数: 此参数可选的,取决于请求的命令,一般初始化时使用,如果使用该参数,必须要自己定义联合体,表示信号量的相关信息。

    union semun{
       int              val;   //信号量的值
       struct semid_ds* buf;   // ipc_stat,ipc_set的缓冲区
       unsigned short* array;  //SETALL,GETALL数组
       struct seminfo* _buf;   //ipc_info缓冲区(Linux专用) 
    };
    

    注意:arg指向的联合体名称,成员均不固定,可以自行选择需要的。一般联合体成员包含信号量值即可。

(三)操作信号量semop函数

利用semop函数可以改变信号量的值,其函数原型为:

# include<sys/sem.h>
int semop(int semid,struct sembuf* sops,unsigned nsops);
                   成功返回0,出错返回-1

semop函数具有原子性,要不然执行所有操作,要不然一个也不执行。
参数:

  • semid: 信号量集合ID。

  • sops参数: 参数为一个指针,指向一个由struct sembuf结构表示的信号量操作数组,数组中的每个sembuf结构体对应一个信号量ID,以及对该信号量ID进行操作的标志:

    struct sembuf{
         unsigned short sem_num;//信号量ID
         short          sem_op; //信号量操作
         short          sem_flg;//操作标志
    };
    

    其中结构体中成员的取值有不同的使用:
    (1)sem_op:

    1.如果sem_op为正值,如1,表示释放sem_num对应的信号量的资源(信号量的值增加),如同V操作。
    2.如果sem_op为负值,如-1,表示获取sem_num对应的信号量资源(信号量的值减少),如同P操作。若此时已经没有资源了进行-1操作:

    • 如果指定了IPC_NOWIT,会出错返回。
    • 未指定,进程会被挂起,知道有资源或捕捉到信号结束挂起。

    3.如果sem_op为0,表示调用进程希望该信号量值变为0。如果当前信号量是0,则此函数立即返回。

    (2)sem_flag:

    • 默认填0。
    • SEM_UNDO:进程退出后,该进程对sem进行的操作都被撤销,例如对信号量值进行加1或减1操作,则进程退出后这些操作都被撤销。
  • nsops参数:
    对应参数sops数据的元素个数。

(四)封装系统调用函数实现一系列功能

在进行进程同步控制时,经常说到P、V操作,那么如何用上述的系统调用封装一系列的函数,让我们使用起来方便。根据需求我们将其分为4类函数:创建/获取信号量;P操作;V操作;删除信号集合。我们将表示信号量信息的union semun联合体(因为业务简单,所以联合体中只包含信号量的值val即可)和函数声明写在自己创建的.h文件中。

# include
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值