# Linux kill 信号原理
有个学生问了我:kill -9 为什么有时候杀不掉进程,我说:不是杀不掉,是因为进程没有响应这个信号,可能 CPU 太过繁忙没有调度到这个要杀掉的进程,所以没有响应该信号,所以感觉没杀掉,但是如果该进程被调度且陷入到内核态后必然会响应该信号。本文将详细描述 Linux 信号的处理过程。
注意:由于 linux 不区别 进程与线程,因为线程就是共享内存地址空间的特殊进程,所以笔者这里统一称为 进程和进程组,笔者自行根据代码描述看线程或者进程上下文,提示:看下copy_process函数(详见:混沌学堂)
# sys_kill 函数
该函数为 kill 的系统调用入口,可以看到我们将用户态传入的 pid 和 sig 信号数字表示 封装进 siginfo 中,然后直接调用 kill_something_info 完成进一步处理。
asmlinkage long sys_kill(int pid, int sig) {
struct siginfo info;
info.si_signo = sig;
info.si_errno = 0;
info.si_code = SI_USER; // 表示用户态发送的信号
info.si_pid = current->tgid;
info.si_uid = current->uid;
return kill_something_info(sig, &info, pid);
}
2
3
4
5
6
7
8
9
10
# kill_something_info 函数
该函数很简单,根据 pid 的值选择发送信号到哪里:
1、pid 为 0 ,表示 发送信号给当前进程的 进程组
2、pid 为 -1, 表示发送给除 1 号进程 和 当前进程组 以外的所有进程
3、pid 小于 0,表示信号被发送到目标进程组,进程 id 由 pid的绝对值表示
4、pid 大于 0, 发送给目标进程
static int kill_something_info(int sig, struct siginfo *info, int pid){
int ret;
rcu_read_lock();
if (!pid) { // pid 为 0 表示 发送信号给当前进程组
ret = kill_pgrp_info(sig, info, task_pgrp(current));
} else if (pid == -1) { // pid 为 -1 表示发送给除 1 号进程 和 当前进程组 以外的所有进程
int retval = 0, count = 0;
struct task_struct * p;
read_lock(&tasklist_lock);
for_each_process(p) { // 遍历进程并找到符合条件的进程发送信号
if (p->pid > 1 && p->tgid != current->tgid) {
int err = group_send_sig_info(sig, info, p);
++count;
if (err != -EPERM)
retval = err;
}
}
read_unlock(&tasklist_lock);
ret = count ? retval : -ESRCH;
} else if (pid < 0) { // pid 小于 0,表示信号被发送到目标进程组,进程 id 由 pid的绝对值表示
ret = kill_pgrp_info(sig, info, find_pid(-pid));
} else { // pid 大于 0 发送给目标进程
ret = kill_pid_info(sig, info, find_pid(pid));
}
rcu_read_unlock();
return ret;
}
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
# kill_pgrp_info 函数
该函数用于向目标进程组发送信号。很简单,遍历进程组中的所有进程 调用 group_send_sig_info 函数给每个进程发送信号。
int kill_pgrp_info(int sig, struct siginfo *info, struct pid *pgrp){
int retval;
read_lock(&tasklist_lock);
retval = __kill_pgrp_info(sig, info, pgrp);
read_unlock(&tasklist_lock);
return retval;
}
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;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# kill_pid_info 函数
该函数用于处理发送信号给单个进程。可以看到最终也是通过 group_send_sig_info 函数发送信号。
int kill_pid_info(int sig, struct siginfo *info, struct pid *pid){
int error;
struct task_struct *p;
rcu_read_lock();
if (unlikely(sig_needs_tasklist(sig)))
read_lock(&tasklist_lock);
p = pid_task(pid, PIDTYPE_PID); // 根据pid 获取到当前进程结构体
error = -ESRCH;
if (p)
error = group_send_sig_info(sig, info, p); // 发送信号
if (unlikely(sig_needs_tasklist(sig)))
read_unlock(&tasklist_lock);
rcu_read_unlock();
return error;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# group_send_sig_info 函数
该函数用于完成实际信号的发送。struct task_struct *p 表示接受信号的目标进程。实际完成发送操作的函数为 __group_send_sig_info 流程如下:
1、处理 stop 停止信号
2、若信号被忽略,那么直接返回
3、若信号为 非实时信号,且当前信号已经设置,那么直接返回
4、将该信号放入进程信号处理队列
5、唤醒进程
int group_send_sig_info(int sig, struct siginfo *info, struct task_struct *p){
unsigned long flags;
int ret;
ret = check_kill_permission(sig, info, p);
if (!ret && sig) {
ret = -ESRCH;
if (lock_task_sighand(p, &flags)) {
ret = __group_send_sig_info(sig, info, p);
unlock_task_sighand(p, &flags);
}
}
return ret;
}
int __group_send_sig_info(int sig, struct siginfo *info, struct task_struct *p) {
int ret = 0;
assert_spin_locked(&p->sighand->siglock);
handle_stop_signal(sig, p); // 处理 stop 停止信号
if (sig_ignored(p, sig)) // 信号被忽略,那么直接返回
return ret;
if (LEGACY_QUEUE(&p->signal->shared_pending, sig)) // 非实时信号,且当前信号已经设置,那么直接返回
return ret;
// 将该信号放入进程信号处理队列
ret = send_signal(sig, info, p, &p->signal->shared_pending);
if (unlikely(ret))
return ret;
__group_complete_signal(sig, p); // 唤醒进程
return 0;
}
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
# handle_stop_signal 函数
该函数用于停止信号相关处理。可以看到该函数处理:
1、sig_kernel_stop 宏定义的停止信号
2、SIGCONT 信号
3、SIGKILL 信号
// 判断信号是否为停止信号。 信号数值 小于 32 (通常我们使用的非实时信号均 小于 32,比如 kill -9)
#define sig_kernel_stop(sig) \
(((sig) < SIGRTMIN) && siginmask(sig, SIG_KERNEL_STOP_MASK))
// 用于判断信号是否属于:SIGSTOP、SIGTSTP、SIGTTIN、SIGTTOU(这些信号描述可以参考 man 7 signal)
#define SIG_KERNEL_STOP_MASK (\
rt_sigmask(SIGSTOP) | rt_sigmask(SIGTSTP) | \
rt_sigmask(SIGTTIN) | rt_sigmask(SIGTTOU) )
static void handle_stop_signal(int sig, struct task_struct *p){
struct task_struct *t;
if (p->signal->flags & SIGNAL_GROUP_EXIT) // 进程正在终止,那么直接返回
return;
if (sig_kernel_stop(sig)) { // 属于停止信号,那么将 SIGCONT 信号从所有进程组中的进程信号队列移出,因为 该信号表示进程继续执行
rm_from_queue(sigmask(SIGCONT), &p->signal->shared_pending); // 首先移除 共享信号队列
t = p;
do { // 遍历进程组,移出 SIGCONT 信号
rm_from_queue(sigmask(SIGCONT), &t->pending); // 然后移出进程 自己的队列
t = next_thread(t); // 该函数获取进程组中的进程
} while (t != p);
} else if (sig == SIGCONT) { // 如果当前为 SIGCONT 信号,那么从 共享信号队列 和 进程自己的信号队列 移出 所有停止信号
if (unlikely(p->signal->group_stop_count > 0)) {
p->signal->group_stop_count = 0;
p->signal->flags = SIGNAL_STOP_CONTINUED;
spin_unlock(&p->sighand->siglock);
do_notify_parent_cldstop(p, CLD_STOPPED);
spin_lock(&p->sighand->siglock);
}
rm_from_queue(SIG_KERNEL_STOP_MASK, &p->signal->shared_pending);
t = p;
do { // 遍历所有进程,移出停止信号
unsigned int state;
rm_from_queue(SIG_KERNEL_STOP_MASK, &t->pending);
state = TASK_STOPPED;
if (sig_user_defined(t, SIGCONT) && !sigismember(&t->blocked, SIGCONT)) { // SIGCONT 定义了 SIGCONT 信号的处理函数,且当前进程没有阻塞 SIGCONT 信号(blocked 集 用于表示阻塞信号)那么设置 TIF_SIGPENDING 标志,让当前进程在被唤醒后等待,信号设置成功才能处理(后面我们会看到,进程被唤醒后,将等待 当前持有的 自旋锁,所以不会立即处理信号,而是 当前 持有的自旋锁 释放后)
set_tsk_thread_flag(t, TIF_SIGPENDING);
state |= TASK_INTERRUPTIBLE;
}
wake_up_state(t, state); // 唤醒停止的进程
t = next_thread(t);
} while (t != p);
if (p->signal->flags & SIGNAL_STOP_STOPPED) { // 当前进程已经被停止,那么此时被信号 SIGCONT 唤醒后,要求继续执行,此时通知 父进程,当前进程被唤醒 CLD_CONTINUED 表示 唤醒父进程的原因,这里表示 子进程 被 SIGCONT信号 唤醒 执行
p->signal->flags = SIGNAL_STOP_CONTINUED;
p->signal->group_exit_code = 0;
spin_unlock(&p->sighand->siglock);
do_notify_parent_cldstop(p, CLD_CONTINUED);
spin_lock(&p->sighand->siglock);
} else {
// 当前进程没有停止,但是处理的过程中可能会有停止信号。这里清除标志位
p->signal->flags = 0;
}
} else if (sig == SIGKILL) { // SIGKILL 信号,也清除标志位,表示所有 停止信号 被 SIGKILL 清除
p->signal->flags = 0;
}
}
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
57
58
59
60
61
62
# sig_ignored 函数
该函数用于判断信号是否应该被忽略。详细描述如下。读者可以根据 man -7 signal 中 action 一列 查看。这里只不过是实现而已。
// 以下信号忽略处理函数
#define SIG_KERNEL_IGNORE_MASK (\
rt_sigmask(SIGCONT) | rt_sigmask(SIGCHLD) | \
rt_sigmask(SIGWINCH) | rt_sigmask(SIGURG) )
static int sig_ignored(struct task_struct *t, int sig)
{
void __user * handler;
if (t->ptrace & PT_PTRACED) // 追踪进程将总是相应信号
return 0;
// 被阻塞的信号永远不会被忽略,因为在解除阻塞时,信号处理程序可能会发生变化
if (sigismember(&t->blocked, sig))
return 0;
// 看看 信号处理函数 是否为 SIG_IGN 函数 或者 信号处理函数为默认函数 且 是否为 SIGCONT、SIGCHLD、SIGWINCH、SIGURG 这些信号如果用户没有设置 对应处理函数,那么默认为忽略(man -7 signal 中有对应描述)
handler = t->sighand->action[sig-1].sa.sa_handler;
return handler == SIG_IGN ||
(handler == SIG_DFL && sig_kernel_ignore(sig));
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# LEGACY_QUEUE 宏
该宏用于判断当前信号是否为 实时信号:
1、若为 实时信号( sig < SIGRTMIN 为非实时信号,也即 只会响应一次),那么每个信号都会被处理,此时将会进入 send_signal 函数添加到队列中
2、若为 非实时信号(sig > SIGRTMIN 为实时信号,每个信号都会被处理),那么判断是否已经将信号添加到队列中,若已经添加,那么直接返回
#define LEGACY_QUEUE(sigptr, sig) \
(((sig) < SIGRTMIN) && sigismember(&(sigptr)->signal, (sig)))
if (LEGACY_QUEUE(&p->signal->shared_pending, sig))
return ret; // 非实时信号,且已经添加到队列,那么直接返回
// 看 _sig 的位图值是否为 1
static inline int sigismember(sigset_t *set, int _sig)
{
unsigned long sig = _sig - 1;
if (_NSIG_WORDS == 1)
return 1 & (set->sig[0] >> sig);
else
return 1 & (set->sig[sig / _NSIG_BPW] >> (sig % _NSIG_BPW));
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# send_signal 函数
该函数用于向 struct sigpending *signals 队列中添加待处理的信号 struct siginfo *info。处理流程如下:
1、将信号传递给监听信号集
2、若 信号 处理 指定为 SEND_SIG_FORCED(强制信号由 force_sig_specific 函数指定,比如我们在 使用ptrace 跟踪进程时,就可以使用该函数强制将进程停止),那么直接添加到信号集合(&signals->signal 对应信号位 置位 1)中即可返回
3、否则,我们分配一个节点来保存信号信息,然后将节点放入 &signals->list 中即可。若队列分配失败,则判断 当前信号为 实时信号且不是由用户调用 kill 系统调用发出的,那么返回 EAGAIN 返回值,外部调用方会进入 abort 流程 。若分配成功,那么添加到信号集合(&signals->signal 对应信号位 置位 1)中
static int send_signal(int sig, struct siginfo *info, struct task_struct *t,
struct sigpending *signals)
{
struct sigqueue * q = NULL;
int ret = 0;
// 将信号传递给监听信号集(若我们没有开启改特性,那么默认为空,这里了解集合)
signalfd_notify(t, sig);
// 若指定了强制设置信号,那么直接添加到信号集合中即可
if (info == SEND_SIG_FORCED)
goto out_set;
// 否则,我们分配一个节点来保存信号信息
q = __sigqueue_alloc(t, GFP_ATOMIC, (sig < SIGRTMIN &&
(is_si_special(info) ||
info->si_code >= 0)));
if (q) { // 若分配失败,表名内存不够,此时只能尽量的保存最后一个信号
// 将信号信息放到 队列节点 struct sigqueue * q 中
list_add_tail(&q->list, &signals->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 = current->pid;
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)) { // 若超出队列大小,且当前信号为 实时信号且不是由用户调用 kill 系统调用发出的,那么返回 EAGAIN 返回值,外部调用方会进入 abort 流程
if (sig >= SIGRTMIN && info->si_code != SI_USER)
return -EAGAIN;
}
out_set: // 将信号添加到 处理集 中
sigaddset(&signals->signal, sig);
return ret;
}
// i386 32位机
#define _NSIG 64 // 信号数量
#define _NSIG_BPW 32 // long 数据位宽
#define _NSIG_WORDS (_NSIG / _NSIG_BPW) // 此时需要两个 long 值才能保存所有信号
typedef struct {
unsigned long sig[_NSIG_WORDS]; // 待处理信号位图
} sigset_t;
// 对当前 _sig 的位图值设置为 1
static inline void sigaddset(sigset_t *set, int _sig)
{
unsigned long sig = _sig - 1;
if (_NSIG_WORDS == 1)
set->sig[0] |= 1UL << sig;
else
set->sig[sig / _NSIG_BPW] |= 1UL << (sig % _NSIG_BPW);
}
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
57
58
59
60
61
62
63
64
# 小结
现在我们可以看到通过 struct sigpending *signals
中的 signal 变量 来表示进程的信号位,通过 list 变量 来表示信号队列,队列中保存具体信号信息。结构体如下。
struct sigpending {
struct list_head list;
sigset_t signal;
};
2
3
4
# __group_complete_signal 函数
该函数在信号被成功添加入信号队列中后调用。用于唤醒进程。我们看到查找合适的进程算法为:遍历一圈,找到能处理该信号的进程,比如没有屏蔽 当前信号的进程。随后我们判断信号是否为致命信号,如果是,那么唤醒所有进程处理致命信号让他们退出。源码如下。
// 判断当前进程是否需要唤醒
static inline int wants_signal(int sig, struct task_struct *p)
{
if (sigismember(&p->blocked, sig)) // 当前进程阻塞了当前信号
return 0;
if (p->flags & PF_EXITING) // 进程正在退出
return 0;
if (sig == SIGKILL) // 信号为强制退出信号
return 1;
if (p->state & (TASK_STOPPED | TASK_TRACED)) // 进程已经停止或者正在被追踪
return 0;
return task_curr(p) || !signal_pending(p); // 当前进程为正在执行的进程 或者 进程没有在等待处理信号(后面我们会看到该函数 检测标志位 TIF_SIGPENDING)
}
// 判断当前信号是否为致命信号(不属于 SIG_KERNEL_IGNORE_MASK 和 SIG_KERNEL_STOP_MASK 信号 同时 信号处理动作为默认动作,比如 SIGKILL 就满足该特性 )
#define sig_fatal(t, signr) \
(!siginmask(signr, SIG_KERNEL_IGNORE_MASK|SIG_KERNEL_STOP_MASK) && \
(t)->sighand->action[(signr)-1].sa.sa_handler == SIG_DFL)
static void __group_complete_signal(int sig, struct task_struct *p)
{
struct task_struct *t;
if (wants_signal(sig, p)) // 若当前进程需要被唤醒,那么把当前进程设置为唤醒进程
t = p;
else if (thread_group_empty(p)) // 只有一个进程,不需要唤醒它。它将在再次运行之前将未阻塞的信号出队处理(注意:这个条件是上述的条件判断失败后再判断执行,所以上一步的判断操作已经 决定了当前进程不想被唤醒,比如:阻塞了信号)
return;
else { // 否则唤醒一个合适的进程来处理该信号
t = p->signal->curr_target; // 取当前进程组中用于开始查找合适的进程起始点
if (t == NULL) // 若不存在,把当前进程作为起始查找的进程
t = p->signal->curr_target = p;
while (!wants_signal(sig, t)) { // 从 t 进程开始 遍历找到一个能处理该信号的进程
t = next_thread(t);
if (t == p->signal->curr_target)
// 遍历了一周,还是没有找到能处理该信号的进程,那么此时不需要唤醒进程。任何符合条件的进程将在处理自己的操作后,很快看到队列中的信号并处理
return;
}
p->signal->curr_target = t; // 保存上一次查找的进程,下一次从该进程开始查找
}
if (sig_fatal(p, sig) && // 当前信号为 致命信号
!(p->signal->flags & SIGNAL_GROUP_EXIT) && // 当前进程没有被要求退出
!sigismember(&t->real_blocked, sig) && // 当前进程没有临时阻塞该信号
(sig == SIGKILL || !(t->ptrace & PT_PTRACED))) { // 信号为 SIGKILL 或者 没有被追踪
// 注意:致命信号将会导致整个进程组退出
if (!sig_kernel_coredump(sig)) { // 当前信号不需要 dump 进程 堆栈信息
p->signal->flags = SIGNAL_GROUP_EXIT; // 设置进程信号处理标志位 整组退出(SIGNAL_GROUP_EXIT表示 整个进程组退出)
p->signal->group_exit_code = sig;
p->signal->group_stop_count = 0;
t = p;
do { // 遍历所有进程向他们的信号集合中设置 SIGKILL 信号,并唤醒他们响应该信号(该信号将会退出导致进程退出)
sigaddset(&t->pending.signal, SIGKILL);
signal_wake_up(t, 1);
t = next_thread(t);
} while (t != p);
return;
}
// 此时需要做一个 core dump 导出进程组的堆栈信息。我们让除选中的进程之外的所有进程都进入组停止状态,这样在调度之前什么都不会发生,从共享队列中取出信号,并进行 core dump
rm_from_queue(SIG_KERNEL_STOP_MASK, &t->pending); // 从当前进程信号队列中移出 停止 信号
rm_from_queue(SIG_KERNEL_STOP_MASK, &p->signal->shared_pending); // 从当前进程组共享信号队列中移出 停止 信号
p->signal->group_stop_count = 0;
p->signal->group_exit_task = t; // 当前进程作为组退出进程处理 core dump
t = p;
do { // 唤醒进程组中的所有进程
p->signal->group_stop_count++; // 记录进程组需要停止的 进程
signal_wake_up(t, 0);
t = next_thread(t);
} while (t != p);
wake_up_process(p->signal->group_exit_task); // 唤醒处理 core dump 的进程
return;
}
// 此时,信号已经放入信号队列,那么通知当前进程处理该信号
signal_wake_up(t, sig == SIGKILL);
return;
}
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# signal_wake_up 函数
该函数用于唤醒指定进程。我们看到,如果是致命信号,那么设置 resume 为 1,当前进程属于 TASK_STOPPED 停止进程 和 被追踪的进程 时 是否仍然被唤醒来响应信号。wake_up_state 函数就不需要分析了,因为那是另外一个模块 sched 调度器的东西,这里只需要知道其中将会唤醒 mask 指定状态的进程即可。
void signal_wake_up(struct task_struct *t, int resume)
{
unsigned int mask;
set_tsk_thread_flag(t, TIF_SIGPENDING); // 设置线程处理信号标志位
mask = TASK_INTERRUPTIBLE;
if (resume) // 当前进程属于 TASK_STOPPED 停止进程 和 被追踪的进程 时 是否仍然唤醒
mask |= TASK_STOPPED | TASK_TRACED;
if (!wake_up_state(t, mask))
kick_process(t);
}
// 设置 thread_info flag 的对应标志位
static inline void set_tsk_thread_flag(struct task_struct *tsk, int flag)
{
set_ti_thread_flag(task_thread_info(tsk), flag);
}
#define TIF_SIGPENDING 2 // 在 flag 的 第三位(数组下标从0开始)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 进程何时处理信号?
我们来看系统调用代码。注释很详细,自行研究,不会的详见 混沌学堂 第一期、第二期。
ENTRY(system_call)
pushl %eax # save orig_eax
SAVE_ALL
GET_THREAD_INFO(%ebp) // 获取 thread_info 结构,其中可以拿到当前进程的 task_struct 结构 。也即:ebp 将保存 thread_info 的地址
cmpl $(nr_syscalls), %eax // 看系统调用号是否超出界限
jae syscall_badsys
syscall_call:
call *sys_call_table(,%eax,4) // 调用系统调用
movl %eax,PT_EAX(%esp) // 返回值放入 eax
syscall_exit:
DISABLE_INTERRUPTS(CLBR_ANY) // 关闭中断
testl $TF_MASK,PT_EFLAGS(%esp) // 检测是否设置了单步执行状态
jz no_singlestep // 我们看这里即可
orl $_TIF_SINGLESTEP,TI_flags(%ebp)
no_singlestep:
movl TI_flags(%ebp), %ecx // 获取进程标志位
testw $_TIF_ALLWORK_MASK, %cx // 检测当前进程是否需要执行进一步的任务(#define _TIF_ALLWORK_MASK (0x0000FFFF & ~_TIF_SECCOMP)
jne syscall_exit_work // 若需要,那么跳转
restore_all: // 否则还原寄存器,返回用户空间
// 这里省略
syscall_exit_work:
testb $(_TIF_SYSCALL_TRACE|_TIF_SYSCALL_AUDIT|_TIF_SINGLESTEP), %cl // 判断标志位低8位没有这三个标识符
jz work_pending // 存在任务,那么执行任务
TRACE_IRQS_ON
ENABLE_INTERRUPTS(CLBR_ANY) // 开启中断
movl %esp, %eax
movl $1, %edx
call do_syscall_trace
jmp resume_userspace // 跳转恢复跳转用户空间
END(syscall_exit_work)
work_pending:
testb $_TIF_NEED_RESCHED, %cl // 当前进程是否需要重调度
jz work_notifysig // 若不存在重调度,那么跳转执行 信号
work_resched:
// 省略
work_notifysig:
movl %esp, %eax
xorl %edx, %edx
call do_notify_resume // 处理信号
jmp resume_userspace_sig // 返回用户态
END(work_pending)
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
do_notify_resume 函数
该函数为实际处理信号的函数,这里就不在详述,因为很简单,一看便知。
void do_notify_resume(struct pt_regs *regs, void *_unused, __u32 thread_info_flags){
...
if (thread_info_flags & (_TIF_SIGPENDING | _TIF_RESTORE_SIGMASK))
do_signal(regs); // 处理信号
...
}
static void fastcall do_signal(struct pt_regs *regs){
// 在这里面回调用户态设置的处理函数,或者自身 调用 do_Exit 退出咯
}
2
3
4
5
6
7
8
9
10
# TI_flags 在哪?
看如下代码描述,很简单。注意我们是在 i386 32 位机中。
struct thread_info {
struct task_struct *task; /* main task structure */
struct exec_domain *exec_domain; /* execution domain */
unsigned long flags; /* low level flags */
unsigned long status; /* thread-synchronous flags */
__u32 cpu; /* current CPU */
__s32 preempt_count; /* 0 => preemptable, <0 => BUG */
mm_segment_t addr_limit; /* thread address space:
0-0xBFFFFFFF for user-thead
0-0xFFFFFFFF for kernel-thread
*/
struct restart_block restart_block;
__u8 supervisor_stack[0];
};
#define TI_FLAGS 0x00000008 // 数数偏移量,正好是 4 + 4( i386 哦)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18