Linux 内核开发 27 POSIX共享内存
1.定义
支持 POSIX 共享内存,linux 内核使用的是通过一个名为tmpfs的特殊文件系统来实现内存共享,并且将文件系统挂载在rootfs的/dev/shm上。
这种实现与linux 文件系统api 相互一致,所以每个文件都有inode 与名称与之对应。使用posix 应用开发者来说,更具灵活性(相比systemv 内存共享模式)。映射实现由tmpfs文件系统处理。
2.相关接口定义
shm_open() 是 POSIX 共享内存的 API 函数之一,用于创建或打开 POSIX 共享内存对象。以下是关于 shm_open() 函数的详细说明:
函数原型
#include <sys/mman.h>
int shm_open(const char *name, int oflag, mode_t mode);
参数说明
name:共享内存对象的名称。在 POSIX 共享内存中,共享内存对象以名称标识。如果要创建新的共享内存对象,需要提供一个名称;如果要打开现有的共享内存对象,也需要提供相同的名称。
oflag:打开标志,指定了对共享内存对象的操作行为。可以是以下值的位掩码组合:
- O_RDONLY:只读模式打开共享内存对象。
- O_RDWR:读写模式打开共享内存对象。
- O_CREAT:如果共享内存对象不存在,则创建它。如果同时指定了 O_CREAT,还需要提供 mode 参数来设置共享内存对象的权限。
O_EXCL:与 O_CREAT 一起使用,表示如果共享内存对象已存在,则返回错误。
mode:权限掩码,仅在指定了 O_CREAT 标志时才有效,用于设置新创建的共享内存对象的权限。在 Unix 系统中,通常使用八进制表示权限,如 0644。
mmap() 是 POSIX 共享内存的 API 函数之一,用于将文件或共享内存对象映射到进程的地址空间,从而实现对文件或共享内存的读写操作。以下是关于 mmap() 函数的详细说明:
函数原型
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
参数说明
addr:映射后的起始地址。通常设为 NULL,系统会自动选择合适的地址。
length:映射的长度,即要映射的文件或共享内存对象的大小。
prot:保护标志,指定了映射区域的保护权限,可以是以下值的位掩码组合:
- PROT_READ:映射区域可读取。
- PROT_WRITE:映射区域可写入。
- PROT_EXEC:映射区域可执行。
flags:标志,指定了映射区域的其他特性,可以是以下值的位掩码组合:
- MAP_SHARED:共享映射,多个进程可以修改映射区域。
- MAP_PRIVATE:私有映射,对映射区域的修改不会影响原文件或其他进程。
fd:文件描述符,指定要映射的文件或共享内存对象的文件描述符。通常由 open()、shm_open() 或类似函数打开。
offset:文件偏移量,指定文件中的起始位置。对于共享内存对象通常为 0。
返回值
- 如果成功,返回值是映射后内存区域的起始地址。
- 如果失败,返回值是 MAP_FAILED,表示发生了错误。
错误处理
在失败时,可以通过检查 errno 来确定具体的错误原因。可能的错误包括但不限于:
- EACCES:权限不足,无法访问文件或共享内存对象。
- EINVAL:指定的参数无效。
- ENOMEM:内存不足,无法分配足够的内存空间。
shm_unlink() 是 POSIX 共享内存的 API 函数之一,用于删除命名的共享内存对象。以下是关于 shm_unlink() 函数的详细说明:
函数原型
#include <sys/mman.h>
int shm_unlink(const char *name);
参数说明
- name:共享内存对象的名称。要删除的共享内存对象由其名称标识。
返回值
- 如果成功,返回值为 0。
- 如果失败,返回值为 -1,并设置 errno 表示错误类型。
错误处理
在失败时,可以通过检查 errno 来确定具体的错误原因。可能的错误包括但不限于:
ENOENT:指定的共享内存对象不存在。
取消内存映射需要使用 munmap() 函数。以下是关于 munmap() 函数的描述:
函数原型
#include <sys/mman.h>
int munmap(void *addr, size_t length);
参数说明
- addr:映射后的起始地址,即调用 mmap() 返回的映射区域的起始地址。
- length:映射的长度,与调用 mmap() 时指定的长度相同。
返回值
- 如果成功,返回值为 0。
- 如果失败,返回值为 -1,并设置 errno 表示错误类型。
- 错误处理
在失败时,可以通过检查 errno 来确定具体的错误原因。可能的错误包括但不限于:
- EINVAL:指定的参数无效。
- ENOMEM:内存不足,无法释放映射区域的内存空间。
3.使用案例分析代码
编写写内存共享demo
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>#define MAXSIZE 1024*4 /*共享内存的大小,建议设置成内存页的整数倍*/#define FILENAME "shm.test"int main()
{/* 创建共享对象,可以查看/dev/shm目录 */int fd = shm_open(FILENAME, O_CREAT | O_TRUNC | O_RDWR, 0777);if (fd == -1) {perror("open failed:");exit(1);}/* 调整大小 */if (ftruncate(fd, MAXSIZE) == -1) {perror("ftruncate failed:");exit(1);}/* 获取属性 */struct stat buf;if (fstat(fd, &buf) == -1) {perror("fstat failed:");exit(1);}printf("the shm object size is %ld\n", buf.st_size);/* 建立映射关系 */char *ptr = (char*)mmap(NULL, MAXSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if (ptr == MAP_FAILED) {perror("mmap failed:");exit(1);}printf("mmap %s success\n", FILENAME);close(fd); /* 关闭套接字 *//* 写入数据 */char *content = "hello world";strncpy(ptr, content, strlen(content));// why sleep?sleep(30);return 0;
}
编写读内存共享demo
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>#define FILENAME "shm.test"int main()
{/* 创建共享对象,可以查看/dev/shm目录 */int fd = shm_open(FILENAME, O_RDONLY, 0);if (fd == -1) {perror("open failed:");exit(1);}/* 获取属性 */struct stat buf;if (fstat(fd, &buf) == -1) {perror("fstat failed:");exit(1);}printf("the shm object size is %ld\n", buf.st_size);/* 建立映射关系 */char *ptr = (char*)mmap(NULL, buf.st_size, PROT_READ, MAP_SHARED, fd, 0);if (ptr == MAP_FAILED) {perror("mmap failed:");exit(1);}printf("mmap %s success\n", FILENAME);close(fd); /* 关闭套接字 */printf("the read msg is:%s\n", ptr);sleep(30);return 0;
}
编译时注意需要增加 -lrt 选项,这样编译器就会在链接时搜索 shm_open
函数所在的库。
如果你已经包含了 -lrt
选项,但仍然遇到此问题,请确保你正确包含了头文件 sys/mman.h
并且没有拼写错误。
gcc -o write write.c -lrtgcc -o read read.c -lrt
执行结果:
以上代码在以下环境编译成功过。
peach@peach:~/posix-demo$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 20.04.6 LTS
Release: 20.04
Codename: focal
4.总结
在使用 POSIX 共享内存时,有一些需要注意的重要点:
- 文件描述符: POSIX 共享内存是使用文件描述符(fd)来标识共享内存区域的,因此在操作共享内存之前,需要先使用 shm_open() 函数打开一个共享内存对象,并获取文件描述符。
- 权限控制: 与其他共享内存机制不同,POSIX 共享内存允许对共享内存对象设置权限,即允许控制哪些进程可以访问共享内存。在调用 shm_open() 时可以设置权限参数,以实现这种控制。
- 映射: 要在进程的地址空间中映射共享内存对象,可以使用 mmap() 函数。需要注意的是,映射到进程地址空间时要指定正确的权限,例如 PROT_READ、PROT_WRITE 等。
- 取消映射: 使用 munmap() 函数取消内存映射,并释放该内存区域。
- 同步机制: 使用 POSIX 共享内存时,需要确保在不同进程之间正确同步共享内存的访问,以避免竞争条件和数据不一致性问题。通常可以使用信号量或互斥锁等同步机制来实现。
- 异常处理: 在使用共享内存时,需要注意处理可能出现的异常情况,例如共享内存对象打开失败、映射内存错误等,避免出现程序崩溃或数据丢失。
- 清理资源: 在不需要共享内存时,应该正确地取消内存映射,并关闭共享内存对象,以释放资源并避免资源泄漏。
- 跨平台兼容性: POSIX 共享内存是基于 POSIX 标准的实现,因此在不同的操作系统上可能会有一些差异。在编写跨平台程序时要格外注意这些细节,确保代码的可移植性和兼容性。
POSIX 共享内存提供了一种高效的共享内存机制,但在使用时需要特别注意上述点,以确保共享内存的正确和可靠操作。