# 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);
}
1
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;
}
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

# 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;
}
1
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;
}
1
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;
}
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

# 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;
    }
}



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
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));
}
1
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));
}
1
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);
}
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
57
58
59
60
61
62
63
64

# 小结

现在我们可以看到通过 struct sigpending *signals 中的 signal 变量 来表示进程的信号位,通过 list 变量 来表示信号队列,队列中保存具体信号信息。结构体如下。

struct sigpending {
    struct list_head list;
    sigset_t signal;
};
1
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;
}
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
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开始)
1
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)
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

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 退出咯
}
1
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 哦)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18