计算机系统篇之异常控制流:异常控制流 FAQ

Posted csstormq

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了计算机系统篇之异常控制流:异常控制流 FAQ相关的知识,希望对你有一定的参考价值。

计算机系统篇之异常控制流(9):异常控制流 FAQ

Author: stormQ

Created: Monday, 21. September 2020 02:47PM

Last Modified: Monday, 21. September 2020 03:24PM


FAQ 1:孤儿进程的父进程一定是 init 进程吗?

验证思路:

构造这样一种情形:子进程在父进程终止后仍运行。此时,在子进程中获得其父进程 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 进程。


FAQ 2:只要父进程收到一个 SIGCHLD 信号,就一定有子进程可以回收吗?

验证思路:

我们知道,只有当子进程已终止时,才可以对其进行回收。如果可以构造这样一种情形:父进程收到了一个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信号时,不一定有子进程可以回收了。


FAQ 3:子进程停止时,内核一定会发送 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信号给父进程。


FAQ 4:为什么不能用信号对其他进程中发生的事件计数?

原因:

由于同一种类型的待处理信号至多有一个,即信号不会排队等待。所以,不能用信号对其他进程中发生的事件计数。

如果你觉得本文对你有所帮助,欢迎关注公众号,支持一下!

以上是关于计算机系统篇之异常控制流:异常控制流 FAQ的主要内容,如果未能解决你的问题,请参考以下文章

计算机系统篇之异常控制流:如何正确地回收子进程

计算机系统篇之异常控制流:如何正确地回收子进程

计算机系统篇之异常控制流:利用 fork 和 execve 实现一个简易的 shell 程序

计算机系统篇之异常控制流:利用 fork 和 execve 实现一个简易的 shell 程序

计算机系统篇之异常控制流:如何正确地让调用线程休眠一段时间

计算机系统篇之异常控制流:如何正确地让调用线程休眠一段时间