はじめに
Linux デスクトップ環境で重要なデータのバックアップをとる際、rsync コマンドは非常に頼もしい存在です。
しかし、バックグラウンドで rsync を回していると、大量のファイル I/O や CPU リソースの消費によって重い作業(ビルドや動画エンコード、ゲーム等)の最中にPCの操作感が重くなるという問題に直面することがあります。
そこで今回、Qt6 (C++17) を用いて、rsync の堅牢性を活かしつつ**「PCの負荷が高くなったら自動でプロセスを一時停止し、空いたら再開する (Smart Auto-Pause)」**機能を備えた Linux 向けバックアップ GUI アプリ 「GXBackup」 を開発しました。
本記事では、GXBackup の機能概要と、Qt6 / Linux C++ での実装テクニック(POSIXシグナルによるプロセス制御、/proc のリアルタイム監視、rsync進捗パースなど)を解説します。
🌟 概要と特徴
GXBackup は Linux 環境向けに構築された C++17 / Qt6 製のオープンソース GUI バックアップツールです。
-
バックエンドに
rsyncを採用:パーミッションや拡張属性 (ACL/xattr) の維持、ハードリンクの保持などrsyncの実績と信頼性をそのまま活用。 -
スマート負荷制限 (Smart Auto-Pause):CPU 使用率や Load Average を監視し、設定閾値を超えたら
SIGSTOPで即座にバックアップを自動待機、負荷が下がったらSIGCONTでシームレス再開。 - nice / ionice 統合:デフォルトで CPU / I/O スケジューリング優先度を最低値 (Idle / Nice 19) に設定。
- システムトレイ常駐 & 追いつき実行 (Catch-Up):タスクトレイに常駐し、PC起動時にスケジュール時刻を過ぎていた場合も次回起動時に「追いつきバックアップ」を実行。
🏗 アーキテクチャ設計
GXBackup は Qt のシグナル・スロット機構を活用し、GUI 層と Core (バックエンド処理) 層を明確に分離した設計を行っています。
💡 実装のポイント
1. QProcess と POSIX シグナルによる rsync の一時停止・再開制御
Qt の QProcess にはプロセスの起動・停止・強制終了メソッドはありますが、プロセスの「一時停止」機能は標準で用意されていません。
GXBackup では、Linux の POSIX シグナル SIGSTOP と SIGCONT を ::kill() システムコールで送信することで、rsync をプロセスレベルで安全に一時停止・再開させています。
// RsyncProcess.cpp より抜粋
#include <csignal>
#include <unistd.h>
void RsyncProcess::pauseBackup()
{
if (m_state != Running) return;
qint64 pid = m_process.processId();
if (pid > 0) {
// PIDに対して SIGSTOP を送信してプロセスを一時停止
::kill(static_cast<pid_t>(pid), SIGSTOP);
setState(Paused);
}
}
void RsyncProcess::resumeBackup()
{
if (m_state != Paused) return;
qint64 pid = m_process.processId();
if (pid > 0) {
// PIDに対して SIGCONT を送信してプロセスを再開
::kill(static_cast<pid_t>(pid), SIGCONT);
setState(Running);
}
}
また、バックアップ実行時には nice および ionice コマンドを経由させることで、CPU スケジューリング優先度を最低 (nice -n 19)、I/O 優先度を Idle クラス (ionice -c 3) に設定し、他アプリのレスポンスを損なわない配慮をしています。
QString program = "nice";
QStringList args;
args << "-n" << "19" << "ionice" << "-c" << "3" << "rsync";
args << "-aHAX" << "--delete" << "--info=progress2";
2. /proc/stat & /proc/loadavg の直接パースによる軽量な負荷監視
サードパーティ依存を増やさず Linux ネイティブにシステム負荷を監視するため、/proc/stat と /proc/loadavg を QTimer で定期パースしています。
CPU 使用率の計算 (/proc/stat)
/proc/stat の cpu 行には、システム起動時からの Tick 数(user, nice, system, idle, iowait 等)が記録されています。直前との差分から CPU 使用率を正確に割り出します。
// SystemMonitor.cpp より抜粋
double SystemMonitor::readCpuUsage()
{
std::ifstream file("/proc/stat");
if (!file.is_open()) return 0.0;
std::string cpuStr;
file >> cpuStr; // "cpu"
unsigned long long user, nice, system, idle, iowait, irq, softirq, steal;
if (!(file >> user >> nice >> system >> idle >> iowait >> irq >> softirq >> steal)) {
return 0.0;
}
unsigned long long idleTicks = idle + iowait;
unsigned long long totalTicks = user + nice + system + idle + iowait + irq + softirq + steal;
// 前回の測定値との差分
unsigned long long totalDiff = totalTicks - m_prevTotalTicks;
unsigned long long idleDiff = idleTicks - m_prevIdleTicks;
m_prevTotalTicks = totalTicks;
m_prevIdleTicks = idleTicks;
if (totalDiff == 0) return 0.0;
double cpuPercent = 100.0 * (1.0 - static_cast<double>(idleDiff) / static_cast<double>(totalDiff));
return std::clamp(cpuPercent, 0.0, 100.0);
}
Load Average 1分値の取得 (/proc/loadavg)
double SystemMonitor::readLoadAverage()
{
QFile file("/proc/loadavg");
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QTextStream in(&file);
QString line = in.readLine();
QStringList parts = line.split(QChar(' '), Qt::SkipEmptyParts);
if (!parts.isEmpty()) {
bool ok = false;
double load1 = parts[0].toDouble(&ok);
if (ok) return load1;
}
}
return 0.0;
}
BackupController は、この SystemMonitor から loadStateChanged(bool highLoad) シグナルを受け取ることで、自動一時停止 (pauseBackup) と自動再開 (resumeBackup) のオーケストレーションを行います。
3. rsync --info=progress2 のリアルタイムログバッファと Regex 解析
rsync の --info=progress2 オプションは、全体の進捗率・転送速度・残り時間を1行の出力としてキャリッジリターン (\r) 更新で出力します。
QProcess から読み込む標準出力をキャリッジリターン分割対応のリングバッファで処理し、正規表現を用いて進行状況を取り出しています。
// RsyncProcess.cpp より抜粋
bool RsyncProcess::parseProgressLine(const QString &line)
{
// 例: " 1,234,567 45% 12.34MB/s 0:00:15 (xfr#12, to-chk=45/100)"
static const QRegularExpression re(R"(\s*([\d,]+)\s+(\d+)%\s+([\d\.\w/]+)\s+([\d:]+))");
QRegularExpressionMatch match = re.match(line);
if (match.hasMatch()) {
QString transferred = match.captured(1);
int percent = match.captured(2).toInt();
QString speed = match.captured(3);
QString remainingTime = match.captured(4);
// GUI更新用シグナルを発行
emit progressUpdated(percent, speed, transferred, remainingTime);
return true; // 進捗行として消費
}
return false; // 通常ログ行
}
💻 動作環境
必須環境
- OS: Linux (Ubuntu, Debian, Fedora, Arch Linux 等)
- コンパイラ: C++17 対応 (GCC 8+ / Clang 7+)
- フレームワーク: Qt 5 または Qt 6
-
依存ツール:
rsync
📝 おわりに
rsync という強力な CLI ツールをベースにしつつ、Qt6 の GUI と Linux のプロセス制御(POSIX シグナル、/proc モニタリング)を組み合わせることで、「重くならないバックアップツール」 を実現できました。
オープンソースとしてリポジトリを公開していますので、ぜひ星やフィードバックをいただけると励みになります!
- 🔗 GitHub リポジトリ: amekusa03/GXBackup