-
认识冯诺依曼系统
-
操作系统概念与定位,理解 "管理"
-
深入理解进程概念,了解 PCB
-
学习进程状态,学会创建进程,掌握僵尸进程和孤儿进程,及其形成原因和危害
-
了解进程调度,Linux 进程优先级,理解进程竞争性与独立性,理解并行与并发
-
理解进程切换,以及 Linux2.6 kernel,O (1) 调度算法架构
-
理解环境变量,熟悉常见环境变量及相关指令getenv/setenv函数
-
理解 C 内存空间分配规律,了解进程内存映像和应用程序区别,认识虚拟地址空间。
书接上文:
三、进程
3-1 进程基本概念与基本操作
从不同维度可对进程做出精准定义,Linux系统下进程有明确的组成结构:
-
课本概念:程序的一次执行实例、正在运行的程序。
-
内核观点:承担系统资源分配的实体,负责占用CPU时间、内存等核心系统资源。
-
Linux实操定义:进程 = 内核数据结构(task_struct) + 自身程序代码与数据。
3-1-2 描述进程------PCB(进程控制块)
操作系统需要统一管理所有进程,因此需要专门的数据结构记录进程所有信息,即PCB。
-
进程的所有属性信息,都会存储在**进程控制块(PCB)**中,可简单理解为进程属性的集合。
-
Linux操作系统中,PCB对应的具体数据结构为task_struct,是PCB在Linux下的具体实现。
task_struct 核心特性:
-
是Linux内核自定义的专用数据结构类型。
-
系统运行时会被加载到内存(RAM)中,完整保存对应进程的全部信息。
3-1-3 task_struct 核心内容分类
task_struct 结构体包含进程运行的所有核心信息,主要分为以下类别:
-
标识符:进程唯一标识,用于系统区分不同进程。
-
状态信息:记录进程任务状态、退出代码、退出信号等核心状态数据。
-
优先级:定义进程相对于其他进程的执行优先级,影响进程调度顺序。
-
程序计数器:保存进程下一条待执行指令的内存地址。
-
内存指针:包含进程代码、数据的内存指针,以及进程间共享内存块的指针。
-
上下文数据:进程暂停执行时,CPU寄存器中留存的临时数据(进程切换核心依据)。
-
I/O状态信息:记录进程的I/O请求、已分配的I/O设备、进程占用的文件列表等信息。
-
记账信息:统计进程占用的CPU总时长、时钟数、运行时长限制、账号信息等。
-
其他信息:包含进程运行所需的其余辅助参数,后续详细讲解。
进程组织方式:
系统中所有正在运行的进程,其对应的 task_struct 结构体,都会以双向链表 的形式在内核中统一组织、管理,可在内核源代码中查阅相关定义。
3-1-4 查看进程的方式
Linux系统提供两种主流方式查看进程信息:
-
通过 /proc 系统文件夹查看 :proc是系统虚拟文件目录,实时存储系统进程信息。例如PID为1的进程信息,可通过
/proc/1文件夹查看。
-
通过用户级工具查看 :常用
top(实时监控进程)、ps(查看静态进程快照)指令获取进程信息。
常驻进程测试代码:
cpp
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
while(1){
sleep(1);
}
return 0;
}
3-1-5 系统调用获取进程标识符
Linux提供专属系统调用函数,可获取进程核心标识:
-
PID(进程ID) :当前进程的唯一标识,通过
getpid()获取。 -
PPID(父进程ID) :创建当前进程的父进程标识,通过
getppid()获取。
获取PID、PPID测试代码:
cpp
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
printf("pid: %d\n", getpid());
printf("ppid: %d\n", getppid());
return 0;
}
3-1-6 系统调用创建进程------fork初识
可通过 man fork 指令查询fork函数官方手册,其核心特性如下:
-
fork 函数拥有两个返回值,是进程创建的核心特性。
-
父子进程代码完全共享 ,数据独立私有,采用写时拷贝机制节省内存。
基础fork创建进程代码:
cpp
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
int ret = fork();
printf("hello proc : %d!, ret: %d\n", getpid(), ret);
sleep(1);
return 0;
}
实际开发中,fork创建进程后,需通过if分支实现父子进程逻辑分流:
cpp
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
int ret = fork();
if(ret < 0){
perror("fork");
return 1;
}
else if(ret == 0){ // 子进程执行逻辑
printf("I am child : %d!, ret: %d\n", getpid(), ret);
}else{ // 父进程执行逻辑
printf("I am father : %d!, ret: %d\n", getpid(), ret);
}
sleep(1);
return 0;
}
课堂核心重点(待后续详解)
-
fork 函数为什么会存在两个返回值?
-
两个返回值分别如何分配给父进程、子进程?
-
同一变量返回值,为何能同时满足 if、else if 不同分支条件(后续课程详解)
3-2 进程状态
3-2-1 Linux内核源代码进程状态定义
为区分进程运行的不同场景,Linux内核定义了多种进程状态(内核中进程也称为任务),所有状态均在内核源码中统一枚举定义,具体如下:
bash
/*
*The task state array is a strange "bitmap" of
*reasons to sleep. Thus "running" is zero, and
*you can test for combinations of others with
*simple bit tests.
*/
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):并非代表进程一定正在占用CPU运行,只要进程处于运行中,或位于CPU运行队列中等待调度,均为R状态。
-
S 可中断睡眠状态(sleeping):进程等待某一事件完成而主动休眠,属于可中断睡眠,可被信号唤醒。
-
D 不可中断磁盘休眠状态(disk sleep) :进程等待IO操作结束,该状态下进程不可被信号中断、唤醒,常用于磁盘读写等关键IO场景。
-
T 停止状态(stopped) :进程被暂停运行。可通过
SIGSTOP信号暂停进程,通过SIGCONT信号恢复进程继续运行。 -
t 追踪停止状态(tracing stop):进程被调试工具、ptrace系统调用追踪时的暂停状态。
-
X 死亡状态(dead):进程彻底释放资源的临时返回状态,瞬时结束,无法在任务列表中观测到该状态。
-
Z 僵尸状态(zombie):子进程退出,父进程未读取其退出状态,子进程残留PCB信息驻留进程表的特殊状态。
3-2-2 进程状态查看指令
Linux主要通过 ps aux、ps axj 查看系统进程状态,各参数作用:
bash
ps aux / ps axj 命令
-
a:显示当前终端下所有用户的全部进程,包含其他用户进程。
-
x:显示无控制终端的进程,多用于查看后台守护进程。
-
j:展示进程组ID、会话ID、父进程ID等作业控制相关信息。
-
u :以用户维度展示进程详情,包含进程所属用户、CPU占用、内存占用等信息。

3-2-3 僵尸进程(Z)
僵尸进程是Linux中特殊的进程状态,有明确的生成条件与运行特性:
-
生成原因 :子进程正常退出,父进程未调用
wait()等系统调用读取子进程的退出返回值。 -
核心特性:子进程退出后,资源大部分释放,但PCB进程控制块会保留在系统进程表中,持续等待父进程读取退出状态。
-
判定条件:子进程退出、父进程正常运行、父进程未回收子进程资源,子进程进入Z僵尸状态。
僵尸进程模拟代码(维持30秒僵尸进程):
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
pid_t id = fork();
if(id < 0){
perror("fork");
return 1;
}
else if(id > 0){
// 父进程:持续运行,不回收子进程
printf("parent[%d] is sleeping...\n", getpid());
sleep(30);
}else{
// 子进程:运行5秒后退出,成为僵尸进程
printf("child[%d] is begin Z...\n", getpid());
sleep(5);
exit(EXIT_SUCCESS);
}
return 0;
}
测试方式 :编译运行代码,新开终端使用 ps aux 实时查看进程状态,可观测子进程5秒后变为Z状态。
编译并在另⼀个终端下启动监控
开始测试
看到结果
3-2-4 僵尸进程的危害
-
进程退出状态需要通过PCB保存,用于告知父进程任务执行结果,若父进程长期不读取,子进程将持续保持Z状态。
-
僵尸进程的PCB不会被系统释放,会持续占用内存空间,属于永久占用的内核数据结构资源。
-
若父进程频繁创建子进程且不回收,会产生大量僵尸进程,持续消耗系统内存,最终造成内存泄漏。
-
系统进程数量存在上限,大量僵尸进程会占用进程名额,导致后续无法创建新进程。
3-2-5 孤儿进程
与僵尸进程对应,孤儿进程是父进程提前退出产生的特殊进程:
-
生成原因:父进程先于子进程退出,剩余继续运行的子进程称为孤儿进程。
-
系统处理机制:孤儿进程会被系统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){
// 子进程:长时间运行
printf("I am child, pid : %d\n", getpid());
sleep(10);
}else{
// 父进程:3秒后提前退出
printf("I am parent, pid: %d\n", getpid());
sleep(3);
exit(0);
}
return 0;
}
3-3 进程优先级
3-3-1 基本概念
进程优先级是CPU分配系统资源的核心依据,决定进程的调度先后顺序:
-
优先级越高的进程,拥有优先被CPU调度执行的权利。
-
合理配置进程优先级,可优化多任务系统的资源分配,提升整机运行性能。
-
支持进程CPU绑定,可将低优先级进程固定在指定CPU核心,优化整体调度效率。
3-3-2 查看系统进程优先级信息
通过 ps -l 命令可查看进程核心优先级相关字段,关键参数解析:

-
UID:进程执行者的用户身份ID。
-
PID:进程唯一标识ID。
-
PPID:父进程ID,标识进程的创建来源。
-
PRI:进程默认优先级(80),数值越小,优先级越高,越先被CPU执行。
-
NI:进程nice值,用于修正进程优先级。
3-3-3 PRI与NI详解
-
PRI:进程基础优先级,决定进程调度基础顺序,数值越小优先级越高。
-
NI(nice值):优先级修正数值,是用户可手动调整的参数。
-
优先级计算公式:PRI(new) = PRI(old) + nice
-
nice值为负数:新PRI值变小,进程优先级升高,执行优先级更高。
-
nice值为正数:新PRI值变大,进程优先级降低。
-
取值范围 :nice值固定为 -20 ~ 19,共40个优先级档位。
-
Linux下调整进程优先级,本质就是修改进程的nice值。
-
总的优先级范围 60, 99
3-3-4 PRI与NI核心区别
-
PRI是进程的真实调度优先级,NI是优先级的修正参数,二者并非同一概念。
-
nice值无法直接决定优先级,只能对原生PRI优先级进行微调修正。
3-3-5 进程优先级查看与修改方式
1、top命令动态修改nice值
-
终端输入
top进入进程监控界面; -
按下 r 键,输入目标进程PID;
-
输入新的nice值,即可完成优先级修改。
2、专用命令 :nice(创建进程时指定优先级)、renice(修改已存在进程优先级)
3、系统调用函数
cpp
#include <sys/time.h>
#include <sys/resource.h>
// 获取进程优先级
int getpriority(int which, int who);
// 设置进程优先级
int setpriority(int which, int who, int prio);
3-3-6 进程核心补充概念
-
竞争性:系统进程数量远多于CPU核心数量,所有进程竞争有限的CPU资源,因此系统通过优先级机制规范资源分配,保证任务高效执行。
-
独立性:多进程并发运行时,各进程拥有独立资源空间,运行过程中相互隔离、互不干扰。
-
并行 :多CPU核心环境下,多个进程在不同CPU核心上同时运行,真实同步执行多个任务。
-
并发:单CPU核心环境下,系统通过快速进程切换,让多个进程在一段时间内交替推进,宏观上实现多任务同时运行,微观上同一时刻仅有一个进程运行。



