计算机系统篇之异常控制流(9):异常控制流 FAQ
Author: stormQ
Created: Monday, 21. September 2020 02:47PM
Last Modified: Monday, 21. September 2020 03:24PM
本文描述了异常控制流相关的常见问题,比如:1)孤儿进程的父进程一定是 init 进程吗?2)子进程停止时,内核一定会发送 SIGCHLD 信号给父进程吗?3)…
验证思路:
构造这样一种情形:子进程在父进程终止后仍运行。此时,在子进程中获得其父进程 PID。如果父进程 PID 不是 1(即init
进程的进程 ID),那么表明孤儿进程的父进程不一定是init
进程。
验证过程:
源码,proc22_main.cpp:
#include <cstdio>
#include <cstdlib>
#include <unistd.h>
void foo(int exit_status)
{
if (0 == fork())
{
for (int i = 0; i < 3; i++)
{
const auto self_pid = getpid();
const auto parent_pid = getppid();
std::printf("I'm child(pid:%d), parent PID: %d\n",
self_pid, parent_pid);
sleep(1);
}
std::exit(exit_status);
}
}
int main()
{
for (int i = 0; i < 5; i++)
{
foo(i);
}
return 0;
}
编译:
$ g++ -o proc22_main proc22_main.cpp -g
运行(On Ubuntu 16.04):
$ ./proc22_main
I'm child(pid:10418), parent PID: 10417
I'm child(pid:10419), parent PID: 10417
I'm child(pid:10420), parent PID: 10417
I'm child(pid:10421), parent PID: 10417
I'm child(pid:10422), parent PID: 10417
$ I'm child(pid:10418), parent PID: 3246
I'm child(pid:10420), parent PID: 3246
I'm child(pid:10419), parent PID: 3246
I'm child(pid:10421), parent PID: 3246
I'm child(pid:10422), parent PID: 3246
I'm child(pid:10418), parent PID: 3246
I'm child(pid:10421), parent PID: 3246
I'm child(pid:10420), parent PID: 3246
I'm child(pid:10422), parent PID: 3246
I'm child(pid:10419), parent PID: 3246
从上面的输出结果中可以看出,在父进程退出后,子进程(PID 为 10420)的父进程 PID 为 3246。
查看 PID 为 3246 的进程启动项:
$ cat /proc/3246/cmdline
/sbin/upstart--user
可以看出,在父进程退出后,子进程(PID 为 10420)的父进程为/sbin/upstart--user
,而非init
进程。
验证结论:
孤儿进程的父进程不一定是 init 进程。
验证思路:
我们知道,只有当子进程已终止时,才可以对其进行回收。如果可以构造这样一种情形:父进程收到了一个SIGCHLD
信号,但子进程尚未终止,那么表明父进程收到一个SIGCHLD
信号时,不一定有子进程可以回收了。
验证过程:
源码,proc26_main.cpp:
#include <cstdio>
#include <cstdlib>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <cstring>
void foo(int exit_status)
{
if (0 == fork())
{
const auto self_pid = getpid();
const auto parent_pid = getppid();
std::printf("I'm child(pid:%d), parent PID: %d\n",
self_pid, parent_pid);
for (int i = 0; i < 3; i++)
{
sleep(1000);
}
std::exit(exit_status);
}
}
void sigchld_hanlder(int sig)
{
if (SIGCHLD != sig)
{
return;
}
const char *msg = "catch SIGCHLD signal\n";
write(STDOUT_FILENO, msg, strlen(msg));
}
bool SetSignalHanlder(int sig, void (*hanlder)(int))
{
struct sigaction sa;
std::memset(&sa, 0, sizeof(sa));
sa.sa_handler = hanlder;
sa.sa_flags = SA_RESTART; // restart syscalls if possible
return 0 == sigaction(sig, &sa, NULL);
}
int main()
{
SetSignalHanlder(SIGCHLD, sigchld_hanlder);
for (int i = 0; i < 1; i++)
{
foo(i);
}
while (true)
{
sleep(1);
}
return 0;
}
编译:
$ g++ -o proc26_main proc26_main.cpp -g
运行:
$ ./proc26_main
I'm child(pid:16174), parent PID: 16173
在运行 proc26_main 后,向子进程(PID 为 16174)发送一个SIGTSTP
信号,用于停止该进程。在另一个 shell 窗口中执行如下命令:
$ kill -SIGTSTP 16174
在执行上述命令后,父进程会打印如下内容:
catch SIGCHLD signal
上面的输出结果表示父进程收到了一个SIGCHLD
信号。
此时,查看进程启动项中所有含proc26_main
的进程:
$ pgrep -f "proc26_main"
16173
16174
可以看出,在父进程收到了一个SIGCHLD
信号后,子进程(PID 为 16174)尚未终止。此时,子进程处于停止状态,不能被回收。
另外,需要注意:子进程处于停止状态时,再向其发送SIGTSTP
信号,内核不会再发送SIGCHLD
信号给父进程。
验证结论:
父进程收到一个SIGCHLD
信号时,不一定有子进程可以回收了。
验证思路:
如果可以构造这样一种情形:子进程变成已停止状态时,父进程未收到SIGCHLD
信号,那么表明子进程停止时,内核不一定会发送SIGCHLD
信号给父进程。
验证过程:
源码,proc27_main.cpp:
#include <cstdio>
#include <cstdlib>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <cstring>
void foo(int exit_status)
{
if (0 == fork())
{
const auto self_pid = getpid();
const auto parent_pid = getppid();
std::printf("I'm child(pid:%d), parent PID: %d\n",
self_pid, parent_pid);
for (int i = 0; i < 3; i++)
{
sleep(1000);
}
std::exit(exit_status);
}
}
void sigchld_hanlder(int sig)
{
if (SIGCHLD != sig)
{
return;
}
const char *msg = "catch SIGCHLD signal\n";
write(STDOUT_FILENO, msg, strlen(msg));
}
void sigtstp_hanlder(int sig)
{
if (SIGTSTP != sig)
{
return;
}
const char *msg = "catch SIGTSTP signal\n";
write(STDOUT_FILENO, msg, strlen(msg));
}
bool SetSignalHanlder(int sig, void (*hanlder)(int))
{
struct sigaction sa;
std::memset(&sa, 0, sizeof(sa));
sa.sa_handler = hanlder;
sa.sa_flags = SA_RESTART; // restart syscalls if possible
return 0 == sigaction(sig, &sa, NULL);
}
int main()
{
SetSignalHanlder(SIGCHLD, sigchld_hanlder);
SetSignalHanlder(SIGTSTP, sigtstp_hanlder);
for (int i = 0; i < 1; i++)
{
foo(i);
}
while (true)
{
sleep(1);
}
return 0;
}
编译:
g++ -o proc27_main proc27_main.cpp -g
运行:
$ ./proc27_main
I'm child(pid:20917), parent PID: 20916
在运行 proc27_main 后,向子进程(PID 为 20917)发送一个SIGTSTP
信号,用于停止该进程。在另一个 shell 窗口中执行如下命令:
$ kill -SIGTSTP 20917
在执行上述命令后,子进程会打印如下内容:
catch SIGTSTP signal
上面的输出结果表示子进程收到了一个SIGTSTP
信号。
但是,父进程未打印catch SIGCHLD signal
,表明父进程未收到SIGCHLD
信号。
因此,可以得出结论:如果导致子进程停止的信号的处理程序不是默认行为时,子进程停止不会导致父进程收到SIGCHLD
信号。 也就是说,子进程停止时,父进程会收到SIGCHLD
信号的前提是导致子进程停止的信号的处理程序是默认行为。
验证结论:
子进程停止时,内核不一定会发送SIGCHLD
信号给父进程。
原因:
由于同一种类型的待处理信号至多有一个,即信号不会排队等待。所以,不能用信号对其他进程中发生的事件计数。
下一篇:计算机系统篇之异常控制流(10):Chapter 8 Exceptional Control Flow 章节习题与解答