面渣逆袭操作系统:面试高频题详解
本文最后更新于44 天前,其中的信息可能已经过时,如有错误请发送邮件到3095478042@qq.com

1. 引论

1.1 什么是操作系统?

操作系统是一种运行在内核态的软件,核心作用是作为应用程序与硬件之间的媒介:一方面向应用程序提供硬件的抽象接口,另一方面统一管理处理器(CPU)、内存、磁盘等硬件资源。

从系统层级来看,结构自上而下为:应用程序(如viddler)→ 操作系统(含虚拟内存、进程、I/O、文件管理模块)→ 硬件(含存储器、磁盘、处理器、总线、I/O设备)。

1.2 操作系统主要功能

操作系统的核心功能围绕硬件资源管理展开,具体包括五大核心模块及两类保障模块:

  1. 处理器(CPU)管理:核心是进程管理,负责CPU资源的分配与调度,确保多个进程高效、有序地使用CPU;
  2. 内存管理:通过虚拟内存机制实现内存的分配与回收,为每个进程提供独立的地址空间,避免进程间内存冲突;
  3. 外存管理:将磁盘等外存设备以“文件”的形式抽象化,负责文件的存储、读取、权限控制及空间分配;
  4. I/O管理:对键盘、鼠标、网卡、磁盘等输入/输出设备进行统一管理,屏蔽不同硬件的差异,提供标准化的I/O接口;
  5. 健壮性与安全性管理:保障操作系统自身稳定运行,同时防止非法操作(如越权访问内存)和外部入侵(如恶意程序攻击)。

2. 操作系统结构

2.1 什么是内核?

内核是操作系统的核心程序,具备计算机系统的最高权限,能够直接控制CPU、内存、硬盘等所有硬件资源,提供操作系统最基础的能力(如进程调度、内存地址映射、I/O设备控制),是操作系统实现资源管理的核心载体。

2.2 用户态与内核态

为实现权限隔离、避免应用程序误操作硬件,操作系统将内存划分为两个独立区域,对应两种程序执行状态,具体对比如下:

执行状态对应内存区域权限范围运行程序类型
用户态用户空间仅能访问局部内存,权限受限应用程序(如浏览器、办公软件)
内核态内核空间可访问所有内存及硬件资源,权限无限制内核程序(如进程调度、内存管理模块)

用户空间的代码仅能操作自身所属的局部内存,无法直接访问内核空间或硬件;若需使用硬件资源,必须通过内核态程序间接实现。

2.3 用户态和内核态的切换

应用程序无法直接进入内核态,需通过系统调用触发状态切换,具体流程如下:

  1. 应用程序(运行于用户态)执行系统调用(如读取文件的read调用),触发软件中断;
  2. CPU接收到中断信号后,立即中断当前正在执行的用户程序,跳转到内核的中断处理程序,此时程序进入内核态
  3. 内核程序在 kernel 空间执行对应操作(如从磁盘读取数据到内核缓冲区);
  4. 内核处理完成后,主动触发中断信号,将CPU的执行权限交回给用户程序,程序回到用户态并继续执行后续代码。

3. 进程和线程

3.1 并行与并发的区别

  • 并发:在一段时间内,多个任务会被交替处理,但某一时刻仅一个任务在执行。
    例:单核CPU通过“时间片轮转”调度进程A和B——A运行1个时间片(如10ms)后切换到B,B运行1个时间片后再切回A,因切换速度极快,宏观上表现为“同时运行”。
  • 并行:在同一时刻,多个任务被同时执行,需多核CPU支持。
    例:双核CPU将进程A分配到核心1,进程B分配到核心2,微观上两条指令同时执行,是物理层面的“同时运行”。

3.2 进程上下文切换

对于单核单线程CPU,某一时刻仅能执行一条CPU指令。上下文切换是操作系统将CPU资源从一个进程分配给另一个进程的核心机制,具体流程如下:

  1. 操作系统首先保存当前进程的“上下文状态”,包括内存空间指针、已执行指令的位置、寄存器值等;
  2. 从内存中读取下一个待执行进程的上下文状态,并加载到CPU寄存器中;
  3. 切换CPU的执行权限,开始运行新进程。

用户感知的“计算机能同时运行多个程序”,本质是操作系统通过高频次的上下文切换(如每秒数十次)模拟实现的。

3.3 进程的状态

进程从创建到终止,会经历多个状态及转换,可分为“核心状态”和“扩展状态”两类:

3.3.1 核心状态(基础流转)

核心状态包括运行态、就绪态、阻塞态,是进程最主要的状态,转换规则如下:

  • 运行态(Running):进程当前占用CPU,正在执行指令;
  • 就绪态(Ready):进程已具备运行条件,但因其他进程占用CPU而暂时等待;
  • 阻塞态(Blocked):进程因等待某一事件(如I/O操作完成、接收信号)而暂停运行,即使分配CPU也无法执行。

状态转换逻辑:

  1. 运行态 → 就绪态:调度程序选择其他进程(如当前进程时间片用完、高优先级进程进入就绪态);
  2. 就绪态 → 运行态:调度程序选中该进程,分配CPU资源;
  3. 运行态 → 阻塞态:进程主动请求某事件(如调用read等待I/O);
  4. 阻塞态 → 就绪态:进程等待的事件完成(如I/O操作结束)。

3.3.2 扩展状态(生命周期两端)

  • 创建态(New):进程正在被创建,如操作系统为其分配PID(进程ID)、初始化内存资源;
  • 终止态(Exit):进程完成执行或因异常(如除零错误)终止,正在从系统中清除资源(如释放内存、删除进程表项)。

状态转换逻辑:创建态 → 就绪态(进程初始化完成);运行态 → 终止态(进程执行结束或异常退出)。

3.4 僵尸进程与孤儿进程

3.4.1 僵尸进程

  • 定义:已完成执行(处于终止态),但进程表中仍保留其描述符的进程;
  • 产生场景:仅存在于父子进程关系中——子进程退出时,其进程描述符需父进程通过wait()waitpid()函数读取后才会释放;若父进程未调用这两个函数,子进程的描述符会一直保留在进程表中,成为僵尸进程;
  • 影响:长期积累会占用进程表资源,导致系统无法创建新进程。

3.4.2 孤儿进程

  • 定义:父进程提前退出,但其子进程仍在运行,这些子进程称为孤儿进程;
  • 处理机制:孤儿进程会被init进程(系统初始化进程,PID=1)自动收养,由init进程调用wait()函数回收其资源;
  • 影响:因资源会被init进程正常回收,不会对系统造成危害。

3.5 进程调度算法

进程调度的核心是“确定某一时刻CPU应运行哪个进程”,常见调度算法如下:

算法名称类型核心逻辑特点
先来先服务(FCFS)非抢占式按进程请求CPU的先后顺序调度有利于长作业,不利于短作业(短作业需等待长作业执行完毕);对I/O密集型进程不友好
短作业优先(SJF)非抢占式按进程“估计运行时间”从短到长调度缩短短作业等待时间,但长作业可能“饿死”(持续有短作业进入时无法调度)
优先级调度可抢占/非抢占为每个进程分配优先级,按优先级从高到低调度;可随时间提高低优先级进程优先级避免低优先级进程饿死,但需合理设置优先级(防止高优先级进程独占CPU)
时间片轮转抢占式就绪进程按FCFS排队,每次分配1个时间片;时间片用完后移至队尾时间片大小关键:过小则切换频繁,过大则实时性差
最短剩余时间优先(SRTF)抢占式新进程到达时,比较其总运行时间与当前进程剩余时间;新进程更短则抢占CPU进一步优化短作业响应时间,但实现复杂度高

3.6 进程间通信(IPC)方式

进程间内存相互独立,需通过特定机制交换数据,常见IPC方式及特点如下:

3.6.1 管道

  • 原理:内核中的一块缓存区,数据从一端写入、另一端读取;
  • 分类:
  • 匿名管道:单向通信,仅支持有亲缘关系的进程(如父子进程);
  • 命名管道:双向通信,支持本机任意进程;
  • 特点:实现简单,但效率低、容量有限,仅能传输无格式字节流。

3.6.2 信号

  • 原理:发送特定软件中断信号(如kill -9 PID发送SIGKILL信号),通知接收进程执行预设处理逻辑;
  • 特点:异步通信,承载信息量少(仅能传递信号编号);是唯一支持异步的IPC方式;
  • 常见信号:SIGINT(Ctrl+C触发,终止进程)、SIGKILL(强制终止,无法忽略)、SIGCHLD(子进程退出信号)。

3.6.3 消息队列

  • 原理:内核中的消息链表,进程可按权限向队列添加/读取消息(含类型标识);
  • 特点:克服信号信息量少、管道无格式的缺点,但需在用户态与内核态间拷贝数据,实时性一般。

3.6.4 共享内存

  • 原理:多个进程将同一块物理内存映射到各自的虚拟地址空间,直接读写内存实现通信;
  • 特点:最快的IPC方式(无需数据拷贝),但需配合信号量等机制实现同步(防止并发写冲突)。

3.6.5 信号量

  • 原理:基于整数计数器的同步机制,通过P操作(减1,无资源则阻塞)和V操作(加1,唤醒阻塞进程)控制资源访问;
  • 特点:仅用于同步(如互斥访问共享资源),不能传递数据;P/V操作必须成对出现。

3.6.6 Socket

  • 原理:基于网络协议(如TCP/UDP)的通信接口;
  • 特点:支持不同机器间的进程通信,是跨主机IPC的核心方式;如网络编程中客户端与服务器的通信。

3.7 进程与线程的联系与区别

3.7.1 联系

  1. 线程是进程内的一条执行流程,一个进程可包含多个线程(即“多线程进程”);
  2. 同一进程的线程共享代码段、数据段、打开的文件等资源;
  3. 每个线程拥有独立的寄存器和栈,确保执行流的独立性。

3.7.2 区别

对比维度进程线程
调度单位资源分配单位(分配内存、文件等资源)CPU调度单位(分配CPU时间片)
资源持有拥有完整的资源平台仅独享寄存器、栈等必要资源
系统开销创建/销毁、上下文切换开销大创建/销毁、上下文切换开销小(共享虚拟内存)
独立性进程间相互独立,一个进程崩溃不影响其他进程线程间共享进程资源,一个线程崩溃可能导致整个进程崩溃

3.8 线程上下文切换

线程切换的开销取决于是否属于同一进程:

  • 不同进程的线程:切换流程与进程上下文切换一致,需保存/加载虚拟内存、资源表等,开销大;
  • 同一进程的线程:因虚拟内存、文件等资源共享,仅需保存/加载线程独立的寄存器、栈,开销远小于进程切换。

3.9 线程的实现方式

线程主要有三种实现方式,对应不同的内核与用户空间协作模式:

3.9.1 内核态线程(1:1模型)

  • 原理:每个用户线程对应一个内核线程,内核直接管理线程的调度与切换;
  • 特点:线程阻塞时不影响其他线程,但内核线程创建开销大,数量受内核限制。

3.9.2 用户态线程(N:1模型)

  • 原理:线程在用户空间实现,内核无感知(仅识别进程);由用户态库负责线程调度;
  • 特点:创建/切换开销小,数量不受限,但一个线程阻塞会导致整个进程的线程阻塞。

3.9.3 混合线程(M:N模型)

  • 原理:结合前两种方式——多个用户线程映射到少量内核线程;用户态管理非阻塞线程切换,内核态管理阻塞线程切换;
  • 特点:平衡开销与灵活性,是现代操作系统(如Linux、Windows)的主流实现方式。

3.10 线程间同步

同步的目标是“确保多线程操作共享资源时,结果正确”,核心是避免并发冲突,常见机制如下:

3.10.1 临界区

  • 定义:访问共享资源的代码片段,需保证“同一时刻仅一个线程执行”;
  • 实现:通过“锁”控制——线程进入临界区前执行“加锁”,成功则进入;离开后执行“解锁”,释放资源。

3.10.2 锁的分类

  • 忙等待锁(自旋锁):加锁失败时,线程循环尝试获取锁,不放弃CPU;适用于临界区执行时间短的场景;
  • 无忙等待锁(阻塞锁):加锁失败时,线程进入阻塞态,放弃CPU;适用于临界区执行时间长的场景。

3.10.3 信号量(同步复用)

  • 原理:通过P操作(申请资源)和V操作(释放资源)实现同步;如初始化信号量为1(互斥锁),确保临界区仅一个线程进入。

3.11 死锁

3.11.1 定义

两个或多个线程互相持有对方所需的资源,且均不释放,导致永久阻塞的状态(如线程A持有资源X、等待资源Y,线程B持有资源Y、等待资源X)。

3.11.2 死锁产生的4个必要条件

死锁的产生需同时满足以下4个条件,缺一不可:

  1. 互斥条件:资源仅能被一个线程占用(如锁、打印机);
  2. 请求并持有条件:线程持有部分资源,同时请求其他线程的资源;
  3. 不可剥夺条件:线程占用的资源只能主动释放,不能被强制抢占;
  4. 环路等待条件:线程与资源形成环形等待链。

3.11.3 死锁的避免(破坏任一条件)

  • 破坏“请求并持有”:线程一次申请所有所需资源;
  • 破坏“不可剥夺”:线程申请新资源失败时,主动释放已持有资源;
  • 破坏“环路等待”:按资源序号从小到大申请(如先申请锁A,再申请锁B),避免环形链;
  • 注:“互斥条件”无法破坏(部分资源本质为互斥资源)。

3.12 活锁与饥饿锁

3.12.1 饥饿锁

  • 定义:线程长期无法获取所需资源,导致无法推进;
  • 例:低优先级线程永远抢不过高优先级线程,始终得不到CPU时间片。

3.12.2 活锁

  • 定义:线程状态不断变化,但整体无法推进(类似“互相谦让”导致的僵局);
  • 例:两人过窄桥,均想让对方先过,每次同时向同一侧避让,始终无法过桥。

4. 内存管理

4.1 什么是虚拟内存?

虚拟内存是操作系统提供的地址映射机制,核心作用是:

  1. 为每个进程提供独立的虚拟地址空间,避免进程间内存冲突;
  2. 突破物理内存限制(将部分不常用数据存储到磁盘,需用时调入内存);

关键概念:

  • 虚拟地址:应用程序代码中使用的地址(如变量的指针地址);
  • 物理地址:内存硬件实际存储数据的地址;
  • 映射关系:操作系统通过页表/段表,将虚拟地址转换为物理地址,对应用程序透明。

4.2 内存分段

  • 原理:将程序按逻辑功能划分为多个“段”(如代码段、数据段、栈段、堆段),每个段有独立的属性(如可读、可写、可执行);
  • 虚拟地址结构:段号 + 段内偏移量
  • 映射方式:通过“段表”实现——段表存储每个段的“段基地址”(物理内存起始地址)和“段界限”(段大小),物理地址 = 段基地址 + 段内偏移量;
  • 例:段表中“段3(栈段)”的基地址为7000,段内偏移量为500,则物理地址 = 7000 + 500 = 7500。

4.3 内存分页

  • 原理:将虚拟内存和物理内存均划分为固定大小的“页”(如Linux中每页4KB),通过“页表”映射虚拟页与物理页;
  • 虚拟地址结构:虚拟页号 + 页内偏移量
  • 访问流程(两次内存访问):
  1. 访问页表:通过虚拟页号查找对应的物理页号;
  2. 计算物理地址:物理地址 = 物理页号 × 页大小 + 页内偏移量,访问该地址获取数据;
  • 特点:页大小固定,对应用程序透明,便于内存碎片管理。

4.4 多级页表

  • 问题:单级页表在大地址空间(如32位系统)下会非常庞大(需存储大量页表项),浪费内存;
  • 原理:将单级页表再次分页,形成多级结构(如二级页表),利用“局部性原理”——仅顶级页表常驻内存,其他级页表需用时才调入;
  • 例:32位虚拟地址分为“10位一级页号 + 10位二级页号 + 12位页内偏移”:
  1. 一级页号索引顶级页表,获取二级页表的物理地址;
  2. 二级页号索引二级页表,获取物理页号;
  3. 结合页内偏移得到最终物理地址。

4.5 块表(TLB)

  • 问题:分页访问需两次内存(页表 + 物理地址),效率低;
  • 原理:利用“局部性原理”(程序短期内仅访问少量页),在CPU中集成高速缓存(TLB,转址旁路缓存),存储最常访问的页表项;
  • 访问优化:
  1. CPU先查询TLB,若命中(找到页表项),直接获取物理地址(仅1次内存访问);
  2. 若未命中,再访问内存中的页表,同时将该页表项存入TLB。

4.6 分页与分段的区别

对比维度分页分段
单位性质物理单位(为管理内存划分)逻辑单位(按程序功能划分)
大小固定(由系统决定,如4KB)不固定(由程序功能决定)
地址空间一维地址(如0x12345678)二维地址(段号 + 段内偏移)
透明性对用户透明对用户可见(需指定段结构)
共享与保护共享、保护受限便于共享(如共享代码段)和保护(按段设权限)

4.7 交换空间

  • 定义:磁盘上的一块专用空间,用于临时存储物理内存中不常用的“页”;
  • 作用:物理内存不足时,操作系统将不常用的页“交换”到交换空间,释放内存给活跃进程;
  • 虚拟内存容量:物理内存大小 + 交换空间大小。

4.8 页面置换算法

当缺页中断发生(虚拟页不在物理内存)且内存无空闲页时,需置换内存中的某一页,常见算法如下:

算法名称核心逻辑特点
最佳置换(OPT)置换“未来最长时间不访问”的页理论最优,无法实现(无法预知未来访问);仅作基准
先进先出(FIFO)置换“最早进入内存”的页(用链表记录顺序,置换链表头)实现简单,可能触发“Belady异常”(内存页数增加,缺页率反而上升)
最近最久未使用(LRU)置换“最近最长时间未访问”的页(假设历史访问可预测未来)近似最优,实现开销大(需维护访问顺序链表)
时钟算法环形链表存储页,表针指向最老页;缺页时检查访问位,0则置换,1则清位前移实现简单,开销低于LRU,是LRU的近似算法
最不常用(LFU)为页设“访问计数器”,置换“访问次数最少”的页可能置换“早期常用、近期不常用”的页,对突发访问不友好
  • 先进先出置换算法(FIFO
  • 最近最久未使⽤的置换算法(LRU
  • 时钟页⾯置换算法

5. 文件管理

5.1 硬链接与软链接的区别

硬链接和软链接是文件系统中两种链接方式,核心区别如下:

对比维度硬链接软链接(符号链接)
inode与源文件共享同一个inode拥有独立的inode
本质目录中指向源文件inode的条目独立文件,内容为源文件的路径字符串
跨文件系统不支持(inode在不同文件系统不唯一)支持(仅依赖路径,与文件系统无关)
源文件删除仅删除一个条目,文件仍存在(inode引用数>0)链接文件存在,但无法访问源文件(路径失效)
链接对象仅支持文件,不支持目录支持文件和目录
硬链接
软链接

6. I/O技术

6.1 零拷贝技术

6.1.1 传统I/O的问题

传统文件传输(如从磁盘读文件并通过网卡发送)需经历:

  • 4次上下文切换(用户态→内核态→用户态→内核态);
  • 4次数据拷贝(磁盘→内核缓冲区→用户缓冲区→socket缓冲区→网卡);
    频繁的切换与拷贝导致I/O效率低下。

6.1.2 零拷贝的实现方式

零拷贝的核心是减少“用户态-内核态切换”和“数据拷贝”,主流实现有两种:

1. mmap + write
  • 原理:mmap()将内核缓冲区直接“映射”到用户空间,用户程序可直接操作内核缓冲区,无需用户态与内核态的数据拷贝;
  • 流程:磁盘→内核缓冲区(DMA拷贝)→socket缓冲区(CPU拷贝)→网卡(DMA拷贝);
  • 优化:减少1次用户态-内核态拷贝,上下文切换2次,数据拷贝3次。
2. sendfile
  • 原理:sendfile()是Linux内核提供的专用系统调用,直接在内核空间完成“内核缓冲区→socket缓冲区”的拷贝,无需用户态参与;
  • 流程:磁盘→内核缓冲区(DMA拷贝)→socket缓冲区(CPU拷贝)→网卡(DMA拷贝);
  • 优化:减少2次上下文切换(仅1次系统调用),数据拷贝3次;
  • 应用:Kafka、RocketMQ等中间件均采用sendfile提升I/O效率。

6.2 阻塞与非阻塞 I/O、同步与异步 I/O

四种I/O模型的核心区别在于“等待数据准备”和“数据拷贝”两个阶段是否阻塞,可通过“老三等UP主更新”的例子理解:

6.2.1 阻塞I/O

  • 流程:调用read后,线程阻塞,直到内核数据准备好并完成“内核→用户”拷贝,read才返回;
  • 等待阶段:“数据准备”和“数据拷贝”均阻塞;
  • 例子:老三不做其他事,一直盯着UP主页面等更新。

6.2.2 非阻塞I/O

  • 流程:调用read后,数据未准备好则立即返回错误;应用程序需轮询内核,直到数据准备好,再等待拷贝完成;
  • 等待阶段:“数据准备”不阻塞,“数据拷贝”阻塞;
  • 例子:老三发现UP主没更新,就去喝茶,过一会儿回来查看,直到更新。

6.2.3 基于非阻塞的I/O多路复用

  • 流程:通过select/poll/epoll监控多个I/O事件,内核数据准备好后以“事件通知”告知应用程序,再发起read
  • 等待阶段:“数据准备”不阻塞(靠事件通知),“数据拷贝”阻塞;
  • 本质:同步I/O(拷贝阶段仍阻塞);
  • 例子:老三没等到更新就去干别的,B站推送“有UP主更新”后,再去查看目标UP主是否更新。

6.2.4 异步I/O

  • 流程:调用aio_read后立即返回;内核自动完成“数据准备”和“内核→用户拷贝”,完成后通知应用程序;
  • 等待阶段:“数据准备”和“数据拷贝”均不阻塞;
  • 例子:老三告诉UP主“更新后通知我”,UP主完成视频后主动发给老三,老三无需等待。

6.3 I/O多路复用

6.3.1 定义

I/O多路复用允许一个进程/线程同时监控多个Socket的I/O事件,实现“多连接复用一个进程/线程”,解决传统“一连接一线程”的资源耗尽问题(如C10K问题)。

6.3.2 三种实现机制对比

机制核心原理优点缺点
select将Socket集合(fd_set)拷贝到内核,内核遍历检查事件;结果拷贝回用户态再遍历跨平台支持(Linux、Windows均支持)1. fd_set大小受限(默认1024);2. 两次遍历(内核+用户态);3. 拷贝开销大
poll用动态链表存储Socket,突破fd_set大小限制无Socket数量限制仍需两次遍历,拷贝开销随Socket数量增加而增大(时间复杂度O(n))
epoll1. 红黑树存储Socket(增删查O(logn));2. 事件驱动,链表记录就绪Socket1. 无数量限制;2. 无需遍历(O(1));3. 减少拷贝仅支持Linux系统,跨平台性差;是解决C10K问题的核心机制

文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇