【进程通信】信号处理机制二——内核实现
十二月 27, 2023 [Linux 与系统] #Linux #Shell 与命令行 #obsidian #操作系统原理1 信号处理的完整流程
- 用户输入命令,在Shell下启动一个前台进程。
- 用户按下Ctrl-C,这个键盘输入产生一个硬件中断。
- 如果CPU当前正在执行这个进程的代码,则该进程的用户空间代码暂停执行,CPU从用户态切换到内核态处理硬件中断。
- 终端驱动程序将Ctrl-C解释成一个SIGINT信号,记在该进程的PCB中(也可以说发送了一个SIGINT信号给该进程)。
- 当某个时刻要从内核返回到该进程的用户空间代码继续执行之前,首先处理PCB中记录的信号,发现有一个SIGINT信号待处理,而这个信号的默认处理动作是终止进程,所以直接终止进程而不再返回它的用户空间代码执行。
2 信号相关数据结构
struct task_struct {
/* Signal handlers: */
struct signal_struct *signal;
struct sighand_struct *sighand;
sigset_t blocked;
sigset_t real_blocked;
/* Restored if set_restore_sigmask() was used: */
sigset_t saved_sigmask;
struct sigpending pending;
}
其中成员 pending 表示进程是否有信号需要处理(1表示有,0表示没有)。成员 blocked 表示被屏蔽的信息,每个位代表一个被屏蔽的信号。成员 sighand 表示信号相应的处理方法。
struct sighand_struct {
atomic_t count;
struct k_sigaction action[_NSIG];
spinlock_t siglock;
wait_queue_head_t signalfd_wqh;
};
sigaction
typedef void __signalfn_t(int);
typedef __signalfn_t __user *__sighandler_t;
struct sigaction {
#ifndef __ARCH_HAS_IRIX_SIGACTION
__sighandler_t sa_handler;
unsigned long sa_flags;
#else
unsigned int sa_flags;
__sighandler_t sa_handler;
#endif
#ifdef __ARCH_HAS_SA_RESTORER
__sigrestore_t sa_restorer;
#endif
sigset_t sa_mask; /* mask last for extensibility */
};
struct k_sigaction {
struct sigaction sa;
#ifdef __ARCH_HAS_KA_RESTORER
__sigrestore_t ka_restorer;
#endif
};
3.1 kill系统调用
/**
* sys_kill - send a signal to a process
* @pid: the PID of the process
* @sig: signal to be sent
*/
SYSCALL_DEFINE2(kill, pid_t, pid, int, sig)
{
struct siginfo info;
info.si_signo = sig;
info.si_errno = 0;
info.si_code = SI_USER;
info.si_pid = task_tgid_vnr(current);
info.si_uid = from_kuid_munged(current_user_ns(), current_uid());
return kill_something_info(sig, &info, pid);
}
3.2 kill_something_info
kill_something_info() 函数根据传入pid 的不同来进行不同的操作,有如下4种可能:
pid等于0时,向当前进程组发送信号。pid大于零时,那么调用函数kill_pid_info来向线程pid所属的线程组发送信号。pid等于-1时,向除了进程1(init)和自己以外的所有进程发送信号。pid小于-1时,信号将送往以-pid为组标识的进程。
static int kill_something_info(int sig, struct siginfo *info, pid_t pid)
{
int ret;
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 {
int retval = 0, count = 0;
struct task_struct * p;
for_each_process(p) {
if (task_pid_vnr(p) > 1 &&!same_thread_group(p, current)) {
int err = group_send_sig_info(sig, info, p);
++count;
}
}
ret = count ? retval : -ESRCH;
}
read_unlock(&tasklist_lock);
return ret;
}
3.2.1 kill_pid_info
向某个进程发送信号
int kill_pid_info(int sig, struct siginfo *info, struct pid *pid)
{
int error = -ESRCH;
struct task_struct *p;
for (;;) {
rcu_read_lock();
p = pid_task(pid, PIDTYPE_PID);
if (p)
error = group_send_sig_info(sig, info, p);
rcu_read_unlock();
if (likely(!p || error != -ESRCH))
return error;
/*
* 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.
*/
}
}
3.2.2 __kill_pgrp_info
向某个进程组发送信号,遍历进程组里的所有进程都发送信号。
int __kill_pgrp_info(int sig, struct siginfo *info, struct pid *pgrp)
{
struct task_struct *p = NULL;
int retval, success;
success = 0;
retval = -ESRCH;
do_each_pid_task(pgrp, PIDTYPE_PGID, p) {
int err = group_send_sig_info(sig, info, p);
success |= !err;
retval = err;
} while_each_pid_task(pgrp, PIDTYPE_PGID, p);
return success ? 0 : retval;
}
3.3 __send_signal
group_send_sig_info
->do_send_sig_info
->send_signal
->__send_signal
static int __send_signal(int sig, struct siginfo *info, struct task_struct *t, int group, int from_ancestor_ns)
{
//如果目标线程忽略信号,那么没必要发送信号。
if (!prepare_signal(sig, t, from_ancestor_ns || (info == SEND_SIG_FORCED)))
goto ret;
//确定把信号添加到哪个信号队列和集合。线程组有一个共享的信号队列和集合,
//每个线程有一个私有的信号队列和集合。如果向线程组发送信号,
//那么应该把信号添加到线程组共享的信号队列和集合中;
//如果向线程发送信号,那么应该把信号添加到线程私有的信号队列和集合中。
pending = group ? &t->signal->shared_pending : &t->pending;
//如果是传统信号,并且信号集合已经包含同一个信号,那么没必要重复发送信号
result = TRACE_SIGNAL_ALREADY_PENDING;
if (legacy_queue(pending, sig))
goto ret;
//判断分配信号队列节点时是否可以忽略信号队列长度的限制:
//对于传统信号,如果是特殊的信号信息,或者信号的编码大于0,那么允许忽略;
//如果是实时信号,那么不允许忽略。
if (sig < SIGRTMIN)
override_rlimit = (is_si_special(info) || info->si_code >= 0);
else
override_rlimit = 0;
//分配一个信号队列节点。
q = __sigqueue_alloc(sig, t, GFP_ATOMIC, override_rlimit);
//如果分配信号队列节点成功,那么把它添加到信号队列中。
list_add_tail(&q->list, &pending->list);
//如果某个进程正在通过信号文件描述符(signalfd)监听信号,那么通知进程。
//signalfd是进程创建用来接收信号的文件描述符,进程可以使用select或poll监听信号文件描述符。
signalfd_notify(t, sig);
//把信号添加到信号集合中
sigaddset(&pending->signal, sig);
//如果向线程组发送信号,那么需要在线程组中查找一个没有屏蔽信号的线程,
//唤醒它,让它处理信号。
complete_signal(sig, t, group);
}
sigqueue_cachep = KMEM_CACHE(sigqueue, SLAB_PANIC);
struct sigqueue *__sigqueue_alloc(int sig, struct task_struct *t, gfp_t flags, int override_rlimit)
{
struct user_struct *user = get_uid(__task_cred(t)->user);
atomic_inc(&user->sigpending);;
struct sigqueue *q = kmem_cache_alloc(sigqueue_cachep, flags);
INIT_LIST_HEAD(&q->list);
q->flags = 0;
q->user = user;
return q;
}
3.4 complete_signal
如果向线程组发送信号,那么需要在线程组中查找一个没有屏蔽信号的线程, 唤醒它,让它处理信号。
static void complete_signal(int sig, struct task_struct *p, int group)
{
struct signal_struct *signal = p->signal;
struct task_struct *t;
/* Now find a thread we can wake up to take the signal off the queue.*/
t = find_wants_signal_task((sig, p));
/* Found a killable thread. If the signal will be fatal,
* then start taking the whole group down immediately. */
if (sig_killable(p, sig)) {
task_clear_jobctl_pending(t, JOBCTL_PENDING_MASK);
//加入到pending signal
sigaddset(&t->pending.signal, SIGKILL);
//唤醒task
signal_wake_up(t, 1);
return;
}
/*
* The signal is already in the shared-pending queue.
* Tell the chosen thread to wake up and dequeue it.
*/
signal_wake_up(t, sig == SIGKILL);
return;
}
4 内核触发信号处理函数
为了尽快让信号得到处理,Linux把信号处理过程放置在进程从内核态返回到用户态前,也就是在 ret_to_user 处:
ret_to_user:
/* Make sure need_resched and sigpending don't change between
sampling and the rti. */
lda $16, 7
call_pal PAL_swpipl
ldl $17, TI_FLAGS($8)
and $17, _TIF_WORK_MASK, $2
bne $2, work_pending
work_pending
work_pending:
mov x0, sp // 'regs'
bl do_notify_resume
#ifdef CONFIG_TRACE_IRQFLAGS
bl trace_hardirqs_on // enabled while in userspace
#endif
ldr x1, [tsk, #TSK_TI_FLAGS] // re-check for single-step
b finish_ret_to_user
4. 1 do_notify_resume
void do_notify_resume(struct pt_regs *regs, unsigned int thread_flags)
{
do {
if (thread_flags & _TIF_NEED_RESCHED) {
/* Unmask Debug and SError for the next task */
local_daif_restore(DAIF_PROCCTX_NOIRQ);
schedule();
} else {
local_daif_restore(DAIF_PROCCTX);
if (thread_flags & _TIF_UPROBE)
uprobe_notify_resume(regs);
if (thread_flags & _TIF_SIGPENDING)
do_signal(regs);
if (thread_flags & _TIF_NOTIFY_RESUME) {
clear_thread_flag(TIF_NOTIFY_RESUME);
tracehook_notify_resume(regs);
}
if (thread_flags & _TIF_FOREIGN_FPSTATE)
fpsimd_restore_current_state();
}
local_daif_mask();
thread_flags = READ_ONCE(current_thread_info()->flags);
} while (thread_flags & _TIF_WORK_MASK);
}
4.2 do_signal
static void do_signal(struct pt_regs *regs)
{
if (get_signal(&ksig)) {
/*
* Depending on the signal settings, we may need to revert the
* decision to restart the system call, but skip this if a
* debugger has chosen to restart at a different PC.
*/
if (regs->pc == restart_addr &&
(retval == -ERESTARTNOHAND ||
retval == -ERESTART_RESTARTBLOCK ||
(retval == -ERESTARTSYS &&
!(ksig.ka.sa.sa_flags & SA_RESTART)))) {
regs->regs[0] = -EINTR;
regs->pc = continue_addr;
}
handle_signal(&ksig, regs);
return;
}
}
4.3 get_signal
int get_signal(struct ksignal *ksig)
{
struct sighand_struct *sighand = current->sighand;
struct signal_struct *signal = current->signal;
int signr;
for (;;) {
struct k_sigaction *ka;
//获取到进程接收队列中的一个信号,如果没有信号,那么就跳出循环
signr = dequeue_signal(current, ¤t->blocked, &ksig->info);
ka = &sighand->action[signr-1];
if (ka->sa.sa_handler == SIG_IGN) /* Do nothing. */
continue;
if (ka->sa.sa_handler != SIG_DFL) {
/* Run the handler. */
ksig->ka = *ka;
break; /* will return non-zero "signr" value */
}
/* Now we are doing the default action for this signal.*/
if (sig_kernel_ignore(signr)) /* Default is nothing. */
continue;
if (sig_kernel_stop(signr)) {
continue;
}
/*Anything else is fatal, maybe with a core dump.*/
current->flags |= PF_SIGNALED;
if (sig_kernel_coredump(signr))
do_coredump(&ksig->info);
/* Death signals, no core dump.*/
do_group_exit(ksig->info.si_signo);
/* NOTREACHED */
}
ksig->sig = signr;
return ksig->sig > 0;
}
4.4 handle_signal
/* OK, we're invoking a handler */
static void handle_signal(struct ksignal *ksig, struct pt_regs *regs)
{
struct task_struct *tsk = current;
sigset_t *oldset = sigmask_to_save();
int usig = ksig->sig;
int ret;
/* Set up the stack frame */
ret = setup_rt_frame(usig, ksig, oldset, regs);
/* Check that the resulting registers are actually sane. */
ret |= !valid_user_regs(®s->user_regs, current);
/* Fast forward the stepping logic so we step into the signal handler. */
if (!ret)
user_fastforward_single_step(tsk);
signal_setup_done(ret, ksig, 0);
}
void signal_setup_done(int failed, struct ksignal *ksig, int stepping)
{
if (failed)
force_sigsegv(ksig->sig, current);
else
signal_delivered(ksig, stepping);
}
setup_frame() 函数来构建这个过程的运行环境(其实就是修改内核栈和用户栈相应的数据来完成)
static int setup_rt_frame(int usig, struct ksignal *ksig, sigset_t *set,
struct pt_regs *regs)
{
struct rt_sigframe_user_layout user;
struct rt_sigframe __user *frame;
int err = 0;
fpsimd_signal_preserve_current_state();
frame = user.sigframe;
__put_user_error(0, &frame->uc.uc_flags, err);
__put_user_error(NULL, &frame->uc.uc_link, err);
err |= __save_altstack(&frame->uc.uc_stack, regs->sp);
err |= setup_sigframe(&user, regs, set);
if (err == 0) {
setup_return(regs, &ksig->ka, &user, usig);
if (ksig->ka.sa.sa_flags & SA_SIGINFO) {
err |= copy_siginfo_to_user(&frame->info, &ksig->info);
regs->regs[1] = (unsigned long)&frame->info;
regs->regs[2] = (unsigned long)&frame->uc;
}
}
return err;
}
现在可以在内核态返回到用户态时自动执行信号处理程序了,但是当信号处理程序执行完怎么返回到内核态呢?Linux的做法就是在用户态栈空间构建一个 Frame(帧)(我也不知道为什么要这样叫),构建这个帧的目的就是为了执行完信号处理程序后返回到内核态,并恢复原来内核栈的内容。
返回到内核态的方式是调用一个名为 sigreturn() 系统调用,然后再 sigreturn() 中恢复原来内核栈的内容。
ref: