linux多线(进)程编程——(6)共享内存
前言
话说进程君的儿子经过父亲点播后就开始闭关,它想要开发出一种全新的传音神通。他想,如果两个人的大脑生长到了一起,那不是就可以直接知道对方在想什么了吗,这样不是可以避免通过语言传递照成的浪费吗?
下面就是它的设计思路。
共享内存
进程间的通信手段分别有:管道,共享内存,消息队列,信号,信号量,套接字。今天我们将学习第二种方式,共享内存(Shared Memory)。这也是进程间通信最为高效的方式。
共享内存的原理其实很简单,进程间由于操作系统的内存映射,实现了物理内存的隔离。如果有一种方法可以让两个进程的内存同时映射到一块物理内存上,那么这块内存内的数据就对两个进程全部可见了。我们也可以直接通过指针访问对应的内存空间实现进程间数据的高效传输。
共享内存的使用
linux系统为共享内存提供了以下几个函数接口,它们以shm开头,表示Shared Memory
(1)向系统申请获取共享
int shmget(ket_t __key, size_t __size, int __shmflg);
这个函数用于向系统申请一块共享内存。
__key:共享内存的键值,这个键值用于在不同进程间标识为一的共享功能区
__size:共享内存的大小(单位:字节),对于相同键值,大小要保持一致
__shmflg:共享内存申请方式
return val:返回共享内存的ID号
(2)挂载共享内存
void* shmat(int __shmid, const void *shmaddr, int __shmflg);
这个函数用于将共享内存挂载到进程内部的虚拟地址上,使用这个函数后就可以直接使用指针操作共享内存
__shmid:共享内存ID
__shmaddr:希望挂载到地址,为NULL由系统自行分配
__shmflg:一般值为0即可
return val:返回共享内存在进程中的虚拟地址的值
(3)分离共享内存
int shmdt(const void *__shmaddr);
这个函数用于将共享内存与进程的虚拟内存分离。
__shmaddr:指向共享内存的虚拟地址
(4)控制共享内存
int shmctl(int __shmid, int __command, struct shmid_ds *__buf);
用来对共享内存执行一些操作,用来告诉系统释放共享内存
__shmid:共享内存ID
__command:要执行的指令,为IPC_RMID时用来告诉系统释放共享内存
__buf:为NULL即可(高级用法一般用不到)
代码案例
运行两个程序,申请共享内存后一个写入hello world,另一个程序读取。
pro1.c:写入数据
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/shm.h>
#include <string.h>#define SHMSIZE 20 // 共享内存大小,与pro2.c保持一致,否则一定报错int main() {int ID = shmget((key_t)1, SHMSIZE, 0666|IPC_CREAT); // 申请共享内存,键值为1,与pro2.c保持一致char* shm_addr = shmat(ID, NULL, 0); // 挂载到进程的虚拟内存地址上char str[SHMSIZE] = "hello, world!"; memcpy(shm_addr, str, sizeof(str)); // 使用指针 shm_addr 操作共享内存shmdt(shm_addr); // 分离共享内存return 0;
}
pro2.c读取数据
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/shm.h>#define SHMSIZE 20 // 共享内存大小,与pro1.c保持一致,否则一定报错int main() {int ID = shmget((key_t)1, SHMSIZE, 0666|IPC_CREAT); // 申请共享内存,键值为1,与pro2.c保持一致char* shm_addr = shmat(ID, NULL, 0);printf("%s\n", shm_addr); // 打印数据shmdt(shm_addr);shmctl(ID, IPC_RMID, NULL); // 释放共享内存,不然即使关闭程序也会内存泄露,共享内存不会回收return 0;
}
运行结果
lol@hyl:~/work/linux_study/Shared_memory/shm_fun$ gcc -o p1 pro1.c
lol@hyl:~/work/linux_study/Shared_memory/shm_fun$ gcc -o p2 pro2.c
lol@hyl:~/work/linux_study/Shared_memory/shm_fun$ ./p1
lol@hyl:~/work/linux_study/Shared_memory/shm_fun$ ./p2
hello, world!
进程2顺利输出hello, world!
注意事项
(1)共享内存操作不规范导致冲突
当共享内存使用不规范,例如发生(1)大小不匹配、(2)未调用shmctl(ID, IPC_RMID, NULL)释放内存等情况时,系统会出现内存报错。 此时程序无法再次运行!即使关闭编译器后再次打开,仍然无法运行,因此共享内存仍然在系统内。
应对这种情况,需要在命令行输入
ipcs -m # 这里面的-m指的是共享内存的意思,大家可以试一下不带这个参数
之后终端会显示:
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x00000001 6 hyl 666 20 0
在这里我们可以看到我们申请的键值为1的ID号为6的共享内存,大小为20个字节,正是它的存在让我们无法再次以键值1申请共享内存,我们需要手动释放这块内存。
在命令行输入
ipcrm -m 6
最后的数字是共享内存的ID,删除后我们就可以再次运行程序了,要记得操作规范,及时在程序中释放哦!
共享内存的进程间安全问题
申请共享内存后,当两个进程想要同时向内存中写入数据会发生什么?
这个问题是一个很复杂的问题,涉及到进程间的安全性问题,学名叫进程间竞态竞争。我们将在信号量的学习中解决它,这里不过多赘述。
(我们展示的程序逻辑很简单,而且有先后运行顺序,不需要担心出现进程间冲突的问题)
小结
这节课我们学习了进程间通信的最高效手段——共享内存。
主要知识点:
(1)共享内存的四个接口函数:shmget、shmat,shmdt、shmctl
(2)共享内存的操作注意事项:大小一致,即使回收
(3)命令行中查看共享内存的指令(ipcs -m)与命令行中删除共享内存的指令(ipcrm -m shmid)
下一节我们将学习:消息队列
结束语
进程君的儿子在领悟到共享内存后,在修真界名声大振,隐隐有赶超其父亲的趋势。被人尊称为:进程公。(青出于蓝胜于蓝)
番外:什么!共享内存的另一种申请方式?请移步linux多线(进)程编程——番外1:内存映射与mmap