一 .引入和基本概念

2.1  程序

程序是存储在磁盘上的静态指令集合。它是一个文件(.exe、可执行文件),是被动的。

2.2  进程与线程的引入

2.2.1 进程的引入

在早期的单道程序时代,程序顺序执行(串行),具有可再现性(简单来说,同一套输入,多次运行结果一样)。但CPU利用率极低(I/O操作时CPU空闲)。
后来引入了多道程序设计,让多个程序同时驻留内存并发执行。这时问题出现了:程序并发执行时,由于CPU不断切换,程序失去了封闭性,执行结果与执行速度有关,出现了“不可再现性”。
这个问题的本质是“程序”这个概念无法描述“正在执行中”的动态变化
因此,引入了进程(Process)。

2.2.2 线程的引入

虽然进程实现了并发,但随着时间的推移,发现了以下问题:

  • 切换开销巨大:进程是资源拥有者,拥有独立的地址空间。当CPU从一个进程切换到另一个进程时,必须切换整个地址空间、更新页表、刷新TLB(快表),耗时极长。

  • 通信成本高昂:进程间内存相互隔离,想要通信必须借助内核IPC(如管道、消息队列),速度慢且复杂。

  • 无法满足“内部并发”:比如打开一个浏览器,一边下载一边渲染页面,如果只用进程来做,创建多个进程极其浪费资源。

本质是 进程切换时切换地址空间的成本远远跟不上CPU可能的执行速度。而如果将资源分配与调度执行分离。线程切换无需切换地址空间,开销极小,从而使得并发执行的粒度能够匹配上CPU的高速处理能力。

因此,引入了线程(Thread)。

2.3 进程和线程的概念

对比维度 程序 (Program) 进程 (Process) 线程 (Thread)
简要介绍 静态的指令集(硬盘上的文件) 动态的执行实例(运行中的程序) 进程内的执行路径(轻量级运行流)
核心角色 提供执行内容和蓝图 资源分配的基本单位 CPU调度的基本单位
存在形态 静态的、被动的 动态的、主动的(有生命周期) 动态的、主动的(依附于进程)
生命周期 永久的(文件不删就一直存在) 临时的(创建而生,终止而亡) 临时的(随进程创建/消亡,或动态创建/销毁)
资源拥有 不拥有任何资源(仅占磁盘空间) 拥有独立资源(独立地址空间、文件、端口) 共享进程资源(仅拥有私有栈和寄存器)
内存布局 仅有代码段 + 静态数据 完整的虚拟地址空间(代码+数据+堆+栈) 同进程内线程共享地址空间,仅栈独立
通信方式 不涉及通信(无法运行) 进程间通信(IPC):管道、消息队列、共享内存、Socket(需陷入内核,开销大) 直接读写共享内存(零拷贝,无需内核介入,极快)
上下文切换 不涉及 重量级切换(需切换页表、刷新TLB/缓存,耗时约1~10微秒) 轻量级切换(仅切换寄存器和栈指针,无需切换地址空间,耗时不到1微秒)
系统开销 几乎为零(仅为存储) 创建/撤销开销大(需分配独立资源) 创建/撤销开销小(仅需分配TCB)
独立性 相互独立(静态文件) (拥有独立地址空间,一个进程崩溃通常不影响其他进程) (共享地址空间,一个线程崩溃可能导致整个进程崩溃)
操作系统感知 感知不到(当作普通文件管理) 强烈感知(通过PCB管理) 看情况(内核级线程感知,用户级线程不感知)

进程存在的唯一标识——PCB(Process Control Block,进程控制块)

字段 作用 补充
pointer(指针) 指向下一个PCB的地址,用于将多个PCB链接成队列(如就绪队列、阻塞队列)。 这就是操作系统把进程挂入“就绪队列”或“阻塞队列”的具体实现方式。
process number(进程号/PID) 进程的唯一身份证号(整数)。 父进程fork()返回的那个数字。
process state(进程状态) 记录当前进程处于就绪、运行、阻塞、挂起等哪种状态。 OS根据它决定下一步动作。
program counter(程序计数器/PC) 记录进程下一条要执行的指令在内存中的地址。 上下文切换(Context Switch) 的核心。当进程被踢下CPU(Running→Ready)时,OS把PC值存到这里;当进程再次上CPU时,OS从这里取出PC恢复执行。
registers(寄存器集合) 保存CPU内部所有通用寄存器、栈指针、状态字的值。 进程切走时,寄存器的值必须原封不动存到这里,否则回来时数据全乱了
memory limits(内存界限) 记录进程占用的内存基址和长度,防止它越界访问其他进程的内存。 保证进程间地址空间隔离,这是进程作为“资源拥有者”的关键凭证。
list of open files(打开文件列表) 记录进程当前打开的所有文件、管道、网络连接的指针。 这就是进程“拥有资源”的具体体现。线程共享这个列表,所以线程间打开的文件是共用的。

上下文切换(Context Switch):CPU从正在运行的process、thread切换时,保存当前运行现场、加载新任务现场的全过程

2.4 进程的状态

2.4.1 含义

状态 含义
New(新建态) 操作系统刚刚收到创建请求,正在分配PCB(进程控制块)和初始资源,但还没允许它进入系统。
Ready(就绪态) 进程具备了一切运行条件(内存、资源都齐了),在就绪队列里排队等待调度。
Running(运行态) 进程此刻正占用CPU执行指令。
Waiting/Blocked(阻塞态) 进程在运行中因资源需要或等待其他进程响应,主动放弃CPU等待。
Terminated(终止态) 进程结束,操作系统回收它占用的所有资源,PCB即将被销毁。

2.4.2 进程控制(process operation)—— 状态切换

原语(Primitive):原语是由若干条机器指令构成、执行过程不可中断的一段内核程序,用于完成特定进程控制功能。(内核底层、原子执行,是实现同步互斥 / 进程管理的基础)

转换线 触发事件 执行的操作(原语) 执行
 New → Ready 系统允许进程从外存进入主存 创建原语 (Create)
(分配PCB、分配初始资源、初始化PCB)
操作系统内核
(父进程调用fork(),由内核响应)
 Ready →Running 调度程序选中该进程 调度原语 (Dispatch/Schedule)
(从就绪队列摘出,恢复进程上下文到CPU)
操作系统调度器
(内核的调度模块)
Running → Ready 时间片用完 / 被高优先级抢占 中断处理原语 (Interrupt Handle)
(保存当前进程上下文到PCB,将其挂入就绪队列)
操作系统内核
(时钟中断或I/O中断处理程序强制触发)
Running →Waiting 请求I/O / 等待资源 / wait() 阻塞原语 (Block/Wait)
(保存现场,将进程从运行态移出,挂入阻塞队列)
进程主动调用
(例如调用read()wait()系统调用)
 Waiting → Ready I/O完成 / 资源到位 / 事件发生 唤醒原语 (Wakeup/Signal)
(将PCB从阻塞队列摘下,状态改为就绪,挂入就绪队列)
操作系统内核
(设备中断处理程序或signal释放资源时触发)
Running→Terminated 正常结束 / 异常 / 被杀死 终止原语 (Exit/Kill)
(回收PCB、回收所有内存和资源、通知父进程)
进程主动调用exit(),或父进程/内核强制kill

创建原语 (Create) -- 父进程与子进程

  • 资源共享(Resource sharing):全部、部分[fork()+写时复制COW]、不共享[(fork()+exec()]
  • 执行(Execution):同时执行,父进程会阻塞直到子进程终止
  • 进程的创建、程序替换、同步与终止的UNIX/Linux 系统编程代码:
#include <stdio.h>

void main(int argc, char *argv[])
{
    int pid;

    /*fork another process */
    // 创建子进程系统调用fork()
    pid = fork();

    // 分支1:fork调用失败(内存不足/进程数上限)
    if (pid < 0) {
        fprintf(stderr, "Fork Failed");
        // 标准错误流打印失败信息
        exit(-1);
      
    }
    // 分支2:pid == 0 代表当前是子进程
    else if (pid == 0) {
        execlp("/bin/ls", "ls", NULL);
        // 加载执行ls命令,替换子进程全部内存镜像
        // execlp执行成功后,后续代码永远不会运行;执行失败才会向下执行
    }
    // 分支3:pid > 0 代表当前是父进程,pid是子进程PID
    else {
        wait(NULL);
     
        printf("Child Complete");
      
        exit(0);
     
    }
}

2.5 进程的通信 (IPC,Inter-Process Communication)

2.5.1 两个的独立进程,怎么找到对方共同的“内核对象”(管道、共享内存、消息队列)

类型 代表机制 如何找到对象? 适用范围
匿名 (Anonymous) 匿名管道(pipe() 靠继承。父进程通过 fork() 把管道的文件描述符直接传给子进程。 仅限于有亲缘关系(父子/兄弟进程)。

命名

(Named)

命名管道(FIFO)、共享内存、消息队列、Socket 靠路径名或Key。进程根据事先约定好的字符串(如 /tmp/my.pipe 或 12345)向内核“申请”打开。

任意进程

(无亲缘关系也能通信)。

2.5.2 通信方式

IPC方式 通信范围 同步/异步 数据边界 速度排名 典型应用
信号 (Signal) 单机 异步 无数据负载(仅传信号编号) 极快 进程异常终止、定时器、SIGCHLD
管道 (Pipe) 匿名(亲缘) / 命名(任意) 同步阻塞 无格式字节流(无边界) Shell命令连接(ls | grep
消息队列 (MQ) 任意进程(单机) 通常异步 有边界数据报(按类型/长度取) 中等 异步任务分发、日志收集
共享内存 (SHM) 任意进程(单机) 同步(需配合锁) 原始内存块(无格式) 极快 数据库缓存区、高速数据交换"
信号量 (Semaphore) 任意进程(单机) 同步工具

计数器(整数)(非数据通道)

保护共享资源(互斥锁/读写锁)
套接字 (Socket) 跨网络(也可单机) 同步/异步 TCP 字节流无边界;UDP 数据报自带边界 较慢 Web服务器、浏览器、分布式系统


Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐