其他篇章【Linux专栏】
其他篇章 【C语言专栏】
上期回顾 【Linux】冯诺依曼---操作系统
文章目录
- 1.进程的概念
- [2. 描述进程PCB---Linux 操作系统下的 PCB 是: task_struct](#2. 描述进程PCB---Linux 操作系统下的 PCB 是: task_struct)
-
- 2.1基本概念
- 2.2task_struct
-
- [2.21 task_struct 内容分类](#2.21 task_struct 内容分类)
- [3. 进程管理](#3. 进程管理)
-
- [3. 1组织进程](#3. 1组织进程)
- [3.2 查看进程](#3.2 查看进程)
- [3.3 通过系统调⽤获取进程标⽰符](#3.3 通过系统调⽤获取进程标⽰符)
- [4. 创建进程](#4. 创建进程)
-
- [4.1 fork 创建进程](#4.1 fork 创建进程)
- [4.2 fork的底层原理](#4.2 fork的底层原理)
- [5. 进程状态](#5. 进程状态)
-
- [5.1 操作系统(OS)层面进程的状态](#5.1 操作系统(OS)层面进程的状态)
- [5.2 Linux系统层面进程的状态](#5.2 Linux系统层面进程的状态)
-
- R运⾏状态(running)
- S睡眠状态(sleeping))
- [D磁盘休眠状态(Disk sleep)](#D磁盘休眠状态(Disk sleep))
- T停⽌状态(stopped)
- X死亡状态(dead)---很少见
- Z僵尸状态(Zombies)
1.进程的概念
- 基本概念:程序的一个执行实例,正在执行的程序等。
- 内核观点:担当分配系统资源(CPU时间,内存)的实体。
- 我们来理解:进程 = 内核数据结构(task_struct) + 自己的程序代码和数据
注意:
- 运行起来的程序,加载到内存的程序都是进程
- 一个任务就是一个进程,一个程序可以启动多个进程,程序只有一个,但是一个程序可以有多个进程
2. 描述进程PCB---Linux 操作系统下的 PCB 是: task_struct
2.1基本概念
- 进程信息被放在⼀个叫做进程控制块的数据结构中,可以理解为进程属性的集合。
- 课本上称之为PCB(process control block), Linux 操作系统下的 PCB 是: task_struct
帮助理解:可以把进程理解为老师管理学生,要想管理好学生,老师就得了解学生的基本情况(姓名,学号...),所以OS要想管理进程,就需要一个"信息表",即 PCB (Process Control Block->进程控制块)
2.2task_struct
- 在 Linux 中描述进程的结构体叫做 task_struct 。
- task_struct 是 Linux 内核的⼀种数据结构类型,它会被装载到RAM(内存)⾥并且包含着进程的信息。
2.21 task_struct 内容分类
-
标示符: 描述本进程的唯⼀标⽰符,⽤来区别其他进程。
-
状态: 任务状态,退出代码,退出信号等。
-
优先级: 相对于其他进程的优先级。
-
程序计数器: 程序中即将被执⾏的下⼀条指令的__地址__。
-
内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
-
上下⽂数据: 进程执⾏时处理器的寄存器中的数据[休学例⼦,要加图CPU,寄存器]。
-
I/O状态信息: 包括显⽰的I/O请求,分配给进程的I/O设备和被进程使⽤的⽂件列表。
-
记账信息: 可能包括处理器时间总和,使⽤的时钟数总和,时间限制,记账号等。
3. 进程管理
3. 1组织进程
Linux内核中, 最基本的组织进程task_struct的方式是采用"双向链表组织"。

3.2 查看进程
1.进程的信息可以通过 /proc 系统文件夹查看
2.大多数进程信息同样可以使用top和ps这些用户级工具来获取
3.3 通过系统调⽤获取进程标⽰符
- 进程id(PID)
- ⽗进程id(PPID)
#include
#include <sys/types.h>
#include <unistd.h>
int main()
{
printf("pid: %d\n", getpid());
printf("ppid: %d\n", getppid());
return 0;
}
4. 创建进程
4.1 fork 创建进程
fork是Linux最基础的进程创建方式(子进程)
1. 本质:复制父进程,创建子进程
- 调用 fork() 后,原来的进程(父进程)会多出一个几乎完全相同的子进程。代码、数据、内存状态基本都复制一份(只有部分会被修改)。
2.返回值区分父子
- 父进程: fork 返回子进程 PID(大于0的整数)
- 子进程: fork 返回 0
- 出错:返回 -1
3.各自独立运行
两个进程有独立的地址空间,后续修改变量互不影响。
4. 典型用途
- 让程序同时干多件事(并发)
- 守护进程、后台服务
- shell 执行命令时,都是先 fork 再 exec
4.2 fork的底层原理
本质: fork 本质是"内核"帮你复制进程描述信息 + 共享物理内存(写时复制),不是真的全量拷贝内存
内核做了哪几件事?
1.创建 task_struct内核为新进程分配一个task_struct (进程控制块 PCB),把父进程的几乎所有信息复制一份:
- 页表
- 文件描述符表
- 信号处理方式
- 当前寄存器上下文
- 内存地址空间结构
2.分配新 PID
- 子进程有独立 PID,与父进程不同
(即父子进程地址空间独立,但物理内存初期共享)
3.写时复制(Copy-On-Write, COW)这是 fork 高效的关键:
- 刚 fork 完,父子共用同一块物理内存
- 页表标记为"只读"
- 谁要修改数据,才触发缺页异常,内核真正复制那一页内存→ 只有修改时才拷贝,不修改就一直共享
4.设置返回值内核分别给父子进程设置不同返回值:
- 父进程:返回子进程 PID(大于0的整数)
- 子进程:返回 0之后两者完全独立调度、独立运行
1.为什么fork()函数要给子进程返回0?给父进程返回子进程pid?
- 为了后面在调用fork()函数之后,可以根据不同的if判断来让父子进程执行不同的代码片段,及为了区分不同的执行流,使其可以执行不同的代码块。
(一般而言,fork()函数之后的代码是 父子共享 的!)2.为什么同一个变量会返回两次呢?
- 底层是因为当该函数出现了return,创建子进程的时候,父进程共享了return,所以相应的就会出现返回两次的情况
两个返回值的含义
-
父进程中,fork () 的返回值 = 子进程的 PID(正数):父进程通过这个返回值,识别自己创建的子进程
-
子进程中,fork () 的返回值 = 0(零):子进程通过返回值 0,识别自己是子进程;
-
若 fork 返回负数(\u003C0):表示子进程创建失败(比如系统资源不足,无法分配新的 task_struct)。
5. 进程状态
5.1 操作系统(OS)层面进程的状态
调用fork()函数创建子进程之后,父子进程并非一直处于运行状态,会在不同状态之间切换;
(进程状态反映进程执行过程的变化)
- 三态模型中:进程状态分为 运行态、就绪态、阻塞态;
- 五态模型中:进程状态分为 新建态、终止态、运行态、就绪态、阻塞态;

-
运行状态
进程占有处理器正在运行或正处于运行队列中。
-
阻塞状态
进程不具备运行条件,正在等待某个事件的完成。
-
挂起状态
进程的挂起状态是指计算机系统中,一个进程因为某些原因而暂时不能继续执行,但仍然保持在进程表中,并且有可能在将来恢复执行的状态。
5.2 Linux系统层面进程的状态

R运⾏状态(running)
- 并不意味着进程⼀定在运⾏中,它表明进程要么是在运⾏中要么在运⾏队列⾥。
S睡眠状态(sleeping)
-
意味着进程在等待事件完成(这⾥的睡眠有时候也叫做可中断睡眠)。
-
S 状态也叫 "浅度睡眠",是 Linux 中最常见的状态(比如我们打开的浏览器、QQ,大部分时间都处于 S 状态,只有当我们操作时,才会切换到 R 态)。
D磁盘休眠状态(Disk sleep)
-
有时候也叫 不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待I/O的结束。
-
D 状态是 "保护态"------ 比如进程正在向磁盘写入数据,如果此时强行中断进程,会导致磁盘数据损坏,所以 Linux 内核禁止中断 D 状态的进程。
T停⽌状态(stopped)
- 可以通过发送 SIGSTOP 信号给进程来停⽌(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运⾏。
X死亡状态(dead)---很少见
- 这个状态只是⼀个返回状态,你不会在任务列表⾥看到这个状态。
Z僵尸状态(Zombies)
-
子进程已经退出,但父进程没有读取子进程的退出状态(比如父进程一直在睡眠,没有调用 wait 函数),此时子进程的 task_struct(PCB)不会被释放,处于 "僵尸" 状态。
-
僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
-
所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程就会进入Z状态
-
处于僵死状态的进程就被成为僵尸进程,其相关资源尤其是task_struct结构体不能被释放,这也就会导致僵尸进程会一直占用内存资源!