进程间共享内存通信


Linux 分别创建client和server端,使用共享内存进行进程间通信 C++实现


mmap/shmget

mmap内存映射shmget共享内存

mmapshmget 是两种不同的机制,用于在Unix和Linux环境中实现内存共享文件映射。尽管它们的目的都是为了提供某种形式的内存共享,但它们的实现和使用场景有所不同。


mmap(内存映射)

mmap 是一个系统调用,它可以将一个文件或其他对象映射进内存。文件被映射到调用进程的地址空间中,进程就像访问内存一样对文件进行访问。这种机制提供了对文件的方便访问,因为文件的内容被直接加载到内存中,而不需要传统的I/O系统调用(如 readwrite)。

优点:

  • 提供了对文件的直接访问,无需传统的I/O系统调用
  • 可以映射整个文件或文件的一部分到内存中
  • 适用于大型文件,因为映射可以只覆盖文件的一部分

使用场景:

  • 高效的文件访问,特别是当需要频繁读取或写入大文件
  • 进程间通信(IPC),通过映射到共享文件实现

shmget(共享内存)

shmget 是System V IPC(进程间通信)的一部分,它用于创建或访问一个共享内存段。这个内存段可以被多个进程同时访问,以实现进程间的数据共享

优点:

  • 提供了快速的进程间通信机制,因为数据直接在内存中共享
  • 可以动态地分配和释放共享内存段

使用场景:

  • 需要高效、低延迟的进程间通信场景
  • 需要在多个进程之间共享大量数据

注意事项:

  • 仅仅使用 shmget 创建共享内存是不够的,还需要使用 shmat共享内存附加到进程的地址空间中,并使用 shmdt 来分离共享内存
  • 共享内存不提供任何同步机制,因此需要其他机制(如信号量、互斥量等)来确保数据的一致性和完整性

总结

  • mmap 主要用于将文件映射到内存中,以便进行高效的文件访问或实现进程间通信
  • shmget 用于创建和访问共享内存段,以实现进程间的数据共享

这两种机制都有其特定的用途和优缺点,选择哪种机制取决于具体的应用场景和需求


shmfile文件夹为空

key_t key = ftok("shmfile", 'a'); // 创建唯一的键

/* Generates key for System V style IPC.  */
extern key_t ftok (const char *__pathname, int __proj_id) __THROW;

在Linux系统中,共享内存并不是以文件系统中的文件形式存在的,而是作为内核管理的一个资源。当你使用共享内存(如通过 shmget(), shmat(), shmdt(), shmctl() 等函数操作共享内存时),这些操作并不是在文件系统中创建一个可见的文件,而是在内核的内存中创建了一个共享内存段

shmfile 在上述例子中是用来通过 ftok() 创建一个唯一的键(key),这个键用于标识共享内存段

ftok() 函数需要两个参数:

  • 一个文件的路径
  • 一个项目标识符

即使这个文件不存在,只要项目标识符是唯一的,ftok() 就可以生成一个唯一的键。

共享内存的“文件”实际上是一个内核对象,它没有对应的文件系统中的文件,因此我们无法在文件系统中直接看到或访问它。然而,可以通过特定的系统调用和工具来查看和管理共享内存段,例如:

  • 使用 ipcs 命令:这个命令可以列出系统中所有的IPC资源,包括消息队列、共享内存段信号量。运行 ipcs -m 可以查看共享内存的信息。

  • 使用 ipcrm 命令:这个命令可以用来删除IPC资源,包括共享内存段。

共享内存的生命周期由创建它的进程和使用它的进程控制。当最后一个引用共享内存的进程释放它,或者显式地使用 shmctl() 函数将其删除后,共享内存段就会消失。

总结来说,共享内存文件在文件系统中不可见,因为它们不是文件系统中的文件,而是内核管理的内存对象。通过系统级的命令如 ipcsipcrm 可以进行查看和管理。


ipcs/ipcrm

ipcs 命令的全称是 Interprocess Communication Status 进程间通信状态,用于显示与进程间通信有关的信息

IPC是操作系统提供给进程之间进行数据交换的一种机制,它主要分为以下三种类型:

  • 消息队列(Message Queues)

    • 消息队列是一个链表,存放在内核中并由消息队列标识符标识
    • Linux内核维护了一个消息队列相关操作函数的集合,用户进程可以调用操作函数完成消息发送和接收
  • 信号量(Semaphores)

    • 信号量主要作为进程间以及同一进程不同线程之间的同步手段
  • 共享内存(Shared Memories)

    • 让任何需要的进程都能访问的内存区域
    • 因为多个进程可以同时操作,所以必须通过某种同步操作,如信号量,来确保进程间互不干扰

ipcs 命令可以显示每种IPC对象的关键信息,包括创建者、所有者、权限、关联的资源等

ipcs命令语法

ipcs [options]

-i ID : 显示特定ID的信息
-q : 显示消息队列
-m : 显示共享内存
-s : 显示信号量
-a : 显示所有信息(消息队列,共享内存,信号量)

ipcrm命令

说明:删除消息队列、共享内存、信号灯

ipcrm [ -M key | -m id | -Q key | -q id | -S key | -s id ] …

-M 以shmkey删除共享内存
-m 以shmid删除共享内存

-Q 以msgkey删除消息队列
-q 以msgid删除消息队列

-S 以semkey删除信号量
-s 以semid删除信号量

常用函数

shmxxx系列

在上述共享内存通信的C++示例代码中,使用了以下五个关键函数:

  1. ftok() - 创建唯一的键
  2. shmget() - 创建或获取共享内存段
  3. shmat() - 将共享内存段附加到调用进程的地址空间
  4. shmdt() - 从调用进程的地址空间分离共享内存段
  5. shmctl() - 控制共享内存段

下面详细介绍每个函数:


1. ftok()

系统建立IPC通讯 (消息队列、信号量和共享内存)时必须指定一个ID值。通常情况下,该id值通过ftok函数得到。

ftok是系统IPC键值的格式转换函数。

key_t ftok(const char *pathname, char proj_id);

ftok() 函数用于创建一个唯一的键(key),这个键可以用于IPC(进程间通信)操作,如共享内存、消息队列和信号量。

它通过将文件名(pathname)和一个项目标识符(proj_id)结合起来生成一个键。

参数:

  • pathname:一个存在的文件路径名目录路径名,用于生成键值,建议使用具有全局可见性的文件路径名。
  • proj_id:一个用户定义的整数值,用于生成键值的低序8位,它也可以是字符型变量,因为这个整型值可以通过将一个字符转换为整数来获得。实际上,ftok函数会将第二个参数的最低字节(即最后8位)作为整型值使用,一个字符变量(char类型)刚好是一个字节,也就是8位。因此,传递一个字符变量作为第二个参数也是可以的。

返回值:

  • 成功时返回一个唯一的键。
  • 失败时返回-1,并设置errno以指示错误。

ftok函数将通过对pathname索引节点号(inode number)proj_id进行异或操作生成一个唯一的键值。返回的键值类型是key_t,通常是一个长整型。

索引节点号,也称为Inode Index,是Linux文件系统中用于标识文件的一个唯一编号。

每个文件在磁盘分区中都有一个索引节点号,通过这个编号,操作系统可以识别文件。索引节点不仅包含了文件的地址信息,而且每个索引节点的地址都是唯一的,因此可以将地址转化为整数型的索引节点号,以此来唯一标识每个文件。此外,索引节点还包含了文件的状态信息、访问计数、逻辑设备号以及连接指针等数据

理解inode,要从文件储存说起。

文件储存在硬盘上,硬盘的最小存储单位叫做”扇区”(Sector)。每个扇区储存512字节(相当于0.5KB)。

操作系统读取硬盘的时候,不会一个个扇区地读取,这样效率太低,而是一次性连续读取多个扇区,即一次性读取一个”块”(block)。这种由多个扇区组成的”块”,是文件存取的最小单位。”块”的大小,最常见的是4KB,即连续八个 sector组成一个 block。

文件数据都储存在”块”中,那么很显然,我们还必须找到一个地方储存文件的元信息,比如文件的创建者、文件的创建日期、文件的大小等等。这种储存文件元信息的区域就叫做inode,中文译名为”索引节点”。

每一个文件都有对应的inode,里面包含了与该文件有关的一些信息。


2. shmget()
int shmget(key_t key, size_t size, int shmflg);

shmget() 函数用于创建一个新的共享内存段,或者获取一个已存在的共享内存段的标识符(ID)。

参数:

  • key:由ftok()生成的唯一键。
  • size:共享内存段的大小(字节)。
  • shmflg:共享内存段的权限和选项,通常是文件权限(如0666)与IPC创建标志(如IPC_CREAT)的组合。

返回值:

  • 成功时返回共享内存段的标识符 shm_id
  • 失败时返回-1,并设置errno以指示错误。

3. shmat()

共享内存区对象映射到调用进程的地址空间

void *shmat(int shm_id, const void *shmaddr, int shmflg);

shmat() 函数用于将共享内存段附加到调用进程的地址空间。这使得进程可以访问共享内存段。

参数:

  • shm_id:共享内存段的标识符,由shmget()返回。
  • shmaddr:指定共享内存段附加到的地址。通常设置为nullptr,让系统选择地址。
  • shmflg:控制共享内存附加行为的标志。

返回值:

  • 成功时返回指向共享内存段的指针 shmaddr
  • 失败时返回(void *)-1,并设置errno以指示错误。

4. shmdt()
int shmdt(const void *shmaddr);

shmdt() 函数用于从调用进程的地址空间分离共享内存段,使得进程不再能够访问该共享内存。

参数:

  • shmaddr:之前由shmat()返回的共享内存段的地址。

返回值:

  • 成功时返回0。
  • 失败时返回-1,并设置errno以指示错误。

5. shmctl()
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);

shmctl() 函数用于对共享内存段进行控制操作,如获取信息、设置选项或删除共享内存段

参数:

  • shm_id:共享内存段的标识符。

  • cmd:控制命令

    • IPC_STAT(获取共享内存段的状态信息)
    • IPC_SET(设置共享内存段的属性)
    • IPC_RMID(删除共享内存段)
  • buf:一个指向shmid_ds结构的指针,用于传递信息或接收信息,具体取决于cmd

返回值:

  • 成功时返回0。
  • 失败时返回-1,并设置errno以指示错误。

shm_open系列


1. shm_open

shm_open操作的文件一定是位于tmpfs文件系统里的,常见的Linux发布版的tmpfs文件系统的存放目录就是/dev/shm

函数原型:

int shm_open(const char *name, int oflag, mode_t mode);

功能:

shm_open用于创建一个新的共享内存对象或打开一个已存在的共享内存对象。它接受三个参数:

  • name:共享内存对象的名称,是一个路径字符串(通常以非文件系统的特殊路径前缀开始,如/shm/)。
  • oflag:标志位,如O_RDONLY(只读)、O_RDWR(读写)、O_CREAT(如果不存在则创建)等。
  • mode:权限模式,如S_IRUSR | S_IWUSR,定义了新创建的共享内存对象的权限。

返回值是一个文件描述符,可用于后续的共享内存操作。


2. ftruncate

函数原型:

int ftruncate(int fd, off_t length);

功能:

ftruncate用于调整文件或共享内存段的大小。它接受两个参数:

  • fd:由shm_open返回的文件描述符。
  • length:新的大小,以字节为单位。

该函数可以用来初始化共享内存段的大小,确保有足够的空间供进程写入数据。


3. mmap

函数原型:

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

功能:

mmap用于将文件或共享内存对象映射到进程的地址空间中,使得进程可以直接通过指针访问共享内存中的数据,而不是通过读写文件描述符。参数含义如下:

  • addr:建议的映射起始地址,通常设为NULL让系统决定。
  • length:映射区域的长度。
  • prot:保护标志,如PROT_READ(可读)、PROT_WRITE(可写)等。
  • flags:映射类型,如MAP_SHARED(映射是共享的,对映射区的修改会影响所有映射该区域的进程)。
  • fd:由shm_open返回的文件描述符
  • offset:映射的偏移量,通常为0。

返回值是一个指向映射区起始地址的指针,或者在失败时返回MAP_FAILED


4. munmap

函数原型:

int munmap(void *addr, size_t length);

功能:

munmap用于取消之前通过mmap建立的内存映射,释放映射到进程地址空间的共享内存区域。参数包括:

  • addr:映射区的起始地址,即mmap返回的指针。
  • length:映射区域的长度,应与mmap时提供的长度相同。

成功执行时返回0,失败返回-1。


函数原型:

int shm_unlink(const char *name);

功能:

shm_unlink用于删除一个共享内存对象。与文件的删除操作不同,即使调用了shm_unlink,已打开的共享内存段仍可继续使用,直到最后一个使用它的进程关闭它。当最后一个引用被关闭时,系统才会真正回收共享内存资源。

参数name是通过shm_open创建共享内存时使用的名称。成功执行时返回0,失败返回-1。


demo

shmget

server.cpp

#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <cstring>

int main() {
    key_t key = ftok("shmfile", 'a'); // 创建唯一的键
    if (key == -1) {
        perror("ftok");
        return 1;
    }

    int shm_id = shmget(key, 1024, 0666 | IPC_CREAT); // 创建共享内存
    if (shm_id < 0) {
        perror("shmget");
        return 1;
    }

    char *shmaddr = static_cast<char*>(shmat(shm_id, nullptr, 0)); // 将共享内存附加到进程地址空间
    if (shmaddr == reinterpret_cast<char*>(-1)) {
        perror("shmat");
        return 1;
    }

    std::cout << "Server: Shared memory created and attached." << std::endl;

    // 等待客户端发送消息
    std::cout << "Waiting for client message..." << std::endl;
    std::cin.get(); // 模拟等待客户端发送消息

    // 读取共享内存中的消息
    std::cout << "Server: Message from client: " << shmaddr << std::endl;

    std::cin.get();
    
    // 清理共享内存
    shmdt(shmaddr); // 从进程地址空间分离共享内存
    shmctl(shm_id, IPC_RMID, nullptr); // 删除共享内存

    return 0;
}

使用POSIX的共享内存(Shared Memory)API来创建一个共享内存区域,并将其附加到进程的地址空间。然后,等待一个模拟的“客户端消息”(实际上是通过用户输入来模拟的),并尝试从共享内存中读取这个消息。最后,清理共享内存资源。

下面是对代码的详细解释:

  1. 创建唯一的键
  • 使用ftok函数创建一个唯一的键(key_t类型),该键用于在系统中唯一标识共享内存
  • ftok需要两个参数:一个文件路径和一个字符。如果文件不存在或由于其他原因失败,它将返回-1。
  1. 创建共享内存
  • 使用shmget函数创建共享内存
  • 它接受三个参数:键(key)、共享内存的大小(在此例中为1024字节)和权限标志(0666表示所有用户都可以读写,但通常还需要与IPC_CREAT标志组合以指示如果键不存在则创建它)。
  • 如果成功,它将返回一个非负整数(共享内存标识符),否则返回-1。
  1. 将共享内存附加到进程地址空间
  • 使用shmat函数将共享内存附加到进程的地址空间。
  • 它接受三个参数:共享内存标识符、附加地址(如果为nullptr,则由系统选择)和标志(在此例中为0)。
  • 如果成功,它将返回一个指向共享内存起始地址的指针,否则返回-1的重新解释。
  1. 模拟等待客户端发送消息
  • 这部分只是使用std::cin.get()来模拟等待客户端发送消息。
  • 在实际应用中,你会有其他方式来接收或检测来自客户端的消息。
  1. 读取共享内存中的消息

    • 这里尝试直接从shmaddr指针(指向共享内存的起始地址)打印消息。
  2. 清理共享内存

    • 使用shmdt函数从进程地址空间分离共享内存。这不会删除共享内存,只是解除了它与进程的关联
    • 使用shmctl函数删除共享内存。它接受三个参数:共享内存标识符、命令(在此例中为IPC_RMID,表示删除)和一个指向struct shmid_ds的指针(在此例中为nullptr,因为我们不需要它)。

注意:这个代码示例主要是为了演示如何使用POSIX的共享内存API,但在实际应用中,你需要考虑更多的错误处理和安全性问题,以及确保在客户端和服务器之间正确传递消息。


client.cpp

#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>

int main() {
    key_t key = ftok("shmfile", 'a'); // 使用与服务器相同的键
    if (key == -1) {
        perror("ftok");
        return 1;
    }

    int shm_id = shmget(key, 1024, 0666); // 连接到共享内存
    if (shm_id < 0) {
        perror("shmget");
        return 1;
    }

    char *shmaddr = static_cast<char*>(shmat(shm_id, nullptr, 0)); // 将共享内存附加到进程地址空间
    if (shmaddr == reinterpret_cast<char*>(-1)) {
        perror("shmat");
        return 1;
    }

    std::cout << "Client: Shared memory attached." << std::endl;

    // 向共享内存写入消息
    std::string message = "Hello from client!";
    strcpy(shmaddr, message.c_str());

    // 模拟消息发送完成
    std::cout << "Client: Message sent to server." << std::endl;
    std::cin.get();

    // 清理共享内存
    shmdt(shmaddr); // 从进程地址空间分离共享内存

    return 0;
}

这段代码是一个简单的客户端程序,用于向一个预先存在的共享内存区域写入一条消息。以下是代码的详细解释:

  1. 创建唯一的键

    • 使用 ftok 函数创建一个唯一的键,这个键与服务器用来创建共享内存区域的键相同。
    • 函数需要两个参数:一个文件路径和一个标识符字符。
  2. 连接到共享内存

    • 使用 shmget 函数连接到共享内存。
    • 这里不需要 IPC_CREAT 标志,因为我们只是连接到已经存在的共享内存区域
  3. 将共享内存附加到进程地址空间

    • 使用 shmat 函数将共享内存附加到进程的地址空间
    • 如果成功,它返回一个指向共享内存区域的指针。
  4. 写入消息到共享内存

    • 创建一个 std::string 类型的变量 message,并给它赋一个值 "Hello from client!"。
    • 使用 strcpy 函数将 message 的 C 字符串表示(通过 c_str() 方法获取)复制到共享内存区域
    • 注意,这里假设消息的长度不会超过共享内存区域的大小,并且没有同步机制来确保服务器在写入时不会同时读取或写入。
  5. 模拟消息发送完成

    • 输出一条消息到控制台,表示消息已成功发送到服务器。然后等待用户输入以继续执行(这通常用于调试或测试目的)。
  6. 清理共享内存

    • 使用 shmdt 函数从进程的地址空间分离共享内存。这不会删除共享内存区域,只是解除了进程与它的关联。

注意

  • 在实际的多进程环境中,当多个进程同时访问共享内存时,需要同步机制来确保数据的一致性和完整性。这个示例代码没有包含任何同步机制。
  • shmget 在这里只是连接到共享内存,而不是创建它。如果共享内存区域不存在,shmget 将失败。
  • strcpy 在这里使用是安全的,因为我们知道消息的长度不会超出共享内存的大小。但在实际应用中,应该检查并确保没有缓冲区溢出。
  • 在实际的应用程序中,服务器和客户端可能需要更复杂的通信协议和错误处理机制。

shmget

服务器端(Server)

服务器端需要执行以下操作:

  1. 创建唯一的键。ftok
  2. 使用该键创建共享内存。shmget
  3. 将共享内存附加到其地址空间。shmat
  4. 向共享内存中写入数据。
  5. 等待客户端或其他进程访问共享内存。
  6. 清理共享内存。shmdt shmctl
#include <iostream>  
#include <cstring>  
#include <sys/ipc.h>  
#include <sys/shm.h>  
#include <unistd.h>  
  
int main() {  
    key_t key = ftok("/tmp", 'S'); // 创建唯一的键  
    std::cout << "server key:" << key << std::endl;
    std::cout << "server key(Hex):" << std::hex << key << std::endl;

    int shm_id = shmget(key, 1024, IPC_CREAT | 0666); // 创建共享内存  
    if (shm_id == -1) {  
        perror("shmget");  
        return 1;  
    }  
    std::cout << "server shm_id:" << std::dec << shm_id << std::endl;
  
    char *shm_addr = (char*)shmat(shm_id, nullptr, 0); // 附加共享内存  
    if (shm_addr == (char*)-1) {  
        perror("shmat");  
        return 1;  
    }  
  
    std::strcpy(shm_addr, "Hello from Server!"); // 写入数据  
  
    // 模拟等待客户端读取数据  
    std::cout << "Server: Data written to shared memory. Waiting for client..." << std::endl;  
    sleep(10); // 等待一段时间让客户端连接  
  
    // 清理  
    shmdt(shm_addr); // 分离共享内存  
    shmctl(shm_id, IPC_RMID, nullptr); // 删除共享内存  
  
    return 0;  
}

客户端(Client)

客户端需要执行以下操作:

  1. 使用与服务器相同的键连接到共享内存。ftok
  2. 将共享内存附加到其地址空间。shmat
  3. 从共享内存中读取数据。
  4. 清理共享内存。shmdt
#include <iostream>  
#include <cstring>  
#include <sys/ipc.h>  
#include <sys/shm.h>  
  
int main() {  
    key_t key = ftok("/tmp", 'S'); // 使用与服务器相同的键  
    std::cout << "client key:" << key << std::endl;
    std::cout << "client key(Hex):" << std::hex << key << std::endl;

    int shm_id = shmget(key, 1024, 0666); // 连接到共享内存  
    if (shm_id == -1) {  
        perror("shmget");  
        return 1;  
    }
    std::cout << "client shm_id:" << std::dec << shm_id << std::endl;
  
    char *shm_addr = (char*)shmat(shm_id, nullptr, 0); // 附加共享内存  
    if (shm_addr == (char*)-1) {  
        perror("shmat");  
        return 1;  
    }  
  
    std::cout << "Client: Data from shared memory: " << shm_addr << std::endl; // 读取数据  
  
    // 清理(通常这不是客户端的责任)  
    shmdt(shm_addr); // 分离共享内存  
    // 注意:不要在这里删除共享内存,因为服务器可能仍然在使用它  
  
    return 0;  
}

shm_open

shm_open 提供了一种基于文件描述符的共享内存对象管理方式

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);

linux共享内存是通过tmpfs这个文件系统来实现的,tmpfs文件系的目录为/dev/shm/dev/shm是驻留在内存 RAM 当中的,因此读写速度与读写内存速度一样,/dev/shm的容量默认尺寸为系统内存大小的一半大小,使用df -h命令可以看到。但实际上它并不会真正的占用这块内存,如果/dev/shm/下没有任何文件,它占用的内存实际上就是0字节,仅在使用shm_open文件时,/dev/shm才会真正占用内存


server.cpp

#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <iostream>
#include <cstring>

#define SHM_NAME "/my_shared_memory"
#define BUF_SIZE 100

int main() {
    // 创建共享内存段,O_CREAT表示如果不存在则创建,O_RDWR为读写模式
    int shm_fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
    if (shm_fd == -1) {
        perror("shm_open");
        return 1;
    }

    // 设置共享内存大小
    if (ftruncate(shm_fd, BUF_SIZE) == -1) {
        perror("ftruncate");
        close(shm_fd);
        return 1;
    }

    // 将共享内存映射到当前进程地址空间
    void* ptr = mmap(0, BUF_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
    if (ptr == MAP_FAILED) {
        perror("mmap");
        close(shm_fd);
        return 1;
    }

    // 向共享内存写入数据
    strcpy(static_cast<char*>(ptr), "Hello from Server!");

    // 等待一段时间以便客户端可以读取数据
    sleep(10);

    // 解映射并关闭共享内存
    if (munmap(ptr, BUF_SIZE) == -1) {
        perror("munmap");
    }
    close(shm_fd);

    if (shm_unlink(SHM_NAME) == -1) {  
        perror("shm_unlink");  
        exit(EXIT_FAILURE);  
    }  
    
    return 0;
}

client.cpp

#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <iostream>
#include <cstring>

#define SHM_NAME "/my_shared_memory"
#define BUF_SIZE 100

int main() {
    // 连接到已存在的共享内存段
    int shm_fd = shm_open(SHM_NAME, O_RDONLY, 0);
    if (shm_fd == -1) {
        perror("shm_open");
        return 1;
    }

    // 将共享内存映射到当前进程地址空间
    void* ptr = mmap(0, BUF_SIZE, PROT_READ, MAP_SHARED, shm_fd, 0);
    if (ptr == MAP_FAILED) {
        perror("mmap");
        close(shm_fd);
        return 1;
    }

    // 从共享内存读取数据
    std::cout << "Message from Server: " << static_cast<char*>(ptr) << std::endl;

    // 解映射并关闭共享内存
    if (munmap(ptr, BUF_SIZE) == -1) {
        perror("munmap");
    }
    close(shm_fd);

    return 0;
}

shm_open与shmget

shm_openshmget都是在Linux系统中用于创建和管理共享内存的接口,但它们属于不同的API族,具有以下几点主要区别:

  1. 所属接口:

    • shm_open属于POSIX标准的一部分,与mmap一起使用,提供了更为通用的文件描述符接口来操作共享内存。
    • shmget则是System V IPC (Inter-Process Communication) 接口的一部分,通常与shmat(附加共享内存到进程地址空间)、shmdt(分离共享内存)和shmctl(控制共享内存)等函数配合使用。
  2. 命名和标识:

    • shm_open使用字符串名称(路径名风格)来标识共享内存对象,这允许使用fopen/open类似的命名规则,包括命名空间的隔离(通过前缀 /shm 或其他约定)。
    • shmget则使用键(通常是整数,可以通过ftok生成)来标识共享内存段,系统维护一个键到共享内存标识符的映射表。
  3. 文件系统表现:

    • 使用shm_open创建的共享内存段可以在文件系统中看到(通常是/dev/shm目录下),可以像普通文件那样被ls列出,尽管它们实际上是内存映射文件。
    • shmget创建的共享内存段并不直接出现在常规文件系统中,它们位于一个特殊的IPC文件系统中,不能直接通过文件系统访问或操作。
  4. 内存映射:

    • shm_open之后通常会紧接着调用mmap来将共享内存映射到进程的地址空间,这提供了一种更灵活的方式处理共享内存,因为mmap不仅可以用于共享内存,还可以用于映射普通文件。
    • shmget后的共享内存通过shmat映射到进程地址空间,这一过程更专注于传统的IPC共享内存操作。
  5. 安全性和权限:

    • shm_open允许设置文件权限(如在调用时指定的mode参数),这影响了哪些用户和进程可以访问共享内存。
    • shmget同样可以设置权限,但它是通过shmctl的IPC权限控制来实现,允许对共享内存段进行更细致的访问控制。

总的来说,选择shm_open还是shmget取决于具体需求,如是否需要利用POSIX接口的灵活性、是否希望共享内存表现为文件系统中的实体,以及对权限控制的具体要求等。现代应用开发更倾向于使用POSIX风格的shm_open,因为它提供了更熟悉的文件描述符接口和更好的兼容性。


参考

Linux: shm_xx系列函数使用详解 https://blog.csdn.net/weixin_45842280/article/details/136384000

linux 共享内存 shm_open ,mmap的正确使用 https://gitcode.csdn.net/65ed7de71a836825ed79b49f.html

posted @ 2024-05-13 17:59  guanyubo  阅读(584)  评论(0编辑  收藏  举报