☰
基于Qt开发Linux任务管理器:系统监控与进程管理源码解析
2026/10/22 15:17:39 网站建设 项目流程

简介:基于Qt开发的任务管理器完整项目,适合正在学习Qt界面开发、系统级编程的在校生或开发者,提供一套可直接编译运行的源码及配套设计报告,帮助理解任务管理类工具的实现思路。压缩包共32个文件,主要包含cpp/h源文件、ui界面描述、qrc资源以及docx设计报告,附带README和LICENSE,整体仅1.5MB,结构轻量清晰。项目在Qt Creator与Ubuntu 14.04环境编写,功能上分为系统信息、进程信息和性能监控三大模块,三者置于Tab Widget中,通过on_tabWidget_currentChanged槽函数切换刷新;状态栏模块显示实时状态,菜单栏集成运行新进程与关机命令。源码完整展示了信号槽事件响应、多页界面切换、系统API调用与进程控制等关键技术,配合设计报告中关于模块划分和核心流程的说明,可快速上手二次开发。目前已有594人学习下载,对准备Qt课程设计或相关项目实战的读者有直接参考价值。

1. 基于Qt开发实现的任务管理器:一份能交作业也能二次开发的Linux系统监控底子

任务管理器这类工具,很多人觉得只是"列一下进程、显示一下CPU",真正动手用Qt在Linux下实现一遍才知道,光是读/proc、算CPU占用率、处理好UI刷新,就能劝退一大批初学者。这套"基于Qt开发实现的任务管理器"的资源,包含完整可编译的Qt Widgets源码和一份设计报告,界面把系统信息、进程信息、性能监控装进Tab Widget,状态栏放实时信息,菜单栏接运行新进程、关机、重启、挂起。适合正在做Qt课程设计、需要Linux进程监控参考代码、或者想往自己工具里集成系统监控模块的开发者。下面按我拆包和复现的顺序,把模块逻辑、编译流程、避坑和二次改造要点完整过一遍。

2. 模块分区:Tab Widget、状态栏和菜单栏怎么分工合作的

打开mainwindow.ui就能看到,主窗口中央是一个Tab Widget,三个页面从上到下依次是系统信息、进程信息、性能。顶部菜单栏放了运行新进程、关机、重启、挂起这类操作入口,底部状态栏显示运行时信息。划分很清楚:Tab内容负责数据展示,菜单栏负责触发动作,状态栏负责兜底反馈。这个设计不只是为了好看,它决定了代码里不同功能模块的边界在哪。

2.1 三个Tab页面与on_tabWidget_currentChanged切换槽

这套程序的核心切换逻辑是通过槽函数on_tabWidget_currentChanged来响应Tab页变化。界面上的每个控件都起了名字,tabWidget的currentChanged信号直接连接到这个槽。当用户切换到不同页面时,槽函数拿到当前页面索引,然后分发给对应的刷新函数。代码如下:

void MainWindow::on_tabWidget_currentChanged(int index) { show_tab_info(index); }

这个函数名是Qt Designer自动连接的老约定:控件名加下划线加信号名。只要mainwindow.ui里tabWidget对象名没改,这个槽就会被自动connect,不需要手动写connect语句。它的作用是:把"页面切换"这个动作翻译成"刷新哪个页面"的逻辑。

接下来是分发函数show_tab_info,它像一个前台接待,根据页码把任务分给不同模块:

void MainWindow::show_tab_info(int tabIndex) { switch (tabIndex) { case 0: show_system_info(); break; case 1: show_process_info(); break; case 2: show_performance(); break; default: break; } }

参数tabIndex是当前Tab页的索引,从0开始。第0页是系统信息,第1页是进程信息,第2页是性能。代码里能看到三个刷新函数各自职责独立,互不干扰。这种分发写法比在每个Tab的初始化函数里做一堆判断要清爽,后续新增页面只需要加一个case和对应刷新函数。

值得注意:show_tab_info里没有任何QTimer逻辑,它是纯被动刷新——用户切到哪页就刷哪页。这样设计的好处是避免程序一启动就把三个模块全部读一遍,浪费资源。但后面我会提到,如果想让进程页的内容自动更新,还需要再加定时器,否则进程列表是静态的。这一点在设计报告的截图里能看出来,截图里进程列表数据是有的,说明作者至少每次切过去都会重读一次。

2.2 系统信息与性能模块:数据从哪里来

系统信息模块的常规做法是调用uname()系统调用,再配合读取/proc/cpuinfo和/proc/version。uname拿到的是操作系统内核层的固定数据,比如主机名、内核版本、系统架构。常见实现如下:

void MainWindow::show_system_info() { struct utsname unameData; if (uname(&unameData) == 0) { ui->labelHostName->setText(unameData.nodename); ui->labelOsInfo->setText(QString("%1 %2") .arg(unameData.sysname) .arg(unameData.release)); } QFile cpuFile("/proc/cpuinfo"); if (cpuFile.open(QIODevice::ReadOnly | QIODevice::Text)) { QString cpuInfo = cpuFile.readAll(); // 按行解析 model name 字段 QRegularExpression modelRegex("model name\\s*:\\s*(.*)"); QRegularExpressionMatch match = modelRegex.match(cpuInfo); if (match.hasMatch()) { ui->labelCpuModel->setText(match.captured(1).trimmed()); } } }

这里unameData.nodename是主机名,sysname是操作系统名,release是内核版本号。读/proc/cpuinfo时用正则抓"model name"行,注意裁剪掉前导空格。这套逻辑在Linux下是稳定的,Qt在Windows下则没有/proc,所以这套源码天然面向Linux环境,移植到Windows需要替换数据源。

性能模块的关键是CPU使用率。CPU使用率没法像读温度那样一次性拿结果,必须采两次样做差值计算。原理是读取/proc/stat的第一行,拿到从系统启动到现在的累计CPU时间,隔一段时间再读一次,算差值里非空闲时间的比例:

QString MainWindow::calcCpuUsage() { QFile statFile("/proc/stat"); if (!statFile.open(QIODevice::ReadOnly | QIODevice::Text)) return "0.0%"; QString firstLine = statFile.readLine(); QStringList parts = firstLine.split(QRegularExpression("\\s+")); parts.removeAll(QString()); // 去掉空字符串 qint64 user = parts[1].toLongLong(); qint64 nice = parts[2].toLongLong(); qint64 system = parts[3].toLongLong(); qint64 idle = parts[4].toLongLong(); qint64 total = user + nice + system + idle; static qint64 prevTotal = 0; static qint64 prevIdle = 0; if (prevTotal == 0) { prevTotal = total; prevIdle = idle; return "0.0%"; // 第一次采样,不计算 } qint64 diffTotal = total - prevTotal; qint64 diffIdle = idle - prevIdle; prevTotal = total; prevIdle = idle; if (diffTotal == 0) return "0.0%"; double usage = 1.0 - static_cast<double>(diffIdle) / diffTotal; return QString::number(usage * 100.0, 'f', 1) + "%"; }

这里特意用static变量记住上一次的total和idle,第一次调用直接返回0.0%,等下一次调用才有真实值。注意/proc/stat的字段从第1个索引开始,第0个是"cpu"字符串,这是我复现时最容易看错的地方。多核CPU下/proc/stat会有多行,每一行代表一个核心,如果只取第一行,得到的是全核平均占用率,这对全局展示是合理的。如果程序里用到了QTimer定时刷新,CPU曲线就靠这个函数一次次叠加采样算出来。

2.3 状态栏与菜单栏:运行新进程、关机重启挂起的实现套路

状态栏在mainwindow.cpp的构造函数里初始化。常见做法是new两个QLabel,addWidget到statusBar,一个显示当前时间,一个显示当前状态文字。时间可以用QTimer每分钟更新一次。菜单栏则通过QAction连接触发函数。

运行新进程模块的交互是弹一个输入框,让用户输入命令,再交给QProcess启动。实现如下:

void MainWindow::on_actionRunNewProcess_triggered() { QString cmd = QInputDialog::getText(this, QStringLiteral("运行新进程"), QStringLiteral("输入命令或程序路径")); if (cmd.isEmpty()) return; bool ok = QProcess::startDetached(cmd); if (!ok) { QMessageBox::warning(this, QStringLiteral("提示"), QStringLiteral("启动失败:%1").arg(cmd)); } }

startDetached会启动一个独立进程并立刻返回,不会阻塞界面。这里有个隐患:用户如果输入了带空格和参数的完整命令行,比如/home/user/my app --verbose,startDetached会把它当成一个整体路径去解析,经常启动失败。更稳妥的写法是拆分成程序路径和参数列表,但QInputDialog拿到的是一整个字符串,所以原项目直接透传给startDetached。这个具体坑在第4章单独讲。

关机、重启、挂起这三个动作本质是调用系统命令。常见写法是:

void MainWindow::on_actionShutdown_triggered() { QProcess::startDetached("/sbin/shutdown", QStringList() << "-h" << "now"); }

菜单栏里对应图标文件分别是shutdown.png、restart.png、suspend.png。这套调用方式在普通用户下会失败,因为/sbin/shutdown需要root权限。很多课程设计到这里只点按钮看报错,但设计报告里如果写"实现关机功能"就得处理权限问题,常见的折中方案是让程序调用loginctl或systemctl命令,或者在程序外部用sudo运行。这一点我们第4章继续展开。

3. 源码包怎么用:文件清单、pro配置与Ubuntu构建全流程

拿到这个压缩包以后,先别急着打开工程文件,压缩包里的内容不只是源码,还包括一份设计报告word和一堆截图。截图文件命名像"图片4.png、图片13.png"这类,一看就是作者在做报告过程中从界面截的图,不是运行必须的素材。真正参与编译的只有下表中列出的文件。

3.1 拿到包先看什么:文件清单与目录规划

下面这张表是把压缩包内容按用途拆分后的核心清单:

文件作用是否参与编译
TaskManager.proqmake工程文件,定义模块、源文件和资源配置是
main.cpp程序入口,创建QApplication和MainWindow是
mainwindow.h / mainwindow.cpp主窗口类声明与实现,全部业务逻辑所在是
mainwindow.ui主窗口界面布局,控件命名和信号槽连接定义是
icon.qrcQt资源文件,声明程序用到的图标资源是
images/*.png菜单栏图标和报告截图视位置而定
设计报告.docx课程设计说明文档否
README.md项目说明否

注意images目录里既有按钮图标(new.png、suspend.png、shutdown.png等),也有报告截图(图片N.png)。图标通过icon.qrc引用,如果换了目录,相对路径找不到会导致编译时报RCC错误。README.md和.gitattributes只是说明性文件,不影响构建。

3.2 环境准备与.pro文件配置

原项目给出的开发环境是Ubuntu 14.04和Qt Creator。我在新一点的发行版上复现时,只装了qtbase5-dev和qt5-qmake就能正常编译,代码没有明显依赖某个古老Qt版本的特性。下面这段TaskManager.pro是这类Qt Widgets项目最典型的配置:

QT += core gui greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = TaskManager TEMPLATE = app SOURCES += main.cpp \ mainwindow.cpp HEADERS += mainwindow.h FORMS += mainwindow.ui RESOURCES += icon.qrc

第一行引入core和gui模块,这是Qt Widgets应用的基础。第二行是一个版本判断:如果Qt主版本大于4,就把widgets模块加上。这个判断主要是为了兼容Qt 4,也是Qt 5时代新建Widgets工程的标准模板。TARGET定义了生成的可执行文件名,也就是编译完成后会得到一个名为TaskManager的可执行文件。TEMPLATE = app表示生成应用程序而不是库。最后把cpp、h、ui、qrc文件都列进对应变量,qmake会据此生成Makefile。

如果是在命令行环境下编译,需要先确认qmake在PATH里。可以用qmake -v查看版本。在Ubuntu上如果执行qmake报command not found,说明qt5-qmake没装,需要先补依赖。注意系统里可能同时存在qmake和qmake-qt5这两个命令,后者是Qt 5专用。

3.3 编译三步:qmake、make、运行

编译流程非常简单,三步就能出可执行文件:

qmake TaskManager.pro make -j4 ./TaskManager

第一步qmake读取工程文件,生成Makefile。如果工程文件不在当前目录,可以指定完整路径;生成的Makefile默认落在当前目录。第二步make根据Makefile调编译器,-j4表示四线程并行编译,能把编译时间缩短到原来的三分之一左右。编译完成后目录下会多出一个TaskManager可执行文件。第三步直接运行,程序会打开图形界面。

如果用的是Qt Creator,操作就更省事:File -> Open File or Project选中TaskManager.pro,然后直接点左下角的绿色运行按钮。Qt Creator会先自动执行qmake再make,编译输出和错误信息都显示在底部面板里。稍有经验的开发者会注意到,打开.pro文件时Qt Creator会弹出一个配置Kit的对话框,选择Desktop默认套件即可,不建议选交叉编译套件,因为它生成的程序跑不起来。

常见编译错误是"Project ERROR: Unknown module(s) in QT: widgets"。这个报错说明系统的Qt版本是Qt 4,或者QT += widgets这一行没被正确识别。解决思路是确认qt5-qmake已安装,并在.pro里保留greaterThan那一行判断。另一个高频错误是RCC资源文件找不到,报错会指向icon.qrc里某个png路径不存在,这时打开images目录对比文件名,修正路径或文件名大小写,因为Linux下文件名是大小写敏感的。

4. 避坑清单:Ubuntu + Qt任务管理器最容易踩的六个坑

代码能编译通过只是第一步,真正跑起来才发现问题都在运行时。这一章是复现这套项目时我踩过、以及替别人排查过程中总结出的高频坑,每条都按现象、原因、解决的顺序写。

4.1 六个高频坑:现象、原因、解决

坑1:运行新进程时路径带空格就启动失败。

现象:在弹窗里输入/home/test/My App/start.sh或者ls -l,程序提示启动失败,但在系统终端里同样命令能正常执行。

原因:QProcess::startDetached的单一字符串参数会被当成完整命令去解析,路径中的空格会被拆断,后面的参数也可能被误认为是可执行文件名的一部分。

解决:把命令字符串拆成程序路径和参数列表,例如:

QProcess::startDetached("/bin/sh", QStringList() << "-c" << cmd);

用/bin/sh -c包一层,让shell自己去解析字符串里的空格和参数。我一般建议改成这种写法,成本低且对用户输入更宽容。

坑2:进程信息页刷新太快导致界面卡顿、CPU占用拉高。

现象:程序运行几秒后窗口拖动明显变慢,用系统监视器看到TaskManager进程本身占用了20%以上的CPU。

原因:作者如果直接把遍历/proc目录的代码放在主线程,并且刷新周期设置成500ms甚至更短,每次都要new大量QTableWidgetItem并设置文本,UI线程被反复阻塞。

解决:把刷新间隔拉到2000ms以上,并且只在进程页可见时才刷新,切换页面时立即刷一次。代码层面可以用QTimer控制,但注意定时器回调里也要判断当前停留在哪个Tab。

坑3:中文乱码,界面显示一堆问号。

现象:系统信息页的标签、按钮上的中文文字在Ubuntu下显示为乱码,尤其在非中文locale下更严重。

原因:源文件是GBK编码,而Qt 5默认把源码字符串当成UTF-8处理,两边不一致就会乱码。

解决:把mainwindow.cpp等源文件统一转成UTF-8编码,代码里用QStringLiteral包装中文字面量。在Qt 5下QString::fromUtf8也能救,但最好的解决办法是从源头统一编码。

坑4:菜单栏点关机、重启没反应,或者提示权限不足。

现象:运行程序后点击关机,系统弹出一个错误对话框或者干脆没反应,程序也没有退出。

原因:普通用户没有权限直接执行/sbin/shutdown,除非系统配置了sudo免密或者有polkit授权规则。

解决:课程设计场景下最简单的做法是调用loginctl命令,例如loginctl poweroff对部分桌面环境有效;另一条路子是给可执行文件配置sudo权限,但不太推荐把系统命令权限放得这么宽。我一般会在设计报告里明确写"模拟关机",真正演示时只展示进程执行结果而不做实际关机。

坑5:CPU占用率显示为0、不变,或者偶尔超过100%。

现象:性能页的CPU百分比一直不动,或者跳出一个如356%这种不合理的数字。

原因:/proc/stat所有字段一次性读出来,在单次采样里直接计算空闲和总时间的比值,没有做差值;多核环境下手动累加各核数值时又重复计入了总计数。

解决:回到2.2节的算法,必须采集两次样本做差值。超过100%的情况通常是把CPU时间累加错了,或者把每次采样时间内的总差值当成了单核时间,核数N的计算要除以处理器个数。

坑6:改了mainwindow.ui,重新运行还是旧界面。

现象:在UI设计器里拖了几个控件、改了容器布局,点击运行后发现窗口还是老样子。

原因:Qt的UI文件需要先通过uic编译成ui_mainwindow.h,再参与整个编译过程。有时IDE没有把ui文件标记为需要重新生成,增量构建就跳过了这一步。

解决:在Qt Creator里执行Clean,把构建目录下的Makefile和中间产物清掉,再重新qmake和构建。如果不放心,直接把整个build目录删掉重建,这是最彻底的"后悔药"。

4.2 定位问题的三板斧:重跑qmake、清构建目录、打印/proc字段

上面六个坑里,有一半靠重新构建就能解决,另一半要靠数据对比来定位。我的排查顺序是固定的,先做构建层面的排除:

make clean rm -f Makefile qmake TaskManager.pro make -j4

这样能排除moc和uic生成文件过期的因素。如果问题还在,就进入数据层排查。比如CPU占用率不对,直接把/proc/stat第一行打印出来看:

cat /proc/stat | head -n 1

输出是类似cpu 231846 3488 78452 ...的一长串数字,从第1个索引开始才是user、nice、system、idle。把原始字段和程序里解析出来的数据对比,立刻就能看出是错位还是漏读。这个方法比自己瞎猜快得多,也是我反复强调"读/proc必须打印原始数据"的原因。

5. 二次开发:刷新周期、进程过滤和图标替换怎么落地

这个资源直接跑起来只是第一步,绝大多数人拿到源码是要改的,要么改界面,要么加功能。这一章讲三个最常被改动的点,都是我在实际二次开发中用过的路子。

5.1 刷新周期在哪里调:定时器、Tab切换、线程取舍

原项目每次切换Tab才刷新一次数据,所以进程列表不会自己更新。要让数据活起来,常规做法是加一个QTimer:

QTimer *refreshTimer = new QTimer(this); refreshTimer->setInterval(2000); connect(refreshTimer, &QTimer::timeout, this, &MainWindow::onRefreshTimeout); refreshTimer->start();

setInterval(2000)表示每2秒触发一次onRefreshTimeout。业务逻辑不复杂时,直接把onRefreshTimeout写成检索当前Tab并刷新:

void MainWindow::onRefreshTimeout() { if (m_isRefreshing) return; m_isRefreshing = true; show_tab_info(ui->tabWidget->currentIndex()); m_isRefreshing = false; }

这里加了一个m_isRefreshing成员变量做防重入保护。因为刷新时间可能超过2秒,如果上一次还没结束下一次又触发,会叠加卡顿。这个标志位是关键,很多人只加QTimer不加保护,最后界面一样卡。

如果进程数量大、机器性能弱,刷新逻辑就要从主线程挪到后台线程。常见做法是开一个QThread或使用QtConcurrent跑采集函数,主线程只负责收到信号后更新表格。要做这个改造,核心是把show_process_info里读取/proc的部分拆成纯数据采集函数,不碰任何UI,把结果封装成结构体,通过信号发回主线程。这个改造动静较大,但对一个要长期运行的任务管理器是值得的。

5.2 给进程列表增加过滤和排序

默认进程页刷新时会把所有进程一股脑塞进表格。如果系统里开了几百个进程,找目标进程全靠滚轮。最轻量的过滤方案是加一个QLineEdit,每次文字变化时重新遍历表格并隐藏不匹配的行:

void MainWindow::on_lineEditFilter_textChanged(const QString &keyword) { for (int row = 0; row < ui->tableProcess->rowCount(); ++row) { QTableWidgetItem *item = ui->tableProcess->item(row, 1); bool match = item->text().contains(keyword, Qt::CaseInsensitive); ui->tableProcess->setRowHidden(row, !match); } }

这里约定第1列是进程名,contains做模糊匹配,第三个参数Qt::CaseInsensitive表示不区分大小写。注意:如果表格开启setSortingEnabled(true),行号会在排序时被打乱,setRowHidden用的是旧行号就会藏错行。我的实践经验是,要么不开内置排序,要么过滤后重新填充所有数据,别混着用。这也是很多人改到一半翻车的地方。

重填方案其实不复杂:清空表格,遍历进程集合只填充包含关键词的行,最后按PID升序插一行排一行。优点是不会出现行号错位,缺点是过滤条件变化时要整体重建,不过以任务管理器的数据量来说完全够用。

5.3 图标资源与qrc文件管理

菜单栏的图标走Qt资源系统,icon.qrc里声明了png文件路径。标准格式如下:

<RCC> <qresource prefix="/"> <file>images/new.png</file> <file>images/suspend.png</file> <file>images/quit.png</file> <file>images/shutdown.png</file> <file>images/restart.png</file> </qresource> </RCC>

prefix写/表示资源根路径,代码里加载图标时用":/images/new.png"这种写法。qrc文件里每个file路径是相对qrc文件所在目录的,所以工程文件里写的<file>images/new.png</file>就表示qrc文件同目录下的images文件夹。资源被编译进可执行文件后,程序运行时不需要再依赖图片文件。

如果要换图标,直接把新png覆盖到images目录下同名文件即可,重新编译后生效。需要注意两点:一是文件名大小写必须严格一致,Linux编译器对资源路径大小写敏感;二是png格式推荐,jpg虽然也能编译进去,但有透明背景需求的按钮图标jpg会显示白色底块。我一般会把按钮图标统一裁剪成16x16或24x24,保证菜单栏显示效果紧凑。

6. 用top对照验证这套任务管理器是否靠谱

二次开发完成后,不能只看界面"好像能刷数据"就交付。我自己常用的验证方法是用top命令做交叉对照,因为任务管理器显示的数据和top读的是同一批/proc数据源,差距如果在合理范围内就说明解析逻辑没问题。

验证分三步走。第一步,启动任务管理器,同时打开系统终端执行top,记录下top第一行显示的CPU占用率和内存占用。第二步,制造压力负载,让CPU使用率有明显变化:

while true; do echo -n > /dev/null; done &

这个死循环会持续占用一个CPU核心,top里能看到某个核跑满。此时看任务管理器性能页的CPU百分比,正常应该在50%左右浮动(双核机器上占用一个核),如果数值纹丝不动,说明两次采样差值逻辑没生效。第三步,批量制造进程,验证进程页的实时性:

for i in $(seq 1 20); do sleep 30 & done

这个命令一次性拉起20个sleep进程,切换任务管理器到进程信息页,能看到20个sleep进程出现在表格里。接着等待它们自然退出,再切换一次页面,列表里的对应条目应该消失。如果任务管理器还带"结束进程"功能,选中其中一个sleep进程序号,进程消失且top里对应PID也消失,说明进程管理链路是通的。

除了对比数据,我还会做一次长稳测试:让任务管理器连续运行12小时,每隔一小时手动切换一遍三个Tab,看有没有内存持续上涨、UI失去响应、进程列表越刷越慢的情况。Qt程序最容易出这类累积性问题,尤其是每2秒new一批QTableWidgetItem不回收的场景。

最后要检查的是菜单项里的关机、重启这两个危险动作。我的习惯是在虚拟机上点一遍,确认程序能正确调起系统级命令,而不是只在真机上"留着不敢点"。从那以后,我每次拿到这类Qt系统监控源码,都强制先跑一遍top对照验证,再做界面和权限检查,最后才会去阅读和修改核心代码逻辑。顺序反过来的话,改到一半经常分不清问题出在数据源、界面刷新还是权限隔离。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询