Qt之共享内存类QSharedMemory的使用及实现原理(全)

news2025/3/31 21:03:41

目录

1.简介

2.使用

3.实现原理

3.1.Windows内存映射

3.2.POSIX 共享内存

3.3.System V 共享内存

3.4.QSharedMemory的实现原理

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);
  • shmidshmget 返回的共享内存段标识符。
  • shmaddr:指定共享内存段附加的地址,通常设置为 NULL,让系统自动选择合适的地址。
  • shmflg:标志位,常用的标志有 SHM_RDONLY(以只读模式附加)。

 shmdt

   将共享内存段从调用进程的地址空间分离。

#include <sys/types.h>
#include <sys/shm.h>

int shmdt(const void *shmaddr);
  • shmaddrshmat 返回的指向共享内存段的指针。

 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 来实现共享内存的创建、访问和管理。

        不同操作系统的实现方式:

  • WindowsQSharedMemory 基于 Windows 的内存映射文件机制实现。通过调用 CreateFileMappingMapViewOfFile 等 Windows API 来创建和访问共享内存区域。
  • LinuxQSharedMemory 可以基于 System V 共享内存或者 POSIX 共享内存实现。在 Linux 系统中,会根据具体情况调用 shmgetshmat 等 System V 共享内存相关的系统调用,或者 shm_openmmap 等 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 等)的底层共享内存机制进行封装,屏蔽了各系统间的差异,为开发者提供统一的接口,方便在不同平台上实现进程间共享内存通信。
  • 简化操作:提供了创建、附加、分离、删除共享内存等操作的简单方法,降低了使用共享内存的复杂度,开发者无需深入了解底层系统调用细节。
  • 数据共享:允许不同进程或线程访问同一块物理内存区域,实现高效的数据共享,避免了数据在不同进程间的多次拷贝,提高了数据传输效率。

        从使用的角度来讲,尤其是在线程或进程间数据通信来讲还是非常方便的;如果你还没有接触过它,那你可以大胆的去使用它,了解它,熟悉它,爱上它!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2323673.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Problem A: 接口使用

1.题目问题 2.样例 3.代码实现 补充&#xff1a;注意空格 // 定义Vehicle接口 interface Vehicle {void start();void stop(); }// 实现Vehicle接口的Bike类 class Bike implements Vehicle {Overridepublic void start() {System.out.println("i am bike,i am running&…

用Python插入Excel表格到Word文档

在日常办公场景中&#xff0c;通过Python脚本自动化整合Excel数据与Word文档&#xff0c;能够实现表格的智能迁移&#xff0c;满足不同场景下数据呈现的专业性要求。直接提取表格内容插入Word适用于需要快速传递核心数据的场景&#xff0c;确保信息精准直达&#xff1b;完整复制…

合合信息TextIn大模型加速器 2.0来了:智能文档解析和图表解析能力全面升级

合合信息“TextIn大模型加速器 2.0”版本来了&#xff1a;文档解析和图表解析能力全面升级 背景 在日常工作中&#xff0c;我们常常遇到无法直接复制的文档内容或图片内容&#xff0c;这些内容通常需要进行识别和解析。一个典型的例子是&#xff0c;当我们需要将折线图转化为…

消息队列Message Queue

前面&#xff0c;我们在黑点点评中秒杀场景中&#xff0c;首次了解到消息队列MQ&#xff0c;它主要解决了秒杀场景中异步场景&#xff0c;提升了并发性&#xff0c;吞吐量。可是还是对消息队列又很多的疑惑&#xff1f; 消息队列是什么 消息队列是一种通信协议或中间件&#…

如何利用AI智能生成PPT提升工作效率

如何利用AI智能生成PPT提升工作效率&#xff1f;PPT制作曾经是每个人办公生活中的一大痛点。你有多久没有在制作PPT时感到焦头烂额&#xff0c;选模板、调整格式、插入图片&#xff0c;每一项都得花费大量的时间和精力&#xff0c;最后还未必能做出一份令人满意的效果。随着人工…

WIN11 企业版 部署Dify+Docker

Dify&#xff08;Do it for you&#xff09;是一款开源的大语言模型应用开发平台&#xff0c;旨在简化AI应用的创建、部署和管理过程&#xff0c;使开发者能够更快速、更轻松地构建和运营基于GPT等模型的AI应用。 Dify平台创建和运营一个AI chatbot应用&#xff0c;涉及到登录…

1.25-20GHz/500ns超快跳频!盛铂SWFA300国产捷变频频率综合器模块赋能雷达/5G/电子战高频精密控制 本振/频综模块

盛铂SWFA300捷变频频率综合器模块简述&#xff1a; 盛铂科技国产SWFA300捷变频频率综合器是一款在频率范围内任意两点频率的跳频时间在500nS以内的高速跳频源&#xff0c;其输出频率范围为1.25GHz至20GHz&#xff0c;频率的最小步进为10kHz。同时它拥有优秀的相位噪声特性&…

代理IP协议详解HTTP、HTTPS、SOCKS5分别适用于哪些场景

“代理IP协议在现代网络通信中扮演着至关重要的角色。它们通过提供中间层服务&#xff0c;帮助用户匿名访问网络、绕过地理限制、提高安全性和加速数据传输。HTTP、HTTPS和SOCKS5是三种最常见的代理IP协议&#xff0c;每种协议都有其特定的用途和适用场景。” HTTP代理及其适用…

AIGC工具平台-通用抠图换背景

本模块采用先进的大模型智能算法&#xff0c;精准识别并分割图像中的人物或物品主体&#xff0c;实现高效、精准、智能化的抠图处理。无论是人物肖像、产品展示&#xff0c;还是复杂场景&#xff0c;该工具均能准确提取主体&#xff0c;并自动适配至背景图像&#xff0c;实现自…

word快速创建虚拟文字

创建虚拟文字的作用&#xff1a;如培训新员工使用 Word&#xff0c;用虚拟文字演示如何设置段落格式。不需要你随便乱敲文字或者去复制一段文字过来。帮你节约了时间&#xff01; 两个函数的使用必须在段落的开头&#xff01;&#xff01;&#xff01; rand函数 在 Word 中…

win10下python脚本运行缺失ccache的问题处理

问题 python脚本运行时&#xff0c;会提醒参考 https://github.com/ccache/ccache/blob/master/doc/INSTALL.md 处理缺失ccache的问题。 下载编译 下载ccache主干版本&#xff0c; 例如 https://github.com/ccache/ccache/archive/refs/heads/master.zip 按照说明编译 mkd…

开发复合组件TLabel + TwwDBLookupCombo

老鸟跳过。。。。。。。。本文只是为小白准备的 -------------- TwwDBLookupCombo 组件是老牌控件包的 Inofpower 中的一个组件。Inofpower 很久也没有更新了&#xff0c;只是作了新版DELPHI的适配&#xff0c;组件的功能从D2007那些开始到现在&#xff0c;可以说几乎没有任何…

0328-内存图2

是否正确待定&#xff1a; Perso类 package com.qc.内存图2;public class Perso {public int age;public String name;public static int flag;public void m1() {}public static void m2() {}Overridepublic String toString() {return "Perso [age" age "…

【ESP32S3】esp32获取串口数据并通过http上传到前端

通过前面的学习&#xff08;前面没发过&#xff0c;因为其实就是跑它的demo&#xff09;了解到串口配置以及开启线程实现功能的工作流程&#xff0c;与此同时还有esp32作为STA节点&#xff0c;将数据通过http发送到服务器。 将这两者联合 其实是可以得到一个&#xff1a;esp32获…

《一本书讲透Elasticsearch:原理、进阶与工程实践》读书笔记

1&#xff1a;es的组成部分&#xff1a; Elasticsearch 引擎&#xff1a;核心组件&#xff0c;处理索引和搜索请求 Kibana&#xff1a;es的可视化的数据界面&#xff0c;用于分析和展示数据 Beats&#xff08;可选&#xff09;轻量级的日志采集器 2&#xff1a;基本概念 es开…

Android15查看函数调用关系

Android15 Camera3中打印函数调用栈 1.使用CallStack跟踪函数调用 修改涉及三个内容&#xff1a; Android.bp中添加对CallStack的引用。CallStack被打包在libutilscallstack.so。代码中包含CallStack的头文件。代码中调用CallStack接口&#xff0c;打印函数调用栈。 例子&am…

macOS 15 通过 MacPorts 安装 PHP 7 构建错误找不到符号在 dns.o 中解决方法

构建遇到的问题如下&#xff1a; "_res_9_dn_expand", referenced from:_php_parserr in dns.o_php_parserr in dns.o_php_parserr in dns.o_php_parserr in dns.o_php_parserr in dns.o_php_parserr in dns.o_zif_dns_get_mx in dns.o..."_res_9_dn_skipname&…

练习:猜数字小游戏

需求&#xff1a; 程序自动生成一个 1 - 100 之间的随机数字&#xff0c;使用程序实现猜出这个数字是多少&#xff1f; 代码&#xff1a; //猜数字小游戏 package demo01; import java.util.Random; import java.util.Scanner; public class HelloJava {public static void …

EMQX Dashboard

EMQX Dashboard EMQX理论基础 https://blog.csdn.net/liudachu/article/details/146495030 1 Dashboard简介 EMQX 提供了一个内置的管理控制台&#xff0c;即 EMQX Dashboard。方便用户通过 Web 页面就能轻松管理和监控 EMQX 集群&#xff0c;并配置和使用所需的各项功能。 访…

PC名词解释-笔记本的S0,S1,S2,S3,S4,S5状态

​&#x1f393;作者简介&#xff1a;程序员转项目管理领域优质创作者 &#x1f48c;个人邮箱&#xff1a;[2707492172qq.com] &#x1f310;PMP资料导航&#xff1a;PM菜鸟&#xff08;查阅PMP大纲考点&#xff09; &#x1f4a1;座右铭&#xff1a;上善若水&#xff0c;水善利…