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
中包含有 QSharedMemory
和 QSystemSemaphore
,QSharedMemory
就是共享内存,不同进程可以访问同一段内存区域。而 QSystemSemaphore
是系统级信号量,在不同进程之间同步信号。
我使用 Qt 有两年多了,用 Qt 开发了 4 个桌面应用以及相应的(静态/动态)库。为了在不同应用程序之间同步数据,我采用过
QSharedMemory
作为 IPC 的通讯机制,然而当数据量较大/数据更新速率较快时,使用QSharedMemomy
常常会影响多个 attach 到共享内存的应用。后来采用 QUdpSocket 作为 IPC 的通讯机制,数据刷新速率到 100 Hz 也不会出现明显的性能下降或者应用崩溃的问题。(多个应用使用共享内存进行通信,崩溃的问题可能是由于没有加上系统级的锁保护共享内存。)
尽管使用 Qt 开发应用有两年的时间了,Qt 中仍然有很多我不知道,也没用过的东西,比如QSystemSemaphore
和与之类似的QSemaphore
,QSystemSemaphore
作为系统级的信号类,是一个比较重量级的类,既可用于多线程间的同步,也可用于多进程间的同步。
关于信号的更多内容可以参考 Qt官方文档。
RunGuard
的单应用机制是通过 QSharedMemory
实现的。在其构造函数中会对共享内存 sharedMem 及系统信号 memLock 进行初始化。构造完对象后就调用 tryToRun
方法尝试运行 QGroundControl 应用了。QSharedMemory
和 QSystemSemaphore
一样,都是拥有 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()
,完成资源的释放。
==========
实现单应用的方法还有一些:
- 绑定特定端口的 Socket,这样只有第一个运行的应用实例能够绑定到 Socket,其他的检测到 Socket 已被绑定就自行退出。
- 使用 QLockFile 创建一个锁,当检测到锁文件存在时,应用退出。(但该文件需要放在一个固定的全局位置,比如
/var/
目录下,如果使用相对路径放在应用目录下面,那么其他路径下的相同程序仍然能够同时运行)。
实现单应用的方法的原理都是基于唯一资源(特定端口的 Socket 或者特定的内存区域或文件),当唯一的资源被占据时,后启动的应用自行退出,就实现了单应用实例。