Login
首页 > 技术资讯 > 后端开发 > 最新文章

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

CSDN博客 2026-06-02 23:39:41 人看过


一、进程状态的本质

进程状态的核心逻辑:进程的不同状态,本质是进程被放在不同的队列中,等待不同的资源

运行队列

一个 CPU 对应一个运行队列 struct runqueue

进程进入运行队列,本质是将该进程的 task_struct 结构体对象放入队列

R(运行状态):只要进程的 PCB 在运行队列中,就是 R 状态,不代表进程一定正在 CPU 上运行

等待队列(阻塞队列)

每个外设(键盘、显示器、网卡、磁盘等)都有自己的等待队列

进程需要等待某个资源时,会从运行队列移出,放入对应外设的等待队列

资源就绪后,进程会被唤醒,重新放回运行队列

挂起状态

当内存空间不足时,操作系统会将暂时不运行的进程的代码和数据保存到磁盘

释放这部分内存给其他进程使用

进程需要运行时,再将代码和数据从磁盘加载回内存

二、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 */ };

R(running):运行状态,进程在运行队列中

S(sleeping):可中断睡眠(浅度睡眠),等待事件完成,可被信号唤醒

D(disk sleep):不可中断睡眠(深度睡眠),等待磁盘 IO 结束,无法被 OS 杀死,只能等待 IO 完成或断电

T(stopped):停止状态,可通过 SIGSTOP 信号暂停,SIGCONT 信号恢复

t(tracing stop):追踪停止状态,进程被调试器暂停(如 gdb)

X(dead):死亡状态,进程已彻底结束,PCB 已被回收,任务列表中不可见

Z(zombie):僵尸状态,进程已退出,但父进程未回收其 PCB

进程状态的查看

ps aux / ps axj命令

三、僵尸进程与孤儿进程

1. 僵尸进程(Z)

成因:子进程退出后,父进程没有读取子进程的退出返回代码

特点:进程以终止状态保持在进程表中,一直等待父进程读取退出状态

危害:PCB(task_struct)会一直占用内存,造成内存泄漏

解决:父进程调用 wait() 系统调用回收,或父进程退出后由 init 进程回收

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

#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耗尽)

解决方法

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

if(id > 0) {    printf("parent[%d] is sleeping...\n", getpid());    wait(NULL); // 阻塞回收子进程,避免僵尸进程    sleep(30); }

父进程退出:父进程结束后,子进程会被 init 进程(PID 1)领养并自动回收

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

2. 孤儿进程

成因:父进程先退出,子进程还在运行

处理:子进程会被 1 号 init 进程 领养,由 init 进程负责回收,无危害

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

核心机制

父进程死了

子进程还活着

操作系统会把孤儿进程过继给 1 号进程(init/systemd)

由 1 号进程负责回收子进程,不会产生僵尸

#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值

基本概念:CPU 资源分配的先后顺序,优先级高的进程优先执行

PRI 与 NI

PRI:进程的优先级,值越小,优先级越高

NI(nice 值):优先级的修正值,范围 -20 ~ 19

计算公式:PRI(new) = PRI(old) + nice

nice 为负值 → PRI 变小 → 优先级提高

需要强调一点的是,进程的nice值不是进程的优先级,他们不是一个概念,但是进程nice值会影响到进程的优先级变化。可以理解nice值是进程优先级的修正修正数据

查看与修改

查看:ps -l 命令,PRI 列和 NI

修改:top 命令 → 按 r → 输入进程 PID → 输入 nice 值

本质:优先级本质是 PCB(task_struct)里面的一个整数字段

竞争性:系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级 独立性:多进程运行,需要独享各种资源,多进程运行期间互不干扰 并行:多个进程在多个CPU下分别,同时进行运行,这称之为并行 并发:多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发

五、进程切换

CPU 寄存器

CPU 内部只有一套寄存器硬件,被所有进程共享

寄存器中保存的数据,只属于当前正在运行的进程

上下文保护与恢复

进程切换时,需要保存当前进程的上下文数据(寄存器中的数据)到 PCB 中

然后恢复下一个进程的上下文数据,从 PCB 加载到 CPU 寄存器

类比:离开学校时保留学籍,回来时恢复学籍,上下文就是进程的 "学籍"

六、环境变量

基本概念:操作系统中用来指定运行环境的一些参数,具有全局特性

常见环境变量

PATH:指定命令的搜索路径

HOME:指定用户的主工作目录

SHELL:当前使用的 Shell,通常是 /bin/bash

常用命令

echo $NAME:显示某个环境变量的值

export NAME=value:设置并导出一个新的环境变量

env:显示所有环境变量

unset NAME:清除环境变量

set:显示本地定义的 shell 变量和环境变量

代码获取方式

main 函数第三个参数:int main(int argc, char *argv[], char *env[])

全局变量:extern char **environ

系统调用:getenv("NAME")setenv("NAME", "value", 1)

核心特性:环境变量可以被子进程继承;普通变量不使用 export 导出,则无法被子进程继承

查看命令

设置一个新的环境变量

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

找到之前设置的环境变量

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

比如:

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

下面是执行步骤

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

如何取消:

输入取消

export PATH=$(echo $PATH | sed 's#:/home/zzy/lesson##g')

也可以重置PATH

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

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

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

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

命令行第三个参数:

#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获取

#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; }

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

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

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

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

#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自己用,不会传给子进程。

版权声明:倡导尊重与保护知识产权。未经许可,任何人不得复制、转载、或以其他方式使用本站《原创》内容,违者将追究其法律责任。本站文章内容,部分图片来源于网络,如有侵权,请联系我们修改或者删除处理。

编辑推荐

热门文章