1.IPC
IPC(Inter-Process Communication,进程间通信)主要有以下几种方式:
- 管道(Pipe):包括匿名管道和命名管道。
- 消息队列(Message Queue)。
- 共享内存(Shared Memory)。
- 信号量(Semaphore)。
- 套接字(Socket)。
- 信号(Signal)。
- 门(Door):主要用于操作系统内核与用户空间进程之间的通信。
- 文件映射(File Mapping)。
- COM/DCOM:主要用于Windows操作系统中进程间的通信。
- RMI(Remote Method Invocation):主要用于Java中的远程方法调用。
2.共享内存
1.实现方式
共享内存主要有以下几种实现方式:
- 基于传统SYSV的共享内存:这是最早期的共享内存实现方式,它使用System V的IPC(进程间通信)机制。通过shmget()创建或获取一块共享内存区域,然后通过shmat()映射到进程的地址空间,shmdt()用于解除映射,shmctl()进行控制操作如删除或修改权限等。
- 基于POSIX mmap文件映射:POSIX标准提供了mmap()函数,可以将普通文件映射到进程的虚拟内存中,从而实现共享内存。这种方式不需要专门的共享内存区段,而是利用文件系统的页面作为共享媒介。
- 通过memfd_create()和fd跨进程共享:这是一种较新的共享内存机制,通过内存文件描述符(Memory File Descriptors, memfd)来实现。memfd_create()创建一个内存对象,并通过文件描述符在进程间共享。
- 基于dma-buf的共享内存:在多媒体和图形领域,dma-buf被广泛用于高效地在用户空间和内核空间之间共享内存。这种机制通常用于高性能图形处理,因为它可以直接访问硬件缓冲区,减少了数据复制的需要。
- Linux内核的实现方式:在Linux操作系统中,共享内存是通过将不同进程的虚拟内存地址映射到相同的物理内存地址来实现的。Linux内核使用struct shmid_kernel结构体来管理共享内存区域。
- 其他操作系统的实现:除了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
函数用于打开一个已命名的文件映射对象,以便在不同的进程之间共享内存。使用方式通常涉及以下步骤:
- 创建文件映射:使用
CreateFileMapping
函数创建一个文件映射对象。这个函数需要提供文件句柄、安全属性、保护类型以及映射文件的名称等参数。 - 映射视图:通过
MapViewOfFile
函数,进程可以获得映射文件的一个视图,这样就可以访问共享的内存地址了。 - 打开文件映射:在另一个进程中,使用
OpenFileMapping
函数通过提供映射文件的名称来打开一个现有的文件映射对象。如果成功,该函数返回一个有效的对象句柄,否则返回NULL。 - 映射到进程地址空间:使用
MapViewOfFile
函数将共享内存映射到当前进程的地址空间中,从而可以通过返回的地址访问共享数据。 - 访问和修改数据:现在,进程可以通过映射的地址访问共享内存中的数据,并进行读取或写入操作。
- 关闭句柄:完成数据交换后,需要使用
UnmapViewOfFile
函数取消地址空间的映射,并使用CloseHandle
函数关闭文件映射对象的句柄。
CreateFileMapping
函数用于创建一个文件映射对象,以便在多个进程之间共享内存。使用方式通常包括以下步骤:
- 创建或打开文件:首先,需要有一个文件的句柄,这通常是通过调用
CreateFile
函数来获取的。如果是为了共享内存,而不是映射文件内容,可以使用特殊的值INVALID_HANDLE_VALUE
(即0xFFFFFFFF)作为hFile
参数。 - 设置安全属性:
lpAttributes
参数用于设置安全属性,这决定了哪些用户和组可以访问该文件映射对象。如果不需要特定的安全设置,可以传递NULL
。 - 指定保护级别:
flProtect
参数用于指定映射文件的保护级别,如读、写和执行权限。 - 指定映射名称:
lpName
参数是一个可选的命名字符串,用于给文件映射对象命名,这样其他进程可以通过这个名称来打开和访问同一个文件映射对象。 - 获取映射句柄:如果函数执行成功,
CreateFileMapping
会返回一个文件映射对象的句柄,该句柄可以用来进行后续的操作,如映射视图等。 - 使用映射对象:在创建了文件映射对象之后,可以使用
MapViewOfFile
函数来映射文件的一个区域到当前进程的地址空间中,从而可以通过内存地址直接访问文件的内容。 - 跨进程共享:为了在不同进程之间共享数据,另一个进程可以使用
OpenFileMapping
函数通过提供映射文件的名称来打开同一个文件映射对象,然后同样使用MapViewOfFile
函数来映射到它的地址空间中。 - 清理资源:当不再需要文件映射时,应该使用
UnmapViewOfFile
函数来取消映射,并使用CloseHandle
函数关闭文件和文件映射对象的句柄。
MapViewOfFile
函数用于将一个文件映射对象的全部或部分映射到调用进程的地址空间。使用方式通常包括以下步骤:
- 创建或打开文件映射对象:首先,需要有一个由
CreateFileMapping
或OpenFileMapping
函数返回的文件映射对象句柄。 - 指定映射区域的基址:
dwDesiredBase
参数允许调用者建议一个首选的虚拟地址作为映射视图的基址。如果该参数设置为NULL
(或特定于平台的值),系统会自动选择一个地址。 - 设置映射区域的大小:
dwNumberOfBytesToMap
参数指定要映射的文件大小,对于整个文件的映射,可以设置为0,表示映射文件的全部内容。 - 确定映射类型和访问权限:
dwFileOffsetHigh
和dwFileOffsetLow
参数共同指定了映射视图在文件中的起始位置(偏移量)。如果映射整个文件,这两个参数都应设置为0。dwViewOfFile
参数用于指示映射类型,如FILE_MAP_ALL_ACCESS允许对所有类型的访问。 - 获取映射地址:如果函数执行成功,
MapViewOfFile
会返回映射视图的基地址,该地址可以用来访问共享内存中的数据。 - 访问映射数据:通过返回的地址,进程可以直接访问文件的内容,就像访问内存中的数组一样。
- 取消映射:完成对共享数据的访问后,需要使用
UnmapViewOfFile
函数来取消映射,释放资源。 - 关闭文件映射对象:最后,使用
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
用于连接进程和共享内存。具体来看:
ftok
:ftok
函数用于生成一个键值(key),这个键值用于IPC(Inter-Process Communication,进程间通信)中,如共享内存和消息队列等。它的函数原型通常是int ftok(const char *pathname, int proj_id);
,其中pathname
是一个已存在的文件路径,而proj_id
是一个项目ID,通常是一个8位的数字。shmget
:shmget
函数用于创建或获取一个共享内存区域。它的函数原型是int shmget(key_t key, size_t size, int shmflg);
,其中key
是通过ftok
得到的键值,size
是要分配的共享内存的大小,shmflg
是标志位,可以包含IPC_CREAT
(如果不存在则创建)和IPC_EXCL
(如果存在则出错)等。shmat
:shmat
函数用于将共享内存区域附加到进程的地址空间中。它的函数原型是void *shmat(int shm_id, const void *shmaddr, int shmflg);
,其中shm_id
是shmget
返回的共享内存标识符,shmaddr
通常设为NULL,shmflg
可以包含SHM_RDONLY
(只读)等标志。