Linux 内核开发 27 POSIX共享内存

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 -lrt

gcc -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 共享内存提供了一种高效的共享内存机制,但在使用时需要特别注意上述点,以确保共享内存的正确和可靠操作。

  • 27
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值