当前位置: 首页 > news >正文

再谈Linux 进程:进程等待、进程替换与环境变量

目录

1.进程等待

为什么需要进程等待?

相关系统调用:wait()和waitpid()

wait():

waitpid():

解析子进程状态(status)

2.进程替换

为什么需要进程替换?

相关系统调用:exec函数家族

3.环境变量

​编辑

​编辑


        本文将对Linux环境下的进程:包括进程创建、终止与进程等待、替换进行讲解,作者使用XShell连接配置为CentOs 7.6的主机进行演示,希望能帮助你更好理解操作系统的运行原理!

1.进程等待

        在操作系统中,进程等待是指父进程主动等待子进程结束执行,并回收子进程资源的过程。这是进程管理中的重要机制,主要用于解决子进程结束后资源释放和状态获取的问题。

为什么需要进程等待?

  1. 回收子进程资源
            子进程结束后,其内核数据结构PCB不会立即释放,会变成僵尸进程(ps:僵尸进程不能被杀死,只能通过进程等待解决!)父进程通过等待机制回收子进程的残留资源,避免内存泄漏和系统资源浪费。

  2. 获取子进程退出状态
            父进程可以通过等待机制获取子进程的执行结果(退出的状态值、被终止收到的信号),进行后续处理。

相关系统调用:wait()和waitpid()

        先来看看man-pages的wait介绍:

wait():

  • 作用:阻塞当前父进程,直到任意一个子进程结束或被信号中断。
  • 参数
    status
    是一个指向整数的指针,用于存储子进程的退出状态(可通过宏解析)。
  • 返回值
    • 成功时返回结束的子进程的PID;
    • 失败时返回 -1
//以下是对进程等待的测试
int testwait()
{pid_t id=fork();if(id<0){perror("fork");return 1;}else if(id==0){//childint cnt=5;while(cnt){printf("I am child,pid:%d,ppid:%d,cnt:%d\n",getpid(),getppid(),cnt);cnt--;sleep(1);}exit(0);}else {//parentint cnt=10;while(cnt){printf("I am father,pid:%d,ppid:%d,cnt:%d\n",getpid(),getppid(),cnt);cnt--;sleep(1);}//这里先不介绍status
-----------------------------------------------------------------------------------------pid_t ret=wait(NULL);//这里wait返回的就是子进程创建成功后返回的pidif(ret==id){printf("success!\n");}
-----------------------------------------------------------------------------------------}return 0; 
}//补充:如果创建了多个子进程,又如何通过wait获得子进程的运行状态呢?
//在---画出的区域可以这样修改:
//对于多个子进程,只需要将wait遍历即可for(i=0;i<n;i++){pid_t ret=wait(NULL);if(ret>0){printf("wait %d success\n",ret);}}

waitpid():

  • 作用:等待指定的子进程(更灵活,可设置非阻塞等待)。
  • 参数
  • pid
    • pid =  -1:等待任意子进程;
    • pid > 0:等待 PID 为 pid 的子进程(一般设定为指定等待子进程的PID);
    • status:同上,用于获取子进程状态。
    • options
      • 0(默认选择):阻塞模式,若子进程未结束则一直等待(阻塞状态)
      • WNOHANG: 非阻塞模式,若子进程未结束则立即返回0
  • 返回值
    • 子进程未结束:根据options返回 0(非阻塞)或阻塞等待;
    • 子进程结束:返回该子进程的 PID;
    • 出错:返回 -1
int testwaitplus()
{pid_t id=fork();if(id<0){perror("fork");return 1;}else if(id==0){//childint cnt=20;while(cnt){printf("I am child,pid:%d,ppid:%d,cnt:%d\n",getpid(),getppid(),cnt);cnt--;sleep(1);}exit(0);}else {//parentint cnt=10;while(cnt){printf("I am father,pid:%d,ppid:%d,cnt:%d\n",getpid(),getppid(),cnt);cnt--;sleep(1);}//pid_t ret=wait(NULL);//这里wait返回的就是子进程创建成功后返回的pidwhile(1){int status=0;// pid_t ret=waitpid(-1,&status,0);pid_t ret=waitpid(-1,&status,WNOHANG);if(ret>0){printf("success!\n");printf("exit successfully,pid:%d\n,exit_code:%d,exit_signal:%d\n",ret,(status>>8)&0xFF,status&0x7F);//还可以用宏if(WIFEXITED(status))//判断是否异常退出{printf("进程正常退出,无异常!exit_code:%d\n",WEXITSTATUS(status));}exit(0);}else if(ret<0){printf("wait failed\n");}else {printf("子进程还未退出,waiting...\n");}sleep(1);}}return 0; 
}

解析子进程状态(status)

        这里的status到底是什么?这里详细介绍一下这个玩意:

status本质上就是一个整形:4个字节对应32个bit位:

0000 0000 0000 0000 0000 0000 0000 0000

前16位不考虑,后十六位进行写入标记;
0000 0000 0000 0000

后七位表示异常信号,倒数第八位(标红)标识core dump,前八位表示退出码,自己想要查看两码可以进行位操作,也可以用宏:
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码

其实本质上就是进行的位操作:

(status>>8)&0xFF(0xFF十六进制)

status&0x7F(0x7F十六进制)

2.进程替换

        在操作系统中,进程替换是指用一个新的程序(可执行文件或脚本)替换当前进程的内存空间、代码和数据,使进程转而执行新程序的过程。这一过程不会创建新进程,而是直接覆盖当前进程的上下文,因此进程的PID保持不变,但执行的内容被完全替换。

为什么需要进程替换?

  1. 执行外部程序
    例如,在Shell中输入ls命令时,Shel
    l会通过进程替换让当前子进程执行ls程序。
  2. 程序升级或切换功能
    一个进程在运行中需要切换到另一个功能模块时,可通过替换自身来实现。

相关系统调用:exec函数家族

       进程替换通过exec系列函数实现,共有 7个函数,统称为exec函数族。它们的作用是加载并执行一个新程序,替换当前进程的内存空间。

函数原型与区别

上述六个都是库函数,都是基于系统调用execve实现的:

给出总结: 

函数名参数形式是否搜索 PATH 环境变量能否自定义环境变量
execl可变参数列表(以 NULL 结尾)
execlp可变参数列表是(根据 PATH 查找程序)
execle可变参数列表是(传入环境变量数组)
execv字符指针数组(argv
execvp字符指针数组
execvpe字符指针数组
execve字符指针数组是(系统调用)

核心区别:

参数传递方式:带 的函数(类似链表)通过逗号分隔的可变参数列表传递参数,最后以NULL结尾;带 的函数(类似数组)通过字符指针数组传递参数,类似main函数的参数形式。

是否搜索 PATH:带 的函数会根据系统环境变量PATH查找程序路径,无需指定绝对路径。

环境变量控制带 的函数可以自定义环境变量,否则继承当前进程的环境变量。

底层系统调用execve 是唯一的系统调用,其他函数均是对它的封装。

exec函数的特点:

执行成功后不返回
        若 exec 调用成功,当前进程的代码、数据、堆、栈等会被新程序完全替换,进程从新程序的入口点开始执行,
不会返回原程序。(也没有办法返回)仅当 exec 调用失败时,才会返回 -1,并继续执行原程序后续代码。

进程 ID 不变
        替换前后进程的PID保持不变,因为替换的是进程的 “内容”,而非进程本身。

文件描述符继承
        原进程打开的文件描述符在EXEC后默认保持打开状态。

环境变量继承
        原进程所拥有的环境变量在EXEC后默认保持相同。

不是说进程替换是将新的程序所带有的代码数据,替换掉原先的代码数据吗?PID保持不变,其他全部都被替换掉了,但是!实际上只有内存管理部分大换血,而环境变量,文件描述符包括IO部分完全相同,或者说原封不动地继承了下去,两个部分解耦合。

void test1()
{//单个进程进行进程替换printf("this is a begin:pid:%d,ppid:%d\n",getpid(),getppid());execl("/usr/bin/ls","ls","-a","-l",NULL); printf("this is a end:pid:%d,ppid:%d\n",getpid(),getppid());//可以看到进程替换后代码和数据进行了替换,pid不变,execl后面的代码不再执行
}extern char** environ;void test2()
{//对exec系统调用函数进行使用char* const arr[]={"ls","-a","-l",NULL };//execlp("testcpp","testcpp",NULL);//execv("/usr/bin/ls",arr);execle("./testcpp","testcpp",NULL,environ);
}

3.环境变量

        抱歉抱歉,环境变量姗姗来迟,前面已经提到了它,现在来补充认识一下吧:

         环境变量是操作系统中存储的一系列键值对,用于控制系统和应用程序的行为。它们在进程的上下文中生效,影响进程的运行环境...

环境变量本质:

存储形式:以 NAME=VALUE 的格式存储,例如PATH=/home/usr/testfile。

进程关联:每个进程启动时会继承其父进程的环境变量,并可修改自身的环境变量(但通常不影响父进程)。

存储位置:进程的环境变量存储在内存中的环境变量表(由指针environ指向)(使用时记得extern char** environ),可通过main函数的第三个参数envp访问。

常见环境变量:

变量名作用描述典型值
PATH命令搜索路径,多个路径用冒号 : 分隔/usr/local/sbin:/usr/local/bin:...
HOME当前用户主目录/home/username
USER当前用户名username
SHELL用户默认 Shell 路径/bin/bash
LANG系统语言和区域设置en_US.UTF-8
PWD当前工作目录(由 Shell 动态更新)/home/username/projects
TERM终端类型(影响命令行显示效果,如颜色、光标控制)xterm-256color
EDITOR默认文本编辑器vim 或 nano
JAVA_HOMEJava 运行环境路径(供 Java 程序查找 JRE/JDK)/usr/lib/jvm/java-17-openjdk
PATH(特殊)注意:部分程序(如 systemd)使用 PATH 以外的变量(如 PATH 需显式设置)-

 查看环境变量:

set//可以查看所有环境变量,包括本地变量
env//查看环境变量

    查看单个环境变量:

    echo $PATH
    echo $USER
    ....

    设置环境变量:

    临时设置(仅当前 Shell 会话有效)

    export NAME=VALUE
    export MY_VAR="hello world"  # 声明为环境变量(可被子进程继承)
    MY_VAR="hello"  # 仅为当前 Shell 的局部变量(不被子进程继承)可以用set查到,echo打印
    #因为echo是内建命令,不会将环境变量继承给子进程#

     删除环境变量:

    unset TEST
    #将TEST环境变量永久删除#

    环境变量与进程的关系

    1. 继承性:子进程会自动继承父进程的环境变量但父进程无法感知子进程对环境变量的修改;进程替换(exec函数族)时,默认继承当前进程的环境变量,除非经过 execle/execve 显式传递新的环境变量数组。

    2. 作用域:全局变量:通过 export 声明或写入系统配置文件,可被所有子进程继承局部变量:未用 export 声明的变量,仅在当前 Shell 进程内有效,不被子进程继承。

    相关文章:

  • 【Node.js】高级主题
  • 鸿蒙UI开发——Builder函数的封装
  • 怎么开发一个网络协议模块(C语言框架)之(一) main
  • 连接表、视图和存储过程
  • 使用LLaMA-Factory微调ollama中的大模型(二)------使用数据集微调大模型
  • 织梦dedecms上传附件不自动改名的办法
  • Vanna.AI:解锁连表查询的新境界
  • ae钢笔工具无法编辑形状图层的路径
  • WPS 64位与EndNote21.5工作流
  • Eigen 直线拟合/曲线拟合/圆拟合/椭圆拟合
  • leetcode hot100刷题日记——14.二叉树的最大深度
  • CAU人工智能class5 激活函数
  • IPD推行成功的核心要素(十二)CDP确保产品开发的正确方向
  • XOR符号
  • UE5 图片导入,拖到UI上变色
  • 在Visual Studio中进行cuda编程
  • Axure元件动作六:设置图片
  • 滚珠导轨在航空航天领域具体应用是什么?
  • 使用腾讯云3台轻量云服务器快速部署K8s集群实战
  • 独立机构软件第三方检测:流程、需求分析及电商软件检验要点?
  • 网站定位授权开启权限怎么做/网络建站公司
  • 自己开发app的软件下载/seo提供服务
  • 河南省住房和城乡建设厅二维码网站/快速排名教程
  • 织梦手机网站建设/百度问答seo
  • 英文网站建设维护/网址大全导航
  • 廉洁 网站建设/网站搜索排名查询