40 | IPC(上):不同项目组之间抢资源,如何协调?
下载APP
关闭
渠道合作
推荐作者
40 | IPC(上):不同项目组之间抢资源,如何协调?
2019-06-28 刘超 来自北京
《趣谈Linux操作系统》
课程介绍
讲述:刘超
时长10:47大小9.86M
我们前面讲了,如果项目组之间需要紧密合作,那就需要共享内存,这样就像把两个项目组放在一个会议室一起沟通,会非常高效。这一节,我们就来详细讲讲这个进程之间共享内存的机制。
有了这个机制,两个进程可以像访问自己内存中的变量一样,访问共享内存的变量。但是同时问题也来了,当两个进程共享内存了,就会存在同时读写的问题,就需要对于共享的内存进行保护,就需要信号量这样的同步协调机制。这些也都是我们这节需要探讨的问题。下面我们就一一来看。
共享内存和信号量也是 System V 系列的进程间通信机制,所以很多地方和我们讲过的消息队列有点儿像。为了将共享内存和信号量结合起来使用,我这里定义了一个 share.h 头文件,里面放了一些共享内存和信号量在每个进程都需要的函数。
共享内存
我们先来看里面对于共享内存的操作。
首先,创建之前,我们要有一个 key 来唯一标识这个共享内存。这个 key 可以根据文件系统上的一个文件的 inode 随机生成。
然后,我们需要创建一个共享内存,就像创建一个消息队列差不多,都是使用 xxxget 来创建。其中,创建共享内存使用的是下面这个函数:
其中,key 就是前面生成的那个 key,shmflag 如果为 IPC_CREAT,就表示新创建,还可以指定读写权限 0777。
对于共享内存,需要指定一个大小 size,这个一般要申请多大呢?一个最佳实践是,我们将多个进程需要共享的数据放在一个 struct 里面,然后这里的 size 就应该是这个 struct 的大小。这样每一个进程得到这块内存后,只要强制将类型转换为这个 struct 类型,就能够访问里面的共享数据了。
在这里,我们定义了一个 struct shm_data 结构。这里面有两个成员,一个是一个整型的数组,一个是数组中元素的个数。
生成了共享内存以后,接下来就是将这个共享内存映射到进程的虚拟地址空间中。我们使用下面这个函数来进行操作。
这里面的 shm_id,就是上面创建的共享内存的 id,addr 就是指定映射在某个地方。如果不指定,则内核会自动选择一个地址,作为返回值返回。得到了返回地址以后,我们需要将指针强制类型转换为 struct shm_data 结构,就可以使用这个指针设置 data 和 datalength 了。
当共享内存使用完毕,我们可以通过 shmdt 解除它到虚拟内存的映射。
信号量
看完了共享内存,接下来我们再来看信号量。信号量以集合的形式存在的。
首先,创建之前,我们同样需要有一个 key,来唯一标识这个信号量集合。这个 key 同样可以根据文件系统上的一个文件的 inode 随机生成。
然后,我们需要创建一个信号量集合,同样也是使用 xxxget 来创建,其中创建信号量集合使用的是下面这个函数。
这里面的 key,就是前面生成的那个 key,shmflag 如果为 IPC_CREAT,就表示新创建,还可以指定读写权限 0777。
这里,nsems 表示这个信号量集合里面有几个信号量,最简单的情况下,我们设置为 1。
信号量往往代表某种资源的数量,如果用信号量做互斥,那往往将信号量设置为 1。这就是上面代码中 semaphore_init 函数的作用,这里面调用 semctl 函数,将这个信号量集合的中的第 0 个信号量,也即唯一的这个信号量设置为 1。
对于信号量,往往要定义两种操作,P 操作和 V 操作。对应上面代码中 semaphore_p 函数和 semaphore_v 函数,semaphore_p 会调用 semop 函数将信号量的值减一,表示申请占用一个资源,当发现当前没有资源的时候,进入等待。semaphore_v 会调用 semop 函数将信号量的值加一,表示释放一个资源,释放之后,就允许等待中的其他进程占用这个资源。
我们可以用这个信号量,来保护共享内存中的 struct shm_data,使得同时只有一个进程可以操作这个结构。
你是否记得咱们讲线程同步机制的时候,构建了一个老板分配活的场景。这里我们同样构建一个场景,分为 producer.c 和 consumer.c,其中 producer 也即生产者,负责往 struct shm_data 塞入数据,而 consumer.c 负责处理 struct shm_data 中的数据。
下面我们来看 producer.c 的代码。
在这里面,get_shmid 创建了共享内存,get_semaphoreid 创建了信号量集合,然后 shmat 将共享内存映射到了虚拟地址空间的 shm 指针指向的位置,然后通过强制类型转换,shared 的指针指向放在共享内存里面的 struct shm_data 结构,然后初始化为 0。semaphore_init 将信号量进行了初始化。
接着,producer 进入了一个无限循环。在这个循环里面,我们先通过 semaphore_p 申请访问共享内存的权利,如果发现 datalength 大于零,说明共享内存里面的数据没有被处理过,于是 semaphore_v 释放权利,先睡一会儿,睡醒了再看。如果发现 datalength 等于 0,说明共享内存里面的数据被处理完了,于是开始往里面放数据。让用户输入多少个数,然后每个数是什么,都放在 struct shm_data 结构中,然后 semaphore_v 释放权利,等待其他的进程将这些数拿去处理。
我们再来看 consumer 的代码。
在这里面,get_shmid 获得 producer 创建的共享内存,get_semaphoreid 获得 producer 创建的信号量集合,然后 shmat 将共享内存映射到了虚拟地址空间的 shm 指针指向的位置,然后通过强制类型转换,shared 的指针指向放在共享内存里面的 struct shm_data 结构。
接着,consumer 进入了一个无限循环,在这个循环里面,我们先通过 semaphore_p 申请访问共享内存的权利,如果发现 datalength 等于 0,就说明没什么活干,需要等待。如果发现 datalength 大于 0,就说明有活干,于是将 datalength 个整型数字从 data 数组中取出来求和。最后将 struct shm_data 清空为 0,表示任务处理完毕,通过 semaphore_v 释放权利。
通过程序创建的共享内存和信号量集合,我们可以通过命令 ipcs 查看。当然,我们也可以通过 ipcrm 进行删除。
下面我们来运行一下 producer 和 consumer,可以得到下面的结果:
总结时刻
这一节的内容差不多了,我们来总结一下。共享内存和信号量的配合机制,如下图所示:
无论是共享内存还是信号量,创建与初始化都遵循同样流程,通过 ftok 得到 key,通过 xxxget 创建对象并生成 id;
生产者和消费者都通过 shmat 将共享内存映射到各自的内存空间,在不同的进程里面映射的位置不同;
为了访问共享内存,需要信号量进行保护,信号量需要通过 semctl 初始化为某个值;
接下来生产者和消费者要通过 semop(-1) 来竞争信号量,如果生产者抢到信号量则写入,然后通过 semop(+1) 释放信号量,如果消费者抢到信号量则读出,然后通过 semop(+1) 释放信号量;
共享内存使用完毕,可以通过 shmdt 来解除映射。
课堂练习
信号量大于 1 的情况下,应该如何使用?你可以试着构建一个场景。
欢迎留言和我分享你的疑惑和见解 ,也欢迎可以收藏本节内容,反复研读。你也可以把今天的内容分享给你的朋友,和他一起学习和进步。
分享给需要的人,Ta购买本课程,你将得20元
生成海报并分享
赞 6
提建议
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
上一篇
39 | 管道:项目组A完成了,如何交接给项目组B?
下一篇
41 | IPC(中):不同项目组之间抢资源,如何协调?
精选留言(13)
- Amark2019-06-29请教一个问题,CPU调度是以进程为单位的吗,还是以线程?
作者回复: 以task,在内核里面,进程和线程都是task
13 - 莫名2019-06-28System V IPC具有很好的移植性,但缺点也比较明显,不能接口自成一套,难以使用现有的fd操作函数。建议对比讲一下比较流行的POSIX IPC。
作者回复: 赞
10 - nightmare2019-06-30信号量大于1的情况,可以让进程不操作共享变量,比如操作不同的变量,比如对一批数据做操作,然后做完之后给消费端读取
作者回复: 是的
2 - Tianz2019-06-28超哥,现在是不是推荐使用 POSIX 系列的 IPC 呢?2
- 当你的世界里有风吹过2022-02-16信号量大于1,可以用于限流。如线程或进程的个数,访问请求的个数等。2
- 艾瑞克小霸王2019-12-06信号量和锁的区别就是 信号量可以控制资源数量(>1), 而锁是 互斥排他的?1
- Jason2019-08-27这篇看的很明白,嘿嘿。1
- 许童童2019-06-28信号量大于 1 的情况下,应该如何使用? 可以让多个进程同时访问一个共享内存。
作者回复: 这个不行,大于1的时候,不能排他,但是可以控制资源
共 2 条评论1 - Geek_93a7212020-09-29如果大于1时,应该使用三个信号量,一个表示任务这种资源,一个表示空间这种资源,第三个将其置为1用于互斥访问。
- Helios2019-10-15信号量大于1的时候应该就不能控制写操作了。应该是控制读操作的进程数量。
- Jason2019-08-28老师好,ftok提示我的机器里没有“/root/sharememory/semaphorekey”这个文件,我随便新建一个文件可以吗?
作者回复: 是的,创建一个就行
共 2 条评论 - trllllllll2019-07-07老师,share.h 里面 include 了两次 ipc.h。
作者回复: 谢谢指正
- Amark2019-06-29如果线程是掉用的到基本单位,那么进程的共享资源呢?
作者回复: 内存,变量,文件,都是共享的呀