Linux处理停止信号相关函数的实现
一、进程是否希望接收信号wants_signal
static inline int signal_pending(struct task_struct *p)
{return unlikely(test_tsk_thread_flag(p,TIF_SIGPENDING));
}
#define wants_signal(sig, p, mask) \(!sigismember(&(p)->blocked, sig) \&& !((p)->state & mask) \&& !((p)->flags & PF_EXITING) \&& (task_curr(p) || !signal_pending(p)))
1.函数解析
1.1. signal_pending
函数
static inline int signal_pending(struct task_struct *p)
{return unlikely(test_tsk_thread_flag(p, TIF_SIGPENDING));
}
作用:检查进程是否有待处理的信号
TIF_SIGPENDING
:线程信息标志,表示有信号待处理test_tsk_thread_flag
:测试线程特定标志位
1.2. wants_signal
宏
#define wants_signal(sig, p, mask) \(!sigismember(&(p)->blocked, sig) \ // 条件1:信号未被阻塞&& !((p)->state & mask) \ // 条件2:进程状态允许&& !((p)->flags & PF_EXITING) \ // 条件3:进程不在退出中&& (task_curr(p) || !signal_pending(p))) // 条件4:调度条件
条件1:信号未被阻塞
!sigismember(&(p)->blocked, sig)
- 检查信号是否在进程的信号掩码中被阻塞
- 如果信号被阻塞,进程不希望接收它
条件2:进程状态允许
!((p)->state & mask)
mask
参数指定了哪些进程状态不允许接收信号
条件3:进程不在退出中
!((p)->flags & PF_EXITING)
PF_EXITING
标志表示进程正在退出- 退出中的进程不需要接收新信号
条件4:调度条件
(task_curr(p) || !signal_pending(p))
两种情况满足其一:
task_curr(p)
:进程当前正在 CPU 上运行!signal_pending(p)
:进程没有其他待处理的信号
二、唤醒任务和信号处理signal_wake_up
int fastcall wake_up_state(task_t *p, unsigned int state)
{return try_to_wake_up(p, state, 0);
}
void kick_process(task_t *p)
{int cpu;preempt_disable();cpu = task_cpu(p);if ((cpu != smp_processor_id()) && task_curr(p))smp_send_reschedule(cpu);preempt_enable();
}
void signal_wake_up(struct task_struct *t, int resume)
{unsigned int mask;set_tsk_thread_flag(t, TIF_SIGPENDING);/** If resume is set, we want to wake it up in the TASK_STOPPED case.* We don't check for TASK_STOPPED because there is a race with it* executing another processor and just now entering stopped state.* By calling wake_up_process any time resume is set, we ensure* the process will wake up and handle its stop or death signal.*/mask = TASK_INTERRUPTIBLE;if (resume)mask |= TASK_STOPPED;if (!wake_up_state(t, mask))kick_process(t);
}
1. wake_up_state()
- 状态唤醒函数
int fastcall wake_up_state(task_t *p, unsigned int state)
{return try_to_wake_up(p, state, 0);
}
作用:尝试唤醒处于特定状态的任务。
参数:
p
:要唤醒的任务指针state
:期望的任务状态掩码
返回值:
1
:成功唤醒了任务0
:任务不在指定的状态,或者已经在运行
2. kick_process()
- 进程强制调度
void kick_process(task_t *p)
{int cpu;preempt_disable();cpu = task_cpu(p);if ((cpu != smp_processor_id()) && task_curr(p))smp_send_reschedule(cpu);preempt_enable();
}
关键逻辑:
preempt_disable()
/preempt_enable()
:防止内核抢占task_cpu(p)
:获取任务当前运行的CPUtask_curr(p)
:检查任务是否正在CPU上运行smp_send_reschedule(cpu)
:向目标CPU发送重新调度IPI中断
3. signal_wake_up()
- 信号唤醒主函数
void signal_wake_up(struct task_struct *t, int resume)
{unsigned int mask;set_tsk_thread_flag(t, TIF_SIGPENDING);/** If resume is set, we want to wake it up in the TASK_STOPPED case.* We don't check for TASK_STOPPED because there is a race with it* executing another processor and just now entering stopped state.* By calling wake_up_process any time resume is set, we ensure* the process will wake up and handle its stop or death signal.*/mask = TASK_INTERRUPTIBLE;if (resume)mask |= TASK_STOPPED;if (!wake_up_state(t, mask))kick_process(t);
}
3.1.步骤1:设置信号挂起标志
set_tsk_thread_flag(t, TIF_SIGPENDING);
- 在任务的
thread_info
中设置TIF_SIGPENDING
标志 - 这个标志告诉任务有未处理的信号等待处理
3.2.步骤2:确定唤醒状态掩码
mask = TASK_INTERRUPTIBLE;
if (resume)mask |= TASK_STOPPED;
状态含义:
TASK_INTERRUPTIBLE
:可中断睡眠状态TASK_STOPPED
:任务被停止(如收到SIGSTOP信号)
resume参数的意义:
resume = 0
:只唤醒TASK_INTERRUPTIBLE
状态的任务resume = 1
:同时唤醒TASK_INTERRUPTIBLE
和TASK_STOPPED
状态的任务
步骤3:尝试唤醒 + 后备机制
if (!wake_up_state(t, mask))kick_process(t);
条件逻辑:
- 如果
wake_up_state()
返回0
(唤醒失败) - 这时调用
kick_process()
强制重新调度
三、选择合适的线程来处理信号__group_complete_signal
static inline int thread_group_empty(task_t *p)
{return list_empty(&p->pids[PIDTYPE_TGID].pid_list);
}
#define sig_fatal(t, signr) \(!T(signr, SIG_KERNEL_IGNORE_MASK|SIG_KERNEL_STOP_MASK) && \(t)->sighand->action[(signr)-1].sa.sa_handler == SIG_DFL)
#define sig_kernel_coredump(sig) \(((sig) < SIGRTMIN) && T(sig, SIG_KERNEL_COREDUMP_MASK))
int fastcall wake_up_process(task_t * p)
{return try_to_wake_up(p, TASK_STOPPED | TASK_TRACED |TASK_INTERRUPTIBLE | TASK_UNINTERRUPTIBLE, 0);
}
static void
__group_complete_signal(int sig, struct task_struct *p)
{unsigned int mask;struct task_struct *t;/** Don't bother zombies and stopped tasks (but* SIGKILL will punch through stopped state)*/mask = EXIT_DEAD | EXIT_ZOMBIE | TASK_TRACED;if (sig != SIGKILL)mask |= TASK_STOPPED;/** Now find a thread we can wake up to take the signal off the queue.** If the main thread wants the signal, it gets first crack.* Probably the least surprising to the average bear.*/if (wants_signal(sig, p, mask))t = p;else if (thread_group_empty(p))/** There is just one thread and it does not need to be woken.* It will dequeue unblocked signals before it runs again.*/return;else {/** Otherwise try to find a suitable thread.*/t = p->signal->curr_target;if (t == NULL)/* restart balancing at this thread */t = p->signal->curr_target = p;BUG_ON(t->tgid != p->tgid);while (!wants_signal(sig, t, mask)) {t = next_thread(t);if (t == p->signal->curr_target)/** No thread needs to be woken.* Any eligible threads will see* the signal in the queue soon.*/return;}p->signal->curr_target = t;}/** Found a killable thread. If the signal will be fatal,* then start taking the whole group down immediately.*/if (sig_fatal(p, sig) && !p->signal->group_exit &&!sigismember(&t->real_blocked, sig) &&(sig == SIGKILL || !(t->ptrace & PT_PTRACED))) {/** This signal will be fatal to the whole group.*/if (!sig_kernel_coredump(sig)) {/** Start a group exit and wake everybody up.* This way we don't have other threads* running and doing things after a slower* thread has the fatal signal pending.*/p->signal->group_exit = 1;p->signal->group_exit_code = sig;p->signal->group_stop_count = 0;t = p;do {sigaddset(&t->pending.signal, SIGKILL);signal_wake_up(t, 1);t = next_thread(t);} while (t != p);return;}/** There will be a core dump. We make all threads other* than the chosen one go into a group stop so that nothing* happens until it gets scheduled, takes the signal off* the shared queue, and does the core dump. This is a* little more complicated than strictly necessary, but it* keeps the signal state that winds up in the core dump* unchanged from the death state, e.g. which thread had* the core-dump signal unblocked.*/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;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);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;
}
1.辅助函数和宏定义
1.1. thread_group_empty()
static inline int thread_group_empty(task_t *p)
{return list_empty(&p->pids[PIDTYPE_TGID].pid_list);
}
- 检查线程组是否只有一个线程(空线程组)
- 通过检查PID链表是否为空来判断
1.2. sig_fatal()
- 判断信号是否致命
#define sig_fatal(t, signr) \(!T(signr, SIG_KERNEL_IGNORE_MASK|SIG_KERNEL_STOP_MASK) && \(t)->sighand->action[(signr)-1].sa.sa_handler == SIG_DFL)
- 信号不在忽略或停止掩码中
- 并且 信号处理方式是默认处理(SIG_DFL)
- 满足这两条就是致命信号,即触发终止进程
1.3. sig_kernel_coredump()
- 判断是否生成core dump
#define sig_kernel_coredump(sig) \(((sig) < SIGRTMIN) && T(sig, SIG_KERNEL_COREDUMP_MASK))
- 检查信号是否会导致生成core dump文件
1.4. wake_up_process()
- 唤醒进程
int fastcall wake_up_process(task_t * p)
{return try_to_wake_up(p, TASK_STOPPED | TASK_TRACED |TASK_INTERRUPTIBLE | TASK_UNINTERRUPTIBLE, 0);
}
- 唤醒处于各种睡眠状态的进程
2.主函数详细解析
2.1.第一阶段:确定需要跳过的任务状态
mask = EXIT_DEAD | EXIT_ZOMBIE | TASK_TRACED;
if (sig != SIGKILL)mask |= TASK_STOPPED;
mask含义:
EXIT_DEAD | EXIT_ZOMBIE
:跳过已退出的线程TASK_TRACED
:跳过被调试器跟踪的线程TASK_STOPPED
:跳过停止的线程(SIGKILL除外,因为它能杀死停止的进程)
2.1.第二阶段:选择目标线程
2.1.1.情况1:主线程可以处理信号
if (wants_signal(sig, p, mask))t = p;
- 如果主线程(
p
)愿意接收这个信号,就选择它
2.1.2.情况2:单线程线程组
else if (thread_group_empty(p))return;
- 如果线程组只有一个线程且它不愿意接收信号,直接返回
- 该线程会在下次运行时自己处理信号
2.1.3.情况3:多线程线程组中寻找合适线程
else {t = p->signal->curr_target;if (t == NULL)t = p->signal->curr_target = p;BUG_ON(t->tgid != p->tgid);while (!wants_signal(sig, t, mask)) {t = next_thread(t);if (t == p->signal->curr_target)return; // 没有线程需要被唤醒}p->signal->curr_target = t;
}
-
信号到来时
curr_target
= 线程A -
检查线程A:
!wants_signal()
→ 跳过 -
t = next_thread(A)
= 线程B -
检查线程B:
wants_signal()
→ 成功! -
更新
curr_target
= 线程B -
唤醒线程B处理信号
2.2.第三阶段:处理致命信号
2.2.1.情况A:非coredump
的致命信号(如SIGKILL)
if (!sig_kernel_coredump(sig)) {p->signal->group_exit = 1;p->signal->group_exit_code = sig;p->signal->group_stop_count = 0;t = p;do {sigaddset(&t->pending.signal, SIGKILL);signal_wake_up(t, 1);t = next_thread(t);} while (t != p);return;
}
行为:
- 设置整个线程组退出标志
- 向所有线程发送SIGKILL
- 唤醒所有线程立即退出
2.2.2.情况B:生成coredump
的致命信号(如SIGSEGV, SIGABRT)
else {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;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);return;
}
行为:
- 清理停止信号队列,防止停止信号干扰
coredump
过程 - 指定一个线程(
group_exit_task
)来生成coredump
- 其他线程进入停止状态等待
- 只唤醒指定的
coredump
线程
2.3.第四阶段:普通信号处理
signal_wake_up(t, sig == SIGKILL);
return;
- 对于非致命信号,只唤醒选中的线程
- 如果是SIGKILL,设置
resume=1
以唤醒停止的线程
3.关键设计思想
线程组信号共享
- 所有线程共享
signal->shared_pending
信号队列 - 只需要一个线程来处理信号
负载均衡
- 使用
curr_target
轮询选择线程处理信号 - 避免总是让同一个线程处理所有信号
致命信号的特殊处理
- 非
coredump
信号:快速终止整个线程组 coredump
信号:有序停止,确保coredump
完整性
四、向整个进程组发送信号__group_send_sig_info
#define LEGACY_QUEUE(sigptr, sig) \(((sig) < SIGRTMIN) && sigismember(&(sigptr)->signal, (sig)))
static int
__group_send_sig_info(int sig, struct siginfo *info, struct task_struct *p)
{int ret = 0;#ifdef CONFIG_SMPif (!spin_is_locked(&p->sighand->siglock))BUG();
#endifhandle_stop_signal(sig, p);if (((unsigned long)info > 2) && (info->si_code == SI_TIMER))ret = info->si_sys_private;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.函数定义
static int __group_send_sig_info(int sig, struct siginfo *info, struct task_struct *p)
参数:
sig
:要发送的信号编号info
:信号信息结构体指针p
:目标进程(进程组的成员)
作用:向进程组发送信号,处理进程组级别的信号逻辑
2.宏定义解析
#define LEGACY_QUEUE(sigptr, sig) \(((sig) < SIGRTMIN) && sigismember(&(sigptr)->signal, (sig)))
作用:检查传统(非实时)信号是否已经在队列中
(sig) < SIGRTMIN
:只对传统信号有效sigismember(&(sigptr)->signal, (sig))
:检查信号位图中该信号位是否已设置
3.函数逻辑分析
3.1. SMP 锁检查
#ifdef CONFIG_SMP
if (!spin_is_locked(&p->sighand->siglock))BUG();
#endif
目的:确保在 SMP 系统中持有信号处理锁
- 防止多处理器竞争条件
- 保证信号处理的原子性
3.2. 停止信号处理
handle_stop_signal(sig, p);
作用:处理特殊的停止信号(SIGSTOP、SIGTSTP、SIGTTIN、SIGTTOU)
- 可能修改进程状态为停止状态
- 处理作业控制相关的逻辑
3.3. 定时器信号特殊处理
if (((unsigned long)info > 2) && (info->si_code == SI_TIMER))ret = info->si_sys_private;
目的:为定时器信号保存返回信息
- 如果信号被后续逻辑忽略或丢弃,可以通过返回值通知定时器子系统
3.4. 信号忽略检查
if (sig_ignored(p, sig))return ret;
作用:如果信号被目标进程忽略,直接返回
- 检查进程的信号处理函数是否为
SIG_IGN
- 检查信号是否被阻塞
- 如果被忽略,直接返回(可能包含定时器信息)
3.5. 传统信号队列检查
if (LEGACY_QUEUE(&p->signal->shared_pending, sig))return ret;
传统信号排队规则:
- 对于传统信号(非实时信号),如果已经有一个相同的信号在共享待处理队列中
- 不再重复排队
- 直接返回,避免信号重复
3.6. 发送信号到共享队列
ret = send_signal(sig, info, p, &p->signal->shared_pending);
if (unlikely(ret))return ret;
关键点:使用共享待处理队列 (shared_pending
)
p->signal->shared_pending
:进程组共享的信号队列- 所有线程共享这个队列中的信号
- 与每个线程私有的
p->pending
队列相对
3.7. 完成信号传递
__group_complete_signal(sig, p);
return 0;
作用:唤醒进程组中合适的线程来处理信号
4.完整的信号传递流程
__group_send_sig_info()↓
检查锁和停止信号↓
检查信号是否被忽略 → 是 → 返回↓ 否
检查传统信号是否已排队 → 是 → 返回 ↓ 否
发送到共享待处理队列↓
唤醒进程组中的线程
5.完整工作流程
5.1.场景1:普通信号发送
1. 调用 __group_send_sig_info(SIGUSR1, ...)
2. 检查锁:确保信号锁已持有
3. handle_stop_signal: SIGUSR1不是停止信号,跳过
4. 检查忽略:SIGUSR1未被忽略
5. 检查排队:共享队列中没有SIGUSR1
6. send_signal: 成功添加到共享队列
7. __group_complete_signal: 选择一个线程唤醒
8. 返回0表示成功
5.2.场景2:重复的非实时信号
1. 调用 __group_send_sig_info(SIGINT, ...)
2. 共享队列中已有一个SIGINT信号
3. LEGACY_QUEUE() 返回true
4. 直接返回,不添加新信号
5. 效果:SIGINT信号不会堆积
5.3.场景3:停止信号处理
1. 调用 __group_send_sig_info(SIGSTOP, ...)
2. handle_stop_signal(SIGSTOP):- 清理所有SIGCONT信号- 准备进程停止
3. 后续步骤正常执行,将SIGSTOP加入队列
五、唤醒父进程__wake_up_parent
static void __wake_up_parent(struct task_struct *p,struct task_struct *parent)
{struct task_struct *tsk = parent;/** Fortunately this is not necessary for thread groups:*/if (p->tgid == tsk->tgid) {wake_up_interruptible_sync(&tsk->wait_chldexit);return;}do {wake_up_interruptible_sync(&tsk->wait_chldexit);tsk = next_thread(tsk);if (tsk->signal != parent->signal)BUG();} while (tsk != parent);
}
Linux 内核中唤醒父进程的函数,用于在子进程状态改变时通知父进程
1.函数定义
static void __wake_up_parent(struct task_struct *p,struct task_struct *parent)
参数:
p
:状态发生改变的进程(通常是子进程)parent
:父进程
作用:唤醒父进程,使其能够检测到子进程的状态变化
2.函数逻辑分析
2.1. 线程组检查(快速路径)
if (p->tgid == tsk->tgid) {wake_up_interruptible_sync(&tsk->wait_chldexit);return;
}
条件:p->tgid == tsk->tgid
- 表示子进程和父进程属于同一个线程组
- 在 Linux 中,线程组的所有线程共享同一个 TGID(Thread Group ID)
处理:直接唤醒父进程的等待队列后返回
2.2. 线程组遍历(慢速路径)
do {wake_up_interruptible_sync(&tsk->wait_chldexit);tsk = next_thread(tsk);if (tsk->signal != parent->signal)BUG();
} while (tsk != parent);
当子进程和父进程不属于同一个线程组时,需要遍历整个线程组:
循环步骤:
- 唤醒当前线程:
wake_up_interruptible_sync(&tsk->wait_chldexit)
- 获取下一个线程:
tsk = next_thread(tsk)
- 一致性检查:验证信号结构是否一致
- 循环直到回到起始点:
tsk != parent
注意:实际上不需要循环去唤醒线程组每一个线程
六、子进程停止唤醒父进程do_notify_parent_cldstop
static void
do_notify_parent_cldstop(struct task_struct *tsk, struct task_struct *parent,int why)
{struct siginfo info;unsigned long flags;struct sighand_struct *sighand;info.si_signo = SIGCHLD;info.si_errno = 0;info.si_pid = tsk->pid;info.si_uid = tsk->uid;/* FIXME: find out whether or not this is supposed to be c*time. */info.si_utime = tsk->utime;info.si_stime = tsk->stime;info.si_code = why;switch (why) {case CLD_CONTINUED:info.si_status = SIGCONT;break;case CLD_STOPPED:info.si_status = tsk->signal->group_exit_code & 0x7f;break;case CLD_TRAPPED:info.si_status = tsk->exit_code & 0x7f;break;default:BUG();}sighand = parent->sighand;spin_lock_irqsave(&sighand->siglock, flags);if (sighand->action[SIGCHLD-1].sa.sa_handler != SIG_IGN &&!(sighand->action[SIGCHLD-1].sa.sa_flags & SA_NOCLDSTOP))__group_send_sig_info(SIGCHLD, &info, parent);/** Even if SIGCHLD is not generated, we must wake up wait4 calls.*/__wake_up_parent(tsk, parent);spin_unlock_irqrestore(&sighand->siglock, flags);
}
这是一个 Linux 内核中通知父进程子进程状态改变的函数,专门用于处理子进程的停止、继续和被跟踪等情况
1.函数定义
static void
do_notify_parent_cldstop(struct task_struct *tsk, struct task_struct *parent,int why)
参数:
tsk
:状态发生改变的进程(子进程)parent
:父进程why
:状态改变原因
2.函数逻辑分析
2.1. 构建 siginfo
信息
struct siginfo info;info.si_signo = SIGCHLD; // 信号类型:子进程状态改变
info.si_errno = 0; // 无错误
info.si_pid = tsk->pid; // 子进程PID
info.si_uid = tsk->uid; // 子进程用户ID
info.si_utime = tsk->utime; // 用户态CPU时间
info.si_stime = tsk->stime; // 内核态CPU时间
info.si_code = why; // 状态改变原因代码
2.2. 根据原因设置状态值
switch (why) {
case CLD_CONTINUED:info.si_status = SIGCONT; // 子进程继续执行break;
case CLD_STOPPED:// 从信号结构中获取退出码的低7位info.si_status = tsk->signal->group_exit_code & 0x7f;break;
case CLD_TRAPPED:// 从进程退出码中获取状态info.si_status = tsk->exit_code & 0x7f;break;
default:BUG(); // 未知原因,内核错误
}
3.状态原因代码说明
原因代码 | 含义 | 典型场景 |
---|---|---|
CLD_STOPPED | 子进程停止 | 收到 SIGSTOP、SIGTSTP 等停止信号 |
CLD_CONTINUED | 子进程继续 | 收到 SIGCONT 继续信号 |
CLD_TRAPPED | 子进程被跟踪 | 调试器设置断点、单步执行等 |
4. 信号发送条件检查
sighand = parent->sighand;
spin_lock_irqsave(&sighand->siglock, flags);if (sighand->action[SIGCHLD-1].sa.sa_handler != SIG_IGN &&!(sighand->action[SIGCHLD-1].sa.sa_flags & SA_NOCLDSTOP))__group_send_sig_info(SIGCHLD, &info, parent);
发送 SIGCHLD 信号的条件:
- 父进程没有忽略 SIGCHLD:
sa_handler != SIG_IGN
- 父进程没有设置 SA_NOCLDSTOP 标志:不屏蔽停止/继续通知
5. 唤醒父进程
__wake_up_parent(tsk, parent);
重要:即使不发送 SIGCHLD 信号,也必须唤醒等待的父进程:
- 保证
wait()
,waitpid()
等系统调用能够返回 - 父进程可能正在等待子进程状态改变
七、处理停止和继续信号handle_stop_signal
#define M(sig) (1UL << ((sig)-1))
#endif
#define T(sig, mask) (M(sig) & (mask))
#define sig_kernel_stop(sig) \(((sig) < SIGRTMIN) && T(sig, SIG_KERNEL_STOP_MASK))
#define sig_user_defined(t, signr) \(((t)->sighand->action[(signr)-1].sa.sa_handler != SIG_DFL) && \((t)->sighand->action[(signr)-1].sa.sa_handler != SIG_IGN))
int fastcall wake_up_state(task_t *p, unsigned int state)
{return try_to_wake_up(p, state, 0);
}static void handle_stop_signal(int sig, struct task_struct *p)
{struct task_struct *t;if (sig_kernel_stop(sig)) {/** This is a stop signal. Remove SIGCONT from all queues.*/rm_from_queue(sigmask(SIGCONT), &p->signal->shared_pending);t = p;do {rm_from_queue(sigmask(SIGCONT), &t->pending);t = next_thread(t);} while (t != p);} else if (sig == SIGCONT) {/** Remove all stop signals from all queues,* and wake all threads.*/if (unlikely(p->signal->group_stop_count > 0)) {/** There was a group stop in progress. We'll* pretend it finished before we got here. We are* obliged to report it to the parent: if the* SIGSTOP happened "after" this SIGCONT, then it* would have cleared this pending SIGCONT. If it* happened "before" this SIGCONT, then the parent* got the SIGCHLD about the stop finishing before* the continue happened. We do the notification* now, and it's as if the stop had finished and* the SIGCHLD was pending on entry to this kill.*/p->signal->group_stop_count = 0;p->signal->stop_state = 1;spin_unlock(&p->sighand->siglock);if (p->ptrace & PT_PTRACED)do_notify_parent_cldstop(p, p->parent,CLD_STOPPED);elsedo_notify_parent_cldstop(p->group_leader,p->group_leader->real_parent,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);/** If there is a handler for SIGCONT, we must make* sure that no thread returns to user mode before* we post the signal, in case it was the only* thread eligible to run the signal handler--then* it must not do anything between resuming and* running the handler. With the TIF_SIGPENDING* flag set, the thread will pause and acquire the* siglock that we hold now and until we've queued* the pending signal.** Wake up the stopped thread _after_ setting* TIF_SIGPENDING*/state = TASK_STOPPED;if (sig_user_defined(t, SIGCONT) && !sigismember(&t->blocked, SIGCONT)) {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->stop_state > 0) {/** We were in fact stopped, and are now continued.* Notify the parent with CLD_CONTINUED.*/p->signal->stop_state = -1;p->signal->group_exit_code = 0;spin_unlock(&p->sighand->siglock);if (p->ptrace & PT_PTRACED)do_notify_parent_cldstop(p, p->parent,CLD_CONTINUED);elsedo_notify_parent_cldstop(p->group_leader,p->group_leader->real_parent,CLD_CONTINUED);spin_lock(&p->sighand->siglock);}}
}
1.函数概述
handle_stop_signal()
专门处理两类信号:
- 停止信号(SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU)
- 继续信号(SIGCONT)
2.宏定义解析
2.1.信号掩码宏
#define M(sig) (1UL << ((sig)-1))
#define T(sig, mask) (M(sig) & (mask))
#define sig_kernel_stop(sig) \(((sig) < SIGRTMIN) && T(sig, SIG_KERNEL_STOP_MASK))
M(sig)
:将信号编号转换为位掩码(信号1对应bit 0)T(sig, mask)
:检查信号是否在指定掩码中sig_kernel_stop(sig)
:判断是否是内核停止信号
2.2.用户定义信号处理检查
#define sig_user_defined(t, signr) \(((t)->sighand->action[(signr)-1].sa.sa_handler != SIG_DFL) && \((t)->sighand->action[(signr)-1].sa.sa_handler != SIG_IGN))
- 检查信号是否有用户自定义的处理函数(既不是默认也不是忽略)
3.第一部分:处理停止信号
if (sig_kernel_stop(sig)) {// 移除所有队列中的SIGCONT信号rm_from_queue(sigmask(SIGCONT), &p->signal->shared_pending);t = p;do {rm_from_queue(sigmask(SIGCONT), &t->pending);t = next_thread(t);} while (t != p);
}
逻辑:当收到停止信号时:
- 从共享待处理队列移除SIGCONT
- 遍历所有线程,从各自私有队列移除SIGCONT
- 目的:停止信号和继续信号互斥,停止时清除所有继续信号
4.第二部分:处理SIGCONT信号
这是函数的重点部分,分为几个阶段:
4.1.阶段1:处理正在进行的组停止
if (unlikely(p->signal->group_stop_count > 0)) {p->signal->group_stop_count = 0;p->signal->stop_state = 1;spin_unlock(&p->sighand->siglock);// 通知父进程停止已完成if (p->ptrace & PT_PTRACED)do_notify_parent_cldstop(p, p->parent, CLD_STOPPED);elsedo_notify_parent_cldstop(p->group_leader,p->group_leader->real_parent,CLD_STOPPED);spin_lock(&p->sighand->siglock);
}
场景:当组停止正在进行时收到SIGCONT
- 立即结束组停止(
group_stop_count = 0
) - 通知父进程"停止已完成"(即使是被SIGCONT中断的)
4.2.阶段2:清理停止信号并唤醒线程
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)) {set_tsk_thread_flag(t, TIF_SIGPENDING);state |= TASK_INTERRUPTIBLE;}wake_up_state(t, state);t = next_thread(t);
} while (t != p);
关键逻辑:根据SIGCONT处理方式决定唤醒策略
情况A:SIGCONT有用户自定义处理函数
if (sig_user_defined(t, SIGCONT) && !sigismember(&t->blocked, SIGCONT)) {set_tsk_thread_flag(t, TIF_SIGPENDING); // 设置信号待处理标志state |= TASK_INTERRUPTIBLE; // 可唤醒INTERRUPTIBLE状态
}
- 设置
TIF_SIGPENDING
,确保线程运行信号处理程序 - 可唤醒
TASK_STOPPED | TASK_INTERRUPTIBLE
状态的线程
情况B:SIGCONT使用默认处理
else {// 只设置state = TASK_STOPPED// 只能唤醒TASK_STOPPED状态的线程
}
4.3.1.设计原理:确保信号处理顺序
问题:如果线程在信号入队前就返回用户模式,可能错过信号处理
解决方案:
- 先设置
TIF_SIGPENDING
标志 - 再唤醒线程
- 线程看到
TIF_SIGPENDING
会暂停并获取信号锁 - 确保信号处理程序正确执行
4.3.阶段3:通知父进程继续状态
if (p->signal->stop_state > 0) {p->signal->stop_state = -1;p->signal->group_exit_code = 0;spin_unlock(&p->sighand->siglock);// 通知父进程"已继续"if (p->ptrace & PT_PTRACED)do_notify_parent_cldstop(p, p->parent, CLD_CONTINUED);elsedo_notify_parent_cldstop(p->group_leader,p->group_leader->real_parent,CLD_CONTINUED);spin_lock(&p->sighand->siglock);
}
5.完整执行流程示例
5.1.场景:调试器控制进程
-
调试器发送SIGSTOP停止进程
-
进程进入TASK_STOPPED状态,通知父进程CLD_STOPPED
-
调试器发送SIGCONT继续进程
-
handle_stop_signal()处理:
- 清理停止信号
- 唤醒停止的线程
- 通知父进程CLD_CONTINUED
-
进程恢复执行
5.2.场景:Shell作业控制
-
用户按Ctrl+Z,shell收到SIGTSTP
-
shell停止前台作业,作业进入停止状态
-
用户输入
fg
,shell发送SIGCONT -
作业恢复执行
6.关键设计要点
停止与继续的互斥性
- 停止时清除SIGCONT,继续时清除停止信号
- 防止信号队列中出现矛盾状态
信号处理程序的正确性
- 确保用户自定义的SIGCONT处理程序能够执行
- 通过
TIF_SIGPENDING
标志保证执行顺序
八、处理进程停止状态完成finish_stop
static void
finish_stop(int stop_count)
{/** If there are no other threads in the group, or if there is* a group stop in progress and we are the last to stop,* report to the parent. When ptraced, every thread reports itself.*/if (stop_count < 0 || (current->ptrace & PT_PTRACED)) {read_lock(&tasklist_lock);do_notify_parent_cldstop(current, current->parent,CLD_STOPPED);read_unlock(&tasklist_lock);}else if (stop_count == 0) {read_lock(&tasklist_lock);do_notify_parent_cldstop(current->group_leader,current->group_leader->real_parent,CLD_STOPPED);read_unlock(&tasklist_lock);}schedule();/** Now we don't run again until continued.*/current->exit_code = 0;
}
1.函数概述
finish_stop()
在进程进入停止状态(TASK_STOPPED)后被调用,负责:
- 通知父进程停止状态
- 调度出去,等待继续信号
- 清理状态,准备恢复执行
2.参数解析
finish_stop(int stop_count)
stop_count
:组停止计数器- 负数:表示被
ptrace
跟踪的单个线程停止 - 0:表示线程组最后一个线程停止
- 正数:表示还有其他线程需要停止
- 负数:表示被
3.第一部分:通知父进程
3.1.情况1:被ptrace
跟踪或单个线程停止
if (stop_count < 0 || (current->ptrace & PT_PTRACED)) {read_lock(&tasklist_lock);do_notify_parent_cldstop(current, current->parent, CLD_STOPPED);read_unlock(&tasklist_lock);
}
适用场景:
stop_count < 0
:单个线程停止(非组停止)current->ptrace & PT_PTRACED
:线程被调试器跟踪
通知方式:
- 每个被跟踪的线程都向自己的父进程(调试器)发送通知
- 使用
CLD_STOPPED
状态码
3.2.情况2:线程组最后一个线程停止
else if (stop_count == 0) {read_lock(&tasklist_lock);do_notify_parent_cldstop(current->group_leader,current->group_leader->real_parent,CLD_STOPPED);read_unlock(&tasklist_lock);
}
适用场景:整个线程组都进入停止状态
通知方式:
- 线程组组长向真正的父进程发送通知
- 使用
CLD_STOPPED
状态码
3.3.情况3:组停止进行中(stop_count > 0)
// 没有else分支,表示不发送通知
场景:组停止还未完成,还有其他线程在运行
- 不发送通知,等待最后一个线程停止时再通知
4.第二部分:进入停止状态
schedule();
作用:主动让出CPU,进入停止状态
执行流程:
- 当前线程状态已经是
TASK_STOPPED
schedule()
选择其他线程运行- 当前线程暂停执行,直到收到
SIGCONT
5.第三部分:恢复执行准备
current->exit_code = 0;
作用:清理退出代码,准备恢复执行
exit_code
的用途:
- 在停止期间可能存储停止原因信号
- 恢复执行前重置为0,表示正常继续
九、处理线程组停止handle_group_stop
static inline int handle_group_stop(void)
{int stop_count;if (current->signal->group_exit_task == current) {/** Group stop is so we can do a core dump,* We are the initiating thread, so get on with it.*/current->signal->group_exit_task = NULL;return 0;}if (current->signal->group_exit)/** Group stop is so another thread can do a core dump,* or else we are racing against a death signal.* Just punt the stop so we can get the next signal.*/return 0;/** There is a group stop in progress. We stop* without any associated signal being in our queue.*/stop_count = --current->signal->group_stop_count;if (stop_count == 0)current->signal->stop_state = 1;current->exit_code = current->signal->group_exit_code;set_current_state(TASK_STOPPED);spin_unlock_irq(¤t->sighand->siglock);finish_stop(stop_count);return 1;
}
1.函数概述
handle_group_stop()
负责处理线程组的协同停止,主要用于:
coredump
生成时的有序停止- 作业控制中的进程停止
- 信号处理中的组停止逻辑
2.逐行解析
2.1.第一部分:检查coredump
初始化线程
if (current->signal->group_exit_task == current) {current->signal->group_exit_task = NULL;return 0;
}
场景:当前线程被指定为coredump
生成者
逻辑:
group_exit_task == current
:这个线程负责生成coredump- 清除标记,不停止自己,继续执行
coredump
- 返回0表示继续正常执行
2.2.第二部分:检查线程组退出状态
if (current->signal->group_exit)return 0;
场景:线程组正在退出过程中
逻辑:
group_exit == 1
:整个线程组正在退出- 不进入停止状态,继续处理退出逻辑
- 返回0表示继续执行
设计原理:退出优先级高于停止,确保进程能及时退出。
2.3.第三部分:执行组停止
stop_count = --current->signal->group_stop_count;
if (stop_count == 0)current->signal->stop_state = 1;
current->exit_code = current->signal->group_exit_code;
set_current_state(TASK_STOPPED);
1. 递减停止计数器
stop_count = --current->signal->group_stop_count;
- 递减组停止计数器
- 返回值表示停止后剩余的线程数
2. 检查是否是最后一个停止的线程
if (stop_count == 0)current->signal->stop_state = 1;
- 如果
stop_count == 0
,表示这是最后一个停止的线程 - 设置
stop_state = 1
,标记组停止完成
3. 设置退出代码
current->exit_code = current->signal->group_exit_code;
- 保存停止原因信号到线程的
exit_code
- 用于父进程查询停止原因
4. 设置停止状态
set_current_state(TASK_STOPPED);
- 将当前线程状态改为
TASK_STOPPED
- 准备让出CPU
2.4.第四部分:完成停止过程
spin_unlock_irq(¤t->sighand->siglock);
finish_stop(stop_count);
return 1;
关键操作:
- 释放信号锁:
spin_unlock_irq()
- 调用
finish_stop(stop_count)
:通知父进程并调度出去 - 返回1:表示调用者应该重新检查信号
3.完整执行流程
3.1.场景1:coredump
生成者线程
1. 线程C被指定为group_exit_task
2. handle_group_stop()检测到:group_exit_task == current
3. 清除group_exit_task,返回0
4. 线程C继续执行,生成coredump
3.2.场景2:普通停止线程
1. 线程A执行handle_group_stop()
2. group_stop_count从3减为2
3. 设置exit_code = 停止信号
4. 状态改为TASK_STOPPED
5. 调用finish_stop(2) → 不通知父进程(非最后一个)
6. schedule()让出CPU
3.3.场景3:最后一个停止线程
1. 线程C执行handle_group_stop()
2. group_stop_count从1减为0
3. 设置stop_state = 1(组停止完成)
4. 调用finish_stop(0) → 通知父进程CLD_STOPPED
5. 所有线程现在都停止了