Linux详解 --- 进程间通信2 (共享内存、信号量)

共享内存

 共享内存是属于SystemV系列的IPC方式,System V是OS特地设计的通信方式,也就是说OS会负责管理这块。而对于管道通信,管道是基于文件实现的,所以管道并不算OS特地设计的。
 System V系列的所申请的资源要手动删除,它并不会自动清除(除非重启),System V IPC资源的生命周期随内核

共享内存的原理
在这里插入图片描述
在这里插入图片描述
共享内存的原理:让不同的进程,看到同一份资源。
首先,现在物理内存中开辟一段空间,这段空间就是共享内存。
然后,通过修改映射关系 (修改页表),使得虚拟地址空间中也开辟一块空间。
实际上,这一过程就是将在物理内存中申请的共享内存,映射到进程地址空间中,对于进程而言,它访问它的共享区的时候实际上就是在访问共享内存了。

共享内存的查看及删除

	ipcs					#查看System V系列的所有资源
	ipcs -m					#查看共享内存
	ipcrm -m [shmid]		#删除shmid对应的共享内存
	ipcrm -a				#删除进程间通信的所有资源

共享内存建立的过程

申请共享内存

· shmget函数

    int shmget(key_t key, size_t size, int shmflg);    //包含于<sys/shm.h>

功能:
 用来创建共享内存 / 获取共享内存(但是不创建)
参数:
 key:共享内存段的名字,一般由ftok函数生成,而ftok函数是通过PATHNAME和PROJ_ID创建的key值   在下面细说
 size:共享内存的大小。最好是4096字节的整数倍。
 shmflg:由9个权限标志构成,它们的用法和open文件时所使用的mode模式的标志是一样的。 #下面会细说
返回值:
 成功:返回一个非负整数,该整数就是shmid,也就是共享内存的标识码。
 失败:返回-1

<shmget参数详细说明>
key:给ftok函数提供pathname和proj_id就可以创建出key值。我们通过key值就可以开辟共享内存了。共享内存的本质就是:让不同的进程通过同一个key值,看到同一份资源!

size:这里的size最好是4096字节的整数倍!4096 bytes是一页的大小。解释一下“页”:在内存与外设进行交互的时候(IO的时候),内存是以4KB (4096 bytes) 为单位进行划分的,也是以4KB为单位与外设进行交互的。所以这里的size如果不是4096的整数倍,会造成浪费。比如:4097,那么OS就会为你开辟2页的空间 (2 * 4096),然后再把其中一页的4095个字节不让你使用,这样就是4097字节了,浪费还是很大的。

shmflg:shmflg一般我们只用其中的2个,IPC_CREAT和IPC_EXCL
  携带IPC_CREAT:根据key值去创建共享内存,如果已经有该key值对应的共享内存了,不会去创建新的共享内存,而是去获取这个已经存在的共享内存。
  携带IPC_EXCL:一般是和CREAT一起使用,确保一定能够开辟一个新的共享内存!
 注意:shmflg这个参数的位置,也可以附带上权限,如下图。如果不附带权限的设置的话,无法get到共享内存,因为没有权限。
在这里插入图片描述
ftok函数

    key_t ftok(const char* pathname, int proj_id);    // 包含于<sys/types.h> <sys/ipc.h>

功能:
 用来创建key值,可以把这个key值传给shmget函数。
参数:
 pathname:路径名
 proj_id:自己随便给一个就行
返回值:
 成功:返回key值  #只要由pathname和proj_id相同,key值就是一样的!
 失败:返回-1

关于PATHNAME和PROJ_ID的定义
在这里插入图片描述
//PATHNAME就是pwd的结果
//PROJ_ID就是随便写的

将共享内存挂接到进程地址空间中

· shmat函数

	void* shmat(int shmid, const void* shmaddr, int shmflg);	//包含于<sys/shm.h>

功能:
 挂接共享内存到进程地址空间中
参数:
 shmid:共享内存标识
 shmaddr:指定连接的地址 #一般写NULL
 shmflg:它的2个可能取值是 SHM_RND 和 SHM_RDONLY #一般写0
返回值:
 成功:返回一个指针,指向共享内存的起始地址 #类似与malloc的返回值,返回值的用法也是类似
 失败:返回-1
实际用法
在这里插入图片描述

'去’关联共享内存

· shmdt函数

	int shmdt(const void* shmaddr);		//包含于<sys/shm.h>

功能:
 去关联共享内存与当前进程
参数:
 shmaddr:shmat所返回的指针
返回值:
 成功返回0,失败返回-1
在这里插入图片描述

释放共享内存

· shmctl函数

    int shmctl(int shmid, int cmd, struct shmid_ds* buf);	//包含于<sys/shm.h>

功能:
增/删/改 共享内存,一般用来删除
参数:
 shmid:shmget函数的返回值
 cmd:删除共享内存填 IPC_RMID 就行
 buf:这里指定共享内存中的某一块地址。一般填NULL就行
返回值:
 成功:返回0
 失败:返回-1,并设置错误码
在这里插入图片描述

如何使用共享内存

 共享内存的使用方法类似于“对malloc所申请的空间的操作”。 我们拿指针去接收共享内存的起始地址,然后通过指针去访问共享内存。 #其实很好理解,共享内存这块内存就相当于2个进程的公共部分,我们只要有指向这个地方的指针,就可以轻而易举的访问了。
在这里插入图片描述

问题:server端在创建共享内存之后,client该怎么找到?
 通过相同的key值去找到共享内存。
 PATHNAME + PROJ_ID ----> key
 有了相同的key值,通过向shmget中传key值就可以得到相同的shmid了,也就是同一块共享内存。

问题:为什么共享内存是速度最快的IPC方法?
原因:①:共享内存的拷贝次数少
   ②:在使用共享内存时不涉及系统调用接口(也就是不会有内核态到用户态之间的转化,因为都是在用户层进行操作的)
   ③:不提供任何保护机制(没有同步与互斥)

问题:为什么共享内存的拷贝次数少?
 共享内存的使用方法就和使用堆空间类似,直接向共享内存中写入,另一个进程直接就能看到。它与管道不同,管道还需要拷贝数据到管道,另一个进程再从管道中拷贝数据到自己当中。

信号量

(这里只是介绍一下概念,我会在多线程的博客当中主要介绍信号量)
信号量主要作用于同步和互斥,而不是存储进程间通信的数据。
信号量的本质是一个计数器

问题:那它是什么的计数器呢?
 它是描述临界资源个数的计数器,临界资源就是多个进程/线程所共享的资源。

· 信号量分为:
二元信号量:信号量的值只能是0和1,它在同一时刻只能被一个线程获取
多元信号量:信号量的取值是整数,可以被多个线程同时获得,直到信号量的值变为0

问题:要申请一个资源,进程必须要直接占有这个资源吗?
 不用。进程只要申请信号量成功了,就一定有属于它的资源。

· 二元信号量
 二元信号量只能取0和1,因此它被用来实现互斥这一机制。
 当信号量为1的时候,进程可以使用这块资源,在使用之前,让信号量变为0,代表该资源在被人使用。(这个操作也叫P操作
 当该资源的使用结束了,让信号量重新变为1。(这个操作叫V操作

· 信号量相关接口

 #include <sys/sem.h>
 // 创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
 int semget(key_t key, int num_sems, int sem_flags);
 
 // 对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
 int semop(int semid, struct sembuf semoparray[], size_t numops); 
  
 // 控制信号量的相关信息
 int semctl(int semid, int sem_num, int cmd, ...);

备注:关于信号量更详细的使用我会在后面的多线程博客中具体说明。并且我们比较常用的信号量接口时POSIX标准的。

  • 37
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 50
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值