linux 內核信號的實現和使用
把以前寫的一些東西發一下,和大家一起學習。
1, 基本數據結構
* linux信號數結構
下圖是《深入理解linux內核第3版》“信號”一章的圖
{task_struct }[...][signal]------------------------------[sighand][blocked][real_balocked][saved_sigmask][pending][notifier][notifier_mask][...]
* 信號處理數據結構struct sigaction { __sighandler_t sa_handler; //信號處理函數指針 unsigned long sa_flags; //信號標志位選項 __sigrestore_t sa_restorer; sigset_t sa_mask; /* mask last for extensibility */ //每};struct k_sigaction { struct sigaction sa;};
* 信號處理函數原型/* Type of a signal handler. */typedef void (*__sighandler_t)(int);
* 保存信號的值的結構根據機器的CPU的位數:. 若是32位,需要兩個長整數的數組(共64位);. 若是64位,只需要一個長整數的數組(也是64位);
#define _NSIG 64#ifdef __i386__# define _NSIG_BPW 32#else# define _NSIG_BPW 64#endif#define _NSIG_WORDS (_NSIG / _NSIG_BPW)typedef unsigned long old_sigset_t; /* at least 32 bits */// 保存信號值的bit位數組,每個位代表一個信號值typedef struct { unsigned long sig[_NSIG_WORDS]; //根據位數定義保存整數信號的值} sigset_t;
* 進程描述符中的信號處理結構struct sighand_struct { atomic_t count; struct k_sigaction action[_NSIG]; //每個信號值對應一個k_sigaction結構 spinlock_t siglock; //信號自旋鎖 wait_queue_head_t signalfd_wqh; //信號等待隊列};
* 信號處理函數安裝/** For backwards compatibility. Functionality superseded by sigaction.*/asmlinkage unsigned longsys_signal(int sig, __sighandler_t handler){ struct k_sigaction new_sa, old_sa; int ret; // 設置信號處理函數 new_sa.sa.sa_handler = handler; // 設置發送信號sig後就清除該信號和防止目前的信號被屏蔽的標志位 new_sa.sa.sa_flags = SA_ONESHOT | SA_NOMASK; // 先清空new_sa中的所有信號位 sigemptyset(&new_sa.sa.sa_mask); // 正在 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){ // 獲取目前的進程描述符地址,也就是直接獲取current中的地址 struct task_struct *t = current; struct k_sigaction *k; sigset_t mask; // 檢查信號的合法性: // (1) 若sig大於64或小於1,返回參數值錯誤 // (2) 若信號處理結構不空,且sig是SIGKILL或SIGSTOP返回參數錯誤,因為KILL和STOP是由內核處理,不能被屏蔽 if (!valid_signal(sig) || sig < 1 || (act && sig_kernel_only(sig))) return -EINVAL; // 獲取當前進程對sig信號的默認處理結構,保存在進程描述符的sighand變量中。 k = &t->sighand->action[sig-1]; // 用自旋鎖加鎖 spin_lock_irq(¤t->sighand->siglock); // 把當前進程的信號處理結構,賦值給一個臨時變量oact保存下來 if (oact) *oact = *k; // 若要設置的信號結構不為NULL if (act) { // 把SIGKILL和SIGSTOP信號從屏蔽信號字段中刪除,這兩個信號是不可屏蔽的 sigdelsetmask(&act->sa.sa_mask, sigmask(SIGKILL) | sigmask(SIGSTOP)); // 用新的信號處理結構,替換掉當前進程的進程處理結構(sighand) *k = *act;
/* * POSIX 3.3.1.3: * "Setting a signal action to SIG_IGN for a signal that is * pending shall cause the pending signal to be discarded, * whether or not it is blocked." * * "Setting a signal action to SIG_DFL for a signal that is * pending and whose default action is to ignore the signal * (for example, SIGCHLD), shall cause the pending signal to * be discarded, whether or not it is blocked" */ // (1) 若把一個等待信號的處理函數設置為SIG_IGN,會導致該信號丟失,無論該信號是否被阻塞。 // (2) 若把一個等待狀態且默認處理方式是忽略的信號(如SIGCHLD)的處理函數設置為SIG_DFL,那麼,無論該信號是否被阻塞,都可能導致等待的信號丟失。
// (1) 若信號的處理結構被設置為SIG_IGN,則直接返回TRUE。 // (2) 若處理函數設置為SIG_DFL且sig是內核忽略的信號,直接返回TRUE。 // 若信號的處理函數被設置成SIG_IGN,則把這些信號從進程描述符的shared_pending共享信號隊列中刪除。 if (sig_handler_ignored(sig_handler(t, sig), sig)) { // 清空mask信號bit位結構 sigemptyset(&mask); // 根據信號值,設置對應的信號位 sigaddset(&mask, sig); // 把sig信號從進程描述符的阻塞信號隊列中刪除。 rm_from_queue_full(&mask, &t->signal->shared_pending); // 把sig信號從當前進程描述符的pending隊列中刪除。 do { rm_from_queue_full(&mask, &t->pending); // 針對進程的線程,做同樣的處理。 t = next_thread(t); } while (t != current); } } spin_unlock_irq(¤t->sighand->siglock); return 0;}
* 信號信息結構typedef struct siginfo { int si_signo; //信號id,包括實時(id為32~64)和非實時信號(id為0~32) int si_errno; // int si_code; // union { int _pad[SI_PAD_SIZE]; /* kill() */ struct { pid_t _pid; /* sender's pid */ __ARCH_SI_UID_T _uid; /* sender's uid */ } _kill; /* POSIX.1b timers */ struct { timer_t _tid; /* timer id */ int _overrun; /* overrun count */ char _pad[sizeof( __ARCH_SI_UID_T) - sizeof(int)]; sigval_t _sigval; /* same as below */ int _sys_private; /* not to be passed to user */ } _timer;
/* POSIX.1b signals */ struct { pid_t _pid; /* sender's pid */ __ARCH_SI_UID_T _uid; /* sender's uid */ sigval_t _sigval; } _rt; /* SIGCHLD */ struct { pid_t _pid; /* which child */ __ARCH_SI_UID_T _uid; /* sender's uid */ int _status; /* exit code */ clock_t _utime; clock_t _stime; } _sigchld; /* SIGILL, SIGFPE, SIGSEGV, SIGBUS */ struct { void __user *_addr; /* faulting insn/memory ref. */#ifdef __ARCH_SI_TRAPNO int _trapno; /* TRAP # which caused the signal */#endif } _sigfault; /* SIGPOLL */ struct { __ARCH_SI_BAND_T _band; /* POLL_IN, POLL_OUT, POLL_MSG */ int _fd; } _sigpoll; } _sifields;} siginfo_t;
* 信號發送系統調用函數調用關系如下:sys_kill() -> kill_something_info() -> if(pid>0) kill_pid_info() -> if(pid!=-1) __kill_pgrp_info() -> else group_send_sig_info()
信號發送是通過系統調用long kill(pid_t pid, int sig);來實現的。現在我看看這個系統調用的實現:
asmlinkage longsys_kill(pid_t pid, int sig){ struct siginfo info; // 設置信號值 info.si_signo = sig; // 設置錯誤號為0 info.si_errno = 0; // 設置信號發送標示,0標示kill,raise等系統調用發送的;整數表示是內核發送的 info.si_code = SI_USER; // 設置信號進程號 info.si_pid = task_tgid_vnr(current); // 設置信號userid info.si_uid = current->uid; // 向pid發送信號 return kill_something_info(sig, &info, pid);}
* 發送信號中間函數/** kill_something_info() interprets pid in interesting ways just like kill(2).** POSIX specifies that kill(-1,sig) is unspecified, but what we have* is probably wrong. Should make it like BSD or SYSV.*/// 該函數根據不同的pid,進程不同的行為。主要把pid分為三類:// (1) pid > 0// (2) pid == -1// (3) pid <= 0static int kill_something_info(int sig, struct siginfo *info, pid_t pid){ int ret; if (pid > 0) { //若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) { //傳入的進程號<=0 ret = __kill_pgrp_info(sig, info, pid ? find_vpid(-pid) : task_pgrp(current)); } else { //傳入的進程號為-1 int retval = 0, count = 0; struct task_struct * p; 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){ int error = -ESRCH; struct task_struct *p; rcu_read_lock();retry: // 獲取pid對應的進程描述符結構(task_strcut *) p = pid_task(pid, PIDTYPE_PID); // 獲取成功 if (p) { // 向進程描述符p,發送信號 error = group_send_sig_info(sig, info, p); // 若發送信號失敗,且錯誤等於-ESRCH,重試。 // 注意:此時有可能進程已經不是原來的進程了,所以需要重新獲取一次task_struct。 if (unlikely(error == -ESRCH)) /* * The task was unhashed in between, try again. * If it is dead, pid_task() will return NULL, * if we race with de_thread() it will find the * new leader. */ goto retry; } rcu_read_unlock(); return error;}
. 發送信號函數最終的信號發送是由send_signal來實現的。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);
// 檢查信號是否可以被發送,若被發送的進程處於退出狀態,丟棄信號。 if (!prepare_signal(sig, t)) return 0; // 若是向單個進程發送信號,group是1,此時pending為共享阻塞信號處理 pending = group ? &t->signal->shared_pending : &t->pending; /* * Short-circuit ignored signals and support queuing * exactly one non-rt signal, so that we can get more * detailed information about the cause of the signal. */ // 若信號是非實時信號(<32),且信號已經發送,正在被處理/還沒有被處理,丟棄該信號。 if (legacy_queue(pending, sig)) return 0; /* * fast-pathed signals for kernel-internal things like SIGSTOP * or SIGKILL. */ if (info == SEND_SIG_FORCED) goto out_set;
/* Real-time signals must be queued if sent by sigqueue, or some other real-time mechanism. It is implementation defined whether kill() does so. We attempt to do so, on the principle of least surprise, but since kill is not allowed to fail with EAGAIN when low on memory we just make sure at least one signal gets delivered and don't pass on the info struct. */ 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_errno = 0; 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_errno = 0; q->info.si_code = SI_KERNEL; q->info.si_pid = 0; q->info.si_uid = 0; break; default: copy_siginfo(&q->info, info); break; }
} else if (!is_si_special(info)) { if (sig >= SIGRTMIN && info->si_code != SI_USER) /* * Queue overflow, abort. We may abort if the signal was rt * and sent by user using something other than kill(). */ return -EAGAIN; }out_set: signalfd_notify(t, sig); sigaddset(&pending->signal, sig); complete_signal(sig, t, group); return 0;}