进程的由来

早期计算机如何工作?

单任务批处理:一次只能运行一个程序,程序独占所有资源(CPU、内存、I/O)。

缺陷:

CPU闲置:程序等待I/O(如磁盘读写)时,CPU空转,利用率极低。

无容错:程序崩溃 = 系统崩溃。

无并发:无法同时处理多个任务。

Why为什么导致这种情况?

硬件无支持:

无中断机制 → CPU无法被强制打断。

无MMU(内存管理单元)→ 程序直接操作物理内存,互相覆盖数据。

状态保存问题:

切换程序时,需保存寄存器、内存状态,但早期无标准化方法(无PCB)。

进程由此诞生

进程 = 程序的一次执行实例 + 运行时状态 + 资源集合

程序:静态的代码(如.exe文件)。

进程:动态的实体(运行时的程序,拥有内存、CPU时间、文件等资源)。

进程解决的四大核心问题

一. 并发:

首先最主要的硬件支持!

1. 时钟中断:强制收回CPU控制权。

2. PCB(进程控制块):保存/恢复进程状态(如寄存器值)。

怎么使用 PCB 和 时钟中断?

时间片轮转 上下文切换

什么是PCB?

为什么出现PCB:

如果没有PCB操作系统无法跟踪哪些程序正在运行、哪些在等待。

无法公平分配CPU时间(如高优先级程序抢占低优先级程序)。

进程隔离的真正实现在于PCB

PCB中含有:

页表指针 记录虚拟内存到物理内存的映射关系,确保进程只能访问自己的内存区域。

内存边界寄存器 定义进程可用的堆、栈范围,越界访问会触发异常(如Segmentation Fault)。

二. 隔离:

每个进程拥有独立的虚拟地址空间,通过MMU映射到物理内存。

虚拟内存的真正含义:

虚拟地址是 CPU 运行程序时生成的 当你写代码访问某个变量,比如 x = 10,编译后变成访问地址如 0x00400000。

这个地址就是虚拟地址,程序里看到的所有地址都是虚拟地址。

虚拟地址并不直接访问物理内存,而是交由硬件的 MMU(Memory Management Unit) 来转换。

共享物理地址:

进程 A 的虚拟地址 0x1000 和进程 B 的虚拟地址 0x2000 可以映射到同一个物理页(比如物理地址 0x8000)。

一个物理页的“地址”是其起始地址(如 0x8000),但实际代表一整块内存。 例如 物理页 0x8000 表示从 0x8000 到 0x8FFF

页表记录的是物理页的起始地址(如 0x8000) 虚拟地址的页内偏移量(Offset) 自动定位到物理页中的具体位置。

操作系统分配一个物理页(如 0x8000 ~ 0x8FFF)。

进程 A 的虚拟页 0x1000 映射到 0x8000。

进程 B 的虚拟页 0x2000 也映射到 0x8000。

偏移量的作用:

进程 A 的虚拟页首地址是 0x1000。

访问 0x1003:

页内偏移量 = 0x003。

实际物理地址 = 0x8000(物理页首地址) + 0x003 = 0x8003。

三. 资源管理:

操作系统以进程为单位分配:

CPU时间(调度器) 控制哪个进程什么时候用 CPU。

内存(虚拟内存系统) 每个进程有自己的虚拟地址空间,互相隔离,防止读写别人的数据。

文件/设备(文件描述符表) 比如打开的文件、网络连接、标准输入输出等,每个进程有自己的文件表。

进程切换

中断触发: 时钟中断或系统调用强制CPU进入内核态。

保存状态: 将当前进程的寄存器值存入其PCB。

选择新进程: 调度器从就绪队列选取下一个进程。

恢复状态: 从新进程的PCB加载寄存器值到CPU。

切换页表: 更新MMU的虚拟内存映射。

跳转执行: CPU从新进程的PC地址继续运行。

进程fork子进程

在一个进程创建的时候,进程可以系统调用 创建新进程来协助工作!

示例:

Unix 在shell使用 ls shell本身自己是一个进程 ls 又是另一个进程 fork出来子进程之后 使用 exec(“ls”)执行

Windows 创建新进程 则是使用 CreateProcess() 调用包括多个参数

写时复制

进程创建之后 父子进程 共享相同的物理内存页 页被设置为_只读_

父子进程的页表都指向者同一块物理内存

一旦其中一个尝试写入该页 则会出现_页错误_ 也就触发了

写时复制 : 操作系统 复制出该页的一个副本 标记副本为可写 更新该进程的页表

父子进程不再共享同一物理页:修改后,写入进程(子进程)获得独立副本,未修改的进程(父进程)继续使用原页。

原页保持只读:原页的权限不会被恢复为可写,其他共享该页的进程(如父进程或其他子进程)仍受 COW 保护,直到它们尝试写入时再触发同样的复制流程。

好处:

减少不必要的复制:例如 fork() 后子进程只读数据(如程序代码)时,无需复制物理页。

降低内存压力:多个进程可以共享相同的只读页(如 C 库的代码段),直到实际需要修改。