linux 服务 stop 实现,Linux下SIGSTOP的特殊特征和实现

一、问题的引出

在多线程用户态程序中,为了更加准确详细的从一个线程观察另一个线程的行为,可能有时候需要让目标线程暂时安静下来,从而便于观测和监控。关于这个行为,首先想到的当然就是向一个线程发送一个SIGSTOP信号(注意,不是向进程,就是通过内核的tkill系统调用,或者说pthread_kill),从而让线程处于STOP状态,之后再通过SIGCONT让线程继续运行,这样是最为简单而环保的方法。但是事实测试的时候会发现这个信号即使是只发给内核的单个线程,也会造成整个线程组中所有线程被停止,这就是一个比较奇怪的现象了。

二、内核对tkill的处理

linux-2.6.37.1\kernel\signal.c

SYSCALL_DEFINE2(tkill, pid_t, pid, int, sig)

{

/* This is only valid for single tasks */

if (pid <= 0)

return -EINVAL;

return do_tkill(0, pid, sig);

}

-->>>do_tkill--->>>do_send_sig_info(sig, info, p, false)---->>>send_signal--->>>__send_signal

pending = group ? &t->signal->shared_pending : &t->pending;

从代码上看,tkill发出的信号是发送给了线程私有的pending信号队列,所以直到这里看来,它依然是应该只有目标线程会接受这个信号。

三、停止线程组代码实现

do_signal--->>>get_signal_to_deliver

signr = tracehook_get_signal(current, regs, info, return_ka);

if (unlikely(signr < 0))

goto relock;

if (unlikely(signr != 0))

ka = return_ka;

else {

if (unlikely(signal->group_stop_count > 0) &&

do_signal_stop(0))结合后面的说明,如果说线程正处在一个线程组停止状态并且还有未处于stop状态的线程,则执行do_signal_stop自行停止调度。

goto relock;

signr = dequeue_signal(current, &current->blocked,

info);

if (!signr)

break; /* will return 0 */

if (signr != SIGKILL) {

signr = ptrace_signal(signr, info,

regs, cookie);

if (!signr)

continue;

}

………………

内核定义的停止信号

#define SIG_KERNEL_STOP_MASK (\

rt_sigmask(SIGSTOP)   |  rt_sigmask(SIGTSTP)   | \

rt_sigmask(SIGTTIN)   |  rt_sigmask(SIGTTOU)   )

if (sig_kernel_stop(signr)) {所以SIGSTOP信号将会走入该流程。

/*

* The default action is to stop all threads in

* the thread group.  The job control signals

* do nothing in an orphaned pgrp, but SIGSTOP

* always works.  Note that siglock needs to be

* dropped during the call to is_orphaned_pgrp()

* because of lock ordering with tasklist_lock.

* This allows an intervening SIGCONT to be posted.

* We need to check for that and bail out if necessary.

*/

if (signr != SIGSTOP) {这里也说明了很多TTY操作,如SIGTTIN等也会对线程组产生影响。

spin_unlock_irq(&sighand->siglock);

/* signals can be posted during this window */

if (is_current_pgrp_orphaned())

goto relock;

spin_lock_irq(&sighand->siglock);

}

if (likely(do_signal_stop(info->si_signo))) {

/* It released the siglock.  */

goto relock;

}

/*

* We didn't actually stop, due to a race

* with SIGCONT or something like that.

*/

continue;

}

下面是do_signal_stop的代码,这个是对于这个特征的核心代码

/*

* This performs the stopping for SIGSTOP and other stop signals.

* We have to stop all threads in the thread group.

* Returns nonzero if we've actually stopped and released the siglock.

* Returns zero if we didn't stop and still hold the siglock.

*/

static int do_signal_stop(int signr)

{

struct signal_struct *sig = current->signal;

int notify;

if (!sig->group_stop_count) {如果说gropu_stop_Count为零,则说明线程组STOP还没有启动,所以在下面的指令中要把这个值设置为需要被STOP的线程的数目。

struct task_struct *t;

if (!likely(sig->flags & SIGNAL_STOP_DEQUEUED) ||

unlikely(signal_group_exit(sig)))

return 0;

/*

* There is no group stop already in progress.

* We must initiate one now.

*/

sig->group_exit_code = signr;

sig->group_stop_count = 1;

for (t = next_thread(current); t != current; t = next_thread(t))

/*

* Setting state to TASK_STOPPED for a group

* stop is always done with the siglock held,

* so this check has no races.

*/

if (!(t->flags & PF_EXITING) &&

!task_is_stopped_or_traced(t)) {

sig->group_stop_count++;便利线程组中所有线程,对每一个尚未被处理的线程在group_stop_count中加一。

signal_wake_up(t, 0);

}

}

/*

* 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.

*/

notify = sig->group_stop_count == 1 ? CLD_STOPPED : 0;

notify = tracehook_notify_jctl(notify, CLD_STOPPED);

/*

* tracehook_notify_jctl() can drop and reacquire siglock, so

* we keep ->group_stop_count != 0 before the call. If SIGCONT

* or SIGKILL comes in between ->group_stop_count == 0.

*/

if (sig->group_stop_count) {

if (!--sig->group_stop_count)这个线程组全部完成了STOP。

sig->flags = SIGNAL_STOP_STOPPED;

current->exit_code = sig->group_exit_code;

__set_current_state(TASK_STOPPED);所有执行这个函数的线程都被设置为了TASK_STOPPED状态,接下来执行schedule函数之后该线程将会被从运行队列中移除,次数受该函数开始if中设置的group_stop_count值决定。。

}

spin_unlock_irq(&current->sighand->siglock);

if (notify) {

read_lock(&tasklist_lock);

do_notify_parent_cldstop(current, notify);

read_unlock(&tasklist_lock);

}

/* Now we don't run again until woken by SIGCONT or SIGKILL */

do {从这里该线程将会切换出去,让出调度权,

schedule();

} while (try_to_freeze());

tracehook_finish_jctl();

current->exit_code = 0;

return 1;

}

四、调试器如何做到对单个线程的SIGSTOP

由于调试器和被调试任务之间有一种内核可以感知到的调试关系,也就是被调试任务的PT_PTRACED的标志位被置位,所以当一个线程收到信号并且要处理的时候,内核会首先给调试器一个机会

get_signal_to_deliver---->>ptrace_stop--->>do_notify_parent_cldstop

if ((current->ptrace & PT_PTRACED) && signr != SIGKILL) {

ptrace_signal_deliver(regs, cookie);

/* Let the debugger run.  */

ptrace_stop(signr, signr, info);如果调试器在这个函数之后取消掉信号,也就是让exit_code清零,则下面的continue将会忽略这个信号。

/* We're back.  Did the debugger cancel the sig?  */

signr = current->exit_code;

if (signr == 0)

continue;

所以在没有启动线程组暂停之前,调试器优先获得控制权,所以调试器可以判断出是自己发送的信号,在进行必要的操作之后,通过ptrace系统调用再取消这个信号,从而让线程组退出夭折。linux-2.6.21\arch\i386\kernel\ptrace.c

long arch_ptrace(struct task_struct *child, long request, long addr, long data)

case PTRACE_SYSEMU: /* continue and stop at next syscall, which will not be executed */

case PTRACE_SYSCALL:    /* continue and stop at next (return from) syscall */

casePTRACE_CONT:    /* restart after signal. */

ret = -EIO;

if (!valid_signal(data))

break;

if (request == PTRACE_SYSEMU) {

set_tsk_thread_flag(child, TIF_SYSCALL_EMU);

clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE);

} else if (request == PTRACE_SYSCALL) {

set_tsk_thread_flag(child, TIF_SYSCALL_TRACE);

clear_tsk_thread_flag(child, TIF_SYSCALL_EMU);

} else {

clear_tsk_thread_flag(child, TIF_SYSCALL_EMU);

clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE);

}

child->exit_code = data;调试器可以通过PTRACE_CONT清除这个信号。

/* make sure the single step bit is not set. */

clear_singlestep(child);

wake_up_process(child);

ret = 0;

break;

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值