操作系统在进行线程切换时需要进行哪些动作?
-
保存当前线程的上下文
保存寄存器状态、保存栈信息。
-
调度器选择下一个线程
调度算法决策:根据策略(如轮转、优先级、公平共享)从就绪队列选择目标线程。
处理优先级:实时系统中可能涉及优先级继承或调整。
-
恢复目标线程的上下文
加载寄存器状态:从目标线程的TCB中恢复PC、SP、通用寄存器等。
恢复内核堆栈:若目标线程在内核态被挂起,需切换内核堆栈指针。
-
更新线程控制块(TCB)、调度队列
切换堆栈指针:用户态堆栈指针切换至目标线程的堆栈。
设置新线程状态:将目标线程状态设为
运行中(Running)
。模式切换(若需要):从内核态返回到用户态,恢复目标线程执行。
什么是用户态和内核态?
用户态是普通应用程序的运行级别,具有较低的权限。
-
在用户态下,程序只能访问有限的系统资源。
-
用户态程序只能执行普通指令,不能执行特权指令。
-
用户态程序通常只能访问自己的虚拟地址空间。
内核态是操作系统中具有最高权限的运行级别,操作系统的核心代码和关键任务在内核态下运行。
-
在内核态下,程序可以访问系统的全部资源。
-
内核态代码可以直接访问和修改系统的核心数据结构,例如PCB等等。
-
操作系统的核心功能都在内核态下运行。
用户态什么时候会切换到内核态?
-
用户态程序通过系统调用请求操作系统服务。此时,操作系统会切换到内核态,执行相应的内核代码。
-
当中断/异常发生时,操作系统会从用户态切换到内核态。
进程之间的通信方式有哪些?
匿名管道
-
匿名管道是特殊文件只存在于内存,没有存在于文件系统中。
-
通信的数据是无格式的流并且大小受限。
-
通信的方式是单向的,数据只能在一个方向上流动,如果要双向通信,需要创建两个管道。
-
匿名管道是只能用于存在父子进程间通信。
命名管道
-
突破了匿名管道只能在亲缘关系进程间的通信限制,因为使用命名管道的前提,需要在文件系统创建一个类型为 p 的设备文件,那么毫无关系的进程就可以通过这个设备文件进行通信。
-
另外,不管是匿名管道还是命名管道,进程写入的数据都是缓存在内核中,另一个进程读取数据时候自然也是从内核中获取,同时通信数据都遵循先进先出原则,不支持 lseek 之类的文件定位操作。
消息队列
-
克服了管道通信的数据是无格式的字节流的问题,消息队列实际上是保存在内核的「消息链表」,消息队列的消息体是可以用户自定义的数据类型,发送数据时,会被分成一个一个独立的消息体,当然接收数据时,也要与发送方发送的消息体的数据类型保持一致,这样才能保证读取的数据是正确的。消息队列通信的速度不是最及时的,毕竟每次数据的写入和读取都需要经过用户态与内核态之间的拷贝过程。
共享内存
-
可以解决消息队列通信中用户态与内核态之间数据拷贝过程带来的开销,它直接分配一个共享空间,每个进程都可以直接访问,就像访问进程自己的空间一样快捷方便,不需要陷入内核态或者系统调用,大大提高了通信的速度,享有最快的进程间通信方式之名。但是便捷高效的共享内存通信,带来新的问题,多进程竞争同个共享资源会造成数据的错乱。
信号量
-
可以保护共享资源,以确保任何时刻只能有一个进程访问共享资源,这种方式就是互斥访问。
-
信号量不仅可以实现访问的互斥性,还可以实现进程间的同步,信号量其实是一个计数器,表示的是资源个数,其值可以通过两个原子操作来控制,分别是 P 操作和 V 操作。
Socket 通信
-
如果要与不同主机的进程间通信,那么就需要 Socket 通信了。Socket 实际上不仅用于不同的主机进程间通信,还可以用于本地主机进程间通信,可根据创建 Socket 的类型不同,分为三种常见的通信方式,一个是基于 TCP 协议的通信方式,一个是基于 UDP 协议的通信方式,一个是本地进程间通信方式。
进程有哪五种状态?
进程的调度算法?
先来先服务 FCFS
最简单的一个调度算法,就是非抢占式的先来先服务(First Come First Severd, FCFS)算法了。
顾名思义,先来后到,每次从就绪队列选择最先进入队列的进程,然后一直运行,直到进程退出或被阻塞,才会继续从队列中选择第一个进程接着运行。
这似乎很公平,但是当一个长作业先运行了,那么后面的短作业等待的时间就会很长,不利于短作业。 FCFS 对长作业有利,适用于 CPU 繁忙型作业的系统,而不适用于 I/O 繁忙型作业的系统。
最短作业优先 SJF
最短作业优先(Shortest Job First, SJF)调度算法同样也是顾名思义,它会优先选择运行时间最短的进程来运行,这有助于提高系统的吞吐量。
这显然对长作业不利,很容易造成一种极端现象。
比如,一个长作业在就绪队列等待运行,而这个就绪队列有非常多的短作业,那么就会使得长作业不断的往后推,周转时间变长,致使长作业长期不会被运行。
高响应比优先 HRRN
前面的「先来先服务调度算法」和「最短作业优先调度算法」都没有很好的权衡短作业和长作业。
那么,高响应比优先 (Highest Response Ratio Next, HRRN)调度算法主要是权衡了短作业和长作业。
每次进行进程调度时,先计算「响应比优先级」,然后把「响应比优先级」最高的进程投入运行,「响应比优先级」的计算公式:
从上面的公式,可以发现:
-
如果两个进程的「等待时间」相同时,「要求的服务时间」越短,「响应比」就越高,这样短作业的进程容易被选中运行;
-
如果两个进程「要求的服务时间」相同时,「等待时间」越长,「响应比」就越高,这就兼顾到了长作业进程,因为进程的响应比可以随时间等待的增加而提高,当其等待时间足够长时,其响应比便可以升到很高,从而获得运行的机会;
时间片轮转 RR
最古老、最简单、最公平且使用最广的算法就是时间片轮转(Round Robin, RR)调度算法。
每个进程被分配一个时间段,称为时间片(Quantum),即允许该进程在该时间段中运行。
-
如果时间片用完,进程还在运行,那么将会把此进程从 CPU 释放出来,并把 CPU 分配另外一个进程;
-
如果该进程在时间片结束前阻塞或结束,则 CPU 立即进行切换;
另外,时间片的长度就是一个很关键的点:
-
如果时间片设得太短会导致过多的进程上下文切换,降低了 CPU 效率;
-
如果设得太长又可能引起对短作业进程的响应时间变长。将
通常时间片设为 20ms~50ms
通常是一个比较合理的折中值。
优先级调度 HPF
前面的「时间片轮转算法」做了个假设,即让所有的进程同等重要,也不偏袒谁,大家的运行时间都一样。
但是,对于多用户计算机系统就有不同的看法了,它们希望调度是有优先级的,即希望调度程序能从就绪队列中选择最高优先级的进程进行运行,这称为最高优先级(Highest Priority First,HPF)调度算法。 进程的优先级可以分为,静态优先级或动态优先级:
-
静态优先级:创建进程时候,就已经确定了优先级了,然后整个运行时间优先级都不会变化;
-
动态优先级:根据进程的动态变化调整优先级,比如如果进程运行时间增加,则降低其优先级,如果进程等待时间(就绪队列的等待时间)增加,则升高其优先级,也就是随着时间的推移增加等待进程的优先级。
该算法也有两种处理优先级高的方法,非抢占式和抢占式:
-
非抢占式:当就绪队列中出现优先级高的进程,运行完当前进程,再选择优先级高的进程。
-
抢占式:当就绪队列中出现优先级高的进程,当前进程挂起,调度优先级高的进程运行。
但是依然有缺点,可能会导致低优先级的进程永远不会运行。
多级反馈队列调度算法
多级反馈队列(Multilevel Feedback Queue)调度算法是「时间片轮转算法」和「最高优先级算法」的综合和发展。
顾名思义:
-
「多级」表示有多个队列,每个队列优先级从高到低,同时优先级越高时间片越短。
-
「反馈」表示如果有新的进程加入优先级高的队列时,立刻停止当前正在运行的进程,转而去运行优先级高的队列;
来看看,它是如何工作的:
-
设置了多个队列,赋予每个队列不同的优先级,每个队列优先级从高到低,同时优先级越高时间片越短;
-
新的进程会被放入到第一级队列的末尾,按先来先服务的原则排队等待被调度,如果在第一级队列规定的时间片没运行完成,则将其转入到第二级队列的末尾,以此类推,直至完成;
-
当较高优先级的队列为空,才调度较低优先级的队列中的进程运行。如果进程运行时,有新进程进入较高优先级的队列,则停止当前运行的进程并将其移入到原队列末尾,接着让较高优先级的进程运行;
可以发现,对于短作业可能可以在第一级队列很快被处理完。
对于长作业,如果在第一级队列处理不完,可以移入下次队列等待被执行,虽然等待的时间变长了,但是运行时间也会更长了,所以该算法很好的兼顾了长短作业,同时有较好的响应时间。
调度算法 | 公平性 | 系统吞吐量 | 响应时间 | 复杂度 | 适用场景 |
FCFS | 低 | 低 | 高 | 低 | 简单系统 |
SJF | 低 | 高 | 高 | 中 | 批处理系统(需解决饥饿) |
RR | 高 | 中 | 低 | 中 | 交互式系统 |
优先级调度 | 中 | 中 | 中 | 中 | 实时系统 |
多级反馈队列 | 高 | 高 | 低 | 高 | 复杂系统 |
HRRN | 高 | 高 | 低 | 中 | 平衡短作业和长作业的系统 |
公平共享调度 | 高 | 中 | 中 | 高 | 多用户系统 |
实时调度算法 | 高 | 高 | 低 | 高 | 实时系统 |
线程和进程有什么区别?
进程 | 线程 | |
定义 | 操作系统分配资源的基本单位 | 进程内的一个执行单元 |
资源分配 | 独立地址空间、文件描述符表等 | 共享所属进程的资源 |
创建和销毁开销 | 大 | 小 |
通信方式 | 需要IPC机制 | 直接通过共享内存 |
调度 | 操作系统调度的基本单位 | CPU调度的基本单位 |
内存使用 | 独立内存空间 | 共享内存空间 |
应用场景 | 需要独立运行、资源隔离 | 需要并发执行、提高效率 |
为什么要有虚拟内存?
虚拟内存是一种计算机存储管理技术,它可以在硬盘上模拟出一个比物理内存更大的地址空间,使得操作系统和应用程序可以使用比实际可用内存更大的内存。以下是虚拟内存的主要作用和原因:
-
扩展可用内存
背景:计算机的物理内存(RAM)是有限的,而现代应用程序和操作系统可能需要比实际物理内存更多的内存空间来运行。
作用:虚拟内存通过将一部分硬盘空间(通常是磁盘分区或文件)作为扩展内存使用,从而扩展了可用的内存空间。当物理内存不足时,操作系统会将暂时不用的数据从物理内存移动到虚拟内存(磁盘空间)中,这个过程称为“交换”(Swapping)或“分页”(Paging)。
优势:允许运行比实际物理内存更大的程序,提高了系统的灵活性和可用性。
-
隔离进程地址空间
背景:在多任务操作系统中,多个进程同时运行,每个进程都有自己的代码、数据和堆栈。
作用:虚拟内存为每个进程提供了一个独立的虚拟地址空间。每个进程都认为自己独占了整个内存空间,而实际上这些地址空间是由操作系统通过虚拟内存管理单元(MMU)映射到物理内存中的。
优势:
保护:防止一个进程访问或破坏另一个进程的内存空间,提高了系统的稳定性和安全性。
简化编程:程序员可以编写代码时假设整个地址空间都是可用的,而不需要担心内存冲突。
-
提高内存利用率
背景:物理内存的分配和回收是一个复杂的过程,容易导致内存碎片化,降低内存的利用率。
作用:虚拟内存通过分页或分段机制,将内存划分为固定大小的页面(通常是4KB)或段。操作系统可以动态地分配和回收这些页面,从而更高效地利用物理内存。
优势:减少了内存碎片化,提高了内存的利用率,使得更多的程序可以同时运行。
I/O是什么?
I/O(Input/Output,输入/输出)是指计算机系统中与外部设备进行数据交换的过程。
I/O系统主要由以下几个部分组成:输入设备、输出设备、存储设备。
I/O操作可以根据数据传输的方向和方式分为:
-
输入:从外部设备向计算机系统传输数据。
-
输出:从计算机系统向外部设备传输数据。
IO 处理机制与 IO 模型的关系与区别
1. 核心概念区分
I/O 处理机制(如 Polling、Interrupt、DMA) 属于底层硬件与操作系统协作的技术,解决的是数据如何从设备传输到内存的问题,关注的是物理层面的效率(CPU 利用率、数据传输速度等)。
例如:DMA 控制器如何绕过 CPU 直接搬运磁盘数据到内存。
I/O 模型(如阻塞、非阻塞、多路复用、异步) 属于应用程序与操作系统交互的编程范式,解决的是如何组织和管理 I/O 操作的问题,关注的是代码逻辑的简洁性和性能优化。
例如:使用
select
函数同时监控多个 socket 的读写状态。
I/O处理机制三种
I/O操作可以通过不同的模式进行,主要取决于数据传输的控制方式和性能要求:
-
程序控制I/O(Polling):
-
本质:CPU主动轮询设备状态(“忙等待”模式)。
-
原理:CPU通过轮询(Polling)的方式不断检查I/O设备的状态,以确定设备是否准备好进行数据传输。
-
优点:实现简单,不需要额外的硬件支持。
-
缺点:CPU效率低,因为CPU需要不断检查设备状态,可能导致CPU资源浪费。
-
-
中断驱动I/O(Interrupt-Driven I/O):
-
本质:设备主动通知CPU(事件驱动模式)。
-
原理:I/O设备在准备好数据后,通过中断信号通知CPU。CPU在接收到中断信号后,执行中断处理程序来完成数据传输。
-
优点:CPU不需要频繁检查设备状态,可以更高效地执行其他任务。
-
缺点:中断处理需要一定的开销,频繁的中断可能会降低系统性能。
-
-
直接内存访问(DMA,Direct Memory Access):
-
本质:绕过CPU,设备直接与内存交互。
-
原理:DMA允许I/O设备直接与内存进行数据传输,而不需要CPU的直接参与。DMA控制器负责管理数据传输过程。
-
优点:大大减少了CPU的负担,提高了数据传输效率。
-
缺点:需要额外的硬件支持(DMA控制器),并且DMA操作需要与CPU协调,以避免内存访问冲突。
-
I/O模型有哪些?(编程层面)
-
阻塞式I/O(Blocking I/O)
-
原理
-
线程发起
read()
调用后阻塞等待数据就绪,直到数据到达后才返回。
-
-
特点:
-
效率较低,因为线程在等待I/O操作完成时无法执行其他任务。
-
-
非阻塞式I/O(Non-Blocking I/O)
-
原理:
-
线程发起
read()
后立即返回,需轮询检查状态(返回EAGAIN
时重试)。
-
-
特点:
-
避免了线程阻塞,但轮询操作会占用大量CPU资源,效率较低。
-
-
I/O多路复用(I/O Multiplexing)
-
原理:
-
“I/O多路复用”允许一个进程同时监视多个输入/输出源,当任何一个源准备就绪时,该进程都能够及时地处理它。
-
当任何一个I/O事件准备好时,系统调用返回,应用程序可以处理相应的I/O操作。
-
-
特点:
-
提高了线程的利用率,减少了线程上下文切换的开销。
-
同步和异步的区别?
-
同步:
-
原理:在同步模式下,调用方在发起一个操作后,必须等待该操作完成,才能继续执行后续代码。
-
缺点:效率较低,特别是在涉及I/O操作(如网络请求、文件读写)时,调用方会浪费大量时间等待操作完成,导致资源利用率低。
-
适用场景:
-
对实时性要求不高,任务执行时间较短的场景。
-
任务之间依赖性强,必须按顺序执行的场景。
-
简单的脚本或工具,对性能要求不高的场景。
-
-
-
异步:
-
原理:在异步模式下,任务可以并发执行。调用方在发起一个操作后,不需要等待该操作完成,可以继续执行其他任务。当操作完成时,系统会通知调用方。
-
缺点:实现复杂,代码逻辑可能较难理解和维护,需要处理回调、事件通知等机制。
-
适用场景:
-
高并发场景,如Web服务器、聊天服务器等。
-
涉及大量I/O操作的场景,如网络请求、文件读写等。
-
需要提高系统吞吐量和资源利用率的场景。
-
-
阻塞和非阻塞的区别?
阻塞(Blocking)和非阻塞(Non-Blocking)是描述I/O操作(输入/输出操作)行为的两种模式。
-
阻塞:
-
在阻塞模式下,当调用方发起一个I/O操作(如读取文件、从网络接收数据)时,调用方会被挂起,直到I/O操作完成。调用方无法执行其他任务,必须等待I/O操作完成才能继续执行。
-
-
非阻塞:
-
在非阻塞模式下,当调用方发起一个I/O操作时,调用方不会被挂起。如果I/O操作尚未完成,调用方会立即返回一个错误码(如
EAGAIN
或EWOULDBLOCK
),表示操作尚未准备好。调用方可以继续执行其他任务,稍后再尝试I/O操作。
-
什么是物理地址,什么是逻辑地址?
-
逻辑地址(Logical Address)
-
由程序生成的地址,也称为虚拟地址(Virtual Address)。在编写代码时,程序员使用的指针或变量地址均属于逻辑地址。这些地址并非真实的物理内存位置,而是由操作系统和硬件共同管理的抽象层。
-
逻辑地址是程序在运行时使用的地址。它是程序中的变量、函数等在虚拟地址空间中的地址。逻辑地址通过内存管理单元(MMU)映射到物理地址。
-
-
物理地址(Physical Address)
-
实际存在于计算机硬件内存(RAM)或内存映射设备中的地址。CPU通过内存总线直接访问这些地址,以访问数据。
-
-
逻辑地址与物理地址之间的映射
-
MMU负责将逻辑地址转换为物理地址。
-
页表记录了逻辑页面与物理页面帧之间的映射关系。
-
段表记录了逻辑段与物理内存区域之间的映射关系。
-
-
转换过程
在支持虚拟内存的系统中,逻辑地址需通过以下步骤转换为物理地址:
-
分段机制(可选):
-
逻辑地址由段选择符(Segment Selector)和段内偏移(Offset)组成。
-
段选择符通过段描述符表(如GDT/LDT)找到段基址(Base Address)。
-
线性地址(Linear Address)= 段基址 + 偏移量。
-
注:现代操作系统(如Linux)通常使用平坦内存模型,段基址为0,逻辑地址直接等于线性地址。
-
-
分页机制:
-
线性地址被拆分为页号(Page Number)和页内偏移(Page Offset)。
-
页表(Page Table)将页号映射为物理页框号(Frame Number)。
-
物理地址 = 物理页框号 × 页大小(如4KB) + 页内偏移。
-
示例: 假设逻辑地址为
0x0804A000
,分页后:
页号:
0x0804A
,页内偏移:0x000
页表映射页号
0x0804A
到物理页框0x12345
物理地址:
0x12345 × 4096 + 0x000 = 0x12345000