处于 TASK_INTERRUPTIBLE 状态的进程是否只能通过信号的传递来唤醒?

处于 TASK_INTERRUPTIBLE 状态的进程是否只能通过信号的传递来唤醒?

Linux 编程接口

22.3 可中断和不可中断进程睡眠状态

我们需要在之前的声明中添加一个附带条件,即 SIGKILL 和 SIGSTOP 始终立即对进程执行操作。在不同的时间,内核可能会让进程进入睡眠状态,并且有两种睡眠状态:

  • 任务_可中断: 该进程正在等待某个事件。例如,它正在等待终端输入、将数据写入当前空的管道或增加 System V 信号量的值。进程可能在此状态下花费任意长度的时间。如果在此状态下为进程生成信号,则操作将被中断,并且该进程将通过信号的传递而被唤醒。当通过 ps(1) 列出时,处于 TASK_INTERRUPTIBLE 状态的进程在 STAT(进程状态)字段中用字母 S 标记。

  • TASK_UNINTERRUPTIBLE :进程正在等待某些特殊类别的事件,例如磁盘 I/O 的完成。如果为处于此状态的进程生成信号,则直到进程退出此状态后才会传递该信号。处于 TASK_UNINTERRUPTIBLE 状态的进程由 ps(1) 列出,STAT 字段中带有 D。

因此,它表示处于 TASK_INTERRUPTIBLE 状态的进程正在等待某个事件,并通过为该进程生成的信号的传送而被唤醒。

  • 进程等待的事件和唤醒进程的信号之间有什么关系?信号必须表明事件的发生吗?或者信号可以与事件无关吗?

  • 处于 TASK_INTERRUPTIBLE 状态的进程是否只能通过信号的传递来唤醒?

例如,当一个进程调用wait()waitpid()等待任何子进程的终止时,

  • 它的状态是否变为TASK_INTERRUPTIBLE?
  • 什么可以让调用wait()/waitpid()返回?仅仅是SIGCHLD的传递吗?当子进程终止时,是否一定会生成 SIGCHLD 并将其传递给进程?

谢谢。

答案1

进程等待的事件和唤醒进程的信号之间有什么关系?信号必须表明事件的发生吗?或者信号可以与事件无关吗

信号和进程正在等待的事件之间不需要有任何关系。使用您的示例,进程可以处于可中断的睡眠状态,等待子进程终止,并且可以唤醒它以响应任何信号。

考虑以下示例程序:

#include <stdio.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

static void signal_handler(int signo)
{
#define msg "received signal\n"
    write(STDOUT_FILENO, msg, sizeof(msg));
#undef msg
}

int main(void)
{
    const struct sigaction act = {
        .sa_handler = signal_handler,
        // Not including SA_RESTART in sa_flags
    };

    if (sigaction(SIGHUP, &act, NULL) < 0) {
        perror("sigaction");
        return 1;
    }

    const int pid = fork();
    if (pid < 0) {
        perror("fork");
        return 1;
    }

    if (pid == 0) {
        // Child -- hang out a while
        sleep(60);
    } else {
        // parent
        if (syscall(SYS_wait4, pid,  NULL, 0, NULL) < 0) {
            perror("wait");
        }
    }

    return 0;
}

该程序首先设置一个信号处理程序来响应SIGHUP信号。我愿意不是包含该SA_RESTART标志,这会导致某些库函数自动重新启动中断的系统调用。

然后该程序用于fork()创建一个新进程;孩子睡了一分钟,父母等待孩子结束。

请注意,这里我使用syscall(SYS_wait4,...而不是waitpid().为什么?我想消除waitpid()C 库函数中实现的任何行为——我跳过 C 库函数并直接进入内核。

现在,我可以运行这个程序:

$ ./a.out

然后在另一个终端,我看到:

$ ps aux | grep a.out
username     7839  0.0  0.0   2128   692 pts/0    S+   22:28   0:00 ./a.out
username     7840  0.0  0.0   2128    76 pts/0    S+   22:28   0:00 ./a.out

请注意,这S意味着可中断睡眠,所以是的,在调用中被阻止的进程wait()处于可中断睡眠状态。 (请注意,在调用中被阻止的进程sleep()也处于可中断的睡眠状态。)

现在,如果我向该进程发送信号SIGHUP

$ kill -HUP 7839

我看到程序的输出:

received signal
wait: Interrupted system call

来自received signal函数signal_handler(),来自Interrupted system call对的调用,perror()以响应对的调用返回的错误waitpid()(真的wait4())。信号的传递HUP中断了睡眠,执行了信号处理程序,然后导致调用等待返回EINTR(中断的系统调用)。

处于 TASK_INTERRUPTIBLE 状态的进程是否只能通过信号的传递来唤醒?

传递信号是一种方式,但不是唯一的方式仅有的TASK_INTERRUPTIBLE可以唤醒处于该状态的进程的方式。例如,考虑一个进程阻塞等待输入。当输入可用时,进程被唤醒。在这种情况下,唤醒过程中不涉及任何信号。

它的[在调用中被阻止的进程wait()]状态是否变为TASK_INTERRUPTIBLE?

是的,见上文。

什么可以使 wait()/waitpid() 调用返回?难道只是发送SIGCHLD吗?当子进程终止时,是否一定会生成 SIGCHLD 并将其传递给进程?

这里发生了两件事:(1) 来自内核系统调用接口的行为,以及 (2) 来自 C 库函数的行为。通常,你的程序调用C库函数,而C库函数调用系统调用。库函数可以检查系统调用的返回值。例如,如果前一个调用返回EINTR并且您永远不知道,它可以再次调用系统调用。

相关内容