文章目录
- 前言
- 一、进程参数和环境变量的意义
- 二、子进程程序结束返回值到哪里去了?
- 三、进程退出函数
- 四、实际使用案例
- 五、僵尸进程
- 总结
前言
本篇文章我们深入的讲解多进程编程。
一、进程参数和环境变量的意义
进程参数和环境变量是两种不同的机制,但它们都在操作系统中扮演着重要的角色。它们用于传递信息给正在运行的进程,以影响它们的行为和配置。
进程参数(Process Arguments):
进程参数是在启动进程时传递给它的命令行参数。它们是在运行进程时指定的,并且可以用于向程序提供特定的输入或配置信息。进程参数通常以空格分隔,并作为命令行命令的一部分传递给可执行文件。
进程参数的意义:
向程序传递输入:可以通过命令行参数向程序传递不同的输入数据,以便程序根据不同的参数执行不同的操作或处理不同的数据。
配置程序行为:可以使用进程参数来配置程序的行为,例如设置日志级别、指定文件路径、设定运行模式等。
示例:
$ ./my_program arg1 arg2 --option=value
环境变量(Environment Variables):
环境变量是在操作系统中设置的全局变量,可以被所有运行的进程访问。它们是键值对的形式,其中键是环境变量的名称,值是环境变量的内容。环境变量提供了一种在进程之间共享配置和信息的机制。
环境变量的意义:
配置系统参数:环境变量可以用于配置整个系统的行为,例如设置默认的语言、指定默认的编译器、定义系统路径等。
程序运行时的配置:程序可以读取环境变量来进行特定的配置,例如读取数据库连接信息、API密钥等。
进程通信:在多进程或多线程的场景中,环境变量可以作为通信媒介,在进程之间传递信息。
示例:
$ export MY_VAR="Hello World"
通过使用进程参数和环境变量,我们可以在不修改程序代码的情况下,通过外部的输入和配置来改变进程的行为和属性。这是一种灵活和可定制的机制,使我们能够根据需要对程序进行动态的调整和配置。
二、子进程程序结束返回值到哪里去了?
当子进程程序结束时,其返回值会被传递给父进程。父进程可以通过一些机制来获取子进程的返回值,这样可以对子进程的执行结果进行处理。
一种常见的获取子进程返回值的方法是使用系统调用wait()或waitpid()。这些调用会使父进程等待子进程的结束,并获取子进程的返回值。父进程可以通过这个返回值来了解子进程的执行状态,通常返回值为一个整数,用于表示子进程的退出状态。
在父进程中,可以使用以下方法来获取子进程的返回值:
1.使用wait()系统调用:
wait()函数用于使父进程等待其子进程结束,并获取子进程的终止状态。当调用wait()函数时,父进程会暂停执行,直到一个子进程结束。
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
wait()函数的参数是一个指向整型的指针status,用于获取子进程的退出状态。通过该指针,父进程可以获取子进程的退出状态码,以判断子进程的执行结果。
wait()函数会阻塞父进程的执行,并等待一个子进程终止。当子进程终止后,父进程会继续执行,并返回被终止的子进程的进程ID。同时,子进程的退出状态码会存储在status指针所指向的位置。
#include <sys/types.h>
#include <sys/wait.h>
...
int status;
pid_t child_pid = wait(&status);
if (WIFEXITED(status)) {
int exit_status = WEXITSTATUS(status);
// 处理子进程的退出状态
}
2.使用waitpid()系统调用:
waitpid()函数可以用于等待指定的子进程结束,并获取其终止状态。该函数也可以用于处理多个子进程的情况。
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
waitpid()函数的第一个参数pid指定了要等待的子进程的进程ID。如果pid为负值,那么waitpid()会等待任意子进程终止。如果pid为0,则等待与调用者位于同一进程组的任意子进程。如果pid为正值,则等待具有指定进程ID的子进程。
waitpid()函数的第二个参数status用于获取子进程的退出状态码。第三个参数options指定额外的选项,比如WNOHANG表示不阻塞父进程,而是立即返回。
waitpid()函数会阻塞父进程的执行,直到指定的子进程终止。当子进程终止后,父进程会继续执行,并返回被终止的子进程的进程ID。同时,子进程的退出状态码会存储在status指针所指向的位置。
#include <sys/types.h>
#include <sys/wait.h>
...
int status;
pid_t child_pid = waitpid(child_pid, &status, 0);
if (WIFEXITED(status)) {
int exit_status = WEXITSTATUS(status);
// 处理子进程的退出状态
}
在这些方法中,status变量用于存储子进程的退出状态,可以使用宏WIFEXITED(status)来判断子进程是否正常退出,WEXITSTATUS(status)则可以获取子进程的退出状态值。
需要注意的是,子进程的返回值范围通常是一个字节,即0-255之间的整数。如果子进程需要返回更多信息,可以使用其他机制,例如通过标准输出或文件来传递额外的数据。
三、进程退出函数
当一个进程完成其任务或者发生了错误时,可以使用进程退出函数来终止进程的执行。两个常用的进程退出函数是exit()和abort()。
1.exit()
exit()函数用于正常终止进程的执行。它接受一个整数参数作为进程的退出状态码,并将控制返回给操作系统。这个状态码可以被其他进程或者父进程通过调用wait()或waitpid()来获取。
#include <stdlib.h>
void exit(int status);
exit()函数会执行以下操作:
调用通过atexit()函数注册的退出处理程序(exit handlers)。
关闭已打开的文件描述符。
刷新标准I/O缓冲区。
解除所有由进程分配的内存。
返回到操作系统,并传递退出状态码。
退出状态码可以是任意的整数值,一般情况下非零的状态码表示进程执行失败,而0表示成功。
2.abort()
abort()函数用于异常终止进程的执行。当调用abort()函数时,进程会向操作系统发送终止信号(SIGABRT),导致进程异常终止。与exit()函数不同,abort()函数不接受任何参数。
#include <stdlib.h>
void abort(void);
abort()函数会执行以下操作:
强制将进程终止,不执行后续的代码。
调用通过atexit()函数注册的退出处理程序。
使用默认的终止信号(SIGABRT)向操作系统发送终止请求。
abort()函数通常用于处理严重错误或异常情况,例如发生内存错误或者其他无法恢复的错误。它会生成一个包含调试信息的core dump文件,方便开发人员进行调试和错误分析。
总结:exit()函数用于正常终止进程的执行,而abort()函数用于异常终止进程的执行。它们在进程退出时执行不同的操作,但都是用于终止进程的执行。
四、实际使用案例
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char** argv, char** envp)
{
pid_t pid = 0;
int a = 1;
int b = 0;
int status = 0;
printf("parent :%d\n", getpid());
if((pid = fork()) == 0) exit(0);
printf("child = %d\n", pid);
if((pid = fork()) == 0) abort();
printf("child = %d\n", pid);
if((pid = fork()) == 0) a / b, exit(1);
printf("child = %d\n", pid);
sleep(20);
while((pid = wait(&status)) > 0)
{
printf("child pid :%d status :%d\n", pid, status);
}
return 0;
}
运行代码后使用ps查看进程情况:
sleep后再查看进程情况:
为什么延时后被创建出来的线程就不见了呢,我们下面来讲解。
五、僵尸进程
当一个进程终止时,它的一部分信息需要被保留,以便父进程在需要时查询和处理该子进程的退出状态。此时,子进程变成了一种中间状态,称为"僵尸进程"(Zombie process)或"defunct"进程。
僵尸进程的产生主要是因为父进程没有及时处理子进程的退出状态。在父进程调用wait()或waitpid()等函数之前,子进程的进程描述符和一些资源(如打开的文件描述符)都被维护在系统中,以备父进程查询使用。
僵尸进程的存在不是一个严重的问题,因为内核会回收子进程其他的资源(如内存),但僵尸进程会占用一定的系统资源(主要是进程表项),如果过多的僵尸进程积累,可能会消耗过多的系统资源。
解决僵尸进程的常见方法是父进程及时处理子进程的退出状态,即调用wait()或waitpid()等函数来回收子进程的资源,并从进程表中删除子进程的记录。这样子进程就会完全终止并从系统中清除。
有时,如果父进程已经终止,或者父进程没有合适的处理子进程的代码,僵尸进程可能会长时间存在。在这种情况下,可以通过重新启动父进程、修复父进程的代码或使用一些系统工具来清除僵尸进程。
总结:僵尸进程是指已经终止的子进程,但其父进程尚未及时处理子进程的退出状态。它们处于一种临时的中间状态,不再执行任何代码,但仍然占用一些系统资源。及时处理子进程的退出状态是预防和解决僵尸进程问题的关键。
总结
本篇文章就讲解到这里,下一篇文章我们继续分析多进程编程。