OS-02 进程描述与控制
OS-02 进程描述与控制
一、进程的基本概念
进程是操作系统中最重要的概念之一。简单来说,进程就是正在执行的程序的一个实例。但"正在执行"包含了比程序本身更多的内涵——程序是静态的、存储在磁盘上的指令序列,而进程是动态的,包括了程序代码、当前的活动状态以及相关的资源。
1.1 进程映像的三要素
一个进程在内存中的组织形式称为进程映像(Process Image),它由三个核心部分组成:
程序段(代码段):存放可执行程序的指令代码。这是程序本身的二进制表示,多个进程可以共享同一段代码(例如多个用户同时运行同一个编辑器,但每个进程有自己的数据段)。
数据段:存放程序运行过程中使用的全局变量、静态变量等数据。这里的"数据"是程序需要操作的对象。
进程控制块(PCB):这是进程存在的唯一标识,也是操作系统管理进程的核心数据结构。PCB中记录了进程的各种信息,主要包括:
- 进程标识符(PID),用于唯一标识一个进程
- 进程状态(就绪、运行、阻塞等)
- CPU寄存器的值(当进程被切换时需要保存)
- 程序计数器(下一条要执行的指令地址)
- 内存分配信息(页表、段表等)
- 打开的文件列表
- 优先级、调度信息
- 进程间通信相关的信息
PCB常驻在内存的系统空间中。操作系统通过PCB来感知和管理进程——当一个进程创建时,内核为其分配一个PCB;当进程结束时,内核回收这个PCB。
一个重要的特性:进程是资源拥有的基本单位。操作系统以进程为单位分配内存、文件句柄等资源。这与后面要讲的"线程"形成了对比——线程是CPU调度的基本单位,但不拥有资源。
二、进程的状态与转换
进程在执行过程中会经历不同的状态。操作系统根据这些状态来决定"接下来该运行哪个进程"。
2.1 三态模型
就绪态:进程已经获得了除CPU之外的所有资源,只等待CPU调度器的分配。在就绪队列中,可能有多个进程在等待CPU,调度器需要从中选择一个来执行。
运行态:进程获得了CPU的使用权,正在执行指令。在单核CPU中,任何时候只有一个进程处于运行态。
阻塞态:进程因为等待某个事件的发生(如I/O操作完成、获取锁、接收到消息)而暂停执行。进入阻塞态的进程会主动放弃CPU,调度器会从就绪队列中选择另一个进程运行。当等待的事件发生时,阻塞态的进程会被唤醒,转移到就绪态。
四种转换关系:
就绪→运行:调度器选中该进程,分配CPU给它。这是调度器的核心职责。
运行→就绪:有两种情况——一种是该进程的时间片用完了,被调度器强制切换出去;另一种是被更高优先级的进程抢占(在可抢占调度算法中)。
运行→阻塞:进程主动请求等待某种资源或事件。这是进程自身的主动行为,比如请求一个暂时不可用的锁、等待I/O完成等。
阻塞→就绪:进程等待的事件发生了。这是由外部事件驱动的,例如I/O完成中断服务程序会唤醒等待I/O完成的进程。
一道典型题目:一个进程在运行过程中,以下哪些事件会导致它从运行态变为阻塞态?
A. 时间片用完 B. 请求读磁盘文件 C. 被更高优先级进程抢占 D. 收到其他进程发送的消息
答案:B和D。A和C是运行→就绪(被动地被调度器切换)。B和D是进程主动等待事件(读磁盘需等待I/O完成、接收消息需等待消息到达)。
三、进程通信
进程之间相互独立运行,但很多时候它们需要协同工作,这就需要在进程之间传递信息。操作系统提供了三种基本的进程通信(IPC)机制。
3.1 共享内存
共享内存是最快的一种IPC方式。它的工作原理是:操作系统将同一块物理内存区域映射到多个进程的地址空间中,这些进程就可以直接读写这块内存来实现数据共享。由于数据不需要在进程间复制,共享内存的通信速度非常快。
但共享内存也带来了同步问题——多个进程同时读写共享区域可能导致数据不一致。因此,共享内存通常需要与其他同步机制(如信号量)配合使用。
3.2 消息传递
消息传递机制通过操作系统提供的send和receive系统调用来实现进程间通信。进程A将消息通过操作系统发送给进程B,操作系统负责消息的缓冲和路由。消息传递分为直接通信(直接指定接收进程)和间接通信(通过信箱/端口)两种方式。
消息传递的优势在于不需要共享内存,所以适合分布式系统中的进程通信。缺点是消息的发送和接收涉及系统调用,性能不如共享内存。
3.3 管道
管道是一个连接读写进程的字节流,本质上是一个内核缓冲区。数据从管道的一端写入,从另一端读出。管道是半双工的(Unix/Linux管道的原始设计),数据只能向一个方向流动。管道的缓冲区有固定的容量限制(在Linux中通常为65536字节)。
命名管道(FIFO)是管道的扩展,它允许两个无亲缘关系的进程通过一个在文件系统中命名的管道来通信。
四、进程与线程
4.1 线程的引入
进程有两个基本属性:资源拥有的基本单位和CPU调度的基本单位。但这两个属性产生了矛盾——进程的创建、切换和销毁都需要较大的开销(因为要处理资源的分配和回收)。为了减少这些开销,线程的概念被引入。
线程(Thread)被设计为CPU调度的基本单位,而进程仍作为资源拥有的基本单位。多个线程可以存在于同一个进程中,共享进程的资源(代码段、数据段、打开的文件等),但每个线程拥有自己独立的栈空间、寄存器和程序计数器。
4.2 进程与线程的区别
- 资源拥有:进程是资源拥有的基本单位,线程不拥有资源,共享所属进程的资源
- 调度单位:线程是CPU调度的基本单位
- 切换开销:进程切换需要切换地址空间,开销大;线程切换不涉及地址空间切换,开销小
- 通信方式:进程间通信需要IPC机制;线程间可以直接读写共享内存(但需同步)
- 独立性:进程独立性强,一个进程崩溃不影响其他进程;线程崩溃可能影响整个进程
共享资源:代码段、数据段、打开的文件、信号处理函数、工作目录
非共享资源:程序计数器、寄存器、栈空间、线程ID
4.3 用户级线程与内核级线程
用户级线程的管理完全在用户空间完成,内核不知道线程的存在。线程调度由用户空间的线程库(如POSIX Threads库的许多实现)负责,切换时不需要系统调用,速度非常快。但缺点是如果某一个线程执行了阻塞的系统调用,整个进程的所有线程都会阻塞。
内核级线程由操作系统内核直接管理。线程的创建、调度、销毁都由内核完成。内核知道每个线程的存在,可以利用多处理器来并行执行多个线程。当一个线程阻塞时,内核可以调度同一进程中的另一个线程。缺点是线程切换需要系统调用,开销较大。
一个典型例子:假设你打开一个文本编辑器,这个程序以一个进程运行。你在这个编辑器中打开了三个文件进行编辑——每个文件的编辑可能由一个线程负责。这三个线程共享同一份程序代码和已打开文件的列表,但每个线程可以独立地滚动浏览自己的文件内容。