百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT技术 > 正文

探索共享内存:POSIX vs SYSV,哪个更适合你的应用?

wptr33 2025-01-23 21:51 24 浏览

共享内存是一种允许多个进程直接访问同一块物理内存区域的技术,能够极大地提高进程间通信的效率。POSIX和SYSV共享内存都是实现共享内存的标准,但它们在API设计、功能、灵活性和兼容性等方面存在一些重要的区别。下面我们将详细探讨它们的不同之处,并介绍各自的使用方法。

1.共享内存的概述

共享内存(Shared Memory)是进程间通信的一种机制,允许多个进程直接访问同一块内存区域。这种方式通常比管道、消息队列等其他 IPC 机制更高效,因为它避免了数据在进程间的拷贝,而是直接让进程访问物理内存。

在 UNIX 中,进程通过共享内存区来共享数据,通常需要配合互斥锁(mutex)或者信号量(semaphore)来保证数据一致性,避免数据竞争。

2.SYSV(System V)共享内存

System V 是早期 UNIX 系统中的一组标准接口,后来被许多 UNIX 和类 UNIX 系统采用,包括 Linux。SYSV 共享内存机制基于内核的一个全局标识符来管理共享内存段。

2.1.创建和操作

在 SYSV 共享内存中,共享内存区通过 shmget() 系统调用来创建。其他进程则通过 shmat() 来附加共享内存,使用完毕后通过 shmdt() 来分离。

  • shmget():创建共享内存段。
  • int shmget(key_t key, size_t size, int shmflg);
    • key: 一个唯一的标识符(通常由 ftok() 或手动指定)。
    • size: 要创建的共享内存区的大小。
    • shmflg: 控制共享内存段的权限和标志。
  • shmat():将共享内存段映射到当前进程的地址空间。
  • void* shmat(int shmid, const void* shmaddr, int shmflg);
    • shmid: 共享内存段的标识符(由 shmget() 返回)。
    • shmaddr: 映射地址,通常设置为 NULL,让系统选择合适的地址。
    • shmflg: 映射标志(通常设置为 0)。
  • shmdt():将共享内存段从进程的地址空间分离。
  • int shmdt(const void* shmaddr);
  • shmctl():对共享内存段进行控制(如删除共享内存段、获取信息等)。
  • int shmctl(int shmid, int cmd, struct shmid_ds* buf);
    • cmd: 操作命令(如 IPC_RMID 删除共享内存)。
    • shmid_ds: 存储共享内存状态的结构体。

2.2.优缺点

  • 优点: 高效的进程间通信,不需要进行数据拷贝。 灵活,可以在多个进程之间共享数据。
  • 缺点: 管理复杂:需要手动管理内存块的大小、权限以及删除共享内存等。 安全性较差:没有直接的内存保护机制,容易出现内存泄漏或数据冲突。

下面是一个对共享内存进行读写的例子:

示例 1:写入共享内存的程序

这个程序会创建一个共享内存对象,并将数据写入其中。

// writer.c
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#define SHM_KEY 1234  // 共享内存的键值
#define SIZE 4096     // 共享内存的大小

int main() {
    // 创建共享内存段
    int shmid = shmget(SHM_KEY, SIZE, IPC_CREAT | 0666);  // 创建共享内存
    if (shmid == -1) {
        perror("shmget failed");
        return 1;
    }

    // 将共享内存附加到进程的地址空间
    void *shm_ptr = shmat(shmid, NULL, 0);
    if (shm_ptr == (void *)-1) {
        perror("shmat failed");
        return 1;
    }

    // 写入数据到共享内存
    const char *message = "Hello from writer process!";
    strcpy(shm_ptr, message);

    printf("Writer: Written to shared memory: %s\n", message);

    // 分离共享内存
    if (shmdt(shm_ptr) == -1) {
        perror("shmdt failed");
        return 1;
    }

    return 0;
}

示例 2:读取共享内存的程序

这个程序将从共享内存中读取数据并显示出来。

// reader.c
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <string.h>

#define SHM_KEY 1234  // 共享内存的键值
#define SIZE 4096     // 共享内存的大小

int main() {
    // 获取已存在的共享内存段
    int shmid = shmget(SHM_KEY, SIZE, 0666);  // 获取共享内存
    if (shmid == -1) {
        perror("shmget failed");
        return 1;
    }

    // 将共享内存附加到进程的地址空间
    void *shm_ptr = shmat(shmid, NULL, 0);
    if (shm_ptr == (void *)-1) {
        perror("shmat failed");
        return 1;
    }

    // 读取共享内存中的数据
    printf("Reader: Read from shared memory: %s\n", (char *)shm_ptr);

    // 分离共享内存
    if (shmdt(shm_ptr) == -1) {
        perror("shmdt failed");
        return 1;
    }

    return 0;
}

步骤解释 writer.c 程序:

  • 使用 shmget() 创建或获取一个共享内存段。
  • 使用 shmat() 将共享内存段附加到进程的地址空间。
  • 使用 strcpy() 将字符串 "Hello from writer process!" 写入共享内存。
  • 完成后,使用 shmdt() 分离共享内存。

reader.c 程序:

  • 使用 shmget() 获取共享内存段。
  • 使用 shmat() 将共享内存段附加到进程的地址空间。
  • 从共享内存读取数据,并输出到终端。
  • 完成后,使用 shmdt() 分离共享内存。

编译和运行 编译代码:

g++ -o writer writer.cpp
g++ -o reader reader.cpp

运行程序:

首先运行 writer 程序来写入共享内存:

./writer

它会将字符串 "Hello from writer process!" 写入共享内存。

然后运行 reader 程序来读取共享内存中的数据:

./reader

输出将会是:

Reader: Read from shared memory: Hello from writer process!

sysv的共享内存可以使用工具ipcs进行查看:

ipcs -m

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status
0x000004d2 0          root       666        4096       0

3.POSIX 共享内存

POSIX 是一个由 IEEE 提出的标准接口,旨在提升不同操作系统间的兼容性。POSIX 共享内存(shm_open() 和 mmap())提供了一种基于文件的共享内存机制,它通过虚拟文件系统(VFS)来管理共享内存。

3.1.创建和操作

  • shm_open():创建或打开一个共享内存对象。
  • int shm_open(const char* name, int oflag, mode_t mode);
    • name: 共享内存对象的名称,通常以 / 开头(如 /myshm)。
    • oflag: 文件访问标志,通常为 O_CREAT 或 O_RDWR。
    • mode: 权限设置。
  • ftruncate():设置共享内存的大小。
  • int ftruncate(int fd, off_t length);
  • mmap():将共享内存映射到进程的地址空间。
  • void* mmap(void* addr, size_t length, int prot, int flags, int fd, off_t offset);
    • addr: 映射到的地址(通常为 NULL,让系统选择地址)。
    • length: 映射的大小。
    • prot: 保护标志,通常为 PROT_READ 或 PROT_WRITE。
    • flags: 映射标志(MAP_SHARED 表示共享内存)。
    • fd: 文件描述符,指向共享内存对象。
    • offset: 从共享内存对象的哪个位置开始映射。
  • munmap():解除映射。
  • int munmap(void* addr, size_t length);
  • shm_unlink():删除共享内存对象。
  • int shm_unlink(const char* name);

3.2.优缺点

  • 优点: 更加现代和灵活的接口,支持命名和无命名共享内存。 支持文件描述符和虚拟内存映射,允许内存共享和文件操作结合。 内存管理相对简单,操作符可以基于文件系统进行。
  • 缺点: 由于共享内存是通过文件描述符管理的,所以在跨进程访问时,需要确保所有进程都拥有适当的文件描述符。 对于不熟悉文件系统的开发人员来说,接口相对复杂。

下面是一个对共享内存进行读写的例子:

示例 1:写入共享内存的程序

这个程序会创建一个共享内存对象,并将数据写入其中。

// writer.c
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

#define SHM_NAME "/my_shm"
#define SIZE 4096

int main() {
    // 创建共享内存对象,并以可读写模式打开
    int fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
    if (fd == -1) {
        perror("shm_open failed");
        return 1;
    }

    // 调整共享内存大小
    if (ftruncate(fd, SIZE) == -1) {
        perror("ftruncate failed");
        return 1;
    }

    // 映射共享内存到进程的地址空间
    void *shm_ptr = mmap(NULL, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (shm_ptr == MAP_FAILED) {
        perror("mmap failed");
        return 1;
    }

    // 将数据写入共享内存
    const char *message = "Hello from writer process!";
    strcpy(shm_ptr, message);

    printf("Writer: Written to shared memory: %s\n", message);

    // 解除映射
    munmap(shm_ptr, SIZE);

    // 关闭共享内存
    close(fd);

    return 0;
}

示例 2:读取共享内存的程序

这个程序将从共享内存中读取数据并显示出来。

// reader.c
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

#define SHM_NAME "/my_shm"
#define SIZE 4096

int main() {
    // 打开已存在的共享内存对象
    int fd = shm_open(SHM_NAME, O_RDWR, 0666);
    if (fd == -1) {
        perror("shm_open failed");
        return 1;
    }

    // 映射共享内存到进程的地址空间
    void *shm_ptr = mmap(NULL, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (shm_ptr == MAP_FAILED) {
        perror("mmap failed");
        return 1;
    }

    // 从共享内存中读取数据
    printf("Reader: Read from shared memory: %s\n", (char *)shm_ptr);

    // 解除映射
    munmap(shm_ptr, SIZE);

    // 关闭共享内存
    close(fd);

    return 0;
}

步骤解释:

writer.c 程序:

  • 使用 shm_open 创建或打开一个名为 /my_shm 的共享内存对象,并将其映射到进程的地址空间。
  • 使用 strcpy 将 "Hello from writer process!" 字符串写入共享内存。
  • 映射结束后,调用 munmap 来解除映射,并关闭文件描述符。

reader.c 程序:

  • 使用 shm_open 打开已有的共享内存对象 /my_shm。
  • 将共享内存映射到进程的地址空间,并读取共享内存中的数据。
  • 输出读取的内容,并解除映射,关闭文件描述符。

编译和运行 编译代码:

编译这两个程序时,使用如下命令:

g++ -o writer writer.cpp 
g++ -o reader reader.cpp

运行程序:

首先运行 writer 程序来写入共享内存。

./writer

它会在共享内存中写入字符串 "Hello from writer process!"。

然后运行 reader 程序来读取共享内存中的数据。

./reader

你会看到输出:

Reader: Read from shared memory: Hello from writer process!

4.POSIX vs SYSV 共享内存的差异

特性

POSIX 共享内存

SYSV 共享内存

命名方式

通过文件名(如 /myshm)进行标识

通过共享内存 ID(shmget() 的返回值)进行标识

创建方式

shm_open()

shmget()

内存映射

使用 mmap() 将共享内存映射到进程地址空间

使用 shmat() 映射共享内存

删除共享内存

使用 shm_unlink() 删除

使用 shmctl() 删除(通过 IPC_RMID)

跨平台兼容性

支持 POSIX 标准,跨平台性好

只适用于支持 System V 的操作系统(如 Linux)

内存保护

使用 mmap() 时可以指定访问权限(如只读、只写等)

shmat() 不支持直接控制访问权限

灵活性

更灵活,支持更广泛的文件操作与内存映射结合

不支持文件描述符,功能更为单一

清理机制

不会自动清理(需要手动调用 shm_unlink())

需要手动删除共享内存(通过 shmctl())

5.总结

  • SYSV 共享内存 更加原始且底层,依赖内核分配的共享内存块,操作起来相对简单但灵活性较低,适用于老旧系统和传统应用。
  • POSIX 共享内存 提供了更加现代和灵活的接口,能够通过文件系统进行共享内存管理,并支持内存映射和更精细的权限控制,适用于新的应用程序开发。

对于开发者来说,POSIX 共享内存在功能上通常更优越,尤其是在需要跨平台兼容和处理复杂内存映射的情况下;而 SYSV 共享内存则依然在某些较老的系统或特定的应用场景中得到了广泛应用。

相关推荐

redis的八种使用场景

前言:redis是我们工作开发中,经常要打交道的,下面对redis的使用场景做总结介绍也是对redis举报的功能做梳理。缓存Redis最常见的用途是作为缓存,用于加速应用程序的响应速度。...

基于Redis的3种分布式ID生成策略

在分布式系统设计中,全局唯一ID是一个基础而关键的组件。随着业务规模扩大和系统架构向微服务演进,传统的单机自增ID已无法满足需求。高并发、高可用的分布式ID生成方案成为构建可靠分布式系统的必要条件。R...

基于OpenWrt系统路由器的模式切换与网页设计

摘要:目前商用WiFi路由器已应用到多个领域,商家通过给用户提供一个稳定免费WiFi热点达到吸引客户、提升服务的目标。传统路由器自带的Luci界面提供了工厂模式的Web界面,用户可通过该界面配置路...

这篇文章教你看明白 nginx-ingress 控制器

主机nginx一般nginx做主机反向代理(网关)有以下配置...

如何用redis实现注册中心

一句话总结使用Redis实现注册中心:服务注册...

爱可可老师24小时热门分享(2020.5.10)

No1.看自己以前写的代码是种什么体验?No2.DooM-chip!国外网友SylvainLefebvre自制的无CPU、无操作码、无指令计数器...No3.我认为CS学位可以更好,如...

Apportable:拯救程序员,IOS一秒变安卓

摘要:还在为了跨平台使用cocos2d-x吗,拯救objc程序员的奇葩来了,ApportableSDK:FreeAndroidsupportforcocos2d-iPhone。App...

JAVA实现超买超卖方案汇总,那个最适合你,一篇文章彻底讲透

以下是几种Java实现超买超卖问题的核心解决方案及代码示例,针对高并发场景下的库存扣减问题:方案一:Redis原子操作+Lua脚本(推荐)//使用Redis+Lua保证原子性publicbo...

3月26日更新 快速施法自动施法可独立设置

2016年3月26日DOTA2有一个79.6MB的更新主要是针对自动施法和快速施法的调整本来内容不多不少朋友都有自动施法和快速施法的困扰英文更新日志一些视觉BUG修复就不翻译了主要翻译自动施...

Redis 是如何提供服务的

在刚刚接触Redis的时候,最想要知道的是一个’setnameJhon’命令到达Redis服务器的时候,它是如何返回’OK’的?里面命令处理的流程如何,具体细节怎么样?你一定有问过自己...

lua _G、_VERSION使用

到这里我们已经把lua基础库中的函数介绍完了,除了函数外基础库中还有两个常量,一个是_G,另一个是_VERSION。_G是基础库本身,指向自己,这个变量很有意思,可以无限引用自己,最后得到的还是自己,...

China&#39;s top diplomat to chair third China-Pacific Island countries foreign ministers&#39; meeting

BEIJING,May21(Xinhua)--ChineseForeignMinisterWangYi,alsoamemberofthePoliticalBureau...

移动工作交流工具Lua推出Insights数据分析产品

Lua是一个适用于各种职业人士的移动交流平台,它在今天推出了一项叫做Insights的全新功能。Insights是一个数据平台,客户可以在上面实时看到员工之间的交流情况,并分析这些情况对公司发展的影响...

Redis 7新武器:用Redis Stack实现向量搜索的极限压测

当传统关系型数据库还在为向量相似度搜索的性能挣扎时,Redis7的RedisStack...

Nginx/OpenResty详解,Nginx Lua编程,重定向与内部子请求

重定向与内部子请求Nginx的rewrite指令不仅可以在Nginx内部的server、location之间进行跳转,还可以进行外部链接的重定向。通过ngx_lua模块的Lua函数除了能实现Nginx...