C语言笔记(25)System V IPC对象(下)

System V IPC - 信号灯
在这里插入图片描述
信号灯又叫信号量 , 用于不同的任务 , 可以使进程之间也可以使线程之间用来实现同步或者是互斥的一种机制, 进程或线程之间用来同步或互斥的机制
Linux里面支持信号灯的种类 ,主要有三类 :
1.Posix 无名信号灯 . 无名信号量 ,主要是用于线程, 一个进程内部线程之间的同步 当然也可以用来互斥 ,这是无名信号灯 .
2. Posix 有名信号灯 因为有对应的文件被创建, 所以Posix有名信号灯可以用于进程之间的同步和互斥
3. System V 信号灯

对于信号灯来说 , 我们首先了解它 的含义 ,我们通常所说的信号灯, Posix 里面无论是无名信号灯, 还是有名信号灯, 信号灯实际上代表的是一类资源 .
一般叫 :
计数信号灯. 信号灯是有值, 信号灯的值就是它代表的资源的数量. 通常它的含义就是代表某一类资源 . Posix 无名信号灯 和 无名信号灯 都属于计数信号灯

System V 信号灯
比较特殊, System V 信号灯 实际上是一个集合 , 是一个包含一个或多个计数信号灯的集合 .
有什么作用 ?
内部包含了多个计数信号灯,通过 System V 信号灯 这样的信号灯集合可以使得我们同时对信号灯集中的多个信号灯同时操作 , 我们之前讲Posix的信号灯 ,那么我们无论是p操作还是v操作 都是针对某一个计数信号灯去操作 ,而System V 信号灯 因为它是一个集合, 它允许我们对这个集合中的多个计数信号灯进行操作 . 这样做的好处是什么 ? 这样做可以使得我们在申请多个资源的时候 能够避免死锁.
在这里插入图片描述
我们在实际的运用过程中一个任务要运行 ,它可能需要多个资源 我们原先如果每个资源拥有一个计数信号灯来表示的话 , 那么我们如果要申请多个资源要分别对这些计数信号灯 做P操作 ,比方说,现在有个进程1 和 进程2这两个进程在运行的时候 假设需要两类资源 ,资源A和资源B那么资源A和资源B , 那么他们的数量 只有一个 ,那么进程1 执行的时候它要去访问资源A , 去访问资源B ,先申请A资源, 再申请B资源 那么它需要分别去对这两个信号灯进行P操作, 那么进程2 执行的时候它也要去访问资源A , 去访问资源B , 先申请B 资源 , 再申请 A资源 .那么它需要分别去对这两个信号灯进行P操作, 但是每个进程中它对资源进行p操作的顺序是不一样的, 如果是这样的话, 进程1它申请A资源成功了, 那么还没有等到它申请B资源的时候, 进程2执行了 ,那进程2申请B资源成功 ,但是当它去申请A资源的时候 , 因为A资源已经被进程1 已经申请了, 所以进程2 对这个信号量进行操作的时候进程2就会阻塞, 当进程1再执行的时候 ,它去申请B资源的时候, 很显然已经申请不到了, 这样一来这两个进程同时都会阻塞 . 一方面等待对方释放资源, 同时又占有对方所需要的资源 ,那么这种情况就是死锁, 之所以出现的原因 就是进程中, 你占用了一部分资源 , 又在等待对方你所需要的资源 , 那么互相就会引起一个死锁 ,
在这里插入图片描述
那么有了这个 System V 信号灯 它有一个集合, 它把这个多个计数信号灯放在一个集合中, 那么集合, System V 信号灯 允许我们同时对这个集合的信号灯去操作 .所以通过信号灯集合去申请资源的话, 那么它实际上是对A和B这两种资源同时去申请 , 同时去申请的话, 它的基本原则是 : 要么都满足资源都给你, 只要有一个不满足 ,所有资源都不给你 .你必须等待, 直到你获得所有的资源为止,这时候才能分配给你资源 .所以有了集合之后, 就使得我们这种在申请多个资源的时候 ,有可以引起死锁的这个问题就能够得到解决 .这是 System V 信号灯的特点 .
在这里插入图片描述
System V 信号灯 使用步骤
第一步 : 打开 或 创建一个信号灯 semget , 获取到信号灯集合的ID ,
第二步 : 信号灯初始化 semctl , 需要对信号灯集合的每个信号灯进行初始化 .初始化函数 semctl
第三步 : semop 初始化好之后 ,我们的进程也好, 线程也好就可以对这个信号灯集合中的一个或多个信号灯, 同时进行各种操作. P/V操作 semop
第四步 : 删除信号灯 . semctl 这个semctl 函数既可以用来初始化信号灯, 也可以删除信号灯 .
在这里插入图片描述
信号灯创建 函数:
返回值 : 整型 ,当函数执行成功时返回的是 信号灯的id , 失败时返回 -1
第一参数 : key 和信号灯关联的key值 ,这个key值可以是 IPC_PRIVATE 或 ftok , 私有的用IPC_PRIVATE ,如果是多个进程都要用到的话 , 那么用ftok .
第二个参数 : nsems表明我们创建的这个集合中信号灯的个数 , 所以我们在使用System V 信号灯之前,先要明确我们这个程序中有几类资源, 每个资源需要一个计数信号灯. 那么我们在创建信号灯集合的时候, 就要指定集合中所包含的计数信号灯的个数 . 这里 的每个信号灯代表一类资源 .
第三个参数 : semflg 标志位 , IPC_CREAT | 0666 ,IPC_EXCL ,一般指定IPC_CREAT | 0666, 这样的话,如果和这个key值关联的信号灯不存在就创建, 如果已经存在就打开, 返回它的id .
IPC_EXCL这个标志位实际上跟我们open函数 里面也有一个标志位, 这两个的标志位是一样的, 通常是和 IPC_CREAT一起使用的, 它的作用是,如果这个对象这个信号灯不存在, 就创建, 如果存在了,如果加上了IPC_EXCL会出错, 所以这个标志位就是用来检查这个对象是不是已经存在了 .那么为什么需要检查呢? 因为信号灯在使用之前,必须要先初始化, 那么初始化只能初始化一次, 所以通常是第一个进程先初始化 , 后面的进程就不能初始化 ,那么对于一个进程来说它怎么判断我是不是第一个进程呢 ? 它在创建打开这个信号灯集合的时候呢 , 加上这个标志位 ,如果是我新建的 函数执行成功. 返回id ,如果我不是第一个进程那么说明这个对象已经创建了, 那么我加上这个IPC_EXCL的话 就会出错,所以我们通过加上这个标志位判断如果出错了 , 并且errno =什么来着, 就说明这个对象已经被其他进程创建好了 .并且初始化了, 我就不能也不应该去初始化 ,
在这里插入图片描述
信号灯的初始化 :
在这里插入图片描述
semctl 函数
返回值 : 成功返回0, 失败返回 -1
第一个参数 : 信号灯集合的id ,
第二个参数 : 我要操作的集合中哪一个信号灯的编号 , 那么System V 信号灯 集合中可以包含多个信号灯,信号灯的编号是从0开始, 这点跟数组一样, 比如刚才我们创建了有3个信号灯的,信号灯集合, 集合中信号灯的编号 就是 012 ,
第三参数 : 指定我们要执行的操作, 这个地方主要有两个操作 ,一个是SETVAL 用来设置信号灯的值, 一个是IPC_RMID 是用来删除整个信号灯集合 .
这函数的参数 是可变的, 有可能是三个参数,也有可能是四个参数 ,取决于第三个参数的命令字, 像如果指定的是SETVAL 就会用到第四个参数 , 它是一个共用体类型 , 那如果是IPC_RMID的话, 那么就不需要第四个参数 .那么我们进行初始化 用到的命令是 SETVAL 那么会用到第四个参数
那么这个共用体类型,怎么去用呢 ?
在这里插入图片描述
这个函数有3-4个参数,取决于第三个参数命令字, 如果需要四个参数的话,那么第四个参数应当是个共用体类型,我们程序必须自己定义这样一个共用体类型(系统头文件里没有这个类型的定义),这个共用体会包含这四个成员, 并且每个成员都有解释, 告诉你每个成员对应的是哪个命令字, 那么我们用的命令字是SETVAL, 所以我们可以看到它对应的成员是一个int整型成员,val ,这个成员中存放的就是要初始化的值, 所以我们要初始化某一个信号灯那么定义一个共用体变量它这个成员里面放上我们要初始化的值 , 然后把共用体作为参数传进来就可以了.
在这里插入图片描述
一般信号灯集合中有几个信号灯.都需要进行这样的初始化 . 这样逻辑上的这个才是正确的 .
在这里插入图片描述
信号灯的P/V操作 - semop
用来实现对信号灯集合中的一个或多个信号灯执行我们指定的操作 .
返回值 : 整型 .成功返回 0 ,失败返回 -1
第一个参数 : 指定要操作的信号灯集合的id ,
第二个参数 : 结构体指针, 这个结构体sembuf , 是我们系统中,系统的头文件中已经定义好的一个结构体,这个结构体是用来描述, 对某一个信号灯的操作, 那么如果我们需要同时对集合中的多个信号灯,操作的话, 那么我们就需要定义一个这样结构体的数组 ,数组中的每一个元素,描述的是对 某一个信号灯的操作, 所以这个地方实际上我们 如果要同时操作多个信号灯的话, 第二个参数应当是对应是结构体数组的首地址,
第三个参数 : 无符号整型 , 就是要操作的信号灯的个数 .根据第三个参数,这个函数在执行的时候会从这个数组中取出相应的结构体, 按照结构体中的描述去操作对应的信号灯 .
在这里插入图片描述
sembuf 结构体
是我们系统中已经定义好的一个结构体类型, 它是用来描述对某一个信号灯的操作.包含三个成员
第一个成员 : semnum 那么这个成员就是用来指定要操作的信号灯的编号. 比方说我们集合中有三个信号灯 , 那么它的有效编号是从0 到2 那么这个地方就是指定我要操作集合中的 哪一个信号灯,
第二个成员 : 指定要执行的操作 sem_op 我们可以是一个正数,也可以是一个负数 ,如果是负数代表的是P操作,因为负数的话相当于让这个信号灯的值 减少, 所以相当于P操作 , 如果是正数的话, 相当于让这个信号灯的值增加.相当于资源增加 .这代表的是V操作., 这里 -2 和-3 也是可以的
第三个成员 : 就是操作方式 , 两个 0 / IPC_NOWAIT . 0的话表示阻塞的方式,那么对这个信号灯集合中的信号灯进行操作的时候 ,比如说P操作, 那么如果当前没有资源的话,如果是 0的话那么进程就会阻塞, 直到完成所指定的操作才会返回 .或者出错才会返回 .IPC_NOWAIT 立刻返回一个结果告诉你执行成功还是失败 .一般来说我们更多的是习惯指定成 0 .如果操作不成功, 我们让进程一直阻塞,直到操作成功为止. 才返回 .

假设我们信号灯集合中有三个信号灯, 编号是 0 ,1 , 2
假设我们这次要去第一个信号灯 和 最后一个信号灯同时进程P操作, 那么我们代码应当怎么去写.
在这里插入图片描述
我们信号灯集合中有三个信号灯, 那么意味着我最多同时能操作三个信号灯, 也有可能操作其中的一个或两个, 那么我们这里的话首先我们应当定义一个结构体数组. 结构体的数组大小应当跟我们的集合中的数量是一致的, 这次我们是同时操作其中的两个信号灯 .那么我们来看一下怎么去填充这个结构体? 对第一个信号灯的操作我们一般是放在buf[0]里, buf[0].sem_num=0; 第一个信号灯的编号编号应当是0 , 然后buf[0].sem_op =-1; buf[0].sem_flg =0; 这样就描述了结构体集合中的第一个信号灯的操作, 那么对信号灯的第二个操作我们应当放在什么地方呢 ? 不是放在buf[2]里 ,而是放在放在buf[1]里也就是说我们对信号灯集合中, 如果多个信号灯要进行操作的话 那么对应这些操作的结构体应当是我数组中连续的元素 .buf[1].sem_num = 2;这样填充好两个结构体以后呢 ?那么我们最后只需要,调用semop(集合id, 结构体数组首地址, 要操作的信号灯个数)
semop(semid, buf, 2) 这样函数在执行的时候, 就会从这个结构体数组中依次取出前两个元素 就是buf[0]和buf[1] 中描述的操作,然后去执行, 所以我们通过semop这个函数可以很方便的,很灵活的对这个信号灯集合中的任意的信号灯去执行我们指定的操作.

信号灯主要是用来实现同步跟互斥的 ,更多的时候是用来实现对共享资源的一个访问的一个控制 .所以我们这里结合共享内存 看如何去使用 ?
在这里插入图片描述
这个模型在之前线程间通信的时候说过, 父子进程要是实现对共享内存的同步读写的话, 实际上这个地方需要两个信号灯, 那么这两个信号灯, 一个代表的是可读的缓冲区 ,一个代表的是可写的缓冲区. 代表的是两类资源 , 所以我们需要在程序中, 我们创建一个包含两个信号灯的信号灯集合, 第一个信号灯代表的是可读的缓冲区的个数, 第二个信号灯代表的是可写的缓冲区的个数.

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值