1. 进程和线程的区别
进程
- 定义:进程是资源分配的基本单位,是程序的一次执行过程
- 特点:
- 拥有独立的地址空间
- 拥有独立的代码段、数据段、堆栈
- 进程间通信需要特殊的机制(如管道、消息队列、共享内存等)
- 进程切换开销大(需要切换页表、刷新TLB等)
- 进程间相互独立,一个进程崩溃不会影响其他进程
线程
- 定义:线程是CPU调度的基本单位,是进程内的执行流
- 特点:
- 共享进程的地址空间和资源
- 拥有独立的栈和寄存器
- 线程间通信可以直接通过共享内存
- 线程切换开销小(只需保存和恢复寄存器)
- 一个线程崩溃可能影响整个进程
对比表
| 特性 | 进程 | 线程 |
|---|---|---|
| 资源分配 | 独立地址空间 | 共享地址空间 |
| 通信方式 | IPC机制 | 共享内存 |
| 切换开销 | 大 | 小 |
| 创建开销 | 大 | 小 |
| 独立性 | 高 | 低 |
| 适用场景 | 需要隔离的任务 | 需要协作的任务 |
2. 进程间通信(IPC)的方式
1. 管道(Pipe)
- 特点:
- 半双工通信(只能单向)
- 只能用于有亲缘关系的进程(父子进程)
- 基于字节流
- 容量有限(通常64KB)
- 使用场景:父子进程通信
- Linux实现:
pipe()系统调用
2. 命名管道(FIFO)
- 特点:
- 可以在无关进程间通信
- 有文件名,可以持久化
- 半双工通信
- 基于文件系统
- 使用场景:任意进程间通信
- Linux实现:
mkfifo()创建,像普通文件一样使用
3. 消息队列
- 特点:
- 消息的链表,存储在内核中
- 可以双向通信
- 支持消息类型,可以按类型接收
- 消息有格式,不是字节流
- 使用场景:需要按类型处理消息的场景
- Linux实现:
msgget(),msgsnd(),msgrcv()
4. 共享内存
- 特点:
- 最快的IPC方式(无需内核参与数据拷贝)
- 多个进程映射同一块物理内存
- 需要同步机制(信号量、互斥锁等)
- 需要考虑缓存一致性问题
- 使用场景:大数据量、高性能要求的场景
- Linux实现:
shmget(),shmat(),shmdt()
5. 信号量(Semaphore)
- 特点:
- 用于进程间同步
- 可以解决互斥和同步问题
- 可以控制多个资源的访问
- 支持P(wait)和V(signal)操作
- 使用场景:资源访问控制、生产者-消费者问题
- Linux实现:
semget(),semop()
6. Socket
- 特点:
- 可用于不同主机间的进程通信
- 支持TCP/IP协议
- 支持多种协议族(AF_INET, AF_UNIX等)
- 全双工通信
- 使用场景:网络通信、分布式系统
- Linux实现:
socket(),bind(),listen(),accept(),connect()
7. 信号(Signal)
- 特点:
- 异步通信机制
- 用于通知进程发生了某个事件
- 不能传递复杂数据
- 主要用于进程控制
- 使用场景:进程控制、异常处理
- Linux实现:
kill(),signal(),sigaction()
IPC方式对比
| 方式 | 数据量 | 速度 | 同步 | 适用范围 |
|---|---|---|---|---|
| 管道 | 小 | 慢 | 需要 | 父子进程 |
| 命名管道 | 小 | 慢 | 需要 | 任意进程 |
| 消息队列 | 中 | 中 | 需要 | 任意进程 |
| 共享内存 | 大 | 快 | 需要 | 任意进程 |
| 信号量 | - | 快 | 是 | 任意进程 |
| Socket | 大 | 中 | 需要 | 网络通信 |
3. 死锁的条件和预防
死锁的定义
多个进程因竞争资源而造成的一种互相等待的现象,若无外力作用,这些进程都将无法继续执行。
死锁的四个必要条件(必须同时满足)
- 互斥条件:资源不能被多个进程同时使用
- 请求与保持条件:进程持有资源的同时请求其他资源
- 不剥夺条件:已获得的资源不能被其他进程强制剥夺
- 循环等待条件:存在进程-资源的循环等待链
死锁预防(破坏必要条件)
破坏互斥条件
- 使用可共享的资源(如只读文件)
- 但很多资源本质上是互斥的(如打印机)
破坏请求与保持条件
- 一次性分配所有需要的资源
- 缺点:资源利用率低,可能造成饥饿
破坏不剥夺条件
- 允许剥夺已分配的资源
- 缺点:实现复杂,可能造成重复工作
破坏循环等待条件
- 对资源进行编号,按顺序申请
- 缺点:资源编号困难,可能降低并发度
死锁避免
- 银行家算法:在分配资源前检查系统是否处于安全状态
- 需要知道进程的最大资源需求
- 适用于资源种类较少的场景
死锁检测和恢复
- 检测:定期检查是否存在循环等待
- 恢复:
- 终止进程
- 回滚操作
- 剥夺资源
4. 页面置换算法
当发生缺页中断且内存已满时,需要选择一个页面换出。
1. 最佳置换算法(OPT)
- 策略:选择未来最长时间不会被访问的页面
- 优点:理论最优,缺页率最低
- 缺点:无法实现(需要预知未来)
- 用途:作为其他算法的性能参考
2. 先进先出(FIFO)
- 策略:选择最早进入内存的页面
- 优点:实现简单,开销小
- 缺点:可能淘汰常用页面,存在Belady异常
- Belady异常:增加物理页框数,缺页率反而上升
3. 最近最少使用(LRU)
- 策略:选择最长时间没有被访问的页面
- 优点:性能好,符合局部性原理
- 缺点:实现复杂,需要硬件支持(时间戳或栈)
- 近似实现:
- 时钟算法(Clock)
- 二次机会算法
- 最不经常使用(LFU)
4. 时钟置换算法(Clock)
- 策略:LRU的近似实现,使用访问位
- 工作原理:
- 页面有访问位(reference bit)
- 访问页面时,访问位置1
- 需要置换时,指针循环扫描
- 访问位为0则置换,为1则置0继续
- 优点:实现简单,性能接近LRU
- 改进:改进时钟算法(考虑修改位)
5. 最不经常使用(LFU)
- 策略:选择访问次数最少的页面
- 缺点:可能保留不再使用的页面
6. 最经常使用(MFU)
- 策略:选择访问次数最多的页面
- 理论:访问次数多的页面可能不再需要
算法对比
| 算法 | 实现难度 | 性能 | 开销 | 硬件支持 |
|---|---|---|---|---|
| OPT | - | 最优 | - | 需要预知 |
| FIFO | 简单 | 一般 | 小 | 不需要 |
| LRU | 复杂 | 好 | 大 | 需要 |
| Clock | 中等 | 较好 | 中 | 需要访问位 |
5. 进程调度算法
调度目标
- 公平性:每个进程都能获得CPU时间
- 效率:提高CPU利用率
- 响应时间:交互式进程快速响应
- 吞吐量:单位时间内完成的作业数
1. 先来先服务(FCFS)
- 策略:按到达顺序调度
- 优点:实现简单,公平
- 缺点:短作业等待时间长,平均等待时间长
- 适用场景:批处理系统
2. 短作业优先(SJF)
- 策略:优先调度执行时间短的作业
- 优点:平均等待时间最短
- 缺点:长作业可能饥饿,需要预知执行时间
- 变种:
- 最短剩余时间优先(SRTF):可抢占版本
- 最高响应比优先(HRRN):考虑等待时间
3. 优先级调度
- 策略:按优先级调度
- 优先级确定:
- 静态优先级:创建时确定
- 动态优先级:根据运行情况调整
- 问题:低优先级进程可能饥饿
- 解决:优先级老化(aging)
4. 时间片轮转(RR)
- 策略:每个进程分配一个时间片,时间片用完切换
- 优点:公平,响应时间好
- 缺点:时间片大小影响性能
- 太小:上下文切换开销大
- 太大:退化为FCFS
- 适用场景:分时系统
5. 多级反馈队列(MLFQ)
- 策略:
- 多个队列,优先级递减
- 时间片大小递增
- 新进程进入最高优先级队列
- 时间片用完降级
- 高优先级队列为空才调度低优先级
- 优点:兼顾短作业和长作业
- 适用场景:通用操作系统(如Linux)
Linux调度算法
- O(1)调度器:使用多级队列
- CFS(完全公平调度器):基于虚拟运行时间,红黑树实现
- 实时调度:FIFO和RR策略
6. 虚拟内存
概念
虚拟内存是一种内存管理技术,将物理内存和磁盘空间结合使用,为每个进程提供独立的虚拟地址空间。
工作原理
- 地址空间分离:每个进程拥有独立的虚拟地址空间
- 地址转换:通过页表将虚拟地址转换为物理地址
- 按需加载:只加载需要的页面到内存
- 页面置换:内存不足时,将页面换出到磁盘
优点
- 扩大地址空间:程序可以使用比物理内存更大的地址空间
- 内存保护:每个进程的地址空间相互隔离
- 共享内存:多个进程可以共享同一物理页面(如代码段)
- 提高内存利用率:按需分配,避免浪费
- 简化内存管理:程序不需要关心物理内存布局
缺点
- 地址转换开销:需要查页表,可能访问TLB
- 缺页中断开销:页面不在内存时需要从磁盘加载
- 页面置换开销:内存不足时需要换出页面
实现机制
- 分页:将虚拟地址空间和物理内存分成固定大小的页
- 页表:存储虚拟页到物理页的映射
- TLB:快表,缓存页表项,加速地址转换
- 缺页中断:访问不在内存的页面时触发
7. 文件系统
概念
文件系统是操作系统用于管理文件和目录的机制,定义了数据在存储设备上的组织方式。
常见文件系统
Linux文件系统
ext2/ext3/ext4
- ext4是Linux最常用的文件系统
- 支持日志功能(ext3/ext4)
- 支持大文件和快速文件系统检查
XFS
- 高性能文件系统
- 支持大文件和快速恢复
- 适用于大容量存储
Btrfs
- 支持写时复制(COW)
- 支持快照和子卷
- 支持数据压缩和去重
ZFS
- 支持快照、克隆、压缩
- 内置RAID功能
- 数据完整性校验
Windows文件系统
FAT32
- 兼容性好
- 不支持大于4GB的文件
- 不支持权限控制
NTFS
- 支持大文件和权限控制
- 支持日志和压缩
- Windows默认文件系统
文件系统功能
- 文件存储:组织文件在磁盘上的存储
- 目录管理:提供层次化的目录结构
- 文件保护:控制文件的访问权限
- 磁盘空间管理:分配和回收磁盘空间
- 元数据管理:管理文件的属性信息(inode)
文件系统结构
- 超级块(Superblock):文件系统的元数据
- inode表:存储文件的元数据
- 数据块:存储文件的实际数据
- 目录项:目录中的文件名和inode映射
文件系统操作
- 挂载(Mount):将文件系统连接到目录树
- 卸载(Umount):断开文件系统连接
- 格式化:创建新的文件系统
8. 中断和异常
中断(Interrupt)
概念
由外部设备或硬件引起的事件,需要CPU暂停当前工作去处理。
特点
- 外部事件引起:如键盘输入、网络数据到达
- 异步发生:发生时间不确定
- 可屏蔽:可以通过中断屏蔽位屏蔽某些中断
- 可嵌套:高优先级中断可以打断低优先级中断处理
分类
- 硬件中断:由硬件设备产生(如时钟中断、I/O中断)
- 软件中断:由软件指令产生(如系统调用)
处理流程
- 保存当前上下文(寄存器、程序计数器等)
- 根据中断号查找中断处理程序
- 执行中断处理程序
- 恢复上下文,继续执行
异常(Exception)
概念
由CPU执行指令时检测到的错误或特殊情况。
特点
- 内部事件引起:由程序执行引起
- 同步发生:执行到特定指令时发生
- 不可屏蔽:必须立即处理
- 可预测:可以知道何时发生
分类
- 故障(Fault):可恢复,如缺页异常
- 陷阱(Trap):有意触发,如系统调用
- 终止(Abort):严重错误,如硬件故障
常见异常
- 除零异常:除以0
- 缺页异常:访问不在内存的页面
- 非法指令:执行无效指令
- 段错误:访问非法内存地址
中断和异常的区别
| 特性 | 中断 | 异常 |
|---|---|---|
| 来源 | 外部 | 内部 |
| 时机 | 异步 | 同步 |
| 可屏蔽 | 是 | 否 |
| 可预测 | 否 | 是 |
| 处理 | 可延迟 | 必须立即处理 |
9. 同步机制
为什么需要同步
多个进程或线程并发访问共享资源时,可能导致数据不一致、竞态条件等问题。
1. 互斥锁(Mutex)
- 作用:保证同一时刻只有一个线程访问共享资源
- 操作:lock(加锁)、unlock(解锁)
- 特点:
- 获取锁失败时,线程进入睡眠
- 适用于锁持有时间较长的场景
- 需要上下文切换,开销较大
2. 信号量(Semaphore)
- 作用:控制对共享资源的访问数量
- 操作:P(wait,减1)、V(signal,加1)
- 分类:
- 二进制信号量:值为0或1,类似互斥锁
- 计数信号量:值可以大于1,控制资源数量
- 应用:生产者-消费者问题、读者-写者问题
3. 条件变量(Condition Variable)
- 作用:用于线程等待特定条件满足
- 操作:wait(等待条件)、signal(通知一个等待线程)、broadcast(通知所有等待线程)
- 特点:必须与互斥锁配合使用
- 应用:生产者-消费者问题
4. 读写锁(RWLock)
- 作用:区分读操作和写操作
- 特点:
- 多个读者可以同时持有读锁
- 写者需要独占访问
- 读者和写者互斥
- 应用:读多写少的场景
5. 自旋锁(Spinlock)
- 作用:类似互斥锁,但获取锁失败时不会睡眠
- 特点:
- 获取锁失败时,线程循环等待(自旋)
- 适用于锁持有时间很短的场景
- 会消耗CPU资源
- 适用场景:多核系统、锁持有时间短
6. 屏障(Barrier)
- 作用:同步多个线程,等待所有线程到达某一点
- 应用:并行计算、分阶段处理
同步机制对比
| 机制 | 用途 | 特点 | 适用场景 |
|---|---|---|---|
| 互斥锁 | 互斥访问 | 阻塞等待 | 一般互斥 |
| 信号量 | 资源计数 | 可控制数量 | 资源池 |
| 条件变量 | 条件等待 | 需配合互斥锁 | 条件同步 |
| 读写锁 | 读写分离 | 读并发,写独占 | 读多写少 |
| 自旋锁 | 互斥访问 | 忙等待 | 锁持有时间短 |
10. 内存管理
内存管理的目标
- 地址转换:将逻辑地址转换为物理地址
- 内存分配:为进程分配内存空间
- 内存保护:防止进程访问非法内存
- 内存共享:允许多个进程共享内存
- 内存扩充:使用虚拟内存技术
内存分配方式
1. 连续分配
- 固定分区:内存分成固定大小的分区
- 优点:实现简单
- 缺点:内部碎片,利用率低
- 动态分区:按需分配不同大小的分区
- 优点:无内部碎片
- 缺点:外部碎片,需要紧凑
2. 分页(Paging)
- 原理:将内存和地址空间分成固定大小的页
- 优点:
- 无外部碎片
- 管理简单
- 易于实现虚拟内存
- 缺点:
- 有内部碎片
- 不易实现共享和保护
3. 分段(Segmentation)
- 原理:按逻辑单位(代码段、数据段等)分配
- 优点:
- 符合程序逻辑结构
- 易于共享和保护
- 无内部碎片
- 缺点:
- 有外部碎片
- 管理复杂
4. 段页式
- 原理:先分段,段内再分页
- 优点:结合分段和分页的优点
- 缺点:实现复杂,需要两次地址转换
内存保护
- 地址越界检查:检查访问的地址是否在进程地址空间内
- 访问权限控制:检查是否有读、写、执行权限
- 内存映射:通过页表实现地址转换和保护
内存分配算法
- 首次适应(First Fit):使用第一个足够大的空闲块
- 最佳适应(Best Fit):使用最小的足够大的空闲块
- 最坏适应(Worst Fit):使用最大的空闲块
- 快速适应(Quick Fit):维护多个空闲链表
11. 系统调用
概念
系统调用是操作系统提供给应用程序的接口,用于访问系统资源和服务。
常见系统调用分类
- 进程控制:fork、exec、exit、wait
- 文件操作:open、read、write、close
- 设备管理:ioctl、read、write
- 信息维护:getpid、alarm、sleep
- 通信:pipe、shmget、msgget
系统调用与库函数的区别
- 系统调用:直接与内核交互,开销大,需要用户态和内核态切换
- 库函数:可能封装系统调用,也可能不涉及系统调用,执行效率更高
12. 进程状态
进程的五种基本状态
- 创建(New):进程正在被创建
- 就绪(Ready):进程已获得除CPU外的所有资源,等待CPU调度
- 运行(Running):进程正在CPU上执行
- 阻塞(Blocked):进程等待某个事件发生(如I/O完成)
- 终止(Terminated):进程执行完毕或被终止
Linux进程状态
- R(Running):运行或可运行
- S(Sleeping):可中断的睡眠状态
- D(Disk sleep):不可中断的睡眠状态
- T(Stopped):停止状态
- Z(Zombie):僵尸进程
13. 上下文切换
概念
上下文切换是指CPU从一个进程(或线程)切换到另一个进程(或线程)时,需要保存当前进程的状态并恢复新进程的状态。
上下文切换的开销
- 保存和恢复寄存器状态
- 更新页表
- 刷新TLB(快表)
- 更新进程控制块(PCB)
减少上下文切换的方法
- 使用线程代替进程
- 减少不必要的进程/线程创建
- 使用异步I/O
- 合理设置时间片大小
14. 内存碎片
内部碎片
- 分配给进程的内存块大于实际需要的内存
- 无法被其他进程使用
- 常见于固定分区分配
外部碎片
- 内存中存在许多小的空闲块
- 无法满足大进程的内存需求
- 常见于动态分区分配
解决方法
- 紧凑(Compaction):移动进程,合并空闲块
- 分页:将内存分成固定大小的页
- 分段:按逻辑单位分配内存
15. 缓存一致性
问题
多核系统中,多个CPU核心可能同时访问同一块内存,导致缓存数据不一致。
解决方案
- 写直达(Write Through):同时更新缓存和内存
- 写回(Write Back):只更新缓存,需要时才写回内存
- MESI协议:Modified、Exclusive、Shared、Invalid四种状态
16. 磁盘调度算法
常见算法
先来先服务(FCFS)
- 按请求顺序服务
- 简单但效率低
最短寻道时间优先(SSTF)
- 选择距离当前磁道最近的请求
- 可能导致饥饿
扫描算法(SCAN)
- 磁头在一个方向上移动,服务所有请求
- 到达边界后反向移动
循环扫描(C-SCAN)
- 只在一个方向上服务请求
- 到达边界后立即返回起点
LOOK算法
- SCAN的改进,到达最后一个请求后立即反向
17. 缓冲区溢出
概念
当程序向缓冲区写入的数据超过缓冲区容量时,会覆盖相邻内存区域,可能导致程序崩溃或执行恶意代码。
防护措施
- 使用安全的编程语言(如Java、Python)
- 使用边界检查
- 使用栈保护技术(如栈金丝雀)
- 地址空间布局随机化(ASLR)
- 数据执行保护(DEP)
18. 协程(Coroutine)
概念
协程是一种比线程更轻量级的并发执行单元,由程序控制切换,不需要操作系统参与。
协程与线程的区别
| 特性 | 线程 | 协程 |
|---|---|---|
| 调度 | 操作系统调度 | 程序自身调度 |
| 切换开销 | 较大(需要系统调用) | 很小(只需保存寄存器) |
| 并发数量 | 受限于系统资源 | 可以创建大量协程 |
| 阻塞影响 | 阻塞整个线程 | 只阻塞当前协程 |
应用场景
- 高并发网络编程
- I/O密集型任务
- 异步编程
19. 零拷贝(Zero Copy)
传统数据拷贝流程
- 数据从磁盘读取到内核缓冲区
- 从内核缓冲区拷贝到用户缓冲区
- 从用户缓冲区拷贝到Socket缓冲区
- 从Socket缓冲区发送到网络
零拷贝技术
- sendfile系统调用:数据直接从内核缓冲区发送到Socket,无需经过用户空间
- mmap + write:使用内存映射,减少拷贝次数
- splice系统调用:在两个文件描述符之间移动数据
优势
- 减少CPU拷贝次数
- 减少上下文切换
- 提高数据传输效率
20. 大端和小端
概念
- 大端(Big Endian):高位字节存储在低地址
- 小端(Little Endian):低位字节存储在低地址
示例
对于0x12345678:
- 大端存储:12 34 56 78(从低地址到高地址)
- 小端存储:78 56 34 12(从低地址到高地址)
应用
- 网络字节序通常使用大端
- x86架构使用小端
- ARM可以配置为大端或小端
21. 内存对齐
概念
内存对齐是指数据在内存中的起始地址必须是某个值的整数倍。
对齐的原因
- 提高CPU访问效率
- 某些硬件平台要求对齐访问
- 避免跨缓存行访问
对齐规则
- char:1字节对齐
- short:2字节对齐
- int:4字节对齐
- long:8字节对齐(64位系统)
- double:8字节对齐
22. 用户态和内核态
概念
- 用户态:进程执行用户代码时的状态,权限受限
- 内核态:进程执行内核代码时的状态,拥有最高权限
切换场景
- 系统调用
- 异常(如除零、缺页)
- 中断
切换开销
- 需要保存和恢复寄存器
- 更新页表
- 刷新TLB
- 切换栈指针
23. 自旋锁和互斥锁
自旋锁(Spinlock)
- 线程在获取锁时,如果锁被占用,会一直循环等待
- 适用于锁持有时间短的场景
- 会消耗CPU资源
互斥锁(Mutex)
- 线程在获取锁时,如果锁被占用,会进入睡眠状态
- 适用于锁持有时间长的场景
- 需要上下文切换,开销较大
选择原则
- 锁持有时间短 → 使用自旋锁
- 锁持有时间长 → 使用互斥锁
- 多核系统 → 自旋锁更合适
- 单核系统 → 互斥锁更合适
24. 生产者-消费者问题
问题描述
生产者进程生产数据放入缓冲区,消费者进程从缓冲区取数据消费。
需要解决的问题
- 互斥:同一时刻只能有一个进程访问缓冲区
- 同步:缓冲区满时生产者等待,缓冲区空时消费者等待
解决方案
- 使用信号量实现
- 使用互斥锁和条件变量实现
- 使用消息队列实现
25. 读者-写者问题
问题描述
多个读者可以同时读取数据,但写者需要独占访问。
解决方案
- 读者优先:只要有读者在读,写者就要等待
- 写者优先:写者等待时,新的读者不能开始读
- 公平策略:按照到达顺序服务
26. 哲学家就餐问题
问题描述
5个哲学家围坐在圆桌旁,每人面前有一碗饭和一根筷子。哲学家需要两根筷子才能吃饭。
死锁条件
如果所有哲学家同时拿起左边的筷子,就会发生死锁。
解决方案
- 限制同时就餐的哲学家数量
- 使用资源分级
- 使用信号量
- 让哲学家按顺序拿筷子
27. 银行家算法
目的
避免死锁,确保系统始终处于安全状态。
算法步骤
- 检查请求是否超过可用资源
- 检查请求是否超过进程的最大需求
- 尝试分配资源
- 检查分配后系统是否处于安全状态
- 如果安全,则分配;否则,等待
安全状态
存在一个安全序列,使得所有进程都能完成执行。
28. 分页和分段
分页(Paging)
- 将内存分成固定大小的页
- 逻辑地址和物理地址都分成页号和页内偏移
- 优点:管理简单,无外部碎片
- 缺点:不易实现共享和保护
分段(Segmentation)
- 按逻辑单位(代码段、数据段等)划分
- 每个段大小可以不同
- 优点:易于共享和保护,符合程序逻辑
- 缺点:管理复杂,有外部碎片
段页式
- 结合分段和分页的优点
- 先分段,段内再分页
29. TLB(快表)
概念
Translation Lookaside Buffer,用于缓存页表项,加速地址转换。
工作原理
- CPU访问虚拟地址
- 先查TLB
- 如果命中,直接使用物理地址
- 如果未命中,查页表,更新TLB
TLB缺失处理
- 硬件处理:由MMU自动处理
- 软件处理:由操作系统处理(如MIPS)
30. 缺页中断
概念
当访问的页面不在内存中时,触发缺页中断。
处理流程
- 保存当前进程状态
- 查找页面在磁盘上的位置
- 分配物理页框
- 从磁盘读取页面到内存
- 更新页表
- 恢复进程执行
页面置换
如果内存已满,需要选择一个页面换出。
31. 工作集
概念
工作集是进程在一段时间内访问的页面集合。
工作集模型
- 基于局部性原理
- 进程的工作集大小相对稳定
- 可以预测进程的内存需求
应用
- 内存分配策略
- 页面置换算法
- 负载均衡
32. 抖动(Thrashing)
概念
系统频繁进行页面置换,导致CPU利用率下降。
原因
- 进程数量过多
- 分配给每个进程的物理页框过少
- 工作集大小超过可用内存
解决方法
- 减少进程数量
- 增加物理内存
- 使用工作集模型
- 使用局部置换策略
33. 文件描述符
概念
文件描述符是操作系统用来标识打开文件的非负整数。
标准文件描述符
- 0:标准输入(stdin)
- 1:标准输出(stdout)
- 2:标准错误(stderr)
文件描述符的限制
- 每个进程有文件描述符数量限制
- 可以通过ulimit命令查看和修改
- 系统级别也有总限制
34. 硬链接和软链接
硬链接(Hard Link)
- 指向文件的inode
- 删除原文件不影响硬链接
- 不能跨文件系统
- 不能链接目录
软链接(Symbolic Link)
- 指向文件路径
- 删除原文件后软链接失效
- 可以跨文件系统
- 可以链接目录
区别
| 特性 | 硬链接 | 软链接 |
|---|---|---|
| inode | 相同 | 不同 |
| 跨文件系统 | 否 | 是 |
| 链接目录 | 否 | 是 |
| 原文件删除 | 仍有效 | 失效 |
35. inode
概念
inode(index node)是文件系统中存储文件元数据的数据结构。
inode包含的信息
- 文件类型和权限
- 文件所有者
- 文件大小
- 时间戳(创建、修改、访问时间)
- 指向数据块的指针
inode的限制
- 每个文件系统有inode数量限制
- 创建文件需要分配inode
- inode用完后无法创建新文件
36. 系统负载(Load Average)
概念
系统负载表示系统在一段时间内的平均活跃进程数。
三个数值
- 1分钟平均负载
- 5分钟平均负载
- 15分钟平均负载
负载的含义
- 负载 < CPU核心数:系统空闲
- 负载 = CPU核心数:系统满负荷
- 负载 > CPU核心数:系统过载
查看方法
1 | uptime |
37. 僵尸进程和孤儿进程
僵尸进程(Zombie)
- 子进程已经结束,但父进程还没有回收
- 进程表中仍保留进程信息
- 占用进程表项,但不占用内存
孤儿进程(Orphan)
- 父进程已经结束,但子进程还在运行
- 会被init进程(PID=1)收养
- 不会造成资源泄漏
解决方法
- 父进程调用wait()或waitpid()回收子进程
- 使用信号处理SIGCHLD
- 使用双fork技术
38. 信号(Signal)
概念
信号是Linux系统中进程间通信的一种方式,用于通知进程发生了某个事件。
常见信号
- SIGHUP(1):挂起信号
- SIGINT(2):中断信号(Ctrl+C)
- SIGQUIT(3):退出信号
- SIGKILL(9):强制终止(不可捕获)
- SIGTERM(15):终止信号
- SIGSTOP(19):停止信号(不可捕获)
- SIGCONT(18):继续信号
信号处理
- 忽略信号
- 捕获信号并处理
- 执行默认动作
39. 守护进程(Daemon)
概念
守护进程是在后台运行的进程,不依赖于终端。
特征
- 父进程是init进程(PID=1)
- 没有控制终端
- 工作目录通常是根目录
- 文件描述符0、1、2指向/dev/null
创建步骤
- fork()创建子进程,父进程退出
- setsid()创建新会话
- 再次fork()避免获得控制终端
- 改变工作目录
- 重定向文件描述符
- 设置文件创建掩码
40. 内存映射(mmap)
概念
将文件或设备映射到进程的地址空间,可以直接通过内存访问。
优点
- 提高I/O效率
- 实现进程间共享内存
- 减少数据拷贝
应用场景
- 大文件处理
- 进程间通信
- 动态库加载
与read/write的区别
- mmap:将文件映射到内存,直接访问
- read/write:需要系统调用,有数据拷贝
41. 协程调度
概念
协程调度是指如何选择下一个要执行的协程。
调度方式
- 协作式调度:协程主动让出CPU
- 抢占式调度:由调度器决定何时切换
调度算法
- 轮询调度
- 优先级调度
- 多级反馈队列
42. 内存泄漏
概念
程序分配了内存但没有释放,导致可用内存逐渐减少。
检测方法
- 使用内存检测工具(valgrind、AddressSanitizer)
- 监控进程内存使用情况
- 代码审查
预防措施
- 及时释放不再使用的内存
- 使用智能指针(C++)
- 使用垃圾回收机制(Java、Python)
- 遵循RAII原则
43. CPU缓存
缓存层次
- L1缓存:最快,容量最小,每个核心独享
- L2缓存:较快,容量中等,每个核心独享
- L3缓存:较慢,容量较大,多个核心共享
缓存一致性协议
- MESI协议
- MOESI协议
- 目录协议
缓存命中率
- 缓存命中:数据在缓存中
- 缓存未命中:需要从内存读取
- 命中率越高,性能越好
44. NUMA(非统一内存访问)
概念
在多处理器系统中,每个CPU有本地内存,访问本地内存比访问远程内存快。
NUMA架构
- 每个NUMA节点有本地内存和CPU
- 跨节点访问内存有额外开销
- 需要考虑进程和内存的亲和性
优化策略
- 将进程绑定到特定NUMA节点
- 在进程所在的节点分配内存
- 使用numactl工具管理
45. 系统调用开销
开销来源
- 用户态到内核态的切换
- 参数传递和验证
- 上下文保存和恢复
- 返回用户态
优化方法
- 批量系统调用
- 使用更高效的系统调用(如epoll代替select)
- 减少系统调用次数
- 使用用户态库函数
46. 进程和线程的创建开销
进程创建开销
- 需要创建新的地址空间
- 需要复制页表
- 需要创建PCB
- 开销较大(通常几毫秒到几十毫秒)
线程创建开销
- 共享地址空间
- 只需要创建TCB(线程控制块)
- 开销较小(通常几微秒到几十微秒)
优化建议
- 使用线程池避免频繁创建
- 使用协程进一步减少开销
- 考虑使用进程池
47. 内存分配器
常见分配器
- glibc malloc:Linux默认分配器
- tcmalloc:Google开发,多线程性能好
- jemalloc:Facebook开发,减少碎片
分配策略
- 首次适应:使用第一个足够大的空闲块
- 最佳适应:使用最小的足够大的空闲块
- 最坏适应:使用最大的空闲块
内存碎片
- 外部碎片:内存中有很多小的空闲块
- 内部碎片:分配的内存块大于实际需要
48. 系统调用追踪
工具
- strace:追踪系统调用和信号
- ltrace:追踪库函数调用
- perf:性能分析工具
使用场景
- 调试程序问题
- 性能分析
- 安全审计
- 学习系统调用
示例
1 | # 追踪进程的系统调用 |
49. 进程间同步
同步原语
- 互斥锁(Mutex):保证互斥访问
- 信号量(Semaphore):控制资源访问数量
- 条件变量(Condition Variable):等待特定条件
- 读写锁(RWLock):区分读写操作
- 屏障(Barrier):等待所有线程到达
死锁预防
- 避免嵌套锁
- 按固定顺序获取锁
- 使用超时机制
- 死锁检测和恢复
50. 性能优化
CPU优化
- 减少系统调用
- 使用缓存友好的数据结构
- 避免false sharing
- CPU亲和性绑定
内存优化
- 减少内存分配
- 使用内存池
- 优化数据结构布局
- 减少内存拷贝
I/O优化
- 使用异步I/O
- 批量I/O操作
- 使用零拷贝技术
- 合理使用缓存
总结
操作系统面试问题涵盖了进程管理、内存管理、文件系统、同步机制等多个方面。掌握这些知识点不仅有助于面试,也有助于深入理解操作系统的工作原理。在实际工作中,这些知识对于系统性能优化、问题排查和架构设计都非常重要。
学习建议
- 理解基本概念和原理
- 结合实际代码和系统实践
- 关注Linux内核实现细节
- 多做练习题和实验
- 阅读相关源码和文档