深入理解操作系统中进程与线程的区别及切换机制(下)
前言
上一篇文章中我们了解了进程的执行方式,包括早期单核处理器上的顺序执行以及引入多任务概念实现的伪并行。我们还探讨了进程的状态模型。进程可以处于就绪、运行、阻塞和结束等不同的状态。
在本篇文章中,我将探讨研究进程的状态模型、控制结构和切换机制。希望通过这篇文章的分享,能够帮助更多的人理解和掌握进程管理的知识,为他们在计算机领域的学习和工作提供帮助。
进程的控制结构
也可以说是数据结构,毕竟操作系统也是一个进程,只要是应用程序就必然符合一条定律:程序=算法+数据结构,进程作为一种抽象概念,可将其视为一个容器,该容器聚集了相关资源,包括地址空间,线程,打开的文件,保护许可等。因此对于单个进程,可以基于一种数据结构来表示它,这种数据结构称之为进程控制块(PCB),这人家的专有名词;
每个进程都有一个唯一的PCB作为其标识,当进程被销毁时,PCB也会随之消失。如下:
PCB之间通过链表连接,形成各种队列,如就绪队列和阻塞队列。比如:
- 就绪队列:把所有就绪状态的PCB链在一起;
- 阻塞队列:把所有因等待某事件而处于等待状态的进程链在一起
另外,在单核CPU系统中,只有一个运行指针,因为在某个时刻,只能运行一个程序。
进程的切换
进程的切换是为了保证每个进程都能获得公平的CPU时间片来执行自己的程序,这也叫做上下文切换,上下文切换涉及以下内容:
- 存储进程的相关信息,如计数器、寄存器等,以便在切换回来时能正常加载。
- 更新进程的状态,如从运行态切换到就绪态等。
- 将当前进程放入就绪队列或阻塞队列中。
- 根据调度算法从就绪队列中选择一个进程加载并运行。
- 更新内存管理的数据结构(建立虚拟内存与物理内存的关系)。
- 新进程将自己的堆栈信息加载到CPU的计数器和寄存器中,占用时间片。
发生进程上下文切换有哪些场景?
时间片用完,强制进行上下文切换。
内存不足,将无用的进程交换出去挂起,待资源充足后再切换回来。
进程调用sleep函数进入睡眠状态,让出CPU,需要重新进行系统调度(对于线程也适用)。
有更高优先级的程序需要运行,当前进程需要让出CPU,确保高优先级进程能使用时间片。
发生硬件中断时,CPU立即处理相关中断服务程序,如键盘输入。即使是单核CPU,也能良好处理中断程序和进程之间的时间片占用。不必担心持续敲击键盘会导致系统崩溃,尤其现在大多数是多核处理器。
线程
在早期的操作系统中,以进程作为独立运行的基本单位,直到后来计算机科学家们提出了更小的能独立运行的基本单位,即线程。
程是进程中的一条执行流程,多个线程可以共享代码段、数据段、打开的文件等资源,但每个线程都有一套独立的寄存器和栈,确保线程的控制流是相对独立的。可以将线程视为CPU调度的基本单位。可以想象一个我们的Java多线程,代码公用、全局变量公用等,但是进程会控制好线程自己的独立栈信息等;
线程的上下文切换
线程与进程最大的区别在于:线程是调度的基本单位,而进程则是资源拥有的基本单位。
线程的上下文切换是指在切换线程时,需要保存和恢复线程的执行上下文。与进程相比,线程上下文切换的代价要小得多。
如果一个进程只有主线程,那么线程的切换流程与进程切换相同。
如果是进程内的某个线程进行切换,代价会更小。只需要保存线程的相关寄存器和计数器等信息,因为其他资源和虚拟内存是进程内共享的,无需切换。因此,线程的上下文切换开销较进程小很多。
总结
进程和线程是操作系统中的两个重要概念。进程是程序的一次执行过程,拥有自己的地址空间和资源,是资源分配的基本单位。进程之间通过上下文切换来共享CPU,保证公平分配。进程切换涉及到保存和加载进程的相关信息、状态变更、队列操作、调度算法等。
线程是进程中的独立执行流程,可以共享进程的资源,但有独立的寄存器和栈。线程的上下文切换相比进程较小,只需要保存线程的相关寄存器和计数器等信息。