Linux 信號機制承載著異步事件通知、進程控制、錯誤處理等核心功能。本文系統梳理 task_struct 相關數據結構、sigaction 安裝流程、siginfo_t 信息承載、kill() 系統調用遞歸調用鏈,以及核心發送函數 send_signal() 的實現邏輯,幫助開發者建立完整的內核級信號認知。
struct task_struct {
// ...
struct signal_struct *signal; // 進程組共享信號狀態
struct sighand_struct *sighand; // 信號處理函數表 + 鎖
sigset_t blocked; // 當前阻塞信號集
sigset_t real_blocked; // 臨時阻塞集(用於 sigsuspend)
sigset_t saved_sigmask; // 保存的信號掩碼
struct sigpending pending; // 私有 pending 隊列
struct sigpending *shared_pending; // 指向 signal->shared_pending
// ...
};
struct k_sigaction {
struct sigaction sa; // 用戶層可見結構
};
struct sigaction {
__sighandler_t sa_handler; // 處理函數指針
unsigned long sa_flags; // SA_RESTART, SA_SIGINFO 等
__sigrestore_t sa_restorer; // 內核用於恢復上下文
sigset_t sa_mask; // 執行處理函數時臨時阻塞的信號集
};
#define _NSIG 64
#if __WORDSIZE == 64
#define _NSIG_BPW 64
#else
#define _NSIG_BPW 32
#endif
#define _NSIG_WORDS (_NSIG / _NSIG_BPW)
typedef struct {
unsigned long sig[_NSIG_WORDS]; // 每 bit 對應一個信號
} sigset_t;
struct sighand_struct {
atomic_t count; // 引用計數(fork 時共享)
struct k_sigaction action[_NSIG]; // 64 個信號的處理動作
spinlock_t siglock; // 保護 action/pending 的自旋鎖
wait_queue_head_t signalfd_wqh; // signalfd 等待隊列
};
typedef struct siginfo {
int si_signo; // 信號號碼
int si_errno; // 錯誤碼
int si_code; // 來源標識:SI_USER, SI_KERNEL, SI_QUEUE, SI_TIMER 等
union {
struct { pid_t _pid; uid_t _uid; } _kill; // kill()/raise()
struct { timer_t _tid; int _overrun; sigval_t _sigval; } _timer; // POSIX 定時器
struct { pid_t _pid; uid_t _uid; sigval_t _sigval; } _rt; // sigqueue()
struct { pid_t _pid; uid_t _uid; int _status; clock_t _utime; clock_t _stime; } _sigchld; // SIGCHLD
struct { void __user *_addr; int _trapno; } _sigfault; // SIGSEGV/SIGFPE 等
struct { long _band; int _fd; } _sigpoll; // SIGPOLL
} _sifields;
} siginfo_t;
asmlinkage unsigned long sys_signal(int sig, __sighandler_t handler) {
struct k_sigaction new_sa, old_sa;
new_sa.sa.sa_handler = handler;
new_sa.sa.sa_flags = SA_ONESHOT | SA_NOMASK; // 舊語義:一次性、不阻塞
sigemptyset(&new_sa.sa.sa_mask);
int ret = do_sigaction(sig, &new_sa, &old_sa);
return ret ? ret : (unsigned long)old_sa.sa.sa_handler;
}
int do_sigaction(int sig, struct k_sigaction *act, struct k_sigaction *oact) {
struct task_struct *t = current;
struct k_sigaction *k = &t->sighand->action[sig-1];
// 合法性檢查:sig ∈ [1,64],SIGKILL/SIGSTOP 不可捕獲/忽略
if (!valid_signal(sig) || sig < 1 || (act && sig_kernel_only(sig)))
return -EINVAL;
spin_lock_irq(&t->sighand->siglock);
if (oact) *oact = *k; // 保存舊動作
if (act) {
sigdelsetmask(&act->sa.sa_mask, sigmask(SIGKILL)|sigmask(SIGSTOP));
*k = *act; // 替換處理動作
// POSIX 規範:設置 SIG_IGN 導致 pending 信號丟棄
// 設置 SIG_DFL 且默認忽略(如 SIGCHLD)也丟棄 pending
if (sig_handler_ignored(sig_handler(t, sig), sig)) {
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, sig);
rm_from_queue_full(&mask, &t->signal->shared_pending);
do {
rm_from_queue_full(&mask, &t->pending);
t = next_thread(t);
} while (t != current);
}
}
spin_unlock_irq(&t->sighand->siglock);
return 0;
}
sys_kill(pid, sig)
→ kill_something_info(sig, info, pid)
→ pid > 0 : kill_pid_info() → group_send_sig_info()
→ pid == -1 : 向所有進程發送(除 init 與當前線程組)
→ pid < -1 : __kill_pgrp_info() → 向進程組發送
asmlinkage long sys_kill(pid_t pid, int sig) {
struct siginfo info = {
.si_signo = sig,
.si_errno = 0,
.si_code = SI_USER, // 用戶空間發送標識
.si_pid = task_tgid_vnr(current),
.si_uid = current->uid,
};
return kill_something_info(sig, &info, pid);
}
static int kill_something_info(int sig, struct siginfo *info, pid_t pid) {
if (pid > 0) { // 單進程
rcu_read_lock();
ret = kill_pid_info(sig, info, find_vpid(pid));
rcu_read_unlock();
return ret;
}
read_lock(&tasklist_lock);
if (pid != -1) { // 進程組
ret = __kill_pgrp_info(sig, info, pid ? find_vpid(-pid) : task_pgrp(current));
} else { // pid == -1,廣播
int retval = 0, count = 0;
for_each_process(p) {
if (p->pid > 1 && !same_thread_group(p, current)) {
int err = group_send_sig_info(sig, info, p);
++count;
if (err != -EPERM) retval = err;
}
}
ret = count ? retval : -ESRCH;
}
read_unlock(&tasklist_lock);
return ret;
}
int kill_pid_info(int sig, struct siginfo *info, struct pid *pid) {
struct task_struct *p;
rcu_read_lock();
retry:
p = pid_task(pid, PIDTYPE_PID);
if (p) {
int error = group_send_sig_info(sig, info, p);
if (unlikely(error == -ESRCH)) goto retry; // 線程組變更重試
rcu_read_unlock();
return error;
}
rcu_read_unlock();
return -ESRCH;
}
所有路徑最終匯聚到 send_signal(),負責將信號放入 pending 隊列並喚醒目標進程。
static int send_signal(int sig, struct siginfo *info, struct task_struct *t, int group) {
struct sigpending *pending;
struct sigqueue *q;
assert_spin_locked(&t->sighand->siglock);
// 1. 目標進程是否可接收信號(非退出狀態)
if (!prepare_signal(sig, t)) return 0;
// 2. 選擇 pending 隊列:group=1 → shared_pending,否則私有 pending
pending = group ? &t->signal->shared_pending : &t->pending;
// 3. 非實時信號去重:若已 pending 則丟棄(排隊深度為 1)
if (legacy_queue(pending, sig)) return 0;
// 4. 強制發送(SIGKILL/SIGSTOP 等內核內部用)
if (info == SEND_SIG_FORCED) goto out_set;
// 5. 實時信號/帶信息信號需分配 sigqueue 節點入隊
q = __sigqueue_alloc(t, GFP_ATOMIC, (sig < SIGRTMIN && (is_si_special(info) || info->si_code >= 0)));
if (q) {
list_add_tail(&q->list, &pending->list);
switch ((unsigned long)info) {
case (unsigned long)SEND_SIG_NOINFO:
q->info.si_signo = sig; q->info.si_code = SI_USER;
q->info.si_pid = task_pid_vnr(current); q->info.si_uid = current->uid; break;
case (unsigned long)SEND_SIG_PRIV:
q->info.si_signo = sig; q->info.si_code = SI_KERNEL; break;
default:
copy_siginfo(&q->info, info); break;
}
} else if (!is_si_special(info)) {
// 實時信號且非 kill() 發送,內存不足返回 -EAGAIN
if (sig >= SIGRTMIN && info->si_code != SI_USER) return -EAGAIN;
}
out_set:
signalfd_notify(t, sig); // 通知 signalfd
sigaddset(&pending->signal, sig); // 置位 pending bitmap
complete_signal(sig, t, group); // 喚醒目標進程(設置 TIF_SIGPENDING,踢調度)
return 0;
}
| 特性 | 實現要點 |
|---|---|
| 信號集 | 64 位位圖,sigset_t 封裝,支持 32/64 位統一 |
| 處理函數表 | sighand_struct 引用計數共享,spinlock 保護 |
| Pending 隊列 | 私有 task->pending + 共享 signal->shared_pending |
| 非實時信號 | 不排隊,同一信號僅保留一個 pending 位 |
| 實時信號 | 分配 sigqueue 節點鏈表排隊,支持 siginfo_t 承載數據 |
| 發送權限 | kill_pid_info 通過 security_task_kill / check_kill_permission 校驗 |
| 信號處理時機 | 返回用戶態前 do_signal() / get_signal() 檢查 TIF_SIGPENDING |
掌握上述流程,不僅能理解 man 7 signal 文檔背後的內核邏輯,也能在排查「信號丟失、處理函數不生效、實時信號排隊阻塞」等疑難雜症時有的放矢。