【linux学习】linux下进程状态和环境变量的解析

大家好,我是程序员小青蛙,今天介绍进程状态和环境变量,欢迎大家学习。

一、进程状态的本质

  1. 进程状态的核心逻辑 :进程的不同状态,本质是进程被放在不同的队列中,等待不同的资源
  2. 运行队列
    • 一个 CPU 对应一个运行队列 struct runqueue
    • 进程进入运行队列,本质是将该进程的 task_struct 结构体对象放入队列
    • R(运行状态) :只要进程的 PCB 在运行队列中,就是 R 状态,不代表进程一定正在 CPU 上运行
  3. 等待队列(阻塞队列)
    • 每个外设(键盘、显示器、网卡、磁盘等)都有自己的等待队列
    • 进程需要等待某个资源时,会从运行队列移出,放入对应外设的等待队列
    • 资源就绪后,进程会被唤醒,重新放回运行队列
  4. 挂起状态
    • 当内存空间不足时,操作系统会将暂时不运行的进程的代码和数据保存到磁盘
    • 释放这部分内存给其他进程使用
    • 进程需要运行时,再将代码和数据从磁盘加载回内存

二、Linux 内核定义的 7 种进程状态

c

复制代码
static const char * const task_state_array[] = {
    "R (running)",    /* 0 */
    "S (sleeping)",   /* 1 */
    "D (disk sleep)", /* 2 */
    "T (stopped)",    /* 4 */
    "t (tracing stop)", /* 8 */
    "X (dead)",      /* 16 */
    "Z (zombie)",    /* 32 */
};
  1. R(running):运行状态,进程在运行队列中
  2. S(sleeping):可中断睡眠(浅度睡眠),等待事件完成,可被信号唤醒
  3. D(disk sleep) :不可中断睡眠(深度睡眠),等待磁盘 IO 结束,无法被 OS 杀死,只能等待 IO 完成或断电
  4. T(stopped) :停止状态,可通过 SIGSTOP 信号暂停,SIGCONT 信号恢复
  5. t(tracing stop):追踪停止状态,进程被调试器暂停(如 gdb)
  6. X(dead):死亡状态,进程已彻底结束,PCB 已被回收,任务列表中不可见
  7. Z(zombie):僵尸状态,进程已退出,但父进程未回收其 PCB

进程状态的查看

bash 复制代码
ps aux / ps axj命令

三、僵尸进程与孤儿进程

1. 僵尸进程(Z)

  • 成因:子进程退出后,父进程没有读取子进程的退出返回代码
  • 特点:进程以终止状态保持在进程表中,一直等待父进程读取退出状态
  • 危害 :PCB(task_struct)会一直占用内存,造成内存泄漏
  • 解决 :父进程调用 wait() 系统调用回收,或父进程退出后由 init 进程回收

编写一个进程查看僵尸进程

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
int main()
{
pid_t id = fork();
if(id < 0){
perror("fork");
return 1;
}
else if(id > 0){ //parent
printf("parent[%d] is sleeping...\n", getpid());
sleep(30);
}else{
printf("child[%d] is begin Z...\n", getpid());
sleep(5);
exit(EXIT_SUCCESS);
}
return 0;
}

可以看到都是处在S状态,休眠状态,

最后处在Z状态,属于僵死状态,

子进程 Z+ 状态

  • Z僵尸进程状态,进程已经执行完毕退出,但 PCB(进程控制块)还没被父进程回收
  • +:同样表示前台进程
  • 关键标记 <defunct>:英文意为 "失效的 / 死亡的",在 Linux 中专门表示僵尸进程

僵尸进程是已经执行完毕退出,但父进程没有调用 wait() / waitpid() 回收其退出状态的子进程。

  • 进程的代码和数据已经被释放,但 PCB(task_struct)仍然留在系统中,等待父进程读取退出码。
  • 尸进程不占用 CPU 和内存数据段,但会永久占用 PCB 结构体(存放在内存中)
  • 如果大量产生僵尸进程,会占用内核进程表资源,导致系统无法创建新进程(PID耗尽)

解决方法

  1. 父进程调用 wait() / waitpid() 回收子进程

    复制代码
    if(id > 0) {
        printf("parent[%d] is sleeping...\n", getpid());
        wait(NULL); // 阻塞回收子进程,避免僵尸进程
        sleep(30);
    }
  2. 父进程退出 :父进程结束后,子进程会被 init 进程(PID 1)领养并自动回收

  3. 信号处理 :父进程通过 SIGCHLD 信号异步回收子进程

2. 孤儿进程

  • 成因:父进程先退出,子进程还在运行
  • 处理 :子进程会被 1 号 init 进程 领养,由 init 进程负责回收,无危害

父进程先退出,子进程还在运行,这个子进程就叫孤儿进程。

核心机制

  • 父进程死了
  • 子进程还活着
  • 操作系统会把孤儿进程过继给 1 号进程(init/systemd)
  • 由 1 号进程负责回收子进程,不会产生僵尸
cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t id = fork();
if(id < 0){
perror("fork");
return 1;
}
else if(id == 0){//child
printf("I am child, pid : %d\n", getpid());
sleep(10);
}else{//parent
printf("I am parent, pid: %d\n", getpid());
sleep(3);
exit(0);
}
return 0;
}

父进程已死,子进程还在,称为孤儿进程

孤儿进程会被回收,不会造成内存泄露


四、进程优先级

UID :代表执行者的身份

PID :代表这个进程的代号

PPID:代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号

PRI:代表这个进程可被执行的优先级,其值越小越早被执行

NI:代表这个进程的nice值

  1. 基本概念:CPU 资源分配的先后顺序,优先级高的进程优先执行
  2. PRI 与 NI
    • PRI:进程的优先级,值越小,优先级越高
    • NI(nice 值):优先级的修正值,范围 -20 ~ 19
    • 计算公式:PRI(new) = PRI(old) + nice
    • nice 为负值 → PRI 变小 → 优先级提高
    • 需要强调一点的是,进程的nice值不是进程的优先级,他们不是一个概念,但是进程nice值会影响到进程的优先级变化。可以理解nice值是进程优先级的修正修正数据
  3. 查看与修改
    • 查看:ps -l 命令,PRI 列和 NI
    • 修改:top 命令 → 按 r → 输入进程 PID → 输入 nice 值
  4. 本质 :优先级本质是 PCB(task_struct)里面的一个整数字段

竞争性:系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级

独立性:多进程运行,需要独享各种资源,多进程运行期间互不干扰

并行:多个进程在多个CPU下分别,同时进行运行,这称之为并行

并发:多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发


五、进程切换

  1. CPU 寄存器
    • CPU 内部只有一套寄存器硬件,被所有进程共享
    • 寄存器中保存的数据,只属于当前正在运行的进程
  2. 上下文保护与恢复
    • 进程切换时,需要保存当前进程的上下文数据(寄存器中的数据)到 PCB 中
    • 然后恢复下一个进程的上下文数据,从 PCB 加载到 CPU 寄存器
  3. 类比:离开学校时保留学籍,回来时恢复学籍,上下文就是进程的 "学籍"

六、环境变量

  1. 基本概念:操作系统中用来指定运行环境的一些参数,具有全局特性
  2. 常见环境变量
    • PATH:指定命令的搜索路径
    • HOME:指定用户的主工作目录
    • SHELL:当前使用的 Shell,通常是 /bin/bash
  3. 常用命令
    • echo $NAME:显示某个环境变量的值
    • export NAME=value:设置并导出一个新的环境变量
    • env:显示所有环境变量
    • unset NAME:清除环境变量
    • set:显示本地定义的 shell 变量和环境变量
  4. 代码获取方式
    • main 函数第三个参数:int main(int argc, char *argv[], char *env[])
    • 全局变量:extern char **environ
    • 系统调用:getenv("NAME")setenv("NAME", "value", 1)
  5. 核心特性 :环境变量可以被子进程继承;普通变量不使用 export 导出,则无法被子进程继承

查看命令

设置一个新的环境变量

使用set可以发现pwd是咋样的

找到之前设置的环境变量
这时候我们可以将我们写的文件配置成环境变量,就可以不用进行指令执行了

比如:

我们写好一个程序,并且编译完成后,需要指令./进行执行才能完成运行,下面我们将hello文件设置为环境变量,然后可以完成不需要指令就可以运行

下面是执行步骤

bash 复制代码
export PATH=$PATH:/home/zzy/lesson//后面这里是你当前所在的路径

如何取消:

输入取消

bash 复制代码
export PATH=$(echo $PATH | sed 's#:/home/zzy/lesson##g')

也可以重置PATH

bash 复制代码
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

解释一下:通过代码如何获取环境变量

int main(int argc, char *argv[], char *env[])

在main 函数中定义了环境变量,当进入时自动调用环境变量

命令行第三个参数:

cpp 复制代码
#include <stdio.h>
int main(int argc, char *argv[], char *env[])
{
int i = 0;
for(; env[i]; i++){
printf("%s\n", env[i]);
}
return 0;
}

通过第三方变量environ获取

cpp 复制代码
#include <stdio.h>
int main(int argc, char *argv[])
{
extern char **environ;
int i = 0;
for(; environ[i]; i++){
printf("%s\n", environ[i]);
}
return 0;
}

通过系统调用进行获取和设置环境变量

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("%s\n", getenv("PATH"));
return 0;
}

环境变量通常是具有全局属性的

环境变量通常具有全局属性,可以被子进程继承下去

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
int main()
{
char * env = getenv("MYENV");
if(env){
printf("%s\n", env);
}
return 0;
}

直接查看,发现没有结果,说明该环境变量根本不存在

解决:

导出环境变量

export MYENV="hello world"

再次运行程序,发现结果有了!说明:环境变量是可以被子进程继承下去的!想想为什么?

说明:父进程设置的环境变量,被子进程继承了

  • 父进程:当前的 bash 终端进程
  • 子进程:你运行的 ./test 程序进程

解答:环境变量是父进程地址空间的一部分,fork() 创建子进程时会完整复制,因此可以被子进程继承。

如果只进行MYENV="helloworld",不调用export导出,在用我们的程序查看,会有什么结果?为什么?

程序中调用 getenv("MYENV")返回 NULL ,也就是什么也查不到,不会输出 helloworld

核心原因是:MYENV="helloworld" 定义的只是 Shell 的「局部变量」,不是环境变量,不会被子进程继承。

不加export,只有shell自己用,不会传给子进程。


谢谢大家观看,有不对的地方评论指出

相关推荐
Hello:CodeWorld13 小时前
PCIe(PCI Express)技术详解:架构、演进与实践
linux·嵌入式硬件·express
comcoo13 小时前
OpenClaw 本地部署避坑指南|环境配置 + 故障排查全流程
运维·人工智能·openclaw安装包·open claw部署
红茶要加冰13 小时前
四、ansible的templates
linux·运维·服务器·ansible
云飞云共享云桌面13 小时前
企业降本增效新思路:SolidWorks共享部署实战经验分享
运维·服务器·网络·人工智能·3d·自动化
Bert.Cai14 小时前
Linux uname命令详解
linux·运维·服务器
电商API_1800790524714 小时前
价格波动预警|用API实时监控淘宝京东商品价格,实现自动化竞品调价与捡漏
大数据·运维·数据库·人工智能·数据挖掘·自动化
lunzi_082614 小时前
【学习笔记】《Python编程 从入门到实践》第1章:Python环境搭建与Hello World(完整版)
笔记·python·学习
佚明zj14 小时前
Ubuntu 24.04 安装 Fcitx5 拼音输入法教程
运维·服务器·ubuntu
红茶要加冰14 小时前
五、ansible的流程控制
linux·运维·服务器·ansible