首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Linux 內核信號機制全景解析:從數據結構到發送/處理流程

     編輯:Java 綜合教學

前言:信號是進程間通信的基石

Linux 信號機制承載著異步事件通知、進程控制、錯誤處理等核心功能。本文系統梳理 task_struct 相關數據結構、sigaction 安裝流程、siginfo_t 信息承載、kill() 系統調用遞歸調用鏈,以及核心發送函數 send_signal() 的實現邏輯,幫助開發者建立完整的內核級信號認知。

一、核心數據結構圖譜

1.1 進程描述符中的信號相關字段

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
    // ...
};

1.2 信號處理函數描述符

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;           // 執行處理函數時臨時阻塞的信號集
};

1.3 信號集實現(位圖)

#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;

1.4 共享處理函數表

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 等待隊列
};

1.5 信號詳細信息結構

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;

二、信號處理函數安裝:sys_signal → do_sigaction

2.1 兼容性入口 sys_signal

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;
}

2.2 核心實現 do_sigaction

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() → 向進程組發送

3.1 sys_kill 構造 siginfo

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);
}

3.2 kill_something_info 分發邏輯

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;
}

3.3 單進程發送:kill_pid_info → group_send_sig_info

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()

所有路徑最終匯聚到 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 文檔背後的內核邏輯,也能在排查「信號丟失、處理函數不生效、實時信號排隊阻塞」等疑難雜症時有的放矢。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved