文章目录
- 1. system V共享内存
- 1.1 共享内存示意图
- 2. 共享内存函数
- 2.1 shmget函数
- 2.2 代码实现
- 2.2.1 shmat
- 2.2.2 shmdt
- 3. 信息量
1. system V共享内存
system V的意思是一套标准,共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。
1.1 共享内存示意图
这是两个互不相关的进程,如果有一个共享内存。
第一步:在物理内存中创建一块空间。
第二步:通过页表把物理内存映射到进程地址空间的共享区。
2. 共享内存函数
2.1 shmget函数
第二个参数我们一般建议设置成为页(4KB)的整数倍。
假设我们的内存是4GB,那么就会有2的20次方个页。那么这么多页就会被管理起来(struct page)。
第三个参数意思是:看共享内存存不存在。
常用的宏标记:
IPC_CREAT:创建共享内存,如果已经存在,就获取之,不存在,就创建之。
IPC_EXCL:不单独使用,必须和IPC_CREAT配合使用。如果不存在指定的共享内存,创建之,如果存在,就出错返回。
IPC_EXCL意义是:可以保证,如果shmget函数调用成功,一定是一个全新的共享内存。
那么共享内存存在哪里?
因为在内存中,会有大量的进程进行通信,那么就会有大量的共享内存。那么共享内存也会被管理起来(struct shmid_ds),它里面包含了共享内存的的属性(权限,id等),它里面有个struct ipc_perm(权限),里面包含了key,也就是shmget第一个参数(共享内存的唯一性)。
为什么这个参数需要用户自己提供?
因为我们可以约定好用同一个key,让进程可以看到同一份资源。
那么我们如何设置key呢?
可以用这个函数来创建唯一的key值。
pathname:该路径是必须存在的,ftok根据路径名,提取文件信息,再根据这些文件信息及project ID合成key,该路径可以随便设置。
proj_id是可以根据自己的约定,随意设置。这个数字,有的称之为project ID; 在UNIX系统上,它的取值是1到255。
2.2 代码实现
我们在这创建两个进程,让IpcShmSer充当创建共享内存的角色,让IpcShmCli充当使用共享内存的角色。
然后我们需要写一个创建key的函数,并且能让这两个进程都能看到:
我们创建一个Comm.hpp来完成这项工作。
这里我们再创建一个.hpp来打印信息,可以更好的去调试。
我们可以先看一下这样写的结果:
下一步,我们开始创建共享内存,现在我们有了key,再去设置一下共享内存的大小:
现在我们就在IpcShmSer里面把共享内存创建好:
运行结果:
从运行结果我们可以看出:当我们运行完毕创建全新的共享内存的代码后(进程退出),但是当后面再次运行时,代码无法运行,说明共享内存是存在的。所以它不像打开文件那样,进程退出,会自动刷新文件的缓冲区和关闭文件。而共享内存不会退出。所以我们得到的结论是:system V下的共享内存,生命周期是随内核的,如果我们不显示的删除,那么只能通过操作系统重启来解决。
那么我们怎么查看当前用户有哪些IPC资源呢?
在Linux下,有一个命令:ipcs -m
这里nattch的意思是:映射进程的数量。perms的意思是:权限为多少。
这里权限为0,说明没有人能访问它,我们需要设置一下。
那么我们怎么显示删除呢?
方法一:通过命令:ipcrm -m shmid
为什么删除的是shmid,而不是key呢?
key是操作系统内部的唯一标识符,而shmid是用户层的,ipcrm也是用户层的命令,所以用的是shmid。
方法二:通过系统接口进行删除
第二个参数常用的是:IPC_RMID:从系统中删除由shmid标识的共享内存区。
如果我们用了IPC_RMID,那么第三个参数设置成空。
2.2.1 shmat
现在我们就需要使用共享内存,在使用它之前,我们需要将共享内存和自己的进程产生关联。
第二个参数:如果shmaddr 是NULL,系统将自动选择一个合适的地址。
我们先学习这一种情况,其余情况以后再说。
第三个参数:默认为0的话,共享内存具有可读可写权限。
shmat返回值是该段所连接的实际地址,如果出错返回 -1。
2.2.2 shmdt
完整代码:
这是IpcShmSer的代码,下面我们就写IpcShmCli代码。
IpcShmCli不需要删除共享内存,因为和它没有关系。
下面我们就可以使用共享内存了,我们让客户端去写:
这里为什么不需要调用系统接口呢?
我们把共享内存映射到我们的进程地址空间(堆,栈之间),对于每一个进程而言,挂接到自己的上下文中的共享内存,,属于自己的空间,类似于堆栈空间,可以被用户直接使用。
IpcShmSer也是直接使用就可以了。下面我们看一下运行情况:
我们可以看到:先运行的IpcShmSer不管共享内存里面有没有数据,直接打印。
当我们运行IpcShmCli后,IpcShmSer就会开始打印出共享内存的数据。
从运行结果来看,共享内存,没有任何访问控制,不会被阻塞,可以直接通信,但是不安全。
那么我们也可以从键盘上去输入到共享内存中:
能被多个进程看到的资源被称为临界资源。
如果没有对临界资源进行任何保护,双方进程在进行访问的时候,就可能是乱序的,可能会因为读写交叉而导致的各种乱码,废弃数据,访问控制方面的问题。
对多个进程而言,访问临界资源的代码叫做临界区。
只有这两个黄色的叫做临界区。
我们把一件事情,要么没做,要么做完,没有中间状态叫做原子性。
任何时刻,只允许一个进程,访问临界资源叫做互斥。
3. 信息量
什么是信号量?
信号量本质是一个计数器,它表示能有多少进程进临界资源里。
信号量为1的时候,表现的就是互斥特性,也叫做二元信号量。常规信号量叫做多元信号量。
所以,任何进程想要访问临界资源,必须先申请信号量,如果申请成功,就能访问临界资源中的一部分。
每一个进程要申请信号量,前提是每一个进程都必须看到这个信号量,那么能被多个进程看见,说明信号量也是临界资源。
前面我们知道信号量是可以保护临界资源的,那么信号量也是临界资源,谁来保护它呢?
举个例子:
现在有一个变量cnt,我们想让cnt减减,它的一个过程是什么样的呢?
CPU首先会从内存中读取,然后在CPU中进行减减,减减完之后,再返回到内存中。
但是现在,当CPU进行第2步,还没有进行第3步的时候,又来一个进行进行程序替换会怎么样?
假设这个进程需要减50次,那么原来进程的数据就会保存到它的进程上下文中,去执行新的进程。
那么现在cnt就会变成50,但是当原来进程再继续执行的时候,会取出进程的上下文数据,cnt又变成了99。这样,就会造成数据混乱。所以,这里的cnt会有很多中间状态,不具备原子性。
但是,信号量是一个计数器,它对应的操作是有原子性的。
那么信号量对应的操作是什么?
信号量对应的操作是PV操作,P操作相当于减减,就是申请资源,V操作相当于加加,就是释放资源。
内存共享没有访问控制,但是可以通过信号量来资源保护。