Linux系统编程(多进程编程深入1)

简介: Linux系统编程(多进程编程深入1)

前言

本篇文章我们深入的讲解多进程编程。


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

进程参数和环境变量是两种不同的机制,但它们都在操作系统中扮演着重要的角色。它们用于传递信息给正在运行的进程,以影响它们的行为和配置。

进程参数(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()等函数来回收子进程的资源,并从进程表中删除子进程的记录。这样子进程就会完全终止并从系统中清除。

有时,如果父进程已经终止,或者父进程没有合适的处理子进程的代码,僵尸进程可能会长时间存在。在这种情况下,可以通过重新启动父进程、修复父进程的代码或使用一些系统工具来清除僵尸进程。

总结:僵尸进程是指已经终止的子进程,但其父进程尚未及时处理子进程的退出状态。它们处于一种临时的中间状态,不再执行任何代码,但仍然占用一些系统资源。及时处理子进程的退出状态是预防和解决僵尸进程问题的关键。


总结

本篇文章就讲解到这里,下一篇文章我们继续分析多进程编程。


相关文章
|
2天前
|
存储 Linux 程序员
【Linux-14】进程地址空间&虚拟空间&页表——原理&知识点详解
【Linux-14】进程地址空间&虚拟空间&页表——原理&知识点详解
|
3天前
|
Unix Linux
【Linux】一文了解【进程优先级相关知识点】&【PRI / NI值】背后的修正原理(13)
【Linux】一文了解【进程优先级相关知识点】&【PRI / NI值】背后的修正原理(13)
|
3天前
|
Linux 调度
【Linux】盘点广义层面上【三种最基本的进程状态】
【Linux】盘点广义层面上【三种最基本的进程状态】
|
3天前
|
Linux Shell
【Linux】深度解析Linux中的几种进程状态
【Linux】深度解析Linux中的几种进程状态
|
3天前
|
Linux Shell 调度
【Linux】用三种广义进程状态 来理解Linux的进程状态(12)
【Linux】用三种广义进程状态 来理解Linux的进程状态(12)
|
3天前
|
Linux Shell 调度
【Linux系列】fork( )函数原理与应用详解——了解【父子进程及其特性】(代码演示,画图帮助理解,思维导图,精简)(11)
【Linux系列】fork( )函数原理与应用详解——了解【父子进程及其特性】(代码演示,画图帮助理解,思维导图,精简)(11)
|
3天前
|
Linux Shell
【Linux】解决:为什么重复创建同一个【进程pid会变化,而ppid父进程id不变?】
【Linux】解决:为什么重复创建同一个【进程pid会变化,而ppid父进程id不变?】
|
3天前
|
运维 监控 Linux
提升系统稳定性:Linux内核参数调优实战
【5月更文挑战第1天】 在运维领域,保障服务器的高效稳定运行是核心任务之一。Linux操作系统因其开源、可靠和灵活的特点被广泛应用于服务器中。本文将深入探讨通过调整Linux内核参数来优化系统性能,提升服务器的稳定性和响应能力。文章首先介绍了内核参数调优的必要性和基本原则,然后详细阐述了调优过程中的关键步骤,包括如何监控当前系统状态,确定性能瓶颈,选择合适的参数进行调优,以及调优后的测试与验证。最后,文中提供了一些常见问题的解决策略和调优的最佳实践。
20 5
|
3天前
|
Linux
Linux系统ps命令
这些是一些常见的 `ps`命令选项和用法,用于查看系统中运行的进程及其相关信息。您可以根据需要选择合适的选项以满足您的任务要求。
11 0
|
4天前
|
算法 大数据 Linux
深入理解Linux内核的进程调度机制
【4月更文挑战第30天】操作系统的核心职能之一是有效地管理和调度进程,确保系统资源的合理分配和高效利用。在众多操作系统中,Linux因其开源和高度可定制的特点,在进程调度机制上展现出独特优势。本文将深入探讨Linux内核中的进程调度器——完全公平调度器(CFS),分析其设计理念、实现原理及面临的挑战,并探索未来可能的改进方向。