目录
1.简介
2.使用
3.实现原理
3.1.Windows内存映射
3.2.POSIX 共享内存
3.3.System V 共享内存
4.总结
1.简介
QSharedMemory
是 Qt 框架提供的一个类,用于在不同进程或线程之间实现共享内存的管理。借助共享内存,不同进程或线程能够访问同一块物理内存区域,从而实现高效的数据共享和通信。
QSharedMemory允许多个线程和进程访问共享内存段。它还为单个线程或进程提供了一种锁定内存以进行独占访问的方法。
使用此类时,请注意以下平台差异:
1) Windows: QSharedMemory不“拥有”共享内存段。当所有将QSharedMemory实例附加到特定共享内存段的线程或进程都已销毁其QSharedMomory实例或退出时,Windows内核会自动释放共享内存段。
2) Unix:QSharedMemory“拥有”共享内存段。当最后一个将QSharedMemory实例附加到特定共享内存段的线程或进程通过销毁其QSharedMomory实例而与该段分离时,Unix内核会释放该共享内存段。但是,如果最后一个线程或进程在没有运行QSharedMemory析构函数的情况下崩溃,共享内存段将在崩溃后幸存下来。
3) HP-UX:每个进程只允许连接一个共享内存段。这意味着在HP-UX中,不应在同一进程中的多个线程之间使用QSharedMemory。
在读取或写入共享内存之前,记得用lock()锁定共享内存,并在完成后用unlock()释放锁。
当QSharedMemory的最后一个实例从共享内存段分离时,QSharedMomory会自动销毁该共享内存段,并且不再保留对该段的引用。
警告:除非另有说明,否则QSharedMemory会以Qt特定的方式更改密钥。与非Qt应用程序的互操作是通过首先使用QSharedMemory()创建默认共享内存,然后使用setNativeKey()设置本机密钥来实现的。使用本机密钥时,共享内存不会受到多次访问的保护(例如,无法锁定()),应使用用户定义的机制来实现这种保护。
2.使用
QSharedMemory类的相关函数如下:
void QSharedMemory::setKey(const QString &key)
//指定身份字ID
bool QSharedMemory::create(int size, AccessMode mode=ReadWrite)
/*创建一个大小为size个字节的共享内存段, 然后用给定的访问模式mode附着到共享内存上。Mode取值有以下几种:
QSharedMemory::ReadOnly : 共享内存段是只读的。不允许写入共享内存段。尝试写入使用ReadOnly创建的共享内存段会导致程序中止。
QSharedMemory::ReadWrite : 允许对共享内存段进行读写操作。*/
bool QSharedMemory::attach(AccessMode mode = ReadWrite)
//尝试将共享内存绑定到进程上,如果绑定操作成功,则返回true。如果失败返回false,可调用error()或者errorString()来确定发生了哪个错误。在附加共享内存段成功之后,则可以通过调用data()来获得一个指向共享内存的指针。
bool QSharedMemory::isAttached() const
//判断共享内存是否绑定进程成功。
bool QSharedMemory::detach()
//将进程和共享内存段解绑。如果这是连接到共享内存段的最后一个进程,那么共享内存段将被系统释放,也就是说,内容将被销毁。如果解绑共享内存段完成,则返回true。如果失败返回false,并意味着该段没有连接,或者被另一个进程锁定。
bool QSharedMemory::lock()
//用来锁定共享内存的互斥值,锁住成功则返回true. 若另一个进程已经锁住了共享内存段,本函数将会阻塞直到锁被另一个进程释放。到那时,本函数才会获得锁并返回true. 如果本函数返回false,那就说明你已经忽略了一个由create()或attach()返回的false,而其原因可能是由于某个系统错误而导致setNativeKey()或QSystemSemaphore::acquire()失败。
bool QSharedMemory::unlock()
//释放共享内存段上的锁并返回true,如果共享内存段没有被lock,或者如果锁被其他进程持有,本函数什么都不会做而只是返回false.
void * QSharedMemory::data()
//如果附加了共享内存段,则返回指向共享内存段内容的指针。否则返回null。在对共享内存进行读写操作之前,记得使用lock()锁定共享内存,并且记得在操作完成后使用unlock()释放锁。
const void *QSharedMemory::constData() const
//与data一样。区别是该数据是只读的。
使用示例一:防止软件重复打开
QSharedMemory是Qt提供的一个共享内存的类。它可以用来进程之间传递数据。本文以使用该类为例,实现一个限制同一程序启动实例个数的功能。后面将会给出代码,代码是限制程序只能启动一个实例,当然也可以很方便的改成只允许启动两个实例等等。本文所附代码在VS2019和Qt5.12.12上测试通过。下面是在已运行一个实例的情况下又启动一个实例的截图:
代码如下:
#include "qsharedmemory.h"
#include "qmessagebox.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
bool shallExit = false;
QSharedMemory memory(u8"this-is-a-test");
if (!memory.attach(QSharedMemory::ReadWrite))
{
memory.create(4);
memory.lock();
*(int*)memory.data() = 1;
memory.unlock();
}
else
{
memory.lock();
int* data = (int*)memory.data();
if (*data >= 1)
{
QMessageBox::information(0, u8"提示", u8"已有1个实例在运行,即将退出程序。");
shallExit = true;
}
else
{
*data += 1;
}
memory.unlock();
memory.detach();
}
if (!shallExit)
{
QtTest w;
w.show();
return a.exec();
}
return 0;
}
上方代码中QtTest是主窗口类。此段代码功能是在程序启动时检测共享内存存不存在,如果不存在则新建一个共享内存,如果存在则读取它的内容查看当前有多少个实例正在运行,若达到所设限制则弹窗提示然后退出程序,否则显示主窗口。
注意上面的代码是在main(...)函数中添加限制实例个数的功能的,而不是在主窗口的文件中修改。
使用示例二:进程间通信
两个进程同时挂载一片共享内存,进程A写、进程B读,就达到了通信效果,这种方案的好处是,读写都是基于内存操作,效率非常之高。
进程A写共享内存:
void MyDialog::loadFromFile()
{
if (sharedMemory.isAttached())
{
// 将该进程与共享内存段分离
if (!sharedMemory.detach())
qDebug() << "Unable to detach from shared memory.";
}
QString fileName = QFileDialog::getOpenFileName(0, QString(), QString(),
tr("Images (*.png *.xpm *.jpg)"));
QImage image;
if (!image.load(fileName))
{
qDebug() << "Selected file is not an image, please select another.";
return;
}
// 将数据加载到共享内存中
QBuffer buffer;
buffer.open(QBuffer::ReadWrite);
QDataStream out(&buffer);
out << image;
int size = buffer.size();
// 创建共享内存段
if (!sharedMemory.create(size))
{
qDebug() << sharedMemory.errorString() << "\n Unable to create shared memory segment.";
return;
}
sharedMemory.lock();
char *to = (char*)sharedMemory.data();
const char *from = buffer.data().data();
memcpy(to, from, qMin(sharedMemory.size(), size));
sharedMemory.unlock();
}
进程B读共享内存:
void MainWindow::loadFromMemory()
{
// 将共享内存与该进程绑定
if (!sharedMemory.attach())
{
qDebug() << "Unable to attach to shared memory segment.";
return;
}
// 从共享内存中读取数据
QBuffer buffer;
QDataStream in(&buffer);
QImage image;
sharedMemory.lock();
buffer.setData((char*)sharedMemory.constData(), sharedMemory.size());
buffer.open(QBuffer::ReadOnly);
in >> image;
sharedMemory.unlock();
sharedMemory.detach();
m_pLabel->setPixmap(QPixmap::fromImage(image));
}
3.实现原理
3.1.Windows内存映射
在 Windows 系统中,内存映射(Memory Mapping)是一种强大的技术,它允许程序将文件或者物理内存页映射到进程的虚拟地址空间中,从而让程序可以像访问内存一样直接访问文件或者共享内存区域,提高数据的读写效率。下面从内存映射文件和共享内存两个方面详细介绍 Windows 内存映射。
内存映射文件
内存映射文件是由一个文件到一块内存的映射。Win32提供了允许应用程序把文件映射到一个进程的函数(CreateFileMapping)。通过内存映射文件,可以将物理存储器提交给一个地址空间的区域,这些物理存储器来自一个已经存在于磁盘上的文件。在对该文件进行操作之前必须首先对文件进行映射。
内存映射文件允许程序将磁盘上的文件映射到进程的虚拟地址空间,这样程序就可以通过指针直接访问文件内容,而不需要使用传统的文件 I/O 函数(如 ReadFile
和 WriteFile
)。
示例代码如下:
#include <iostream>
#include <windows.h>
int main() {
// 打开文件
HANDLE hFile = CreateFile(
L"test.txt", // 文件名
GENERIC_READ | GENERIC_WRITE, // 读写权限
0, // 不共享
NULL, // 默认安全属性
OPEN_EXISTING, // 打开已存在的文件
FILE_ATTRIBUTE_NORMAL, // 普通文件属性
NULL // 不使用模板文件
);
if (hFile == INVALID_HANDLE_VALUE) {
std::cerr << "Failed to open file: " << GetLastError() << std::endl;
return 1;
}
// 创建文件映射对象
HANDLE hMapFile = CreateFileMapping(
hFile, // 文件句柄
NULL, // 默认安全属性
PAGE_READWRITE, // 读写权限
0, // 高32位大小
0, // 低32位大小,使用文件实际大小
NULL // 未命名的文件映射对象
);
if (hMapFile == NULL) {
std::cerr << "Failed to create file mapping: " << GetLastError() << std::endl;
CloseHandle(hFile);
return 1;
}
// 映射视图
LPVOID pBuf = MapViewOfFile(
hMapFile, // 文件映射对象句柄
FILE_MAP_ALL_ACCESS, // 读写访问
0, // 高32位偏移
0, // 低32位偏移
0 // 映射整个文件
);
if (pBuf == NULL) {
std::cerr << "Failed to map view of file: " << GetLastError() << std::endl;
CloseHandle(hMapFile);
CloseHandle(hFile);
return 1;
}
// 访问数据
char* data = static_cast<char*>(pBuf);
std::cout << "File content: " << data << std::endl;
// 修改数据
strcpy_s(data, strlen("Hello, Memory Mapping!") + 1, "Hello, Memory Mapping!");
// 解除映射
if (!UnmapViewOfFile(pBuf)) {
std::cerr << "Failed to unmap view of file: " << GetLastError() << std::endl;
}
// 关闭句柄
CloseHandle(hMapFile);
CloseHandle(hFile);
return 0;
}
共享内存
共享内存是内存映射的一种特殊应用,它允许不同的进程访问同一块物理内存区域,从而实现进程间的数据共享和通信。
示例代码如下:
// 写入进程
#include <iostream>
#include <windows.h>
int main() {
// 创建共享内存对象
HANDLE hMapFile = CreateFileMapping(
INVALID_HANDLE_VALUE, // 不使用文件
NULL, // 默认安全属性
PAGE_READWRITE, // 读写权限
0, // 高32位大小
1024, // 低32位大小
L"MySharedMemory" // 共享内存名称
);
if (hMapFile == NULL) {
std::cerr << "Failed to create file mapping: " << GetLastError() << std::endl;
return 1;
}
// 映射视图
LPVOID pBuf = MapViewOfFile(
hMapFile, // 文件映射对象句柄
FILE_MAP_ALL_ACCESS, // 读写访问
0, // 高32位偏移
0, // 低32位偏移
1024 // 映射大小
);
if (pBuf == NULL) {
std::cerr << "Failed to map view of file: " << GetLastError() << std::endl;
CloseHandle(hMapFile);
return 1;
}
// 写入数据
char* data = static_cast<char*>(pBuf);
strcpy_s(data, strlen("Hello, Shared Memory!") + 1, "Hello, Shared Memory!");
// 解除映射
if (!UnmapViewOfFile(pBuf)) {
std::cerr << "Failed to unmap view of file: " << GetLastError() << std::endl;
}
// 关闭句柄
CloseHandle(hMapFile);
return 0;
}
// 读取进程
#include <iostream>
#include <windows.h>
int main() {
// 打开共享内存对象
HANDLE hMapFile = OpenFileMapping(
FILE_MAP_ALL_ACCESS, // 读写访问
FALSE, // 不继承句柄
L"MySharedMemory" // 共享内存名称
);
if (hMapFile == NULL) {
std::cerr << "Failed to open file mapping: " << GetLastError() << std::endl;
return 1;
}
// 映射视图
LPVOID pBuf = MapViewOfFile(
hMapFile, // 文件映射对象句柄
FILE_MAP_ALL_ACCESS, // 读写访问
0, // 高32位偏移
0, // 低32位偏移
1024 // 映射大小
);
if (pBuf == NULL) {
std::cerr << "Failed to map view of file: " << GetLastError() << std::endl;
CloseHandle(hMapFile);
return 1;
}
// 读取数据
char* data = static_cast<char*>(pBuf);
std::cout << "Read from shared memory: " << data << std::endl;
// 解除映射
if (!UnmapViewOfFile(pBuf)) {
std::cerr << "Failed to unmap view of file: " << GetLastError() << std::endl;
}
// 关闭句柄
CloseHandle(hMapFile);
return 0;
}
通过使用 Windows 内存映射技术,可以提高程序的性能和数据处理效率,特别是在处理大文件和进行进程间通信时。
3.2.POSIX 共享内存
POSIX 共享内存是一种在 Linux 系统上使用的共享内存机制,它允许多个进程可以访问同一个内存区域,从而实现进程间的数据共享。共享内存是可用IPC机制中最快的,使用共享内存不必频繁拷贝数据。但也需要注意,由于共享内存段中的数据可以被多个进程同时访问,因此需要在程序设计中考虑好数据同步和互斥机制,以避免出现数据竞争和不一致的情况。
POSIX 共享内存将共享内存对象视为特殊的文件,通过文件系统来管理。进程可以使用特定的系统调用创建、打开、映射和删除这些共享内存对象。与其他共享内存机制相比,POSIX 共享内存具有接口简洁、可移植性好等优点,广泛应用于支持 POSIX 标准的操作系统中。
共享内存使用的基本步骤:
- 通过 shm_open() 函数创建了共享内存区域,此时会在 /dev/shm/ 创建共享内存文件。
- 通过 ftruncate() 函数改变共享内存的大小,一般设置为页大小 sysconf(_SC_PAGE_SIZE) 的整数倍。
- 通过 mmap() 函数将创建的共享内存文件映射到内存。
- 通过 munmap() 卸载共享内存。
- 通过 shm_unlink() 删除内存共享文件。
关键函数说明:内存映射-mmap()函数
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_NONE:内存区域不可访问。
- PROT_WRITE:内存区域可以被写入。
- PROT_READ:内存区域可以被读取。
- PROT_EXEC:内存区域可以被执行。
- flags:指定映射对象的类型。下面列出一些常用项:
- MAP_SHARED:与文件进行共享映射,可以实现多个进程之间共享数据的操作。对映射区域的修改会反映到文件中,同样文件的修改也会反映到映射区域中。
- MAP_PRIVATE:创建一个私有的映射副本,进程之间不共享数据。对映射区域的修改不会反映到文件中,也不会影响其他映射该文件的进程。
- MAP_LOCKED:映射区域会被锁定在物理内存中,防止页面被交换出去,保证内存访问速度,但可能会导致内存资源消耗较大。
- fd:已打开文件的文件描述符,用于与内存映射区域关联。
- offset:文件中的偏移量,指定文件的起始映射位置。
返回值
- 如果函数执行成功,返回一个指向映射区域的指针。
- 如果发生错误,返回 MAP_FAILED(-1) 。可以通过 errno 变量来获取具体的错误信息。
映射方式如下:
下面是一个简单的示例,展示了如何使用 POSIX 共享内存实现两个进程之间的数据共享。
写入进程代码
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#define SHM_NAME "/my_shared_memory"
#define SHM_SIZE 1024
int main() {
int fd;
char *shared_memory;
// 创建或打开共享内存对象
fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
if (fd == -1) {
perror("shm_open");
return 1;
}
// 设置共享内存对象的大小
if (ftruncate(fd, SHM_SIZE) == -1) {
perror("ftruncate");
close(fd);
shm_unlink(SHM_NAME);
return 1;
}
// 将共享内存对象映射到进程的地址空间
shared_memory = mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (shared_memory == MAP_FAILED) {
perror("mmap");
close(fd);
shm_unlink(SHM_NAME);
return 1;
}
// 向共享内存写入数据
const char *message = "Hello, POSIX shared memory!";
strncpy(shared_memory, message, strlen(message));
// 解除内存映射
if (munmap(shared_memory, SHM_SIZE) == -1) {
perror("munmap");
}
// 关闭文件描述符
close(fd);
return 0;
}
读取进程代码
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#define SHM_NAME "/my_shared_memory"
#define SHM_SIZE 1024
int main() {
int fd;
char *shared_memory;
// 打开共享内存对象
fd = shm_open(SHM_NAME, O_RDONLY, 0666);
if (fd == -1) {
perror("shm_open");
return 1;
}
// 将共享内存对象映射到进程的地址空间
shared_memory = mmap(NULL, SHM_SIZE, PROT_READ, MAP_SHARED, fd, 0);
if (shared_memory == MAP_FAILED) {
perror("mmap");
close(fd);
return 1;
}
// 从共享内存读取数据并打印
printf("Read from shared memory: %s\n", shared_memory);
// 解除内存映射
if (munmap(shared_memory, SHM_SIZE) == -1) {
perror("munmap");
}
// 关闭文件描述符
close(fd);
// 删除共享内存对象
if (shm_unlink(SHM_NAME) == -1) {
perror("shm_unlink");
}
return 0;
}
3.3.System V 共享内存
它是早期 Unix 系统引入的 IPC(进程间通信)机制,在 Linux 中被继承。该机制由内核管理共享内存段,进程通过特定的系统调用获取并连接到共享内存段,之后就能直接访问。
System V 共享内存是 Linux 系统中一种用于进程间通信(IPC)的机制,它允许不同的进程访问同一块物理内存区域,从而实现高效的数据共享。与其他 IPC 机制(如管道、消息队列)相比,共享内存避免了数据在用户空间和内核空间之间的多次拷贝,因此在数据传输效率上有显著优势。
相关系统调用:
ftok:
用于生成一个唯一的键(key_t
类型),该键将作为后续共享内存操作的标识符。
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
pathname
:一个已存在的文件路径。proj_id
:一个非零的整数,用于生成键。
shmget
用于创建或获取一个共享内存段。
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
key
:由ftok
生成的键,或使用特殊值IPC_PRIVATE
表示创建一个私有的共享内存段。size
:指定共享内存段的大小。shmflg
:标志位,常用的标志有IPC_CREAT
(如果共享内存段不存在则创建)、IPC_EXCL
(与IPC_CREAT
一起使用,若共享内存段已存在则返回错误)以及权限标志(如0666
表示所有用户都有读写权限)。
shmat
将共享内存段附加到调用进程的地址空间,返回一个指向该共享内存段的指针。
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
shmid
:shmget
返回的共享内存段标识符。shmaddr
:指定共享内存段附加的地址,通常设置为NULL
,让系统自动选择合适的地址。shmflg
:标志位,常用的标志有SHM_RDONLY
(以只读模式附加)。
shmdt
将共享内存段从调用进程的地址空间分离。
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
shmaddr
:shmat
返回的指向共享内存段的指针。
shmctl
用于控制共享内存段,如删除共享内存段。
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
shmid
:共享内存段标识符。cmd
:控制命令,常用的命令是IPC_RMID
(删除共享内存段)。buf
:用于存储共享内存段信息的结构体指针,当cmd
为IPC_RMID
时,可设置为NULL
。
一般的编程步骤如下:
1. 创建共享内存:
通过函数shmget()创建共享内存
2. 映射共享内存:
通过函数shmget()把创建的共享内存映射到进程
3. 使用共享内存:
4. 撤销共享内存映射
函数shmdt()
5. 删除共享内存映射:
函数shmctl()
下面是一个简单的示例,展示了如何使用 System V共享内存实现两个进程之间的数据共享。
写入进程代码
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#define SHM_SIZE 1024
int main() {
key_t key;
int shmid;
char *shmaddr;
// 生成唯一的键
key = ftok(".", 'a');
if (key == -1) {
perror("ftok");
return 1;
}
// 创建共享内存段
shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0666);
if (shmid == -1) {
perror("shmget");
return 1;
}
// 将共享内存段附加到进程的地址空间
shmaddr = shmat(shmid, NULL, 0);
if (shmaddr == (char *)-1) {
perror("shmat");
return 1;
}
// 向共享内存写入数据
const char *message = "Hello, System V shared memory!";
strcpy(shmaddr, message);
// 将共享内存段从进程的地址空间分离
if (shmdt(shmaddr) == -1) {
perror("shmdt");
return 1;
}
return 0;
}
读取进程代码
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#define SHM_SIZE 1024
int main() {
key_t key;
int shmid;
char *shmaddr;
// 生成唯一的键
key = ftok(".", 'a');
if (key == -1) {
perror("ftok");
return 1;
}
// 获取共享内存段
shmid = shmget(key, SHM_SIZE, 0666);
if (shmid == -1) {
perror("shmget");
return 1;
}
// 将共享内存段附加到进程的地址空间
shmaddr = shmat(shmid, NULL, 0);
if (shmaddr == (char *)-1) {
perror("shmat");
return 1;
}
// 从共享内存读取数据并打印
printf("Read from shared memory: %s\n", shmaddr);
// 将共享内存段从进程的地址空间分离
if (shmdt(shmaddr) == -1) {
perror("shmdt");
return 1;
}
// 删除共享内存段
if (shmctl(shmid, IPC_RMID, NULL) == -1) {
perror("shmctl");
return 1;
}
return 0;
}
3.4.QSharedMemory的实现原理
QSharedMemory
是一个跨平台的类,它对不同操作系统的底层共享内存机制进行了封装,使得开发者可以在不同的操作系统上使用统一的接口来操作共享内存。在不同的操作系统中,QSharedMemory
会调用相应的系统 API 来实现共享内存的创建、访问和管理。
不同操作系统的实现方式:
- Windows:
QSharedMemory
基于 Windows 的内存映射文件机制实现。通过调用CreateFileMapping
、MapViewOfFile
等 Windows API 来创建和访问共享内存区域。 - Linux:
QSharedMemory
可以基于 System V 共享内存或者 POSIX 共享内存实现。在 Linux 系统中,会根据具体情况调用shmget
、shmat
等 System V 共享内存相关的系统调用,或者shm_open
、mmap
等 POSIX 共享内存相关的系统调用。
以Qt5.12.12实现源码为例:
QSharedMemory的实现代码在目录.\Qt\Qt5.12.12\5.12.12\Src\qtbase\src\corelib\kernel下,
梳理了一下这些类之间的关系如下图所示:
QSharedMemoryPrivate类的确是根据不同的环境关联不同的实现,Windows版本是在3.1章节的基础上封装起来的:
bool QSharedMemoryPrivate::create(int size)
{
const QLatin1String function("QSharedMemory::create");
if (nativeKey.isEmpty()) {
error = QSharedMemory::KeyError;
errorString = QSharedMemory::tr("%1: key error").arg(function);
return false;
}
// Create the file mapping.
#if defined(Q_OS_WINRT)
hand = CreateFileMappingFromApp(INVALID_HANDLE_VALUE, 0, PAGE_READWRITE, size,
reinterpret_cast<PCWSTR>(nativeKey.utf16()));
#else
hand = CreateFileMapping(INVALID_HANDLE_VALUE, 0, PAGE_READWRITE, 0, size,
reinterpret_cast<const wchar_t*>(nativeKey.utf16()));
#endif
setErrorString(function);
// hand is valid when it already exists unlike unix so explicitly check
return error != QSharedMemory::AlreadyExists && hand;
}
bool QSharedMemoryPrivate::attach(QSharedMemory::AccessMode mode)
{
// Grab a pointer to the memory block
int permissions = (mode == QSharedMemory::ReadOnly ? FILE_MAP_READ : FILE_MAP_ALL_ACCESS);
#if defined(Q_OS_WINRT)
memory = (void *)MapViewOfFileFromApp(handle(), permissions, 0, 0);
#else
memory = (void *)MapViewOfFile(handle(), permissions, 0, 0, 0);
#endif
if (0 == memory) {
setErrorString(QLatin1String("QSharedMemory::attach"));
cleanHandle();
return false;
}
// Grab the size of the memory we have been given (a multiple of 4K on windows)
MEMORY_BASIC_INFORMATION info;
if (!VirtualQuery(memory, &info, sizeof(info))) {
// Windows doesn't set an error code on this one,
// it should only be a kernel memory error.
error = QSharedMemory::UnknownError;
errorString = QSharedMemory::tr("%1: size query failed").arg(QLatin1String("QSharedMemory::attach: "));
return false;
}
size = info.RegionSize;
return true;
}
bool QSharedMemoryPrivate::detach()
{
// umap memory
if (!UnmapViewOfFile(memory)) {
setErrorString(QLatin1String("QSharedMemory::detach"));
return false;
}
memory = 0;
size = 0;
// close handle
return cleanHandle();
}
Posix版本是3.2章节的基础上封装起来的:
bool QSharedMemoryPrivate::create(int size)
{
if (!handle())
return false;
const QByteArray shmName = QFile::encodeName(makePlatformSafeKey(key));
int fd;
#ifdef O_CLOEXEC
// First try with O_CLOEXEC flag, if that fails, fall back to normal flags
EINTR_LOOP(fd, ::shm_open(shmName.constData(), O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, 0600));
if (fd == -1)
EINTR_LOOP(fd, ::shm_open(shmName.constData(), O_RDWR | O_CREAT | O_EXCL, 0600));
#else
EINTR_LOOP(fd, ::shm_open(shmName.constData(), O_RDWR | O_CREAT | O_EXCL, 0600));
#endif
if (fd == -1) {
const int errorNumber = errno;
const QLatin1String function("QSharedMemory::attach (shm_open)");
switch (errorNumber) {
case ENAMETOOLONG:
case EINVAL:
errorString = QSharedMemory::tr("%1: bad name").arg(function);
error = QSharedMemory::KeyError;
break;
default:
setErrorString(function);
}
return false;
}
// the size may only be set once
int ret;
EINTR_LOOP(ret, QT_FTRUNCATE(fd, size));
if (ret == -1) {
setErrorString(QLatin1String("QSharedMemory::create (ftruncate)"));
qt_safe_close(fd);
return false;
}
qt_safe_close(fd);
return true;
}
bool QSharedMemoryPrivate::attach(QSharedMemory::AccessMode mode)
{
const QByteArray shmName = QFile::encodeName(makePlatformSafeKey(key));
const int oflag = (mode == QSharedMemory::ReadOnly ? O_RDONLY : O_RDWR);
const mode_t omode = (mode == QSharedMemory::ReadOnly ? 0400 : 0600);
#ifdef O_CLOEXEC
// First try with O_CLOEXEC flag, if that fails, fall back to normal flags
EINTR_LOOP(hand, ::shm_open(shmName.constData(), oflag | O_CLOEXEC, omode));
if (hand == -1)
EINTR_LOOP(hand, ::shm_open(shmName.constData(), oflag, omode));
#else
EINTR_LOOP(hand, ::shm_open(shmName.constData(), oflag, omode));
#endif
if (hand == -1) {
const int errorNumber = errno;
const QLatin1String function("QSharedMemory::attach (shm_open)");
switch (errorNumber) {
case ENAMETOOLONG:
case EINVAL:
errorString = QSharedMemory::tr("%1: bad name").arg(function);
error = QSharedMemory::KeyError;
break;
default:
setErrorString(function);
}
hand = -1;
return false;
}
// grab the size
QT_STATBUF st;
if (QT_FSTAT(hand, &st) == -1) {
setErrorString(QLatin1String("QSharedMemory::attach (fstat)"));
cleanHandle();
return false;
}
size = st.st_size;
// grab the memory
const int mprot = (mode == QSharedMemory::ReadOnly ? PROT_READ : PROT_READ | PROT_WRITE);
memory = QT_MMAP(0, size, mprot, MAP_SHARED, hand, 0);
if (memory == MAP_FAILED || !memory) {
setErrorString(QLatin1String("QSharedMemory::attach (mmap)"));
cleanHandle();
memory = 0;
size = 0;
return false;
}
#ifdef F_ADD_SEALS
// Make sure the shared memory region will not shrink
// otherwise someone could cause SIGBUS on us.
// (see http://lwn.net/Articles/594919/)
fcntl(hand, F_ADD_SEALS, F_SEAL_SHRINK);
#endif
return true;
}
bool QSharedMemoryPrivate::detach()
{
// detach from the memory segment
if (::munmap(memory, size) == -1) {
setErrorString(QLatin1String("QSharedMemory::detach (munmap)"));
return false;
}
memory = 0;
size = 0;
#ifdef Q_OS_QNX
// On QNX the st_nlink field of struct stat contains the number of
// active shm_open() connections to the shared memory file, so we
// can use it to automatically clean up the file once the last
// user has detached from it.
// get the number of current attachments
int shm_nattch = 0;
QT_STATBUF st;
if (QT_FSTAT(hand, &st) == 0) {
// subtract 2 from linkcount: one for our own open and one for the dir entry
shm_nattch = st.st_nlink - 2;
}
cleanHandle();
// if there are no attachments then unlink the shared memory
if (shm_nattch == 0) {
const QByteArray shmName = QFile::encodeName(makePlatformSafeKey(key));
if (::shm_unlink(shmName.constData()) == -1 && errno != ENOENT)
setErrorString(QLatin1String("QSharedMemory::detach (shm_unlink)"));
}
#else
// On non-QNX systems (tested Linux and Haiku), the st_nlink field is always 1,
// so we'll simply leak the shared memory files.
cleanHandle();
#endif
return true;
}
System V版本是在3.3章节的基础上封装起来的;
bool QSharedMemoryPrivate::create(int size)
{
// build file if needed
bool createdFile = false;
int built = createUnixKeyFile(nativeKey);
if (built == -1) {
errorString = QSharedMemory::tr("%1: unable to make key").arg(QLatin1String("QSharedMemory::handle:"));
error = QSharedMemory::KeyError;
return false;
}
if (built == 1) {
createdFile = true;
}
// get handle
if (!handle()) {
if (createdFile)
QFile::remove(nativeKey);
return false;
}
// create
if (-1 == shmget(unix_key, size, 0600 | IPC_CREAT | IPC_EXCL)) {
const QLatin1String function("QSharedMemory::create");
switch (errno) {
case EINVAL:
errorString = QSharedMemory::tr("%1: system-imposed size restrictions").arg(QLatin1String("QSharedMemory::handle"));
error = QSharedMemory::InvalidSize;
break;
default:
setErrorString(function);
}
if (createdFile && error != QSharedMemory::AlreadyExists)
QFile::remove(nativeKey);
return false;
}
return true;
}
bool QSharedMemoryPrivate::attach(QSharedMemory::AccessMode mode)
{
// grab the shared memory segment id
int id = shmget(unix_key, 0, (mode == QSharedMemory::ReadOnly ? 0400 : 0600));
if (-1 == id) {
setErrorString(QLatin1String("QSharedMemory::attach (shmget)"));
return false;
}
// grab the memory
memory = shmat(id, 0, (mode == QSharedMemory::ReadOnly ? SHM_RDONLY : 0));
if ((void*) - 1 == memory) {
memory = 0;
setErrorString(QLatin1String("QSharedMemory::attach (shmat)"));
return false;
}
// grab the size
shmid_ds shmid_ds;
if (!shmctl(id, IPC_STAT, &shmid_ds)) {
size = (int)shmid_ds.shm_segsz;
} else {
setErrorString(QLatin1String("QSharedMemory::attach (shmctl)"));
return false;
}
return true;
}
bool QSharedMemoryPrivate::detach()
{
// detach from the memory segment
if (-1 == shmdt(memory)) {
const QLatin1String function("QSharedMemory::detach");
switch (errno) {
case EINVAL:
errorString = QSharedMemory::tr("%1: not attached").arg(function);
error = QSharedMemory::NotFound;
break;
default:
setErrorString(function);
}
return false;
}
memory = 0;
size = 0;
// Get the number of current attachments
int id = shmget(unix_key, 0, 0400);
cleanHandle();
struct shmid_ds shmid_ds;
if (0 != shmctl(id, IPC_STAT, &shmid_ds)) {
switch (errno) {
case EINVAL:
return true;
default:
return false;
}
}
// If there are no attachments then remove it.
if (shmid_ds.shm_nattch == 0) {
// mark for removal
if (-1 == shmctl(id, IPC_RMID, &shmid_ds)) {
setErrorString(QLatin1String("QSharedMemory::remove"));
switch (errno) {
case EINVAL:
return true;
default:
return false;
}
}
// remove file
if (!QFile::remove(nativeKey))
return false;
}
return true;
}
只要理解了各个平台的实现,就不难理解QShareMemory的实现原理了。
4.总结
QSharedMemory
是 Qt 框架提供的用于实现进程间共享内存通信的类,总结起来有如下特点:
- 跨平台支持:对不同操作系统(如 Windows、Linux 等)的底层共享内存机制进行封装,屏蔽了各系统间的差异,为开发者提供统一的接口,方便在不同平台上实现进程间共享内存通信。
- 简化操作:提供了创建、附加、分离、删除共享内存等操作的简单方法,降低了使用共享内存的复杂度,开发者无需深入了解底层系统调用细节。
- 数据共享:允许不同进程或线程访问同一块物理内存区域,实现高效的数据共享,避免了数据在不同进程间的多次拷贝,提高了数据传输效率。
从使用的角度来讲,尤其是在线程或进程间数据通信来讲还是非常方便的;如果你还没有接触过它,那你可以大胆的去使用它,了解它,熟悉它,爱上它!