进程的数据输入输出

问题:进程参数 和 环境变量 对于进程意味着什么?

进程参数和环境变量的意义

  • 一般情况下,子进程的创建是为了解决某个子问题
  • 子进程解决问题需要父进程 "数据输入" (进程参数 & 环境变量)
  • 设计原则:

    • 子进程启动时必然用到的参数使用进程参数传递
    • 子进程解决问题可能用到的参数使用环境变量传递

    image.png

思考 : 子进程 如何将结果 返回 父进程?

image.png

#include <stdio.h>

int main()
{
    printf("Test: Hello World!\n");

    return 19;
}
tiansong@tiansong:~/Desktop/linux$ ./a.out 
Test: Hello World!
tiansong@tiansong:~/Desktop/linux$ echo $?  // 打印最近一个进程的执行结果
19

深入理解父子进程

  • 子进程的创建是为了并行的解决子问题(问题分解)
  • 父进程需要通过子进程的结果最终解决问题(并获取结果)
#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);

进程等待系统接口

  • pid_t wait(int *status);

    • 等待一个子进程完成, 并返回子进程标识和状态信息
    • 当有多个子进程完成,随机挑选一个子进程返回
  • pid_t waitpid(pid_t pid, int *status, int options);

    • 可等待特定的子进程或一组子进程
    • 在子进程还未终止时,可通过 options 设置不必等待(直接返回)

进程退出系统接口

  • 头文件: #include <unistd.h>
  • void _exit(int status); // 系统调用,终止当前进程
  • 头文件: #include <stdlib.h>
  • void exit(int status); // 库函数,先做资源清理,再通过系统调用终止进程
  • void abort(void); // 异常终止当前进程(通过产生 SIGABRT 信号终止)

进程退出状态解读

下面的程序运行后会发生什么?
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
    pid_t pid = 0;
    int a = 1;
    int b = 0;
    int status = 0;

    printf("parent = %d\n", getpid());

    if ((pid = fork()) == 0) exit(-1);

    printf("child = %d\n", pid);

    if ((pid = fork()) == 0) abort();

    printf("child = %d\n", pid);

    if ((pid = fork()) == 0) a = a /b,  exit(-1);

    printf("child = %d\n", pid);

    sleep(3);

    while ((pid = wait(&status)) > 0) {
        printf("child: %d, status = %x\n", pid, status);
    }

    return 0;
}
tiansong@tiansong:~/Desktop/linux$ ./a.out 
parent = 8785
child = 8786
child = 8787
child = 8788
child: 8786, status = ff00  // ff ←→ -1
child: 8787, status = 86
child: 8788, status = 88

进程退出状态详解

image.png

core dump : 进程异常终止前状态
描述
WIFEXITED(stat)通过 stat 判断进程是否正常结束 [main-return, exit(...)]
WIFSIGNALED(stat)通过 stat 判断进程是否因为信号而被终止 [abort(...)]
WIFSTOPPED(stat)通过 stat 判断进程是否因为信号而被暂停执行
WEXITSTATUS(stat)获取正常结束时的状态值
WTERMSIG(stat)获取导致进程终止的信号值
WSTOPSIG(stat)获取导致进程终停的信号值
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
    pid_t pid = 0;
    int a = 1;
    int b = 0;
    int status = 0;

    printf("parent = %d\n", getpid());

    if ((pid = fork()) == 0) exit(-1);

    printf("child = %d\n", pid);

    if ((pid = fork()) == 0) abort();

    printf("child = %d\n", pid);

    if ((pid = fork()) == 0) a = a /b,  exit(-1);

    printf("child = %d\n", pid);

    sleep(3);

    while ((pid = wait(&status)) > 0) {
        if (WIFEXITED(status)) {
            printf("Normal - child: %d, code: %d\n", pid, (char)WEXITSTATUS(status));
        }
        else if (WIFSIGNALED(status)) {
            printf("Signaled - child: %d, code: %d\n", pid, WTERMSIG(status));
        }
        else {
            printf("Signaled - child: %d, code: %d\n", pid, WSTOPSIG(status));
        }
    }

    return 0;
}
tiansong@tiansong:~/Desktop/linux$ ./a.out 
parent = 8699
child = 8700
child = 8701
child = 8702
Normal - child: 8700, code: -1
Signaled - child: 8701, code: 6
Signaled - child: 8702, code: 8

僵尸进程深度分析

根据实验做成的推理:父进程创建了三个子进程后休眠,在休眠的这段时间内,根据代码和打印,子进程已经运行结束.意外着三个子进程的生命期已经完结,在系统中应该不再存在.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
    pid_t pid = 0;
    int a = 1;
    int b = 0;
    int status = 0;

    printf("parent = %d\n", getpid());

    if ((pid = fork()) == 0) exit(-1);

    printf("child = %d\n", pid);

    if ((pid = fork()) == 0) abort();

    printf("child = %d\n", pid);

    if ((pid = fork()) == 0) a = a /b,  exit(-1);

    printf("child = %d\n", pid);

    sleep(120);  // 修改这里,便于实验观察

    while ((pid = wait(&status)) > 0) {
        if (WIFEXITED(status)) {
            printf("Normal - child: %d, code: %d\n", pid, (char)WEXITSTATUS(status));
        }
        else if (WIFSIGNALED(status)) {
            printf("Signaled - child: %d, code: %d\n", pid, WTERMSIG(status));
        }
        else {
            printf("Signaled - child: %d, code: %d\n", pid, WSTOPSIG(status));
        }
    }

    return 19;
}
tiansong@tiansong:~/Desktop/linux$ ./a.out &
[1] 8921
parent = 8921
child = 8923
child = 8924
child = 8925

tiansong@tiansong:~/Desktop/linux$ pstree -p -A -s 8921  // 证明三个子进程存在于系统中
systemd(1)---sh(2545)---node(2555)---node(2738)---bash(2947)---a.out(8921)-+-a.out(8923)
                                                                           |-a.out(8924)
                                                                           `-a.out(8925)


tiansong@tiansong:~/Desktop/linux$ ps ax | grep pts/4    // 证明三个子进程存在于系统中
   2947 pts/4    Ss     0:00 /bin/bash --init-file /home/tiansong/.vscode-server/bin/6509174151d557a81c9d0b5f8a5a1e9274db5585/out/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh
   8921 pts/4    S      0:00 ./a.out
   8923 pts/4    Z      0:00 [a.out] <defunct>  // Z 僵尸状态
   8924 pts/4    Z      0:00 [a.out] <defunct>  // Z 僵尸状态
   8925 pts/4    Z      0:00 [a.out] <defunct>  // Z 僵尸状态
   9313 pts/4    R+     0:00 ps ax
   9314 pts/4    S+     0:00 grep --color=auto pts/4
再次实验证明,三个子进程在父进程休眠期间已经运行结束,然而运行结束之后,这三个子进程的"尸体"并没有完成回收(因此进程树中被打印,在进程表中被发现 Z ),进入"僵尸状态"

僵尸进程(僵死状态)

  • 理论上,进程 退出 / 终止 后应立即释放所 有系统资源
  • 然而,为了给父进程提供一些重要信息,子进程 退出 / 终止 所占用的部分资源会暂留
  • 当父进程收集这部分信息后(wait / waitpid), 子进程所有资源被释放

    • 父进程调用 wait(), 为子进程 "收尸" 处理并释放暂留资源
    • 若父进程退出,init / systemd 为子进程"收尸" 处理并释放暂留资源

僵尸进程的危害

  • 僵尸进程保留进程的 终止状态 和 资源 的使用信息

    • 进程为何退出,进程消耗多少 CPU 时间,进程最大内存驻留值,等
  • 如果僵尸进程得不到回收,那么可能影响正常进程的创建

    • 进程创建最重要的资源是内存和进程标识(PID)
    • 僵尸进程的存在可看作一种类型的内存泄露
    • 当系统僵尸进程过多,可能导致进程标识不足,无法创建新进程

wait(...) 的局限

  • 不能等待指定子进程,如果存在多个子进程,只能逐一等待完成
  • 如果不存在终止的子进程,父进程只能阻塞等待
  • 只针对终止的进程,无法发现暂停的进程

waitpid(...)

  • pid_t waitpid(pid_t pid, int *status, int options);
  • 返回值相同,终止子进程标识符
  • 状态值意义相同,记录子进程终止信息
  • 特殊之处
pid意 义
pid > 0等待进程标识为 pid 的进程
pid == 0等待当前进程组中的任意子进程
pid == -1等待任意子进程,即:wait(&stat) ←→ waitpid(-1, &stat, 0)
pid < -1等待指定进程组中的任意子进程

僵尸进程运行指南

下面的程序运行后发生什么?

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

static void worker(pid_t pid)
{
    printf("grand-child: %d\n", pid);
    sleep(150);
}

int main(int argc, char* argv[])
{
    pid_t pid = 0;
    int status = 0;

    printf("parent = %d\n", getpid());

    pid = fork();                           // 父进程

    if( pid < 0 ) {
        printf("fork error\n");
    }
    else if( pid == 0 ) {                   // 子进程
        int i = 0;
        for(i=0; i<5; i++) {
            if( (pid = fork()) == 0 ) {     // 孙进程
                worker(getpid());
                break;
            }
        }

        sleep(60);

        printf("child(%d) is over...\n", getpid());
    }
    else {
        printf("wait child = %d\n", pid);
        sleep(120);
        while( waitpid(pid, &status, 0) == pid ) {
            printf("Parent is over - child: %d, status = %x\n", pid, status);
        }
    }

    return 0;
}
请仔细阅读下述执行流
// 后台运行程序,创建 1 个父进程,1 个子进程,5 个孙子进程
//==================================================  第一阶段
tiansong@tiansong:~/Desktop/linux$ ./a.out &
[1] 15532
parent = 15532
wait child = 15534
grand-child: 15535
grand-child: 15536
grand-child: 15537
grand-child: 15538
grand-child: 15539

// 确认存在 1 个父进程,1 个子进程,5 个孙子进程
tiansong@tiansong:~/Desktop/linux$ ps ax | grep pts/7
  12984 pts/7    Ss     0:00 /bin/bash --init-file /home/tiansong/.vscode-server/bin/6509174151d557a81c9d0b5f8a5a1e9274db5585/out/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh
  15532 pts/7    S      0:00 ./a.out
  15534 pts/7    S      0:00 ./a.out
  15535 pts/7    S      0:00 ./a.out
  15536 pts/7    S      0:00 ./a.out
  15537 pts/7    S      0:00 ./a.out
  15538 pts/7    S      0:00 ./a.out
  15539 pts/7    S      0:00 ./a.out
  15617 pts/7    R+     0:00 ps ax
  15618 pts/7    S+     0:00 grep --color=auto pts/7

// 确认存在 1 个父进程,1 个子进程,5 个孙子进程
tiansong@tiansong:~/Desktop/linux$ pstree -p -A -s 15532
systemd(1)---sh(2545)---node(2555)---node(2738)---bash(12984)---a.out(15532)---a.out(15534)-+-a.out(15535)
                                                                                            |-a.out(15536)
                                                                                            |-a.out(15537)
                                                                                            |-a.out(15538)
                                                                                            `-a.out(15539)
//==================================================  第二阶段

// 子进程结束
tiansong@tiansong:~/Desktop/linux$ child(15534) is over

// 子进程成为僵尸进程, Z 
ps ax | grep pts/7
  12984 pts/7    Ss     0:00 /bin/bash --init-file /home/tiansong/.vscode-server/bin/6509174151d557a81c9d0b5f8a5a1e9274db5585/out/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh
  15532 pts/7    S      0:00 ./a.out
  15534 pts/7    Z      0:00 [a.out] <defunct>
  15535 pts/7    S      0:00 ./a.out
  15536 pts/7    S      0:00 ./a.out
  15537 pts/7    S      0:00 ./a.out
  15538 pts/7    S      0:00 ./a.out
  15539 pts/7    S      0:00 ./a.out
  15815 pts/7    R+     0:00 ps ax
  15816 pts/7    S+     0:00 grep --color=auto pts/7

// 孙进程从当前进程数消失!!
tiansong@tiansong:~/Desktop/linux$ pstree -p -A -s 15532
systemd(1)---sh(2545)---node(2555)---node(2738)---bash(12984)---a.out(15532)---a.out(15534)

// 孙进程被 systemd 托管
tiansong@tiansong:~/Desktop/linux$ pstree -p -A -s 1
systemd(1)-+-ModemManager(912)-+-{ModemManager}(945)
           |                   `-{ModemManager}(956)
           |-NetworkManager(845)-+-{NetworkManager}(927)
           |                     `-{NetworkManager}(930)
           |-VGAuthService(800)
           |-a.out(15535)
           |-a.out(15536)
           |-a.out(15537)
           |-a.out(15538)
           |-a.out(15539)
           ......

//==================================================  第三阶段

// 父进程运行结束
tiansong@tiansong:~/Desktop/linux$ Parent is over - child: 15534, status = 0

// 确认父进程,子进程消失(被父进程清理)
ps ax | grep pts/7
[1]+  Done                    ./a.out
  12984 pts/7    Ss     0:00 /bin/bash --init-file /home/tiansong/.vscode-server/bin/6509174151d557a81c9d0b5f8a5a1e9274db5585/out/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh
  15535 pts/7    S      0:00 ./a.out
  15536 pts/7    S      0:00 ./a.out
  15537 pts/7    S      0:00 ./a.out
  15538 pts/7    S      0:00 ./a.out
  15539 pts/7    S      0:00 ./a.out
  15931 pts/7    R+     0:00 ps ax
  15932 pts/7    S+     0:00 grep --color=auto pts/7

//==================================================  第四阶段

// 孙进程结束
tiansong@tiansong:~/Desktop/linux$ child(15539) is over...
child(15536) is over...
child(15535) is over...
child(15537) is over...
child(15538) is over...

// 孙进程消失,被 systemd 清理
ps ax | grep pts/7
  12984 pts/7    Ss     0:00 /bin/bash --init-file /home/tiansong/.vscode-server/bin/6509174151d557a81c9d0b5f8a5a1e9274db5585/out/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh
  15969 pts/7    R+     0:00 ps ax
  15970 pts/7    S+     0:00 grep --color=auto pts/7
  • 利用 wait(...) 返回值判断是否继续等待子进程

    • while((pid = wait(&status)) > 0) {}
  • 利用 waitpid(...)init / systemd 回收子进程

    • 通过 fork() 创建孙进程解决子进程 [将孙进程托付给init/systemd进程]

TianSong
734 声望138 粉丝

阿里山神木的种子在3000年前已经埋下,今天不过是看到当年注定的结果,为了未来的自己,今天就埋下一颗好种子吧