QGroundControl Source Code Learning Series - 1

QGroundControl Source Code Learning Series - 1

写在前面:这个系列将会以 QGroundControl Stable V3.5.1 的源代码为基础进行学习。
由于 QGroundControl 的项目庞大,包含的文件较多,在涉及到 src 目录下的文件时,直接给出文件名,如 main.cc,其他子模块或子文件夹的文件会给出具体路径(不含前缀 src)。
本系列作者:Briuture

main.cc

程序的入口文件 main.cc,由于 QGroundControl 项目的目标是跨平台构建应用,在进入主函数前会根据不同构建环境的宏定义执行不同的行为。

在桌面(Win/Linux/Mac)平台上,该应用在运行前会检查是否有同一实例正在运行。检查的功能由 RunGuard 类完成,该类在文件 RunGuard.cc 文件中定义。并且仅在 main 函数开头的位置使用(桌面环境):

#ifndef __mobile__
    RunGuard guard("QGroundControlRunGuardKey");
    if (!guard.tryToRun()) {
        return 0;
    }
#endif

RunGuard

RunGuard 翻译成中文即“运行守卫”,确保机器上只有唯一一个实例在运行。它包含的接口比较简单:bool tryToRun(),另外两个接口 bool isAnotherRunning()void release() 虽然声明为 public,但这两个接口没有在外部使用过。由于在 main 函数的开头位置声明的 RunGuard 对象是在堆中分配的内存,因此当 main 执行完毕将要退出时,会由系统执行其析构函数,并释放掉 RunGuard 所占用的内存空间。

RunGuard.h 中包含有 QSharedMemoryQSystemSemaphoreQSharedMemory 就是共享内存,不同进程可以访问同一段内存区域。而 QSystemSemaphore 是系统级信号量,在不同进程之间同步信号。

我使用 Qt 有两年多了,用 Qt 开发了 4 个桌面应用以及相应的(静态/动态)库。为了在不同应用程序之间同步数据,我采用过 QSharedMemory 作为 IPC 的通讯机制,然而当数据量较大/数据更新速率较快时,使用 QSharedMemomy 常常会影响多个 attach 到共享内存的应用。后来采用 QUdpSocket 作为 IPC 的通讯机制,数据刷新速率到 100 Hz 也不会出现明显的性能下降或者应用崩溃的问题。(多个应用使用共享内存进行通信,崩溃的问题可能是由于没有加上系统级的锁保护共享内存。)
尽管使用 Qt 开发应用有两年的时间了,Qt 中仍然有很多我不知道,也没用过的东西,比如 QSystemSemaphore 和与之类似的 QSemaphoreQSystemSemaphore 作为系统级的信号类,是一个比较重量级的类,既可用于多线程间的同步,也可用于多进程间的同步。
关于信号的更多内容可以参考 Qt官方文档

RunGuard 的单应用机制是通过 QSharedMemory 实现的。在其构造函数中会对共享内存 sharedMem 及系统信号 memLock 进行初始化。构造完对象后就调用 tryToRun 方法尝试运行 QGroundControl 应用了。QSharedMemoryQSystemSemaphore 一样,都是拥有 key 的,创建时就有了唯一的名字,在其他应用中要对该队内存进行访问时通过该 key 构造 QSharedMemory,调用对象的 attach 方法尝试连接到该段内存区域即可。

QGroundControl 的单应用机制就比较清楚了:首先尝试创建一段共享内存,若创建成功,说明没有其他的 QGroundControl 实例在运行,否则说明有其他的 QGroundControl 实例在运行。下面是相应的代码:

bool RunGuard::tryToRun()
{
    if ( isAnotherRunning() )   // Extra check
        return false;

    memLock.acquire();
    const bool result = sharedMem.create( sizeof( quint64 ) );
    memLock.release();
    if ( !result )
    {
        release();
        return false;
    }

    return true;
}

tryToRun 方法中还进行了一项额外的检测 isAnotherRunning:尝试 attach 到共享内存上,若 attach 成功,说明该共享内存是被其他进程创建了的。(这样做的目的应该是为了避免某些系统原因导致的 QShareMemory create 失败,而非 QGroundControl 应用实例正在运行导致,但其创建的共享内存只有 8 字节大小 sizeof( qint64 ),操作系统应该不会出现连 8 个字节的内存空间都分配不出来)。

为了避免同一时刻多个 QGroundControl 实例启动,RunGuard 使用 QSystemSemaphore 跨进程同步信号量,信号量的初始值设为 1:QSystemSemaphore memLock( memLockKey, 1 ),因此同一时刻只能有一个应用拿到资源,并完成共享内存的创建。这样就避免了多个 QGroundControl 应用同时启动。

最后,当整个程序退出时,RunGuard 实例会释放掉创建的共享内存 sharedMem.detach(),完成资源的释放。

==========

实现单应用的方法还有一些:

  1. 绑定特定端口的 Socket,这样只有第一个运行的应用实例能够绑定到 Socket,其他的检测到 Socket 已被绑定就自行退出。
  2. 使用 QLockFile 创建一个锁,当检测到锁文件存在时,应用退出。(但该文件需要放在一个固定的全局位置,比如 /var/ 目录下,如果使用相对路径放在应用目录下面,那么其他路径下的相同程序仍然能够同时运行)。

实现单应用的方法的原理都是基于唯一资源(特定端口的 Socket 或者特定的内存区域或文件),当唯一的资源被占据时,后启动的应用自行退出,就实现了单应用实例。

参考

posted @ 2019-10-14 20:27  brifuture  阅读(374)  评论(0编辑  收藏  举报