Linux信号(Understanding Linux Kernel 3rd)
-
总览
-
产生
内核或应用程序产生,一份简短的信息。
-
传递
- 挂起状态
- 非挂起状态
-
信号类型
- 发给进程的信号(所有线程共享)
- 发给线程的信号
-
处理者
- 进程信号是其中一个没有屏蔽这个信号的线程处理。
- 线程就是指定线程处理。
-
处理方式
do_signal
处理- 创建对应的特定栈帧来处理。
-
信号处理函数
- 整个进程中的线程共享。
- 有默认也有自定义。
- 需要的信息也可以自定义。
-
-
信号的作用
-
简讯
- 一份简短的信息。
- 生产者是内核或进程。
- 处理者是具体的线程或者是进程组中符合处理条件的线程。
-
内容
- 标准的信号就只有一个数字。
32
位的整数,每一位对应一个信号位。- 支持实时信号的系统是
64
位,即两个long
.
-
信号定义
trap -l
-
用上面的指令罗列出支持的信号。
-
在编程中一般是宏定义,然后每个宏对应一个数字。
#define SIGHUP 1 #define SIGINT 2 #define SIGQUIT 3 #define SIGILL 4 #define SIGTRAP 5 #define SIGABRT 6 #define SIGIOT 6 #define SIGBUS 7 #define SIGFPE 8 #define SIGKILL 9 #define SIGUSR1 10 #define SIGSEGV 11 #define SIGUSR2 12 #define SIGPIPE 13 #define SIGALRM 14 #define SIGTERM 15 #define SIGSTKFLT 16 #define SIGCHLD 17 #define SIGCONT 18 #define SIGSTOP 19 #define SIGTSTP 20 #define SIGTTIN 21 #define SIGTTOU 22 #define SIGURG 23 #define SIGXCPU 24 #define SIGXFSZ 25 #define SIGVTALRM 26 #define SIGPROF 27 #define SIGWINCH 28 #define SIGIO 29 #define SIGPOLL SIGIO /* #define SIGLOST 29 */ #define SIGPWR 30 #define SIGSYS 31 #define SIGUNUSED 31 /* These should not be considered constants from userland. */ #define SIGRTMIN 32
-
-
目的
-
让某个进程或线程知道发生了什么特殊的事。
每个
signal
对应的有特定的事件。 -
让这个进程或线程处理这件事。
调用对应的信号处理函数。
-
-
信号对应的默认处理类型
-
终止
进程挂掉
-
产生
core
文件用于调试
-
忽略
不处理
-
继续执行
如果进程处于停止则继续执行。
-
停止
可再执行
-
-
信号与处理器架构有关
-
常规信号
1-31
挂起的时候不允许信号重复。
对于多次产生的信号,内核发现如果已经存在有对应的信号在挂起,则只接受第一个。
-
实时信号
32-64
不常用。
挂起的时候允许信号重复。
-
-
信号发送和接收
-
内核提供了许多的系统调用来产生信号。
-
rt_ == realtime
-
实时信号和普通信号的操作函数基本相同。
-
分类
- 发送给进程
- 发送给线程
- 发送给在某个特定线程组的进程。
- 修改信号处理函数
- 检测是否有挂起信号。
- 修改进程阻塞信号集合。
- 等待某个信号。
-
发送
-
任何时候都可以发送。
但是这个时候的进程的状态不确定。可能在任意状态。
-
状态对应处理方式
- 睡眠状态内核保留等待重新执行再发送。
- 信号阻塞将会被挂起,直到取消阻塞。
-
-
-
信号产生
- 内核直接修改目标进程结构体来表示信号已经发送。
-
信号交付
- 内核强制要求内核执行信号响应函数来响应这个信号。
- 如果处于睡眠可唤醒状态,则直接唤醒来响应。
- 交付了之后对对应位进行置位为0。对于多个相同的信号只递交一次。
-
挂起信号
- 生成但是还未交付给进程的信号叫做挂起信号。挂在进程中,挂起的信号分为私有和公有。
- 在添加信号时会检测新生成的信号是否已经存在,如果存在则丢掉。如果是实时信号则没有这种限制。
- 挂起的时长不确定。
-
交付信号可能出现的情况
-
信号交付只交付给正在执行的进程。
current
宏所指向的进程就是执行进程。 -
交付的信号可能被进程阻塞挂起,因此内核保存,等到执行并没有阻塞的时候交付。
-
执行某个信号的处理函数时,这个信号会被阻塞,直到处理完。
-
-
内核处理信号
-
哪些进程阻塞了哪些信号。
-
内核到用户态前,检测是否有信号已经到达,还没有处理。每次时钟中断都会检测。也就是说花费一次时间片可能会检测多次。因为权重可大可小。
-
信号是否忽略需要满足下面的所有条件。
-
目标进程不是出于调试状态。
-
目标进程没有阻塞信号。
-
发送的信号被目标进程显示或者隐式的忽略。
回调函数默认是
ignore
或者修改为了ignore
. -
-
处理信号将会强制的从进程执行代码转到执行信号处理函数。函数执行完了之后恢复执行。
-
-
进程响应信号的三种情况
-
显示忽略
-
执行默认操作
-
Terminate
挂掉进程
-
dump
挂掉并生成
core
-
ignore
隐式忽略
-
stop
进程被调试,修改执行状态为
TASK_STOPPED
状态。 -
continue
进程处于
TASK_STOPPED
状态,改为执行状态。TASK_RUNNING
-
-
捕获信号并调用对应的信号处理函数。
-
-
特殊信号
-
特殊的信号,
SIGKILL|SIGSTOP
不能被忽略,总是执行默认处理及杀死进程,也不能被捕获,不能被阻塞。拥有权限的用户可以用这两个信号执行杀死进程的操作,不管进程做了任务的防御都没有用。 -
执行成功的前提是有这个权限。而且这种方式对于进程是致命的。不友好的。
-
-
POSIX
标准的信号和多线程进程- 信号处理器线程间共享。
- 各个线程拥有自己的挂起和阻塞信号集合。
kill | sigqueue
函数发送的信号是给进程的,而不是某个特定线程。包括其他的信号。- 信号传递给进程之后,进程任意选择一个没有阻塞该信号的线程处理。
- 致命信号发送给多线程进城后,内核会杀死所有的线程。而不是接收到这个信号的线程。
-
私有信号和共享信号
- 内核支持给某个线程发送信号,发送给特定线程的信号是该线程的特有信号也叫私有信号,有一个私有信号队列。
- 共享信号则存放在这个线程的共享队列。
-
特殊进程
0
号进程不接受任何信号。1
号进程不会接受接收自己以外的信号。即自己造成的信号接受,其他的忽略。0
号永不死亡,1
号在init
程序挂了以后死亡。
-
相关结构体
-
系统内的进程,内核必须要追中,哪些信号被屏蔽,哪些被挂起。还要知道这些什么时候执行这些信号。内核将这些信号都放在了对应的结构体里面了。
-
进程描述符内
struct task_struct { ... // 共享的信号 struct signal_struct *signal; // 私有的信号 struct sigpending pending; // 信号处理函数 struct sighand_struct *sighand; // 阻塞的信号集合 sigset_t blocked; // 临时挂起信号 sigset_t real_blocked; sigset_t saved_sigmask; /* restored if set_restore_sigmask() was used */ ... };
-
共享信号
struct signal_struct { ... // 共享的 struct sigpending shared_pending; /* * We don"t bother to synchronize most readers of this at all, * because there is no reader checking a limit that actually needs * to get both rlim_cur and rlim_max atomically, and either one * alone is a single word that can safely be read normally. * getrlimit/setrlimit use task_lock(current->group_leader) to * protect this instead of the siglock, because they really * have no need to disable irqs. */ struct rlimit rlim[RLIM_NLIMITS]; ... };
- 结构体包含的不仅仅是和信号相关的,还有其他的比如说每个处理器的资源限制。
- 还有
pgrp
存放的是线程组第一线程的进程号。 session
存放着第一session
的进程号。
-
共有和私有挂起信号队列
struct sigpending { struct list_head list; sigset_t signal; }; struct sigqueue { struct list_head list; int flags; siginfo_t info; struct user_struct *user; };
-
发送信号的同时可能有多个。发送的对象可能有多个。需要追踪挂起的信号。
-
信号有两种,多进程共享的共有挂起信号,每个线程独有的私有挂起信号。
-
list
双向循环链表
-
lock
指向
sighandler
的lock
. -
flags
表示
sigqueue
的数据机构 -
siginfo_t
信号原因和一些必要信息。
-
user
进程所有者的用户信息。
-
-
各个信号对应的处理函数
struct sighand_struct { // 用了几个 atomic_t count; // 响应信号的函数 struct k_sigaction action[_NSIG]; // 保护信号描述符和信号处理函数 spinlock_t siglock; wait_queue_head_t signalfd_wqh; }; struct k_sigaction { struct sigaction sa; }; struct sigaction { __sighandler_t sa_handler; unsigned long sa_flags; __sigrestore_t sa_restorer; sigset_t sa_mask; /* mask last for extensibility */ };
-
sa_handler
SIGDFL=0
默认处理SIGIGN=1
忽略- 指针 处理函数
-
sa_flags
应该处理信号-
SA_NOCLDSTOP
应用于信号
SIGCHLD
,子进程停止的时候不要发送信号给父进程。 -
SA_NOCHLDWAIT
应用于信号
SIGCHLD
,子进程停止的时候不要创建僵尸进程,直接死亡。表示父进程对子进程的死亡没有兴趣。 -
SA_SIGINFO
- 提供详细信息给信号处理函数。常用的。
-
SA_ONSTACK
提供一个可选的栈给信号处理器。
-
SA_RESART
被中断的系统调用会重启。
-
SA_NODEFER|SA_NOMASK
执行处理函数的时候也不屏蔽信号。
丢掉还是直接替换?
-
SA_RESETHAND|SA_ONESHOT
信号处理函数执行一次后就恢复为默认的。
-
-
sa_mask
执行处理器的时候应该屏蔽那些信号。
-
-
信号集结构体
#ifdef __KERNEL__ /* Digital Unix defines 64 signals. Most things should be clean enough to redefine this at will, if care is taken to make libc match. */ #define _NSIG 64 #define _NSIG_BPW 64 #define _NSIG_WORDS (_NSIG / _NSIG_BPW) typedef unsigned long old_sigset_t; /* at least 32 bits */ typedef struct { unsigned long sig[_NSIG_WORDS]; } sigset_t; #else /* Here we must cater to libcs that poke about in kernel headers. */ #define NSIG 32 typedef unsigned long sigset_t; #endif /* __KERNEL__ */
-
-
信号发起者信息
typedef struct siginfo { int si_signo; int si_errno; int si_code; int __pad0; union { int _pad[SI_PAD_SIZE]; /* kill() */ struct { pid_t _pid; /* sender"s pid */ 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; /* must overlay ._rt._sigval! */ int _sys_private; /* not to be passed to user */ } _timer; /* POSIX.1b signals */ struct { pid_t _pid; /* sender"s pid */ uid_t _uid; /* sender"s uid */ sigval_t _sigval; } _rt; /* SIGCHLD */ struct { pid_t _pid; /* which child */ 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. */ int _imm; /* immediate value for "break" */ unsigned int _flags; /* see below */ unsigned long _isr; /* isr */ short _addr_lsb; /* lsb of faulting address */ } _sigfault; /* SIGPOLL */ struct { long _band; /* POLL_IN, POLL_OUT, POLL_MSG (XPG requires a "long") */ int _fd; } _sigpoll; } _sifields; } siginfo_t;
si_signo
中断编号
si_errno
指令导致信号发起的错误代码。0表示没有错误。
si_code
造成的原因,和信号值有关。
/* Values for `si_code". Positive values are reserved for kernel-generated signals. */ enum { SI_ASYNCNL = -60, /* Sent by asynch name lookup completion. */ # define SI_ASYNCNL SI_ASYNCNL SI_TKILL = -6, /* Sent by tkill. */ # define SI_TKILL SI_TKILL SI_SIGIO, /* Sent by queued SIGIO. */ # define SI_SIGIO SI_SIGIO SI_ASYNCIO, /* Sent by AIO completion. */ # define SI_ASYNCIO SI_ASYNCIO SI_MESGQ, /* Sent by real time mesq state change. */ # define SI_MESGQ SI_MESGQ SI_TIMER, /* Sent by timer expiration. */ # define SI_TIMER SI_TIMER SI_QUEUE, /* Sent by sigqueue. */ # define SI_QUEUE SI_QUEUE SI_USER, /* Sent by kill, sigsend. */ # define SI_USER SI_USER SI_KERNEL = 0x80 /* Send by kernel. */ #define SI_KERNEL SI_KERNEL }; /* `si_code" values for SIGILL signal. */ enum { ILL_ILLOPC = 1, /* Illegal opcode. */ # define ILL_ILLOPC ILL_ILLOPC ILL_ILLOPN, /* Illegal operand. */ # define ILL_ILLOPN ILL_ILLOPN ILL_ILLADR, /* Illegal addressing mode. */ # define ILL_ILLADR ILL_ILLADR ILL_ILLTRP, /* Illegal trap. */ # define ILL_ILLTRP ILL_ILLTRP ILL_PRVOPC, /* Privileged opcode. */ # define ILL_PRVOPC ILL_PRVOPC ILL_PRVREG, /* Privileged register. */ # define ILL_PRVREG ILL_PRVREG ILL_COPROC, /* Coprocessor error. */ # define ILL_COPROC ILL_COPROC ILL_BADSTK /* Internal stack error. */ # define ILL_BADSTK ILL_BADSTK }; /* `si_code" values for SIGFPE signal. */ enum { FPE_INTDIV = 1, /* Integer divide by zero. */ # define FPE_INTDIV FPE_INTDIV FPE_INTOVF, /* Integer overflow. */ # define FPE_INTOVF FPE_INTOVF FPE_FLTDIV, /* Floating point divide by zero. */ # define FPE_FLTDIV FPE_FLTDIV FPE_FLTOVF, /* Floating point overflow. */ # define FPE_FLTOVF FPE_FLTOVF FPE_FLTUND, /* Floating point underflow. */ # define FPE_FLTUND FPE_FLTUND FPE_FLTRES, /* Floating point inexact result. */ # define FPE_FLTRES FPE_FLTRES FPE_FLTINV, /* Floating point invalid operation. */ # define FPE_FLTINV FPE_FLTINV FPE_FLTSUB /* Subscript out of range. */ # define FPE_FLTSUB FPE_FLTSUB }; /* `si_code" values for SIGSEGV signal. */ enum { SEGV_MAPERR = 1, /* Address not mapped to object. */ # define SEGV_MAPERR SEGV_MAPERR SEGV_ACCERR /* Invalid permissions for mapped object. */ # define SEGV_ACCERR SEGV_ACCERR }; /* `si_code" values for SIGBUS signal. */ enum { BUS_ADRALN = 1, /* Invalid address alignment. */ # define BUS_ADRALN BUS_ADRALN BUS_ADRERR, /* Non-existant physical address. */ # define BUS_ADRERR BUS_ADRERR BUS_OBJERR /* Object specific hardware error. */ # define BUS_OBJERR BUS_OBJERR }; /* `si_code" values for SIGTRAP signal. */ enum { TRAP_BRKPT = 1, /* Process breakpoint. */ # define TRAP_BRKPT TRAP_BRKPT TRAP_TRACE /* Process trace trap. */ # define TRAP_TRACE TRAP_TRACE }; /* `si_code" values for SIGCHLD signal. */ enum { CLD_EXITED = 1, /* Child has exited. */ # define CLD_EXITED CLD_EXITED CLD_KILLED, /* Child was killed. */ # define CLD_KILLED CLD_KILLED CLD_DUMPED, /* Child terminated abnormally. */ # define CLD_DUMPED CLD_DUMPED CLD_TRAPPED, /* Traced child has trapped. */ # define CLD_TRAPPED CLD_TRAPPED CLD_STOPPED, /* Child has stopped. */ # define CLD_STOPPED CLD_STOPPED CLD_CONTINUED /* Stopped child has continued. */ # define CLD_CONTINUED CLD_CONTINUED }; /* `si_code" values for SIGPOLL signal. */ enum { POLL_IN = 1, /* Data input available. */ # define POLL_IN POLL_IN POLL_OUT, /* Output buffers available. */ # define POLL_OUT POLL_OUT POLL_MSG, /* Input message available. */ # define POLL_MSG POLL_MSG POLL_ERR, /* I/O error. */ # define POLL_ERR POLL_ERR POLL_PRI, /* High priority input available. */ # define POLL_PRI POLL_PRI POLL_HUP /* Device disconnected. */ # define POLL_HUP POLL_HUP };
_sifields
一个共享体,根据信号的不同,使用变量不同。
-
-
生成信号
-
生成的两个步骤
-
第一步是更新目标进程的值。
-
第二步传递,一般不直接执行,而是根据目标的状态和信号代码来决定。可能会唤醒,可能会强制进程接受信号。
-
-
内核或函数生成信号都是通过下面的几种函数生成的
send_sig(): Sends a signal to a single process send_sig_info(): Like send_sig(), with extended information in a siginfo_t structure force_sig(): Sends a signal that cannot be explicitly ignored or blocked by the process force_sig_info(): Like force_sig(), with extended information in a siginfo_t structure force_sig_specific(): Like force_sig(), but optimized for SIGSTOP and SIGKILL signals sys_tkill(): System call handler of tkill() (see the later section “System Calls Related to Signal Handling”) sys_tgkill(): System call handler of tgkill()
-
上面所有的函数都会调用一个函数
specific_send_sig_info
-
-
内核或者应用产生的信号发送给进程都是通过下面几个函数发送的
send_group_sig_info(): Sends a signal to a single thread group identified by the process descriptor of one of its members kill_pg(): Sends a signal to all thread groups in a process group (see the section “Process Management” in Chapter 1) kill_pg_info(): Like kill_pg(), with extended information in a siginfo_t structure kill_proc(): Sends a signal to a single thread group identified by the PID of one of its members kill_proc_info(): Like kill_proc(), with extended information in a siginfo_t structure sys_kill(): System call handler of kill() (see the later section “System Calls Related to Signal Handling”) sys_rt_sigqueueinfo(): System call handler of rt_sigqueueinfo()
-
所有的函数都会调用函数
group_send_sig_info
-
-
specific_send_sig_info
static int specific_send_sig_info(int sig, struct siginfo *info, struct task_struct *t) { return send_signal(sig, info, t, 0); } static int send_signal(int sig, struct siginfo *info, struct task_struct *t, int group) { int from_ancestor_ns = 0; #ifdef CONFIG_PID_NS from_ancestor_ns = si_fromuser(info) && !task_pid_nr_ns(current, task_active_pid_ns(t)); #endif return __send_signal(sig, info, t, group, from_ancestor_ns); } static int __send_signal(int sig, struct siginfo *info, struct task_struct *t, int group, int from_ancestor_ns) { struct sigpending *pending; struct sigqueue *q; int override_rlimit; trace_signal_generate(sig, info, t); assert_spin_locked(&t->sighand->siglock); if (!prepare_signal(sig, t, from_ancestor_ns)) return 0; 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. */ 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. */ if (sig < SIGRTMIN) override_rlimit = (is_si_special(info) || info->si_code >= 0); else override_rlimit = 0; q = __sigqueue_alloc(sig, t, GFP_ATOMIC | __GFP_NOTRACK_FALSE_POSITIVE, override_rlimit); 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_tgid_nr_ns(current, task_active_pid_ns(t)); 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); if (from_ancestor_ns) q->info.si_pid = 0; 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(). */ trace_signal_overflow_fail(sig, group, info); return -EAGAIN; } else { /* * This is a silent loss of information. We still * send the signal, but the *info bits are lost. */ trace_signal_lose_info(sig, group, info); } } out_set: signalfd_notify(t, sig); sigaddset(&pending->signal, sig); complete_signal(sig, t, group); return 0; }
-
specific_send_sig_info
参数-
sig
信号编号
-
info
0
表示由用户态用户发送1
表示由内核发送。2
表示由内核发送,而且信号还是SIGSTOP|SIGKILL
- 指针
-
t
目标进程
-
-
调用前后
/* * Force a signal that the process can"t ignore: if necessary * we unblock the signal and change any SIG_IGN to SIG_DFL. * * Note: If we unblock the signal, we always reset it to SIG_DFL, * since we do not want to have a signal handler that was blocked * be invoked when user space had explicitly blocked it. * * We don"t want to have recursive SIGSEGV"s etc, for example, * that is why we also clear SIGNAL_UNKILLABLE. */ int force_sig_info(int sig, struct siginfo *info, struct task_struct *t) { unsigned long int flags; int ret, blocked, ignored; struct k_sigaction *action; //获取自旋锁并禁用中断 spin_lock_irqsave(&t->sighand->siglock, flags); action = &t->sighand->action[sig-1]; ignored = action->sa.sa_handler == SIG_IGN; blocked = sigismember(&t->blocked, sig); if (blocked || ignored) { action->sa.sa_handler = SIG_DFL; if (blocked) { sigdelset(&t->blocked, sig); recalc_sigpending_and_wake(t); } } if (action->sa.sa_handler == SIG_DFL) t->signal->flags &= ~SIGNAL_UNKILLABLE; ret = specific_send_sig_info(sig, info, t); //释放自旋锁并回复中断 spin_unlock_irqrestore(&t->sighand->siglock, flags); return ret; }
- 调用前需要禁用本地的中断并且还获取到了自旋锁。调用完后释放自旋锁并恢复之前的中断状态。
-
大致步骤
-
检测目标进程是否忽略了这个信号。忽略返回0。满足下面所有条件会忽略。
-
没有被调试。
调试了gdb会获取
-
没有被阻塞。
阻塞了则会挂起
-
信号被显式或隐式的忽略。
没有被忽略则会处理。
-
-
检测信号是否是实时信号,或者是已经挂起则返回0.
-
send_signal
添加信号到挂起信号集中。 -
如果不是阻塞信号则调用
signal_wake_up
函数告诉进程信号的到来。-
t->thread_info->flags |= TIF_SIGPENDING
表示有信号需要处理。 -
如果是
SIGKILL
,并且目标进程处于TASK_ INTERRUPTIBLE
或者是TASK_STOPPED
调用try_to_wake_up
唤醒进程。 -
如果
try_to_wake_up
返回值为0,表示进程正在执行。- 如果进程在其他的处理器执行,则发起处理器间中断,
- 然后切换该进程到另一个处理器。
- 进程切换后的后检测是否有信号挂起,如果有就处理信号。
-
-
成功在目标进程描述符挂起信号后返回1.
-
-
-
send_signal
-
作用
插入新的信号到挂起队列。
-
参数
-
信号值
sig
-
信号信息
info
值或者指针,同前。
-
目标进程
t
-
挂起信号队列。
signals
-
-
执行步骤
info
参数为2表示内核强制杀死,信号为SIGKILL|SIGSTOP
。则直接跳转到9。- 挂起信号已达到资源上限
t->user->sigpendding
,用户进程都有资源上限,则跳转到9。 - 从内存分配一个
sig_queue
结构体,存放信息。 - 增长资源计数
t->user->sigpendding
,增长t->user
的引用计数。 - 添加信号结构体到挂起队列中。
- 填充信号信息结构体。
- 添加对应的信号位到信号集合中。
- 成功完成返回0.
- 前面步骤可能出现,挂起太多,内存不够,信号是强制执行信号。
- 添加信号到信号集。
- 返回0.即使挂起到队列失败,对应的位也要设置在
mask
中。
-
即使没有内存存储信息也要保证目标进程获取到信号。这样有助于恢复系统,保护系统的稳定性。
-
-
group_send_sig_info
-
作用
- 发送信号给一个进程组。
-
参数
- 信号值
sig
- 信号信息
info
或012
如前。 - 目标进程
p
-
步骤
-
信号值合法检测。
-
检测信号发送者是否有权限。
-
信号值不能为0,且进程没有正在被杀死。
-
获取自旋锁并禁用当前处理器中断。
-
调用
handle_stop_signal
函数,检查新来的信号是否会让某些信号无效。- 如果死进程,则返回。
- 如果是
STOP|TSTP|TTIN|TTOUT
,则会调用函数rm_from_queue
删除共享和私有队列中挂起的SIGCONT
。 - 如果是
SIGCONT
则会调用rm_from_queue
删除共享和私有队列中挂起的STOP|TSTP|TTIN|TTOUT
.并且唤醒进程。
-
如果忽略信号返回0。忽略信号同前的步骤1。
-
如果是非实时信号且已经被挂起,则返回0.
-
调用
send_signal
添加信号到队列中。如果返回是个非0,则同样返回。 -
调用
__group_complete_signal
函数唤醒其中一个线程来处理。 -
释放自旋锁恢复当前处理器之前的中断状态。
-
返回0表示成功处理。
-
-
_ _group_complete_signal
选择处理函数-
作用
- 唤醒合适的线程来处理新的信号。
-
线程满足条件
-
线程没有阻塞这个信号。
-
线程没有状态不是
EXIT_ZOMBIE, EXIT_DEAD, TASK_TRACED, or TASK_STOPPED
如果信号是
SIGKILL
线程可以是TASK_TRACED or TASK_STOPPED
-
线程没有被杀死
PF_EXITING
-
要么是正在执行的线程,要么是没有被设置
TIF_SIGPENDING
.- 唤醒有挂起信号的进程毫无意义。
- 正在执行的进程也可以处理这个挂起信号。
-
-
多个满足条件的线程中选择
- 如果传入的目标进程也满足,则用这个传入的
p
- 不然就搜索线程组,从接受到上一个共享信号的线程开始。
- 如果传入的目标进程也满足,则用这个传入的
-
成功找到处理线程就可以开始传递信号
- 信号检测是否为指明信号,如果是则杀死所有。
- 不是则唤醒合适的线程通知其有新的信号需要处理。
-
-
-
传递信号
-
内核处理信号
- 通过系统调用得知有信号传递,通过传递的信号值和目标进程中的目标进程
pid
来获取对应进程的进程描述符。 - 因为信号可能在任意时刻发送,所以目标进程处于什么状态是未知的。
- 在多处理器主机中,如果目标进程没有执行内核将会延迟递交信号。
- 通过系统调用得知有信号传递,通过传递的信号值和目标进程中的目标进程
-
中断上下文切换与信号处理
- 从内核中断返回用户进程之前,会检测目标进程是否有信号未处理。即通过成员变量的值是否为
SIGPENDING
来确认。 - 内核每次处理完中断或者异常
(处理器产生的软中断)
都会检测是否有 - 中断常见的是时钟中断,当然也有其他的中断,而异常就是非法操作,导致处理器产生的异常。比如异常访问地址,除0操作。
- 从内核中断返回用户进程之前,会检测目标进程是否有信号未处理。即通过成员变量的值是否为
-
非阻塞信号处理
-
使用
do_signal
函数处理非阻塞信号。 -
参数
-
regs
寄存器信息
-
oldset
非空用于返回阻塞信号掩码。
-
-
主要目的
- 简单的讲讲机制,实现很复杂。
-
-
do_signal
调用场景- 从内核中断返回用户进程。
- 中断函数调用,则是简单的返回。
-
do_signal
核心-
循环,不断的调用
dequeue_signal
函数,直到公有信号队列和私有信号队列都没有挂起信号为止。 -
dequeue_signal
返回值是刚刚删除的挂起信号值。返回值为0表示处理完毕。 -
先处理私有再处理公共。返回的信号值会清理对应的掩码。还会调用
recalc_sigpending();
调整当前的状态。
-
-
检测目标进程
-
是否被调试
是则调用
do_notify_parent_cldstop and schedule
函数,通知调试进程。
-
-
dequeue_signal
返回值signr
- 根据返回值确认目标进程的处理方式。
k_sigaction ka = ¤t->sig->action[signr-1];
- 动作分三类,前面有介绍:忽略,默认,执行处理函数。
if (ka->sa.sa_handler == SIG_IGN) continue;
-
默认操作
// 处理默认 if(ka->sa.sa_handler == SIG_DFL) do_default_action(); // 1号进程忽略信号 if (current->pid == 1) continue; // 忽略操作 if (signr==SIGCONT || signr==SIGCHLD || signr==SIGWINCH || signr==SIGURG) continue; // 停止操作停止进程,设置进程为`TASK_STOPPED`。如果是其他三个,且是孤儿进程则继续。 // SIGSTOP总是停止,其他的三个不在孤儿进程再回执行退出。 if (signr==SIGSTOP || signr==SIGTSTP || signr==SIGTTIN || signr==SIGTTOU) { if (signr != SIGSTOP && is_orphaned_pgrp(current->signal->pgrp)) continue; do_signal_stop(signr); } // 检测当前进程是否是第一个关闭线程,是则发起组关闭。 // 唤醒迭代然后修改状态杀死进程,向父进程发起通知。 do_signal_stop();
-
dump
创建一个
core.pid
文件在进程执行路径。文件包含当时的及才能拿地址空间和处理器。
以及一些必要的信息。
然后执行杀死进程操作。
-
自定义函数
- 调用执行。
handle_signal(signr, &info, &ka, oldset, regs); if (ka->sa.sa_flags & SA_ONESHOT) ka->sa.sa_handler = SIG_DFL; return 1;
- 如果是一次性的,改回默认。
- 一次处理一个信号,这样可以保证实时信号能够正确的处理。
-
信号处理函数与上下文切换
-
中断是在内核执行,信号处理函数是用户代码,在用户空间执行。
-
切换涉及到了进程上下文切换。而且还是频繁的交错切换。
-
每次切换到内核态的时候,内核栈都会被清空。
-
如果是还涉及到了系统调用,就更加复杂,因为还要切换回执行的调用函数而不是应用程序。
-
解决方案
- 将内核的上下文,以及内核栈拷贝到函数调用的用户进程栈中。执行完了之后执行相反的操作恢复之前的上下文。
kenerl mode user Mode continue_work_flow do_signal() handle_signal() setup_frame() signal_handler() system_call() sys_sigreturn() restore_sigcontext() continue_work_flow
-
中断返回当前执行进程执行
do_signal
- 如果是可以唤醒就强制唤醒立即执行。
- 不可以唤醒则是在唤醒之后第一时间执行这个函数。
-
非阻塞信号挂起到进程后。这个进程当从中断或者异常中返回时,就会检测这个
do_signal
函数来处理信号。通过setup_frame or setup_rt_frame
创建一个用户栈帧,一般有多个栈帧(类比gdb,主要是函数嵌套)
。 -
修改了指针计数器到处理函数地址,修改了处理函数代码。
-
执行完了处理函数,再执行
setup_frame or setup_rt_frame
添加的额外代码。 -
这些代码调用了
sigreturn or rt_sigreturn
,这个函数调用restore_sigcontext
拷贝之前存在用户进程栈的上下文,栈信息到内核上。 -
执行完了系统调用就返回常规的进程,继续执行。
(这里之前的进程就在执行或者是马上就要执行。)
-
-