大家好,我是程序员小青蛙,今天介绍进程状态和环境变量,欢迎大家学习。
一、进程状态的本质
- 进程状态的核心逻辑 :进程的不同状态,本质是进程被放在不同的队列中,等待不同的资源
- 运行队列 :
- 一个 CPU 对应一个运行队列
struct runqueue - 进程进入运行队列,本质是将该进程的
task_struct结构体对象放入队列 - R(运行状态) :只要进程的 PCB 在运行队列中,就是 R 状态,不代表进程一定正在 CPU 上运行
- 一个 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
进程状态的查看
bashps 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耗尽)解决方法
父进程调用
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 号进程负责回收子进程,不会产生僵尸
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值
- 基本概念: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)
- main 函数第三个参数:
- 核心特性 :环境变量可以被子进程继承;普通变量不使用
export导出,则无法被子进程继承
查看命令
设置一个新的环境变量
使用set可以发现pwd是咋样的
找到之前设置的环境变量
这时候我们可以将我们写的文件配置成环境变量,就可以不用进行指令执行了比如:
我们写好一个程序,并且编译完成后,需要指令./进行执行才能完成运行,下面我们将hello文件设置为环境变量,然后可以完成不需要指令就可以运行
下面是执行步骤
bashexport PATH=$PATH:/home/zzy/lesson//后面这里是你当前所在的路径如何取消:
输入取消
bashexport PATH=$(echo $PATH | sed 's#:/home/zzy/lesson##g')也可以重置PATH
bashPATH=/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自己用,不会传给子进程。
谢谢大家观看,有不对的地方评论指出

















