操作系统基础面试题
整理最常见的操作系统基础面试八股文
一、操作系统基础概念
什么是操作系统,它的功能⭐⭐⭐
概念
操作系统就是计算机中最基本的系统软件,它负责管理计算机的硬件和软件资源,并合理的组织计算机工作流程。
主要功能
(1)处理机管理:进程的创建、终止、同步、调度等。
(2)内存管理:内存地址的分配和回收、地址映射、内存保护、内存扩充(交换,覆盖,虚拟存储技术)。
(3)文件管理:文件的存储空间管理、对文件本身的管理,目录管理,文件共享、文件保护。
(4)设备管理:设备的分配、缓存的管理、虚拟设备(Spooling)。
(5)提供用户接口:GUI,联机/脱机接口,系统调用。
四大特性
(1)并发:操作系统能让多个程序“同时运行”在一个处理器上(通过切换)。
(2)共享:多个程序可以共同使用计算机系统中的资源,如 CPU、内存、磁盘、I/O 设备等。
(3)虚拟:将物理资源抽象成多个或者更大的逻辑资源,为每个用户/程序提供“独占”资源的假象。
- 虚拟内存:使程序可以使用比物理内存更大的地址空间。
- 虚拟 CPU:每个进程都感觉自己独占一个处理器。
(4)异步:程序的执行是不可预知的,系统通过调度机制决定哪个进程/线程何时运行。
二、进程与线程
什么是进程,什么是线程,它们的区别⭐⭐⭐
(1)进程就是程序的一次执行实体,是操作系统分配和管理资源的基本单位。也可以说:进程就是并发执行的程序在执行过程中分配和管理资源的基本单位。
(2)线程就是一个轻量级的进程,当引入线程后,操作系统调度的单位就不再是进程,而是线程了。
(3)主要区别:
| 维度 | 进程 | 线程 |
|---|---|---|
| 基本单位 | 资源分配的基本单位 | 调度的基本单位 |
| 地址空间 | 进程间地址空间独立 | 共享进程地址空间 |
| 上下文切换 | 开销大(携带信息多) | 开销小 |
| 资源共享 | 不共享 | 共享进程资源 |
(4)引入线程的优势:
- 提高了操作系统的并发性
- 线程创建和切换开销小于进程
- 资源利用率更高:多线程可以充分利用多核 CPU,提升 CPU 使用率。
线程共享哪些资源,独立哪些⭐⭐
线程共享进程的:堆、全局变量、静态变量、文件描述符。
线程独立拥有:栈、寄存器、程序计数器。
进程和程序的区别⭐⭐
- 进程是动态的,而程序则是静态的。程序是指令的有序集合,无任何执行含义,而进程则强调执行过程。
- 进程具有并发性,而程序则没有。
进程同步和互斥⭐⭐⭐
基本概念
- 同步:并发执行的程序共同完成一些工作,是一个进程等待其他进程完成某些功能的等待关系。
- 互斥:是若干进程对需要互斥访问资源的竞争关系,也就是一个进程等待另一个占有互斥资源的进程释放该资源的等待关系。
- 临界区:访问临界资源的代码段叫做临界区。
- 临界资源:同一时刻只能被一个进程访问的资源叫做临界资源。
- 设计原则:空闲让进,忙则等待,有限等待,让权等待。
信号量机制
整型信号量:整型信号量就是一个整型变量,通过 P、V 操作实现资源申请与释放。
- 当进程执行
P操作且信号量值为 0 时,会进入 自旋等待(忙等),违背了"让权等待"原则,导致 CPU 浪费。
记录型信号量(重点):记录型信号量在整型信号量基础上增加了一个阻塞队列。
- 当信号量小于 0 时,其绝对值表示阻塞队列中的进程数量。
- 进程在执行
P操作而无法获取资源时,不会忙等,而是进入阻塞队列,等待被V操作唤醒,从而真正实现 让权等待。
经典同步问题
- 生产者–消费者问题:通过互斥信号量和"空/满"信号量,保证缓冲区访问安全,协调生产与消费速度。
- 读者–写者问题:用信号量控制多个读进程可并发,但写进程互斥执行,保证数据一致性。
- 哲学家进餐问题:通过信号量避免死锁与饥饿,保证多个哲学家公平获取左右筷子。
- 吸烟者问题:通过信号量协调桌上材料与吸烟者需求,实现同步。
什么是管程⭐⭐
管程是一种 更高级的进程同步机制,相比信号量更直观和安全。管程把共享资源和同步机制封装起来,用条件变量协调等待与唤醒,更安全直观,是对信号量的进一步抽象。
进程间通信方式有哪些⭐⭐⭐
| 通信方式 | 特点 |
|---|---|
| 管道 | 无名管道只能在父子进程间,半双工;有名管道可以在无亲缘进程间通信,读写互斥 |
| 消息队列 | 内核中的消息链表,支持格式化消息,克服了管道无格式、容量受限的问题 |
| 共享内存 | 效率最高,多进程直接访问同一块内存,但需要配合 信号量 来做同步 |
| 信号量 | 一种计数器机制,用来实现进程或线程之间的互斥与同步 |
| 信号 | 主要用于通知进程某个事件发生,比如中断或非法访问 |
| 套接字 | 不仅支持同一主机的进程通信,还支持跨网络的不同主机通信,分布式环境下最常用 |
进程调度⭐⭐
调度分类
- 作业调度(高级调度):按照某种算法,从外存上的后备队列中选择一个或多个作业,分配给它们 I/O、内存等除 CPU 以外的资源,从而为它们建立起对应的进程。
- 中级调度:介于高与低之间,目的是提高内存以及 CPU 的利用率(内存交换)。
- 进程调度(低级调度):按照某种算法,从就绪队列中选择一个进程,分配给它处理机资源。
衡量指标
CPU 利用率、周转时间、平均周转时间、带权周转时间、平均带权周转时间。
常见调度算法⭐⭐⭐
| 算法 | 思想 | 特点 |
|---|---|---|
| 先来先服务(FCFS) | 按照到达顺序调度 | 简单公平,但对长作业友好、短作业不利 |
| 短作业优先(SJF) | 优先调度执行时间最短的进程 | 平均等待时间最优,但需要估计运行时间,对长作业不公平 |
| 最高响应比优先(HRRN | 综合考虑等待时间和服务时间 | 响应比 = (等待时间 + 服务时间) / 服务时间,避免了长作业饥饿 |
| 时间片轮转(RR) | 每个进程按时间片轮流执行 | 公平性好,适合分时系统,但频繁切换有开销 |
| 优先级调度 | 按优先级高低调度 | 可能导致低优先级进程饥饿,需要结合动态优先级 |
| 多级反馈队列(MFQ) | 进程按优先级分队列,时间片逐级增加 | 综合性最强,既兼顾短作业响应,又防止长作业饥饿,现代操作系统常用 |
死锁⭐⭐⭐
定义
死锁是指:一组进程中的每个进程都在等待该组进程中其他进程所拥有的资源,使得所有的进程都无法向下推进。(专业定义:形成的资源分配图无法化简完全,则存在死锁)。
四个必要条件
- 互斥:资源只能被一个进程占有
- 不可剥夺:资源不能被强行抢占,只能由占有者主动释放
- 请求和保持:进程占有已有资源不释放,同时请求新的资源
- 循环等待:存在环路等待,每个进程都在等待下一个进程占有的资源
解决死锁的四种方式
| 方式 | 思路 |
|---|---|
| 死锁预防 | 破坏死锁产生的四个条件之一(互斥一般无法破坏) |
| 死锁避免 | 安全性检查,分配前判断是否会进入不安全状态 |
| 死锁检测与解除 | 允许死锁发生,但能检测出来,然后解除 |
| 忽略 | 鸵鸟算法,大部分系统(包括Linux)用这个 |
死锁预防具体做法:
- 破坏不可剥夺条件:如果所请求的资源无法满足,那么之前占有的也会释放。
- 破坏请求和保持条件:必须一次性将所需要的资源全部分配给它,如果无法全部分配就一个也不给。
- 破坏循环等待条件:给资源编号,顺序资源分配法。申请了编号为 Ri 的资源后,以后只能申请编号大于 Ri 的资源。
死锁避免核心:银行家算法——假设分配给进程,然后检查系统是否存在安全序列,如果存在则安全,可以分配;不存在则拒绝分配。
不安全状态 ≠ 死锁,但不安全状态可能导致死锁;安全状态一定不会死锁。
银行家算法流程⭐⭐
- 第一步:首先判断请求是否合法:
请求量 ≤ 需求量且请求量 ≤ 系统剩余可用资源,否则拒绝。 - 第二步:假设分配给进程,更新系统资源状态(可用 -= 请求,分配 += 请求,剩余 -= 请求)。
- 第三步:做安全性检查,找是否存在安全序列。如果存在,接受分配;不存在,回滚状态,拒绝分配。
安全序列:按照这个顺序分配资源,可以满足系统所有进程的最大需求。
哲学家进餐问题解法⭐⭐
核心是保证至少有一位哲学家能拿到两只筷子就餐:
- 最多允许 n-1 个哲学家同时拿左边筷子:至少保证有一位哲学家能就餐,吃完释放筷子。
- 资源分级法:奇数号哲学家先拿左手筷子,偶数号先拿右手筷子。最终总有一个哲学家能拿到两只筷子。
- 抢不到就放下:当一位哲学家拿起一只筷子,另一只得不到,则放下刚刚拿起的筷子,让给别人。
- 服务生算法:需要得到服务生允许才能去拿筷子,服务生只允许一名哲学家去拿,吃完再允许下一个。
说⼀说操作系统中缓冲区溢出怎么处理?⭐⭐
什么是缓冲区溢出
缓冲区溢出(Buffer Overflow)是指程序向缓冲区写入数据时超过了其分配的空间,从而覆盖了相邻的内存数据,可能导致程序崩溃、数据破坏,甚至被恶意利用执行任意代码。
操作系统防御机制
- ASLR(地址空间随机化):程序每次运行时,随机化堆、栈、库等在内存中的位置,增加攻击者猜测难度。
- 栈保护(Canary):在栈帧中插入一个随机数(金丝雀值),返回前检查是否被修改,检测到溢出就终止程序。
- 不可执行栈(NX/DEP):标记栈段为不可执行,就算溢出注入了 shellcode 也无法运行。
开发层面则要使用安全的库函数、检查输入长度、启用编译器安全选项,从源头预防。
三、内存管理
内存管理的作用⭐⭐
- 内存空间的分配与回收
- 地址映射(逻辑地址 → 物理地址)
- 内存空间的扩充(虚拟存储技术)
- 内存共享和存储保护
程序执行前做了哪些事情⭐⭐
- 编译(Compile):把源代码翻译成目标文件(机器指令),检查语法和语义错误。
- 链接(Link):将多个目标文件和库文件组合在一起,解决符号引用问题,生成可执行文件。
- 装入(Load):把可执行文件装入内存,分配内存空间,准备执行。
内存分配方式⭐⭐
连续分配方式
- 单一连续分配
- 固定分区分配
- 动态分区分配
非连续分配方式
- 页式存储管理
- 段式存储管理
- 段页式存储管理
虚拟存储技术⭐⭐
局部性原理
虚拟存储基于局部性原理:
- 时间局部性:被执行过的指令,很有可能会继续执行。这是因为绝大部分程序都由很多循环结构构成。
- 空间局部性:被访问的存储单元,其相邻存储单元也很有可能被访问。这是因为程序中存在很多在内存中连续分配的数据结构。
基于局部性原理,才提出虚拟存储技术,主要有:请求分页、请求分段、请求段页式。
核心思路:不把程序的所有页都调入内存,只把当前必须的页调入,当访问的数据不在内存时,才缺页中断调入对应页。这样用户能使用的地址空间远远大于实际物理内存,同时提升了并发度。
页面置换算法⭐⭐
| 算法 | 思想 | 特点 |
|---|---|---|
| 最佳置换(OPT) | 每次调出未来最久才会被用到的页面 | 理论最优,但无法实现(无法预知未来) |
| FIFO | 先进先出,置换最先进入内存的页面 | 实现简单,会出现 Belady 现象(分配内存越多缺页率反而升高) |
| LRU(最近最久未使用) | 置换最近最久没被访问过的页面 | 最常用,符合局部性原理,但实现成本略高 |
什么是抖动(颠簸)⭐⭐
刚刚调入内存的页面又被立刻调出,刚刚调出的又立刻要调入,这样频繁的页面调度现象叫做抖动。
主要原因:分配给进程的内存块太少了,程序工作集放不下。