进程和线程
进程和线程
在多道程序环境下,允许多个程序并发执行,为此操作系统引入了进程(Process)的概念,以便更好地描述和控制程序的并发执行,实 现操作系统的并发性。
进程
进程是 系统资源分配的基本单位。 进程拥有独立的系统资源,包括内存空间、文件描述符、CPU 时间片等,这些资源的分配由操作系统负责。
进程和程序
程序时静态的,进程是动态的,进程可以理解成运行的程序,两者的具体区别如下:
- 程序
- 程序是一组指令的静态集合,它存储在磁盘等持久性存储介质中。
- 程序是被动的,它本身并不执行任何操作。
- 进程
- 进程是程序在内存中执行的实例。
- 当一个程序被加载到内存中并开始执行时,操作系统会创建一个进程。
- 进程是动态的,它代表了程序的执行过程。
操作系统在创建进程时,会为进程分配必要的资源,例如内存空间、文件句柄等。 进程是操作系统进行资源分配和调度的基本单位。 一个程序可以多次运行,每次运行都会创建一个新的进程。
进程控制块
操作系统通过 进程控制块(PCB,Process Control Block) 对进程进行管理,进程控制块中包含一系列进程的元信息,这些元信息帮助操作系统对进程进行管理:
进程描述信息 | 进程控制和管理信息 | 资源分配清单 | 处理机相关信息 |
---|---|---|---|
进程标识符(PID) | 进程状态 | 代码段指针 | 通用寄存器值 |
用户标识符(UID) | 进程优先级 | 数据段指针 | 地址寄存器值 |
代码运行入口地址 | 堆栈段指针 | 控制寄存器值 | |
程序的外存地址 | 文件描述符 | 标志寄存器值 | |
等待时间 | |||
CPU 占用时间 |
上表给出了 PCB 的关键内容,其中进程描述信息用于唯一标识和管理进程,以及实现用户级别的权限控制;进程控制和管理信息用于管理进程的生命周期、调度和执行;资源控制清单记录进程使用的资源,以便进行有效的资源管理和分配;处理机相关信息保存与处理器相关的状态信息,以实现上下文切换。
进程控制块在操作系统进程管理的多个阶段都发挥重要作用:
- 进程创建和终止:在进程创建时,操作系统为它新建一个 PCB(进程控制块)。该结构在进程存在期间常驻内存,可以随时存取,并在进程结束时删除。
- 进程调度:当操作系统进行进程调度时,会根据 PCB 中存储的信息(如优先级、进程状态)来决定哪个进程应该获得 CPU 的控制权。
线程
线程是 系统调度的基本单位 。 多核处理器可以将系统中的不同线程调度到不同的 CPU 逻辑核心上,所以在同一个时刻,系统中的线程可能会在不同的逻辑核心上并行运行。
每个进程启动的时候,都包含一个线程(主线程),可以在主线程外创建更多的线程。
如上图所示,一个进程中的所有线程都共享虚拟地址空间和系统资源,但是每个线程都维护自己的堆栈、寄存器和一些额外信息。
进程的状态
进程状态是指在操作系统中,一个进程在执行过程中的不同阶段。它反映了进程当前正在做什么,以及是否可以被 CPU 执行。
状态种类
不同的操作系统对进程状态的划分可能不同,但常见的包若以下几种:
- 创建状态(New):当进程被创建但还未分配资源或执行时,它处于创建状态。
- 就绪状态(Ready):在就绪状态中,进程已准备好执行,但由于操作系统调度算法或其他原因,尚未获得 CPU 时间片。
- 运行状态(Running):在运行状态中,进程正在执行指令并占用 CPU。
- 阻塞状态(Blocked):当进程在等待某些事件发生时,如等待 I/O 操作完成或等待其他资源时,它会进入阻塞状态。在阻塞状态下,进程暂停执行,直到等待的事件发生。
- 终止状态(Terminated):当进程执行完毕或被操作系统终止时,它进入终止状态。
状态转化
- 就绪态 → 运行态:
- 调度:当操作系统的调度器选择一个就绪状态的进程分配给处理器时,该进程就会从就绪状态转换到运行状态。
- 运行态 → 就绪态:
- 时间片用完:如果系统使用时间共享调度,当进程的时间片用完,它会被中断并放回就绪队列。
- 优先级更高的进程就绪:在优先级调度算法中,如果一个优先级更高的进程变为就绪状态,当前运行的进程可能会被挂起。
- 自愿放弃 CPU: 进程可能主动放弃 CPU,比如它发出了一个系统调用请求其他资源。
- 运行态 → 阻塞态:
- I/O 请求:进程进行 I/O 操作,由于 I/O 设备比 CPU 慢得多,进程会被挂起直到 I/O 完成。
- 等待资源:进程等待不可用的资源,如信号量、互斥锁等。
- 等待事件:如等待其他进程的信号、消息或者某个条件的发生。
- 阻塞态 → 就绪态:
- I/O 完成:当 I/O 操作完成,相应的进程会被移至就绪队列。
- 资源获得:进程所等待的资源变得可用,如获得了互斥锁。
- 事件发生:进程所等待的事件发生了,如接收到了另一个进程发出的信号。
进程内存空间
- 用户空间(User Space):包含进程执行的用户程序代码和数据。在用户空间中,进程可以执行各种任务,如运行应用程序、访问文件系统等。用户空间对于应用程序是可见的,但对于操作系统中的核心功能是不可见的。
- 代码区(Text Segment):也称为"可执行代码区",存储了进程的可执行代码,包括程序的指令和只读数据。这个区域通常是只读的,因为程序的指令在运行时不应该被修改。
- 数据区(Data Segment), 数据区分为两个子区域:
- 初始化数据区(Initialized Data Segment):存储全局和静态变量以及初始化的数据。这些变量在程序运行前就已经分配了内存并初始化。
- 未初始化数据区(Uninitialized Data Segment):也称为"BSS"(Block Started by Symbol)段,存储全局和静态变量,但这些变量没有显式的初始化值。操作系统会在程序启动时自动将这个区域初始化为零。
- 堆区(Heap):堆区是动态分配内存的地方,用于存储程序运行时需要的变量和数据结构。在堆中分配的内存需要手动释放,以避免内存泄漏。
- 栈区(Stack):栈区用于存储函数调用和局部变量。每个函数调用都会在栈上创建一个栈帧,栈帧包含了函数的参数、局部变量以及函数返回地址。栈是一种后进先出(LIFO)的数据结构,它的大小通常有限,由操作系统或编程语言定义。
- 内存映射区域(Memory Mapped Region):这是一些操作系统或运行时库的扩展,用于存储动态链接库(DLL)和共享库的信息以及其他系统数据结构。
- 内核空间(Kernel Space):内核空间包含了操作系统的核心代码和数据结构,如页表、调度程序和系统调用接口等。内核空间具有更高的特权级别,可以执行特权指令并且访问系统的各种资源,内核空间对于用户程序是不可见的。
函数调用时内存结构
EBP (base pointer) 指向函数栈(栈帧)的底部(高地址),函数执行过程中在栈帧中分配局部变量,栈帧由高地址向低地址增长,ESP(stack pointer)一直指向栈顶。
每个函数的栈帧中包含如下内容:
- 上一个函数的 EBP
- 该函数的局部变量
- 如果函数内有
call
指令的话,还需要保存额外信息:- 下一个函数的参数:依次存储从第 n 个到第 1 个,从高地址到低地址
- 返回地址:当前 PC 指向的位置,即
call
指令的下一条指令的地址
在函数的调用过程中,调用函数叫做 caller,被调用函数叫做 callee。当我们从 caller 中调用 callee 时,callee 的 EBP 指向的物理地址的上下存储单元分别包含 caller 的返回地址以及 caller 所在栈帧的 EBP,当我们在 callee 中执行 ret
指令时,计算机可以跳转到 caller 中 call
指令的下一条并开始执行,同时 caller 的栈帧也会被恢复。
进程间通信
- 共享内存(Shared Memory):共享内存允许多个进程访问相同的物理内存区域,这样它们可以直接共享数据,而不需要复制数据。这是一种高效的通信方式,但需要谨慎管理共享数据以避免竞态条件。
- 管道(Pipes):管道是一种单向通信方式,通常用于父子进程之间或兄弟进程之间的通信。有命名管道和匿名管道两种,匿名管道只能在有亲缘关系的进程之间使用。
- 消息队列(Message Queues):消息队列是一种进程间通信的机制,允许进程通过消息来进行异步通信。消息队列通常由操作系统维护,可以支持多个读者和写者。
- 信号(Signals):信号是一种轻量级的通信机制,用于通知进程发生了某些事件。进程可以发送信号给其他进程,比如终止信号、挂起信号等。
- 套接字(Sockets):套接字是一种通用的进程间通信方式,通常用于不同计算机之间的网络通信。它支持多种协议,如 TCP 和 UDP,可以实现客户端-服务器通信。
- 信号量(Semaphores):信号量是一种计数器,用于控制多个进程对共享资源的访问。信号量可以用于解决竞争条件和进程同步的问题。
用户级线程和内核级线程
- 用户级线程(ULT,User Level Thread)
- 用户级线程是由应用程序通过线程库来实现的,操作系统内核并不直接感知到这些线程的存在。
- 线程的创建、调度和管理都由应用程序在用户空间完成。
- 内核级线程(KLT,Kernel Level Thread)
- 内核级线程是由操作系统内核直接支持的线程。
- 线程的创建、调度和管理都由操作系统内核完成。
线程模型
操作系统中的线程实现方式称为线程模型,不同的线程模型在用户级线程(ULT)和内核级线程(KLT)的设计上各有不同。
系统中的线程模型可以分为如下三种:
- 纯用户态:所有的线程操作都在用户空间中进行,内核对线程的存在一无所知。
- 纯系统态:线程由操作系统内核直接支持和管理,内核负责线程的创建、调度和管理。
- 混合方案:应用程序可以在用户空间管理多个用户级线程,这些线程映射到较少数目的内核级线程。
混合方案中的映射关系
如果使用混合方案的线程模型的话,用户级线程和内核级线程的映射方式也可以分为如下几种:
- 一对一
- 多对一:
- 多对多: