进程等待
前面我们了解了如果父进程没有回收子进程, 那么当子进程接收后, 就会一直处于僵尸状态, 导致内存泄漏, 那么我们如何让父进程来回收子进程的资源.
waitpid
我们可以通过 Linux 提供的系统调用函数 wait 系列函数来等待子进程死亡, 并回收资源.
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
wait函数用于等待任何一个子进程结束, 并回收其资源.
status:指向整数的指针, 用于存储子进程的退出状态. 如果不需要这个信息, 可以传递NULL.- 成功时返回被等待的子进程的PID, 失败时返回 -1, 并设置 errno.
waitpid函数允许父进程等待特定的子进程结束
pid:子进程的PID. 如果为 -1, 则等待任何一个子进程。
status:同wait函数.
options:等待选项, 常用的有 WNOHANG (非阻塞等待).- 成功时返回被等待的子进程的PID. 失败时返回
-1,并设置errno。
一般来说, 用 waitpid 多一点, 因为 waitpid 提供的更为细致的操作.
int main()    
{    pid_t id = fork();    if(id<0)    {    perror("fork");    exit(1);    }    if(id==0)//子进程代码    {    int count = 5;    while(count)    {    printf("[%d]我是子进程,我的pid是: %d\n",count,getpid());    sleep(1);    count--;    }    exit(0);//子进程执行完代码后退出, exit 会直接终止本进程                                                                        }                                                                                                    //父进程代码                                                                                         waitpid(id,NULL,0);                                                                                  printf("等待子进程成功!\n");                                                                         return 0;     
}

可以观察到, 在等待子进程结束之前, 父进程卡在了 waitpid(), 直到子进程都被等待成功, 父进程才会继续向后执行.
status 参数
在 wait 和 waitpid 函数中都存在一个 status 的参数.
 在 status 中存储着子进程的退出码和退出信号.
 如果父进程想要了解子进程的退出信息, 可以通过 status 来了解.

status 是一个 int 类型的变量, 一共有 32 个bit, 我们主要看它的低 16 位bit.
那么如何获取退出状态 (退出码) 和 信号
退出状态 (退出码):
(status >> 8) & 0xFF
退出信号:
status & 0x7F
int main()    
{    pid_t id = fork();    if(id<0)    {    perror("fork");    exit(1);    }    if(id==0)//子进程代码    {    int count = 5;    while(count)    {    printf("[%d]我是子进程,我的pid是: %d\n",count,getpid());    sleep(1);    count--;    }    exit(55);//子进程执行完代码后退出                                                                                                                               }    //父进程代码    int status = 0;    waitpid(id,&status,0);    printf("等待子进程成功!\n");    printf("进程退出码: %d,进程退出信号: %d\n",(status >> 8) & 0xFF,status & 0x7F);    return 0;    
}  

当子进程在运行时, 使用 kill -9 617714命令, 来终止子进程, 那么也就能观察到, 退出信号为 9.
option
在前面的参数解释中说到, 这是一个等待选项.
 父进程可以选择一直阻塞下去, 直到等到子进程的死亡,
 或者当子进程还没死亡时, 父进程去执行其他的代码. 等一会再来查看子进程是否死亡.
waitpid(pid,&status,WNOHANG); // 非阻塞等待
waitpid(pid,&status,0); // 阻塞等待int main()
{pid_t id = fork();if(id<0){perror("fork");exit(1);}if(id==0)//子进程代码{int count = 5;while(count){printf("[%d]我是子进程,我的pid是: %d\n",count,getpid());sleep(1);count--;}exit(55);//子进程执行完代码后退出}//父进程代码while(1)//循环访问子进程退出情况{int wait = waitpid(id,NULL,WNOHANG);if(wait>0)//子进程退出成功{printf("子进程退出成功,子进程pid: %d\n",wait);break;}else if(wait==0)//子进程还没退出,父进程干自己的事情{//此处简单模拟父进程干的事情printf("我是父进程\n");}else //等待子进程退出失败{perror("waitpid");exit(1);}sleep(1);}return 0;
}
执行上面的代码就能观察到, 在子进程结束前, 父进程还在向频幕上打印文字.
进程替换
创建子进程是为了完成一些工作, 但是子进程的代码和父进程是一样的,
 有可能子进程并不需要执行父进程的代码, 而是执行一些其他代码.
 这种场景下, 就可以使用进程替换.
我们为什么不直接将子进程要执行的代码写在父进程中呢, 还要去使用进程替换?
1. 如果所有的代码都放在父进程中, 那么父进程的代码会有多么的庞大,
这会提高代码编写和维护的成本.
2. 进程所执行的一定是我们的C/C++程序吗? 进程也可以执行其他的非 C/C++ 程序,
那对于那些非 C/C++ 程序 (java程序), 我们无法将他们和我们所写的 C/C++ 代码整合在一起, java 和 C/C++ 的运行环境都不同.
exec 系列函数
如果想要创建出来的子进程执行全新的程序, 可以使用 exec 系列函数进行程序替换.

一共有6个函数, 其中主要分为两类
 1. execl 系列
 2. execv 系列
execl
int main()    
{    printf("进行程序替换了\n");    int n = execl("/usr/bin/ls","ls","-a","-l",NULL);                                                                                                                   if(n==-1)    {    perror("execl");    }    printf("程序替换完毕!\n");    return 0;    
}    
execl参数: 第一个是要执行程序的路径 (/usr/bin/ls),
第二个参数是要执行的程序的名称 (ls),后面的参数到 NULL 之前, 都是要替换的程序参数 (-a, -l, 都是 ls 程序的参数).
execl 中 l, 表示如何将参数传递要替换的程序. l 表示通过一个列表的方式,
 即向上面的 "-l", "-a"..., 一个列表的形式.
execlp 和 execle 两个函数则分别多了 p 和 e.
p 则代表要执行的程序可以从环境变量 PATH 中找到, 所以不用写执行程序的路径.
e 则表示, 可以传入用户自己定义的环境变量 (_env[]) 给程序使用.
int main()    
{    printf("我要进行程序替换了...\n");    int n = execlp("ls","-l",NULL);                                                                                                                                     if(n==-1)    {    perror("execl");    }    printf("程序替换完毕!\n");    return 0;    
} int main()    
{    const char* _env[]={"MY_ENV=666",NULL};    printf("我要进行程序替换了...\n");    int n = execle("/usr/bin/ls","ls","-l",NULL,_env);//自己定义一个环境变量MY_ENV=666传递给要去执行的程序                                                              if(n==-1)    {    perror("execl");    }    printf("程序替换完毕!\n");    return 0;    
}  
execv
上面的 execl 中的 l, 代表传参使用列表的形式.
 那么 v 很容易就想到了是vector.
 所以 execv 函数在给替换的程序传参时, 是通过一个 vector 来传参的.
int main()    {    char* const set[]={"ls","-a","-l",NULL};  printf("我要进行程序替换了...\n");    int n = execv("/usr/bin/ls",set);                                                                             if(n==-1)    {    perror("execl");    }    printf("程序替换完毕!\n");    return 0;    } 
那么剩下的 execvp 和 execvpe 和之前的 execl 系列中的一样.
 p 代表在环境变量 PATH 中查找, e 可以传入自己定义的环境变量.
- l (list): 传参的方式为使用列表来传递
- v (vector): 使用数组来传递参数
- p (path): 会在环境变量 PATH 中查找程序
- e (env): 可以传递自己定义的环境变量