進程 daemon() 脫離終端後,stdout/stderr 被導向 /dev/null,日誌一片空白。本文從 daemon() 原始碼切入,示範如何透過 dup2() 重導向、SIGUSR1 觸發、tail -f 即時觀察,構建「按需連接、用完即走」的進程控制台回收機制。
int daemonize(int nochdir, int noclose) {
switch (fork()) {
case -1: return -1;
case 0: break; // 子進程繼續
default: _exit(0); // 父進程退出,子進程被 init 收養
}
if (setsid() == -1) return -1; // 新會話、脫離控制終端
if (!nochdir) chdir("/"); // 切換工作目錄,避免掛載點 busy
if (!noclose) {
int fd = open("/dev/null", O_RDWR);
dup2(fd, STDIN_FILENO); // 0
dup2(fd, STDOUT_FILENO); // 1
dup2(fd, STDERR_FILENO); // 2
if (fd > 2) close(fd);
}
return 0;
}
關鍵點:stdin/out/err 全指向 /dev/null,終端輸出徹底斷連。
核心思路:主進程保留一個「控制通道」,收到 SIGUSR1 時將 1/2 重導向到臨時檔案 /tmp/console_;收到 SIGUSR2 恢復 /dev/null。
#include
#include
#include
#include
static int log_fd = -1;
void sig_handler(int sig) {
if (log_fd != -1) close(log_fd);
if (sig == SIGUSR1) {
log_fd = open("/tmp/console_capture.log", O_WRONLY|O_CREAT|O_APPEND, 0600);
fprintf(stderr, "[SIGUSR1] Console capture ENABLED\n");
} else { // SIGUSR2
log_fd = open("/dev/null", O_WRONLY);
fprintf(stderr, "[SIGUSR2] Console capture DISABLED\n");
}
if (log_fd != -1) {
dup2(log_fd, STDOUT_FILENO);
dup2(log_fd, STDERR_FILENO);
}
}
int main() {
signal(SIGUSR1, sig_handler);
signal(SIGUSR2, sig_handler);
daemon(1, 0); // 不切目錄、關閉標準輸出
for (;;) {
fprintf(stderr, "heartbeat %ld\n", time(NULL));
sleep(2);
}
}
gcc -o daemon_console daemon_console.c
./daemon_console
# PID 假設 12345
#!/bin/bash
PID=$1
[ -z "$PID" ] && { echo "Usage: $0 "; exit 1; }
kill -USR1 $PID
echo "Capture started. Press Ctrl+C to stop."
trap "kill -USR2 $PID; exit 0" INT TERM
tail -f -n 0 /tmp/console_capture.log
./attach_console.sh 12345
# 終端即時顯示 heartbeat...
# Ctrl+C → 自動發 SIGUSR2 恢復 /dev/null,進程繼續後台運行
| 問題 | 改良 |
|---|---|
| 信號衝突/丟失 | 改用 Unix Domain Socket 或命名管道 (FIFO) 傳遞控制指令 |
| 日誌檔無限增長 | 加入 logrotate 或自行實作滾動(大小/時間切割) |
| 多進程/執行緒競爭 fd | 使用 pthread_atfork 或進程啟動時各自開啟獨立 log fd |
| 安全性:任意用戶可發信號 | 檢查 getuid()、限制 SIGUSR1 發送者(如 systemd 憑證) |
| 系統日誌整合 | 直接 openlog()/syslog(),避免自管檔案 |
| 方案 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
systemd journal (StandardOutput=journal) | 零代碼、集中查詢、結構化 | 需 systemd 管理、非 systemd 環境無效 | 現代 Linux 服務 |
screen/tmux 跑前台 | 完整終端模擬、可互動 | 佔用終端、非生產部署方式 | 開發調試、臨時任務 |
重導向到檔案 exec > /var/log/app.log 2>&1 | 簡單、持久化 | 無法「按需連接」、需 logrotate | 一般服務 |
| 本文方案(信號+臨時 fd) | 按需、無持久佔用、不侵入業務碼 | 需修改進程碼、信號機制脆弱 | 無法改碼的舊系統、臨時排查 |
結語: Daemon 脫離終端是為穩定,但調試時需要「把輸出拿回來」。信號觸發 dup2 重導向是最輕量的「時光機」——不改架構、不重啟服務、按需連接。下次遇到「後台進程卡住、沒日誌」,先 kill -USR1 再 tail -f,往往能在幾秒內看到關鍵堆疊資訊。