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
并且您永远不知道,它可以再次调用系统调用。