文章目录
linux 共享内存操作(shm_open)
一、背景
在linux系统开发当中,时常需要在多个进程之间交换数据,在多个进程之间交换数据,有很多方法,但最高效的方法莫过于共享内存。
共享内存并不是从某一进程拥有的内存中划分出来的;进程的内存总是私有的。共享内存是从系统的空闲内存池中分配的,希望访问它的每个进程连接它。这个连接过程称为映射,它给共享内存段分配每个进程的地址空间中的本地地址。
linux共享内存是通过tmpfs
文件系统来实现的,tmpfs文件系的目录为/dev/shm
,/dev/shm是驻留在内存 RAM 当中的,因此读写速度与读写内存速度一样,/dev/shm
的容量默认尺寸为系统内存大小的一半大小,使用df -h
命令可以看到。
root@i-3t537lcq:~# free -h
total used free shared buff/cache available
Mem: 1.9Gi 226Mi 175Mi 20Mi 1.5Gi 1.5Gi
Swap: 2.0Gi 62Mi 1.9Gi
root@i-3t537lcq:~# df -h
Filesystem Size Used Avail Use% Mounted on
udev 952M 0 952M 0% /dev
tmpfs 199M 1.4M 198M 1% /run
/dev/vda2 50G 13G 35G 27% /
tmpfs 994M 16K 994M 1% /dev/shm
tmpfs 5.0M 0 5.0M 0% /run/lock
tmpfs 994M 0 994M 0% /sys/fs/cgroup
tmpfs 199M 0 199M 0% /run/user/0
/dev/loop2 64M 64M 0 100% /snap/core20/1778
/dev/loop7 50M 50M 0 100% /snap/snapd/17950
/dev/loop8 138M 138M 0 100% /snap/lxd/24239
/dev/loop4 145M 145M 0 100% /snap/lxd/24323
/dev/loop5 56M 56M 0 100% /snap/core18/2679
/dev/loop3 64M 64M 0 100% /snap/core20/1822
/dev/loop1 50M 50M 0 100% /snap/snapd/18357
/dev/loop9 56M 56M 0 100% /snap/core18/2697
root@i-3t537lcq:~#
实际上它并不会真正的占用这块内存,如果/dev/shm/
下没有任何文件,它占用的内存实际上就是0字节,仅在使用shm_open
文件时,/dev/shm
才会真正占用内存。
二、函数使用说明
Linux的共享内存提供了进程之间共享数据的一种方法,可以避免进程之间的数据复制。Linux的共享内存API有两套,即POSIX共享内存和System V共享内存。POSIX共享内存其实是基于mmap实现的。
共享内存一般基本操作流程如下:
- 使用shm_open来新建或者打开一个共享内存
- 如果是创建一个新的共享内存对象,那么调用ftruncate设置共享内存对象的最大大小
- 使用mmap将共享内存映射到进程的地址空间中,然后操作共享内存
- 操作完毕后,使用munmap解除映射。解除映射后,本进程就不能再访问共享内存,但共享内存对象依然还是存在的
- 调用close关闭shm_open返回的文件描述符。如果进程修改了共享内存,那么在close之前可一调用fsync来同步。
- 如果没有任何进程再访问共享内存对象,可以调用shm_unlink来删除共享内存对象
在Linux系统使用共享内存,POSIX 为创建、映射、同步和取消共享内存段提供五个入口点:
int shm_open(const char *name, int oflag, mode_t mode);
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
int munmap(void *addr, size_t length);
int shm_unlink(const char *name);
int ftruncate(int fd, off_t length);
- shm_open():创建共享内存段或连接到现有的已命名内存段。这个系统调用返回一个文件描述符。
shm_open最主要的操作也是默认的操作就是在/dev/shm/下面,建立一个文件。 - mmap():把共享内存段映射到进程的内存。这个系统调用需要 shm_open() 返回的文件描述符,它返回指向内存的指针。
- shm_unlink():根据(shm_open() 返回的)文件描述符,删除共享内存段。实际上,这个内存段直到访问它的所有进程都退出时才会删除,这与在 UNIX 中删除文件很相似。但是,调用 shm_unlink() (通常由原来创建共享内存段的进程调用)之后,其他进程就无法访问这个内存段了。
- munmap():作用与 mmap() 相反。
- msync():用来让共享内存段与文件系统同步 — 当把文件映射到内存时
- ftruncate(改变文件大小)
参数fd为已打开的文件描述词,而且必须是以写入模式打开的文件。
ftruncate函数是用来截断指定文件的一个系统调用,它可以改变指定文件的大小.当指定的文件的大小比原先的大时,那么多余的部分将会以0填充.
如果原来的文件大小比参数length大,则超过的部分会被删去。
文件名字是用户自己输入的。另外一定要用ftruncate
把文件大小于设置为共享内存大小。
shm_open
int shm_open(const char *name, int oflag, mode_t mode);
功能说明:shm_open 用于创建或者打开共享内存文件。shm_open 也许仅仅是系统函数open的一个包装,不同之处就是shm_open操作的文件一定是位于tmpfs文件系统里的,常见的Linux发布版的tmpfs文件系统的存放目录就是/dev/shm。
用shm_open 创建的文件,如果不调用此函数(shm_unlink)删除,会一直存在于
/dev/shm
目录里,直到操作系统重启或者调用linux命令rm来删除为止。
返回值:成功返回fd>0, 失败返回fd<0
参数说明:
name:要打开或创建的共享内存文件名,由于shm_open 打开或操作的文件都是位于/dev/shm目录的,因此name不能带路径,例如:/var/myshare 这样的名称是错误的,而 myshare 是正确的,因为 myshare 不带任何路径。如果你一定要在name添加路径,那么,请在/dev/shm目录里创建一个目录,例如,如果你想创建一个 bill/myshare 的共享内存文件,那么请先在/dev/shm目录里创建 bill这个子目录,由于不同厂家发布的linux系统的tmpfs的位置也许不是/dev/shm,因此带路径的名称也许在别的环境下打开不成功。
oflag:打开的文件操作属性:O_CREAT、O_RDWR、O_EXCL的按位或运算组合
mode:文件共享模式,例如 0777
代码验证demo
yum install gcc
ftruncate(改变文件大小)
ftruncate(改变文件大小)
定义函数 int ftruncate(int fd,off_t length);
-
函数说明
ftruncate()会将参数fd指定的文件大小改为参数length指定的大小。
参数fd为已打开的文件描述词,而且必须是以写入模式打开的文件。
如果原来的文件大小比参数length大,则超过的部分会被删去。 -
返回值
执行成功则返回0,失败返回-1,错误原因存于errno。 -
错误代码
EBADF 参数fd文件描述词为无效的或该文件已关闭。
EINVAL 参数fd 为一socket 并非文件,或是该文件并非以写入模式打开。
mmap共享内存
Linux的共享内存提供了进程之间共享数据的一种方法,可以避免进程之间的数据复制。Linux的共享内存API有两套,即POSIX共享内存和System V共享内存。POSIX共享内存其实是基于mmap实现的。
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);
int munmap(void *addr, size_t length);
mmap在调用进程的地址空间中映射一段内存。当参数文件描述符fd为-1时,并在flags中指定MAP_ANONYMOUS,就会生成匿名映射,这种内存映射没有后台文件,可在fork的父子继承中共享(子进程调用execl后不再共享),这也是常用的共享内存手段。当fd参数为某特定文件时,就产生文件映射,不同进程映射统一文件可实现映射区域内存共享的目的。进程对共享内存的操作会在msync调用或者munmap调用后同步到后台文件。
三、示例代码
创建内存共享文件并写入数据
shm_write.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
char buf[10];
char *ptr;
int main()
{
int fd;
fd = shm_open("shm_region", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
if (fd < 0)
{
printf("error open shm_region\n");
return 0;
}
ftruncate(fd, 10);
ptr = mmap(NULL, 10, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED)
{
printf("error map\n");
return 0;
}
*ptr = 0x12;
return 0;
}
编译:
gcc -lrt -o shm_write shm_write.c
使用hexdump读取共享内存内容:
hexdump /dev/shm/shm_region
打开内存共享文件读数据
shm_read.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
char buf[10];
char *ptr;
int main()
{
int fd;
fd = shm_open("shm_region", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
if (fd < 0)
{
printf("error open shm_region\n");
return 0;
}
ftruncate(fd, 10);
ptr = mmap(NULL, 10, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED)
{
printf("error map\n");
return 0;
}
while (*ptr != 0x12)
;
printf("ptr : %d\n", *ptr);
return 0;
}
编译:
gcc -lrt -o shm_read shm_read.c
执行结果:
[root@dev c]# ./shm_read
ptr : 18
[root@dev c]#
四、问题总结
shm_write.c:(.text+0x18): undefined reference to `shm_open’
问题描述:
执行编译,文件内容包含shm_open方法
gcc -o shm_write shm_write.c
报错:shm_write.c:(.text+0x18): undefined reference to `shm_open’
问题分析:
没有找到函数定义,需要 指定动态链接库。
man shm_open
解决方法:
编译参数添加 -lrt
gcc -lrt -o shm_write shm_write.c
编译链接库:-lz -lrt -lm -lc都是什么库
-lz 压缩库(Z)
-lrt 实时库(real time):shm_open系列
-lm 数学库(math)
-lc 标准C库(C lib)
-dl ,是显式加载动态库的动态函数库
五、参考
linux 共享内存 shm_open ,mmap的正确使用
原文链接:https://blog.csdn.net/ababab12345/article/details/102931841
Linux编程之共享内存
参考URL: https://blog.csdn.net/rangzh/article/details/112002554