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; //信号等待队列};
* 信号处理函数安装/**为了向后兼容。功能被 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 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() 以有趣的方式解释 pid,就像kill(2) 一样。** POSIX 指定kill(-1,sig) 是未指定的,但我们拥有的*可能是错误的。应该使其像 BSD 或 SYSV 一样。*/// 该函数根据不同的pid,进程不同的行为。主要把pid分为三类:// (1) pid > 0// (2) pid == -1// (3) 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) { //传入的进程号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. */ // 若信号是非实时信号( /* 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 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;}