Day27 进程管理(PCB、状态、调度、原语与资源管理)
day27 进程管理(PCB、状态、调度、原语与资源管理)
进程的含义
进程是程序执行的动态过程,涉及内存资源分配和CPU调度。其核心组件与特性如下:
-
PCB(Process Control Block):操作系统内核中的结构体(如Linux的
task_struct
),存储进程所有状态信息,包括:- PID(进程标识符):唯一身份标识
- 当前工作路径(通过
chdir
设置,pwd
查看) - 文件描述符(fd)列表及打开的文件信息
- 信号处理设置(用于异步I/O)
- 用户ID/组ID
- 资源上限(通过
ulimit -a
查看,例如栈大小8MB、打开文件数1024)
-
资源管理:进程占用CPU、内存(RAM)、闪存(Flash)、固态硬盘(SSD)等物理资源,通过虚拟地址映射(MMU管理)隔离物理内存,每页4KB。
-
内存空间划分:
- 0-3GB:用户空间(包含代码段、数据段、堆、栈)
- 3-4GB:内核空间(共享)
- 虚拟地址优势:提供隔离性和权限控制
进程和程序的区别
特性 | 程序 | 进程 |
---|---|---|
本质 | 静态实体(存储在硬盘的代码和数据集合) | 动态实体(程序执行的完整过程) |
生命周期 | 永久存在 | 暂时存在(创建→调度→消亡) |
状态变化 | 无状态变化 | 有状态变化(就绪/运行/阻塞等) |
并发性 | 无并发 | 可并发执行 |
资源竞争 | 不竞争资源 | 进程间竞争CPU、内存等资源 |
实例关系 | 一个程序可运行多次生成多个进程 | 一个进程可运行一个或多个程序 |
内存布局示例:
.c 源文件 → gcc 编译 → a.out 可执行文件 (ELF格式) → 进程(PID)|├─ 虚拟地址空间 (0-3GB)│ ├─ 代码段 (只读,存放指令)│ ├─ 数据段 (全局/静态变量,读写)│ ├─ 堆 (malloc/free,<3GB)│ ├─ 共享库 (如libc.so,通过mmap映射)│ └─ 栈 (局部变量/参数,8MB)└─ 内核空间 (3-4GB)
- 地址空间说明:
- 32位系统:4GB虚拟地址空间(2^32)
- 64位系统:256TB虚拟地址空间(2^48)
进程的作用与并发并行区别
- 核心作用:实现任务并发执行,提升系统资源利用率
- 并发 vs 并行:
- 并发:单CPU上快速切换执行多个任务(微观串行,宏观并行)
- 并行:多CPU同时执行多个任务(真正同时运行)
并发执行示例:
while (1) { // 主循环while (1) { // 子任务循环(如接收控制指令)// 处理上下左右方向}// 发送视频流
}
说明:通过进程/线程切换实现多任务交替执行,避免单任务阻塞
进程的状态
基本状态转换
就绪态 → (CPU调度) → 运行态 → (条件不满足) → 阻塞态 → (条件满足) → 就绪态
Linux进程状态标识(ps aux
输出)
状态符 | 含义 | 说明 |
---|---|---|
R | 运行/就绪态 | 正在CPU执行或等待调度 |
S | 可中断睡眠态 | 等待事件(可被信号唤醒) |
D | 不可中断睡眠态 | 等待I/O完成(不可唤醒) |
T | 暂停态 | 被信号停止(如SIGSTOP) |
Z | 僵尸态 | 进程已终止但资源未回收 |
(无) | 结束态 | 资源完全回收 |
进程的调度与上下文切换
- 调度核心:内核通过调度算法分配CPU时间片
- 上下文切换:保存/恢复进程状态(寄存器、PC指针等),涉及:
- 程序计数器(EIP)
- 栈指针
- 通用寄存器
调度算法
算法 | 说明 |
---|---|
先来先服务(FCFS) | 按到达顺序执行 |
短任务优先(SJF) | 优先执行预计运行时间短的任务 |
多级反馈队列 | 分优先级队列,动态调整 |
关键特性:宏观上多进程并行,微观上CPU串行执行
查询进程相关命令
1. ps aux
- 功能:查看进程实时状态
- 关键字段:
STAT
:进程状态(R/S/D/T/Z)PID
:进程IDPPID
:父进程ID
- 安装树状工具:
sudo apt install pstree
2. top
- 功能:动态监控CPU/内存占用率最高的进程
3. kill
/ killall
- 发送信号:
kill -2 <PID> # 发送SIGINT(默认终止进程) kill -9 <PID> # 发送SIGKILL(强制终止) killall -9 a.out # 终止所有a.out进程
进程原语
1. fork()
- 功能:创建子进程(父进程的副本)
- 特性:
- 一次调用,两次返回(父进程返回子PID>0,子进程返回0)
- 子进程复制父进程0-3GB空间及PCB,但PID不同
- 写时复制(COW)机制:修改时才复制内存
- 执行顺序不确定(需IPC同步)
代码示例与注释:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>int main(int argc, char *argv[])
{pid_t ret = fork(); // 创建子进程,返回两次if(ret > 0) // 父进程分支(ret = 子进程PID){while(1) // 父进程无限循环{printf("father,发视频....\n");sleep(1); // 暂停1秒控制输出频率}}else if(0 == ret) // 子进程分支(ret = 0){while(1) // 子进程无限循环{printf("child,接收控制....\n");sleep(1);}}else // fork失败{perror("fork err\n");return 1;}return 0;
}
理想运行结果:
father,发视频…
child,接收控制…
father,发视频…
child,接收控制…
(交替输出,顺序取决于调度)
变量隔离验证:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>int a = 20; // 全局变量
int main()
{pid_t ret = fork();if(ret > 0) // 父进程{sleep(3); // 确保子进程先修改printf("father ,a is %d\n", a); // 输出20}else if(0 == ret) // 子进程{a += 10; // 修改副本printf("child ,a is %d\n", a); // 输出30}printf("a is %d\n", a); // 父进程输出20,子进程输出30return 0;
}
理想运行结果:
child ,a is 30
a is 30
father ,a is 20
a is 20
2. getpid()
与 getppid()
- 功能:
getpid()
:获取当前进程PIDgetppid()
:获取父进程PID
代码示例:
#include <stdio.h>
#include <unistd.h>int main()
{pid_t ret = fork();if(ret > 0) // 父进程{sleep(3);printf("father ,pid:%d ppid:%d\n", getpid(), getppid());}else if(0 == ret) // 子进程{printf("child ,pid:%d ppid:%d\n", getpid(), getppid());}printf("pid:%d ppid:%d\n", getpid(), getppid());return 0;
}
理想运行结果:
child ,pid:26205 ppid:26204
pid:26205 ppid:26204
father ,pid:26204 ppid:3199
pid:26204 ppid:3199
(3199为终端父进程ID)
3. 多重fork()分析
#include <stdio.h>
#include <unistd.h>int main()
{fork() && fork() || fork(); // 逻辑运算符触发5个进程printf("pid:%d\n", getpid());return 0;
}
进程关系:
1个父进程 → 生成4个子进程(含1个孙进程)
理想运行结果:
pid:1001
pid:1002
pid:1003
pid:1004
pid:1005
(共5行输出,PID顺序不定)
父子进程的关系
- 核心关系:子进程是父进程的副本(共享代码段,数据段写时复制)
- 关键区别:
fork()
返回值不同(父进程>0,子进程=0)- PID/PPID不同
- 资源计数器重置(如文件描述符)
- 执行起点:子进程从
fork()
后开始执行 - 典型场景:
- 网络服务:父子进程执行相同代码
- 程序替换:
fork()
+exec()
执行新程序
进程的终止
8种终止情况
类型 | 方式 | 说明 |
---|---|---|
正常终止 | 1. main() 中return | 返回退出状态 |
2. exit() | 刷新缓冲区,调用atexit 注册的清理函数 | |
3. _exit() /_Exit() | 不刷新缓冲区,直接终止 | |
4. 主线程return | 仅终止当前线程 | |
5. pthread_exit() | 线程级退出 | |
异常终止 | 6. abort() | 发送SIGABRT信号 |
7. kill 信号 | 如kill -9 PID | |
8. pthread_cancel() | 取消最后一个线程 |
exit()
vs _exit()
#include <stdio.h>
#include <unistd.h>int main()
{printf("hello"); // 无换行符,缓冲区未刷新// exit(0); // 会刷新缓冲区 → 输出"hello"_exit(0); // 不刷新缓冲区 → 无输出return 0;
}
关键区别:
exit(0)
:先刷新I/O缓冲区,再执行清理函数,最后调用_exit()
_exit(0)
:立即终止,不处理缓冲区和清理函数
理想结果:- 用
exit(0)
时输出hello
- 用
_exit(0)
时无输出
僵尸进程和孤儿进程
僵尸进程
-
成因:子进程终止但父进程未回收资源(
wait()
未调用) -
验证代码:
int main() {pid_t ret = fork();if(ret > 0) // 父进程{while(1) { printf("father,发视频....pid:%d ppid:%d\n", getpid(), getppid());sleep(1);}}else // 子进程{sleep(3); exit(0); // 子进程终止,但父进程未回收 → 僵尸}return 0; }
现象:
ps aux
中子进程状态为Z
,资源未释放
孤儿进程
- 成因:父进程先于子进程终止,子进程被
init
接管 - 验证代码:
int main() {pid_t ret = fork();if(ret > 0) // 父进程{sleep(3); exit(0); // 父进程提前终止}else // 子进程{while(1) { printf("child,接收控制....pid:%d ppid:%d\n", getpid(), getppid());sleep(1);}}return 0; }
现象:子进程PPID变为1(
init
进程ID)
资源回收:僵尸进程需父进程调用
wait()
/waitpid()
回收;孤儿进程由init
自动回收