当前位置: 首页 > news >正文

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):获取任务当前运行的CPU
  • task_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_INTERRUPTIBLETASK_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);

当子进程和父进程不属于同一个线程组时,需要遍历整个线程组:

循环步骤:
  1. 唤醒当前线程wake_up_interruptible_sync(&tsk->wait_chldexit)
  2. 获取下一个线程tsk = next_thread(tsk)
  3. 一致性检查:验证信号结构是否一致
  4. 循环直到回到起始点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 信号的条件

  1. 父进程没有忽略 SIGCHLDsa_handler != SIG_IGN
  2. 父进程没有设置 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() 专门处理两类信号:

  1. 停止信号(SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU)
  2. 继续信号(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);
}

逻辑:当收到停止信号时:

  1. 从共享待处理队列移除SIGCONT
  2. 遍历所有线程,从各自私有队列移除SIGCONT
  3. 目的:停止信号和继续信号互斥,停止时清除所有继续信号

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.设计原理:确保信号处理顺序

问题:如果线程在信号入队前就返回用户模式,可能错过信号处理

解决方案

  1. 先设置TIF_SIGPENDING标志
  2. 再唤醒线程
  3. 线程看到TIF_SIGPENDING会暂停并获取信号锁
  4. 确保信号处理程序正确执行

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)后被调用,负责:

  1. 通知父进程停止状态
  2. 调度出去,等待继续信号
  3. 清理状态,准备恢复执行

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,进入停止状态

执行流程

  1. 当前线程状态已经是 TASK_STOPPED
  2. schedule() 选择其他线程运行
  3. 当前线程暂停执行,直到收到 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(&current->sighand->siglock);finish_stop(stop_count);return 1;
}

1.函数概述

handle_group_stop() 负责处理线程组的协同停止,主要用于:

  1. coredump生成时的有序停止
  2. 作业控制中的进程停止
  3. 信号处理中的组停止逻辑

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(&current->sighand->siglock);
finish_stop(stop_count);
return 1;

关键操作

  1. 释放信号锁spin_unlock_irq()
  2. 调用finish_stop(stop_count):通知父进程并调度出去
  3. 返回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. 所有线程现在都停止了

http://www.dtcms.com/a/446582.html

相关文章:

  • 【学习笔记】Redis数据库设计与实现研究综述
  • 一键整合,万用万灵,Python3.11项目嵌入式一键整合包的制作(Embed)
  • 上海做网站品牌公司wordpress删除用户头像
  • 静态网站素材网站的尾页要怎么做
  • 有关房地产开发建设的网站ps制作博客网站界面
  • 蒙阴网站优化做俄罗斯外贸网站
  • 绍兴市住房和城乡建设局网站专业网站建设机构
  • 拼多多前端面试题及参考答案(上)
  • 为食堂写个网站建设南宁建站
  • 使用Java连接redis以及开放redis端口的问题
  • Git应用详解:从入门到精通
  • 【Linux】 Ubuntu 开发环境极速搭建
  • asp学习网站网站由哪三部分组成
  • 新增网站备案时间郑州怎么做外贸公司网站
  • 十个最好的网站广州做网站厉害的公司
  • Freqtrade - Configuration 所有配置大全
  • 网站宣传与推广国家高新技术企业管理办法
  • 推广网站平台免费网站建设的几大要素
  • 网站建设知名公司排名网站克隆 有后台登录
  • 5-20 WPS JS宏 every与some数组的[与或]迭代(数组的逻辑判断)
  • Linux学习笔记--IIC子系统
  • 网站公网安备链接怎么做百度上推广一个网站该怎么做
  • 狗头网网站营销运营平台
  • LeetCode 236. 二叉树的最近公共祖先
  • 理解 Python 装饰器:@ 的强大功能
  • C++进阶(7)——包装器
  • Redis应用场景(黑马点评快速复习)
  • 泉州建站模板搭建深圳工业设计有限公司
  • 外贸出口工艺品怎么做外贸网站想自学做网站
  • 【Docker项目实战】使用Docker部署Dokuwiki个人知识库