信号

信号

信号用来通知进程异步事件,可以把它理解为对中断的一种模拟。它是一个很小的消息,用来达到两个目的:

  • 告知进程发生了一个特定的事件;
  • 强迫进程执行自身所包含的信号处理程序。

linux预先定义了一些常规信号,并为它们定义了一些缺省操作。除此之外,还有一类实时信号,它们需要排队进行处理,我们也可以自己定义信号和信号处理方式。

既然信号是和进程相关的,那么task_struct中就必然包含有与信号相关的域了。

1
2
3
4
5
6
7
8
9
task_struct{
...
struct signal_struct *signal; //进程信号描述符
struct sighand_struct *sighand; //进程信号处理程序描述符
sigset_t blocked; //被阻塞信号掩码
sigset_t real_bloced; //被阻塞信号临时掩码
struct sigpending pending; //存放私有挂起信号
...
}

信号的产生

信号是由内核函数产生的,它们完成信号处理的第一步,也即更新一个/多个进程的描述符。产生的信号并不直接传递,而是根据信号的类型、目标进程的状态唤醒进程,让它们来接收信号。内核提供了一组产生信号的函数,包括为进程、线程组产生信号等,但它们最终都会调用__send_signal()。当然,在调用__send_signal()之前,会检查这个信号是否应该被忽略(进程没有被跟踪、信号被阻塞,显示忽略信号)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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;
int ret = 0, result;
assert_spin_locked(&t->sighand->siglock);
result = TRACE_SIGNAL_IGNORED;
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;
result = TRACE_SIGNAL_DELIVERED;
//如果是kernel内部的某些强制信号,就立马执行
if (info == SEND_SIG_FORCED)
goto out_set;
//如果没有超过挂起信号的上限
if (sig < SIGRTMIN)
override_rlimit = (is_si_special(info) || info->si_code >= 0);
else
override_rlimit = 0;
//产生一个sigqueue对象,并把它加入到队列中去
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 = from_kuid_munged(current_user_ns(), 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;
}
//......
}

在信号产生之后,linux会调用signal_wake_up()通知进程,告知有新的挂起信号到来,如果当前进程占有了CPU,那么就可以立即执行;否则则要强制进行重新调度。

信号的传递

在信号产生之后,如何确保挂起的信号被正确的处理呢?进程在信号产生时,可能并不在CPU上运行。在进程恢复用户态执行时,会进行检查,如果存在非阻塞的挂起信号,就调用do_signal(),这个函数会逐个助理挂起的非阻塞信号,而信号的处理则进一步调用handle_signal()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
handle_signal(struct ksignal *ksig, struct pt_regs *regs)
{
bool stepping, failed;
struct fpu *fpu = &current->thread.fpu;
//是否处于系统调用中
if (syscall_get_nr(current, regs) >= 0) {
//系统调用被打断了,没有执行完,需要重新执行
switch (syscall_get_error(current, regs)) {
case -ERESTART_RESTARTBLOCK:
case -ERESTARTNOHAND:
regs->ax = -EINTR;
break;
case -ERESTARTSYS:
if (!(ksig->ka.sa.sa_flags & SA_RESTART)) {
regs->ax = -EINTR;
break;
}
/* fallthrough */
case -ERESTARTNOINTR:
regs->ax = regs->orig_ax;
regs->ip -= 2;
break;
}
}
//设置栈帧
failed = (setup_rt_frame(ksig, regs) < 0);
if (!failed) {
regs->flags &= ~(X86_EFLAGS_DF|X86_EFLAGS_RF|X86_EFLAGS_TF);
/*
* Ensure the signal handler starts with the new fpu state.
*/
if (fpu->fpstate_active)
fpu__clear(fpu);
}
signal_setup_done(failed, ksig, stepping);
}

这里存在一个问题:handle_signal()处于内核态中,但信号处理程序是在用户态定义的,因此这里存在着堆栈转换的问题。linux采用的方法是:把内核态堆栈中的硬件上下文,拷贝到当前进程的用户态堆栈中。而当信号处理程序完成时,会自动调用sigreturn()把硬件上下文拷贝回内核态堆栈中,并且恢复用户态堆栈中的内容。这里需要构造一个用户态栈帧:

首先内核需要把内核栈中的内容复制到用户态堆栈中去,把内核态堆栈的返回地址修改为信号处理程序的入口。注意,为了让信号处理程序结束时,能够清除栈上的内容,用户态堆栈还应该放入一个信号处理程序的返回地址,它指向__kernel_sigreturn(),把硬件上下文拷贝到内核态堆栈,然后把这个栈帧删除,随后从内核态返回到用户态继续执行。

信号的接口

kill/tkill/kgill系统调用分别用来给某个进程、线程组发送信号。其中,kill(pid, sig)分别接受一个进程的pid号,以及一个所发送的信号。

实时信号的发送则应该使用rt_sigqueueinfo()来进行发送。如果用户需要为信号指定一个操作,那么则应该使用sigaction(sig, &act, &oact)系统调用,act为指定的操作,而old_act用来记录以前的信号。



The link of this page is https://blog.nooa.tech/articles/1d60a972/ . Welcome to reproduce it!

© 2018.02.08 - 2024.05.25 Mengmeng Kuang  保留所有权利!

:D 获取中...

Creative Commons License