【操作系统入门】第三章:进程与线程(一)—— 程序为何"运行"起来?
本系列共10篇,这是第3/10篇。在第二章,我们回顾了操作系统的演进与架构。今天,我们将深入最核心的动态概念——进程与线程,探索操作系统实现并发执行的魔法。
开篇:从静态代码到动态执行
当我们双击一个可执行文件(比如 notepad.exe),一个神奇的变化发生了:硬盘上一段静态的二进制代码,变成了屏幕上可以与用户交互的、“活着"的程序。这个"活着的实体”,就是进程。理解进程,是理解现代操作系统如何工作的关键第一步。
第一部分:进程——程序的执行实例
1.1 进程的正式定义
一个进程,本质上是一个正在执行的程序的实例。它不仅包含程序的代码(文本段),还包括程序运行时所需要的一切资源与状态:
- 程序的代码(文本段)
- 当前活动:如程序计数器(PC)的值和处理器寄存器的内容
- 堆栈:存放临时数据(如函数参数、返回地址、局部变量)
- 数据段:存放全局变量
- 堆:程序运行时动态分配的内存
- 一系列系统资源:如打开的文件、I/O设备等
关键洞察:程序本身只是存储在磁盘上的被动实体(一堆指令和数据),而进程是主动实体,它拥有一个程序计数器,指示下一个要执行的指令,以及一组相关的资源。
1.2 进程控制块(PCB)—— 进程的"身份证"
操作系统是如何管理成百上千个进程的呢?答案是:通过进程控制块(PCB)。每个进程在创建时,操作系统都会为它分配一个唯一的PCB。当进程终止时,其PCB也随之被回收。PCB是进程存在的唯一标志。
一个完整的PCB通常包含以下信息:
- 进程标识符(PID):唯一的数字ID,如同我们的身份证号。
- 进程状态:运行、就绪、等待/阻塞等。
- 程序计数器(PC):指向该进程下一个要执行的指令的地址。
- CPU寄存器:当进程被切换出CPU时,必须保存所有寄存器的值,以便后续能精确地恢复现场。
- CPU调度信息:进程优先级、调度队列指针等。
- 内存管理信息:基址/限址寄存器的值、页表或段表的指针。
- 记账信息:CPU已使用时间、时间限制等。
- I/O状态信息:分配给该进程的I/O设备列表、打开的文件列表等。
PCB是使进程能够"挂起"和"恢复"的关键。当操作系统决定切换到另一个进程时,它只需要将当前进程的寄存器状态保存到其PCB中,然后从下一个进程的PCB中加载寄存器状态即可。这个过程称为上下文切换。
第二部分:进程的状态——生命周期的演绎
一个进程在其生命周期中,会经历几种状态。理解这些状态及其转换,是理解进程调度的基础。
经典的进程五状态模型:
- 新建:进程正在被创建。
- 就绪:进程已获得了除CPU之外的所有必要资源,万事俱备,只欠CPU。在就绪队列中等待调度。
- 运行:指令正在CPU上被执行。
- 阻塞/等待:进程等待某种事件的发生而无法继续执行(如等待I/O操作完成、等待信号量)。即使CPU空闲,它也无法运行。
- 终止:进程已结束执行。
状态转换的典型场景:
- 新建 -> 就绪:操作系统完成进程创建,准备好投入运行。
- 就绪 -> 运行:进程被调度器选中。这是操作系统的核心决策之一。
- 运行 -> 就绪:最常见的原因是时间片用完,或被更高优先级的进程抢占。
- 运行 -> 阻塞:进程主动请求一个必须等待的操作,如发起I/O请求、尝试获取一个已被占用的锁。
- 阻塞 -> 就绪:进程所等待的事件发生了(如I/O完成),它具备了继续运行的条件。
- 运行 -> 终止:进程执行完毕,或出现致命错误被强制终止。
第三部分:为什么需要线程?—— "进程内"的并发
进程的概念很好地实现了程序间的并发与隔离。但随着应用越来越复杂,人们发现进程作为调度和拥有的单位,存在一些沉重的问题:
- 创建进程开销大:分配内存、建立页表、创建PCB等,需要大量的系统调用和内存操作。
- 撤销进程开销大:需要回收所有资源。
- 进程间切换开销大:上下文切换需要保存和恢复整个内存映像(寄存器、内存管理信息等)。
- 进程间通信(IPC)复杂且慢:由于进程间内存空间相互隔离,通信必须通过内核介入(管道、消息队列、共享内存等),开销巨大。
线程的引入:轻量级进程
为了解决这些问题,线程被引入了。线程被称为"轻量级进程",它是CPU调度的基本单位。
核心思想: 将传统进程的两个功能分离开:
- 资源拥有的单位:这仍然是进程。一个进程拥有一个完整的资源集合,如地址空间、打开的文件等。
- 调度和执行的单位:这变成了线程。一个进程内可以有一个或多个线程,它们共享进程的所有资源,但每个线程有自己的执行流。
一个生动的比喻:
- 进程像一个办公室,里面有公共的资源:桌椅、打印机、文件柜(对应内存空间、打开的文件)。
- 线程像办公室里的员工。多个员工(线程)共享办公室的所有公共资源,但他们各自独立地工作(有自己的任务和进度)。
线程的组成:
一个线程拥有自己独立的:
- 线程ID
- 程序计数器(PC)
- 寄存器集合
- 堆栈(用于存放局部变量、返回地址)
但线程共享其所属进程的:
- 代码段
- 数据段
- 堆
- 打开的文件和I/O设备等操作系统资源
线程的优势:
- 响应性:在一个单线程进程中,如果一个操作(如复杂的I/O)阻塞了,整个进程都会停止响应。而在多线程程序中,即使一个线程被阻塞,其他线程仍然可以继续执行,保持程序的响应性(如UI线程与工作线程分离)。
- 资源分享:线程默认共享内存和资源,使得通信和数据共享变得极其方便和高效。
- 经济性:创建和销毁线程比进程快得多。线程上下文切换也比进程上下文切换快,因为它们在同一个地址空间内,不需要切换页表等内存管理结构。
- 可伸缩性:在多处理器架构上,多线程可以真正实现并行执行,每个CPU核心运行一个线程,极大地提高了性能。
第四部分:线程的实现模型
线程是如何在底层实现的?主要有三种模型:
1. 用户级线程
- 实现者:在用户空间通过线程库(如Pthreads)实现。内核对此一无所知,内核视角下仍只有一个进程。
- 管理方式:线程的创建、调度、同步全部在用户空间的线程库中完成,无需内核介入。
- 优点:
- 极快的线程操作,因为不需要陷入内核态。
- 可以自定义调度算法。
- 致命缺点:“一荣俱荣,一损俱损”。如果一个用户级线程执行了一个阻塞式系统调用(如读文件),由于内核只知道这个进程,它会将整个进程阻塞,导致该进程内的所有线程都无法执行。此外,无法利用多核CPU的真正并行。
2. 内核级线程
- 实现者:由操作系统内核直接支持。内核负责线程的创建、调度和管理。
- 管理方式:内核为每个线程维护一个线程控制块(TCB)。
- 优点:
- 一个线程阻塞时,内核可以调度该进程内的其他就绪线程或别的进程的线程。
- 能够很好地利用多处理器,将不同线程分配给不同CPU核心。
- 缺点:
- 线程操作(创建、销毁、同步)需要系统调用,模式切换开销比用户级线程大。
3. 混合模型
- 实现方式:结合上述两者。用户级线程被多路复用到数量等于或小于内核级线程的集合上。
- 工作方式:程序员可以在用户空间创建任意数量的用户级线程,但它们在运行时被映射到一组由内核调度的内核级线程上。
- 优点:结合了二者的优点,既保持了用户级线程的灵活性和部分性能优势,又能利用多核并行,且不会因为一个线程的阻塞导致整个进程阻塞。
- 代表:现代操作系统如Windows、Linux的线程实现本质上都是内核级线程,但通过高效的库和机制,提供了良好的使用体验。
总结与展望
今天我们深入探讨了:
- 进程是资源分配的基本单位,是一个正在执行的程序的实例,拥有独立的地址空间。
- 线程是CPU调度的基本单位,是进程内的一条执行流,共享进程的资源。
- PCB是进程的灵魂,记录了进程的全部信息。
- 引入线程是为了实现更细粒度的并发,减少上下文切换开销,并简化通信。
理解进程与线程的区别与联系,是理解现代软件(从Web服务器到你的手机App)如何高效运行的基础。
2229

被折叠的 条评论
为什么被折叠?



