简体   繁体   中英

Why does ignoring SIGCONT still make a process continue?

This is my code, ignoring SIGCONT :

int main() {
    signal(SIGCONT, SIG_IGN);
    while(1);
}

This is what happens:

> ./main &
[1] 25093
> kill -STOP 25093

[1]+  Stopped                 ./main
> ps -aux | grep 25093
xxx    25093 98.6  0.0   2488   872 pts/8    T    18:23   0:20 ./main
> kill -CONT 25093
> ps -aux | grep 25093
xxx    25093 52.1  0.0   2488   872 pts/8    R    18:23   0:28 ./main
  1. It seems that SIGCONT still made my process continue. Does it mean that a handler of SIGCONT is just a "side-effect"?

  2. I wonder at what time SIGCONT makes the process run again? At what time is the process put into the dispatch queue again? Is it when the kill syscall is performed or when the process is going to be dispatched? (I read a passage about Linux signals that indicates that dispatch code doesn't treat SIGCONT specially. The code segment is showed below.)

if (ka->sa.sa_handler == SIG_DFL) { 
  int exit_code = signr; 
 
  /* Init gets no signals it doesn't want.  */ 
  if (current->pid == 1) 
   continue; 
 
  switch (signr) { 
  case SIGCONT: case SIGCHLD: case SIGWINCH: 
   continue; 
 
  case SIGTSTP: case SIGTTIN: case SIGTTOU: 
   if (is_orphaned_pgrp(current->pgrp)) 
    continue; 
   /* FALLTHRU */ 
 
  case SIGSTOP: 
   current->state = TASK_STOPPED; 
   current->exit_code = signr; 
   if (!(current->p_pptr->sig->action[SIGCHLD-1].sa.sa_flags & SA_NOCLDSTOP)) 
    notify_parent(current, SIGCHLD); 
   schedule(); 
   continue; 
 
  case SIGQUIT: case SIGILL: case SIGTRAP: 
  case SIGABRT: case SIGFPE: case SIGSEGV: 
  case SIGBUS: case SIGSYS: case SIGXCPU: case SIGXFSZ: 
   if (do_coredump(signr, regs)) 
    exit_code |= 0x80; 
   /* FALLTHRU */ 
 
  default: 
   sigaddset(&current->pending.signal, signr); 
   recalc_sigpending(current); 
   current->flags |= PF_SIGNALED; 
   do_exit(exit_code); 
   /* NOTREACHED */ 
  } 
 } 
 ... 
 handle_signal(signr, ka, &info, oldset, regs); 
 return 1; 
} 
... 
return 0; 

This is the intended behavior of SIGCONT according to the POSIX standard. Quoting from POSIX.1-2017 chapter 2 , section 2.4 "Signal Concepts", subsection 2.4.1:

When SIGCONT is generated for a process that is stopped, the process shall be continued, even if the SIGCONT signal is ignored by the process or is blocked by all threads within the process and there are no threads in a call to a sigwait() function selecting SIGCONT . If SIGCONT is blocked by all threads within the process, there are no threads in a call to a sigwait() function selecting SIGCONT , and SIGCONT is not ignored by the process, the SIGCONT signal shall remain pending on the process until it is either unblocked by a thread or a thread calls a sigwait() function selecting SIGCONT , or a stop signal is generated for the process or any of the threads within the process.

You cannot prevent SIGCONT from resuming execution of your process. The most you can do is block its delivery, meaning that if you add SIGCONT to the set of blocked signals your process will not "notice" it (registered handlers will not run until unblocked), but it will n.netheless resume execution.


In Linux, the "continuing" action of SIGCONT is performed right away on signal generation, ie during the kill syscall. This is done before even checking if the signal is blocked or ignored. The code responsible for this is in prepare_signal() :

/*
 * Handle magic process-wide effects of stop/continue signals. Unlike
 * the signal actions, these happen immediately at signal-generation
 * time regardless of blocking, ignoring, or handling.  This does the
 * actual continuing for SIGCONT, but not the actual stopping for stop
 * signals. The process stop is done as a signal action for SIG_DFL.
 *
 * Returns true if the signal should be actually delivered, otherwise
 * it should be dropped.
 */
static bool prepare_signal(int sig, struct task_struct *p, bool force)
{
    // ...

    } else if (sig == SIGCONT) {
        unsigned int why;
        /*
         * Remove all stop signals from all queues, wake all threads.
         */
        siginitset(&flush, SIG_KERNEL_STOP_MASK);
        flush_sigqueue_mask(&flush, &signal->shared_pending);
        for_each_thread(p, t) {
            flush_sigqueue_mask(&flush, &t->pending);
            task_clear_jobctl_pending(t, JOBCTL_STOP_PENDING);
            if (likely(!(t->ptrace & PT_SEIZED))) {
                t->jobctl &= ~JOBCTL_STOPPED;
                wake_up_state(t, __TASK_STOPPED);
            } else
                ptrace_trap_notify(t);
        }

    // ...
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM