IPC通信--共享内存

1.IPC

IPC(Inter-Process Communication,进程间通信)主要有以下几种方式:

  1. 管道(Pipe):包括匿名管道和命名管道。
  2. 消息队列(Message Queue)。
  3. 共享内存(Shared Memory)。
  4. 信号量(Semaphore)。
  5. 套接字(Socket)。
  6. 信号(Signal)。
  7. 门(Door):主要用于操作系统内核与用户空间进程之间的通信。
  8. 文件映射(File Mapping)。
  9. COM/DCOM:主要用于Windows操作系统中进程间的通信。
  10. RMI(Remote Method Invocation):主要用于Java中的远程方法调用。

 

2.共享内存

1.实现方式

共享内存主要有以下几种实现方式:

  1. 基于传统SYSV的共享内存:这是最早期的共享内存实现方式,它使用System V的IPC(进程间通信)机制。通过shmget()创建或获取一块共享内存区域,然后通过shmat()映射到进程的地址空间,shmdt()用于解除映射,shmctl()进行控制操作如删除或修改权限等。
  2. 基于POSIX mmap文件映射:POSIX标准提供了mmap()函数,可以将普通文件映射到进程的虚拟内存中,从而实现共享内存。这种方式不需要专门的共享内存区段,而是利用文件系统的页面作为共享媒介。
  3. 通过memfd_create()和fd跨进程共享:这是一种较新的共享内存机制,通过内存文件描述符(Memory File Descriptors, memfd)来实现。memfd_create()创建一个内存对象,并通过文件描述符在进程间共享。
  4. 基于dma-buf的共享内存:在多媒体和图形领域,dma-buf被广泛用于高效地在用户空间和内核空间之间共享内存。这种机制通常用于高性能图形处理,因为它可以直接访问硬件缓冲区,减少了数据复制的需要。
  5. Linux内核的实现方式:在Linux操作系统中,共享内存是通过将不同进程的虚拟内存地址映射到相同的物理内存地址来实现的。Linux内核使用struct shmid_kernel结构体来管理共享内存区域。
  6. 其他操作系统的实现:除了Linux和UNIX系统外,其他操作系统如Windows也有自己独特的共享内存实现机制,例如使用CreateFileMapping和MapViewOfFile函数来创建和映射共享内存区域。

 2.Windows下代码示例

Process1.cpp

#include <windows.h>
#include <iostream>

int main()
{
    // 创建共享内存对象
    HANDLE hMapFile = CreateFileMapping(
        INVALID_HANDLE_VALUE,    // 使用系统分页文件
        NULL,                    // 默认安全属性
        PAGE_READWRITE,          // 可读可写
        0,                       // 高位文件大小
        256,                     // 低位文件大小
        "SharedMemory");         // 共享内存名称

    if (hMapFile == NULL)
    {
        std::cout << "Could not create file mapping object: " << GetLastError() << std::endl;
        return 1;
    }

    // 映射共享内存对象到当前进程的地址空间
    char* pBuf = (char*)MapViewOfFile(
        hMapFile,               // 共享内存对象句柄
        FILE_MAP_ALL_ACCESS,    // 可读写
        0,                      // 高位文件偏移
        0,                      // 低位文件偏移
        256);                   // 映射大小

    if (pBuf == NULL)
    {
        std::cout << "Could not map view of file: " << GetLastError() << std::endl;
        CloseHandle(hMapFile);
        return 1;
    }

    // 写入数据到共享内存
    strcpy_s(pBuf, 256, "Hello from Process1!");
	std::cout << "Has been written : " << pBuf << std::endl;
    // 等待用户输入,以便其他进程可以访问共享内存
    system("pause");

    // 清理
    UnmapViewOfFile(pBuf);
    CloseHandle(hMapFile);

    return 0;
}

Process2.cpp

 

#include <windows.h>
#include <iostream>

int main()
{
    // 打开共享内存对象
    HANDLE hMapFile = OpenFileMapping(
        FILE_MAP_ALL_ACCESS,   // 可读写
        FALSE,                 // 不继承句柄
        "SharedMemory");       // 共享内存名称

    if (hMapFile == NULL)
    {
        std::cout << "Could not open file mapping object: " << GetLastError() << std::endl;
        return 1;
    }

    // 映射共享内存对象到当前进程的地址空间
    char* pBuf = (char*)MapViewOfFile(
        hMapFile,               // 共享内存对象句柄
        FILE_MAP_ALL_ACCESS,    // 可读写
        0,                      // 高位文件偏移
        0,                      // 低位文件偏移
        256);                   // 映射大小

    if (pBuf == NULL)
    {
        std::cout << "Could not map view of file: " << GetLastError() << std::endl;
        CloseHandle(hMapFile);
        return 1;
    }

    // 读取共享内存中的数据
    std::cout << "Message from Process1: " << pBuf << std::endl;
	system("pause");
    // 清理
    UnmapViewOfFile(pBuf);
    CloseHandle(hMapFile);

    return 0;
}

编译运行

Has been written : Hello from Process1!
请按任意键继续. . .

Message from Process1: Hello from Process1!
请按任意键继续. . .

OpenFileMapping函数用于打开一个已命名的文件映射对象,以便在不同的进程之间共享内存。使用方式通常涉及以下步骤:

  1. 创建文件映射:使用CreateFileMapping函数创建一个文件映射对象。这个函数需要提供文件句柄、安全属性、保护类型以及映射文件的名称等参数。
  2. 映射视图:通过MapViewOfFile函数,进程可以获得映射文件的一个视图,这样就可以访问共享的内存地址了。
  3. 打开文件映射:在另一个进程中,使用OpenFileMapping函数通过提供映射文件的名称来打开一个现有的文件映射对象。如果成功,该函数返回一个有效的对象句柄,否则返回NULL。
  4. 映射到进程地址空间:使用MapViewOfFile函数将共享内存映射到当前进程的地址空间中,从而可以通过返回的地址访问共享数据。
  5. 访问和修改数据:现在,进程可以通过映射的地址访问共享内存中的数据,并进行读取或写入操作。
  6. 关闭句柄:完成数据交换后,需要使用UnmapViewOfFile函数取消地址空间的映射,并使用CloseHandle函数关闭文件映射对象的句柄。

CreateFileMapping函数用于创建一个文件映射对象,以便在多个进程之间共享内存。使用方式通常包括以下步骤:

  1. 创建或打开文件:首先,需要有一个文件的句柄,这通常是通过调用CreateFile函数来获取的。如果是为了共享内存,而不是映射文件内容,可以使用特殊的值INVALID_HANDLE_VALUE(即0xFFFFFFFF)作为hFile参数。
  2. 设置安全属性lpAttributes参数用于设置安全属性,这决定了哪些用户和组可以访问该文件映射对象。如果不需要特定的安全设置,可以传递NULL
  3. 指定保护级别flProtect参数用于指定映射文件的保护级别,如读、写和执行权限。
  4. 指定映射名称lpName参数是一个可选的命名字符串,用于给文件映射对象命名,这样其他进程可以通过这个名称来打开和访问同一个文件映射对象。
  5. 获取映射句柄:如果函数执行成功,CreateFileMapping会返回一个文件映射对象的句柄,该句柄可以用来进行后续的操作,如映射视图等。
  6. 使用映射对象:在创建了文件映射对象之后,可以使用MapViewOfFile函数来映射文件的一个区域到当前进程的地址空间中,从而可以通过内存地址直接访问文件的内容。
  7. 跨进程共享:为了在不同进程之间共享数据,另一个进程可以使用OpenFileMapping函数通过提供映射文件的名称来打开同一个文件映射对象,然后同样使用MapViewOfFile函数来映射到它的地址空间中。
  8. 清理资源:当不再需要文件映射时,应该使用UnmapViewOfFile函数来取消映射,并使用CloseHandle函数关闭文件和文件映射对象的句柄。

MapViewOfFile函数用于将一个文件映射对象的全部或部分映射到调用进程的地址空间。使用方式通常包括以下步骤:

  1. 创建或打开文件映射对象:首先,需要有一个由CreateFileMappingOpenFileMapping函数返回的文件映射对象句柄。
  2. 指定映射区域的基址dwDesiredBase参数允许调用者建议一个首选的虚拟地址作为映射视图的基址。如果该参数设置为NULL(或特定于平台的值),系统会自动选择一个地址。
  3. 设置映射区域的大小dwNumberOfBytesToMap参数指定要映射的文件大小,对于整个文件的映射,可以设置为0,表示映射文件的全部内容。
  4. 确定映射类型和访问权限dwFileOffsetHighdwFileOffsetLow参数共同指定了映射视图在文件中的起始位置(偏移量)。如果映射整个文件,这两个参数都应设置为0。dwViewOfFile参数用于指示映射类型,如FILE_MAP_ALL_ACCESS允许对所有类型的访问。
  5. 获取映射地址:如果函数执行成功,MapViewOfFile会返回映射视图的基地址,该地址可以用来访问共享内存中的数据。
  6. 访问映射数据:通过返回的地址,进程可以直接访问文件的内容,就像访问内存中的数组一样。
  7. 取消映射:完成对共享数据的访问后,需要使用UnmapViewOfFile函数来取消映射,释放资源。
  8. 关闭文件映射对象:最后,使用CloseHandle函数关闭文件映射对象的句柄。

3. Linux下代码示例

 进程A(写入数据)

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

int main() {
    // 创建共享内存对象
    key_t key = ftok("shmfile", 65);
    int shmid = shmget(key, 1024, 0666 | IPC_CREAT);

    // 将共享内存对象连接到已存在的共享内存段
    char *str = (char *)shmat(shmid, (void *)0, 0);

    // 向共享内存中写入数据
    std::string data = "Hello from Process A!";
    std::copy(data.begin(), data.end(), str);

    std::cout << "Data written to shared memory: " << data << std::endl;

    sleep(5); // 等待一段时间,以便其他进程可以读取数据

    // 从共享内存中读取数据
    std::string received_data(str);
    std::cout << "Received data from shared memory: " << received_data << std::endl;

    // 分离共享内存对象
    shmdt(str);

    return 0;
}

 进程B(读取数据)

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

int main() {
    // 创建共享内存对象
    key_t key = ftok("shmfile", 65);
    int shmid = shmget(key, 1024, 0666 | IPC_CREAT);

    // 将共享内存对象连接到已存在的共享内存段
    char *str = (char *)shmat(shmid, (void *)0, 0);

    sleep(2); // 等待一段时间,以便其他进程可以写入数据

    // 从共享内存中读取数据
    std::string received_data(str);
    std::cout << "Received data from shared memory: " << received_data << std::endl;

    // 向共享内存中写入数据
    std::string data = "Hello from Process B!";
    std::copy(data.begin(), data.end(), str);

    std::cout << "Data written to shared memory: " << data << std::endl;

    // 分离共享内存对象
    shmdt(str);

    return 0;
}

 

在进程A中,首先创建一个共享内存对象并将其连接到已存在的共享内存段。然后,将数据写入共享内存,并等待一段时间以便其他进程可以读取数据。接着,从共享内存中读取数据并打印出来。最后,分离共享内存对象。

在进程B中,同样创建一个共享内存对象并将其连接到已存在的共享内存段。然后,等待一段时间以便其他进程可以写入数据。接着,从共享内存中读取数据并打印出来。然后,将数据写入共享内存。最后,分离共享内存对象。

ftok用于获取key,shmget用于获取共享内存,而shmat用于连接进程和共享内存。具体来看:

  1. ftokftok函数用于生成一个键值(key),这个键值用于IPC(Inter-Process Communication,进程间通信)中,如共享内存和消息队列等。它的函数原型通常是int ftok(const char *pathname, int proj_id);,其中pathname是一个已存在的文件路径,而proj_id是一个项目ID,通常是一个8位的数字。
  2. shmgetshmget函数用于创建或获取一个共享内存区域。它的函数原型是int shmget(key_t key, size_t size, int shmflg);,其中key是通过ftok得到的键值,size是要分配的共享内存的大小,shmflg是标志位,可以包含IPC_CREAT(如果不存在则创建)和IPC_EXCL(如果存在则出错)等。
  3. shmatshmat函数用于将共享内存区域附加到进程的地址空间中。它的函数原型是void *shmat(int shm_id, const void *shmaddr, int shmflg);,其中shm_idshmget返回的共享内存标识符,shmaddr通常设为NULL,shmflg可以包含SHM_RDONLY(只读)等标志。
03-19
<think>好的,用户想了解进程间通信IPC)的实现方法及示例。我需要先回顾一下常见的IPC方式,然后结合引用内容来组织回答。首先,根据用户提供的引用,有提到管道、共享内存、信号、网络套接字等。我需要确保每个方法的说明准确,并附上示例代码或应用场景。 首先,管道分为匿名管道和命名管道。匿名管道用于父子进程间通信,引用4提到pipe和fork的使用。命名管道则是FIFO,允许无亲缘关系的进程通信。这部分需要解释清楚,并给出示例代码结构。 接下来是共享内存,引用5提到QSharedMemory类,但用户可能需要更通用的实现,比如POSIX或System V的共享内存。这里需要说明共享内存的原理,以及如何在代码中创建和附加共享内存段。 然后是信号,引用2提到信号作为IPC的一种方式,适用于事件处理。需要说明如何注册信号处理函数,以及进程间如何发送信号,比如使用kill命令或系统调用。 网络套接字在引用3中提及,分为TCP和UDP。这部分需要简要说明套接字的使用步骤,并可能给出简单的服务器-客户端代码示例。 此外,消息队列和信号量也是常见的IPC方式,但用户提供的引用中没有直接提到,可能需要简要补充,但主要依据引用内容来回答。 需要注意引用中的内容是否正确对应每个IPC方法。例如,引用1提到Android中的IPC方式,但用户的问题可能更广泛,所以需要以通用方法为主,适当提到Android的情况。 在组织回答时,需要分点列出每种方法,说明原理、适用场景,并附上示例代码或代码结构。同时,确保使用正确的LaTeX格式,如独立公式用$$...$$,行内公式用$...$。 最后,生成相关问题时要基于提到的IPC方法,比如性能比较、适用场景、优缺点等。需要确保问题与内容相关,并能引导用户进一步学习。 现在需要检查是否有遗漏的IPC方法,比如消息队列或信号量,但由于引用中没有详细提及,可能只简要提到。或者根据用户提供的引用,可能不需要深入这些内容。需要保持回答基于提供的引用,但必要时补充常识内容。 确认每个部分的引用标注正确,例如管道的部分引用4,共享内存引用5,信号引用2,网络套接字引用3,Android相关引用1。综合这些信息,组织成结构清晰的回答,并附上相关问题。</think>### 进程间通信IPC)实现方法及示例 进程间通信IPC)是操作系统协调多进程协同工作的核心机制。以下是常见的IPC实现方式及示例: --- #### 1. **管道(Pipe)** - **匿名管道** 适用于父子进程间通信。通过`pipe`系统调用创建,结合`fork`实现单向数据流。 **示例代码**: ```c int fd[2]; pipe(fd); // 创建管道 if (fork() == 0) { // 子进程 read(fd[0], buffer, size); // 从管道读取 } else { // 父进程 write(fd[1], data, size); // 向管道写入 } ``` 引用自[^4]。 - **命名管道(FIFO)** 允许无亲缘关系进程通过文件系统路径访问。 **示例**: ```bash mkfifo /tmp/myfifo # 创建命名管道 echo "data" > /tmp/myfifo & # 进程A写入 cat < /tmp/myfifo # 进程B读取 ``` --- #### 2. **共享内存(Shared Memory)** 通过共享物理内存区域实现高效数据交换,适用于需要高频通信的场景。 **实现步骤**: 1. 创建共享内存段 2. 进程将内存段映射到自身地址空间 3. 读写共享数据 **示例(Qt框架)**: ```cpp QSharedMemory shared("key"); shared.create(1024); // 创建1KB共享内存 shared.attach(); // 附加到共享内存 memcpy(shared.data(), data, size); // 写入数据 ``` 引用自[^5]。 --- #### 3. **信号(Signal)** 用于异步通知特定事件(如进程终止)。 **示例**: ```c // 注册信号处理函数 signal(SIGINT, handler); // 发送信号给进程 kill(pid, SIGUSR1); ``` 需注意信号处理函数的可重入性[^2]。 --- #### 4. **网络套接字(Socket)** 支持跨机器进程通信,分为TCP(可靠流式)和UDP(无连接数据报)。 **TCP示例(Python)**: ```python # 服务端 import socket s = socket.socket() s.bind(('localhost', 8080)) s.listen() conn, addr = s.accept() conn.send(b"Hello from server") # 客户端 s = socket.socket() s.connect(('localhost', 8080)) print(s.recv(1024)) ``` 引用自[^3]。 --- #### 5. **Android中的Binder** 专为Android设计的高效IPC机制,基于内核驱动和代理模式。 **示例**:定义AIDL接口文件并生成跨进程通信代码[^1]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值