1. 死锁发生的条件是什么?
多个线程同时持有某些资源并请求对方占有的资源,而且不释放已有资源、互相等待对方释放,形成一个资源环路,就会导致死锁。
- 互斥条件:资源一次只能被一个线程占用,比如锁、文件等不可共享资源。
- 占有且等待:线程持有已有资源的同时,阻塞等待其他资源不释放。例如线程 A 锁住 L1,又去请求 L2。
- 不可剥夺:已经获得的资源不能被强行剥夺,只能由线程主动释放。
- 循环等待:多个线程之间形成资源的循环等待链。例如 T1 等 T2 释放资源,T2 又等 T1 释放资源。
2. 如何避免死锁?
避免死锁的关键是破坏死锁的四个必要条件中的一个或多个。最
核心做法是:使用资源有序分配、避免循环等待。使用 tryLock 等方式避免无限阻塞;减少锁粒度,能不用锁的尽量不用;或者设置超时机制,防止线程永久卡死。
3. 介绍下操作系统内存管理
操作系统的内存管理(Memory Management)是指操作系统对主存(RAM)进行分配、使用、回收和保护的过程。通过地址映射、分段、分页和虚拟内存等机制,为每个进程提供独立、安全且高效的内存使用环境。它将程序使用的逻辑地址转换为物理地址,采用分页方式划分内存以避免碎片,通过页表和 TLB 实现高效映射。虚拟内存允许进程使用超过物理内存的空间,并结合缺页中断与页面置换算法实现按需加载。当内存不足时,系统可将部分内存页换出到磁盘(Swap)。同时,操作系统通过访问控制保护各进程间的内存隔离,保障系统稳定和安全。
内存管理主要负责为进程分配和回收内存空间,同时提供地址转换、内存保护和虚拟内存机制,以支持多线程并发执行、资源隔离和内存高效利用。常见机制包括分页、分段、虚拟内存、交换空间等。
- 内存地址空间:逻辑地址 VS 物理地址
- 逻辑地址(虚拟地址):程序员写代码时使用的地址,从 0 开始。
- 物理地址:真实的内存地址(RAM 中的地址)。
- 操作系统使用 内存管理单元(MMU) 将逻辑地址转换为物理地址,保护进程之间的内存不互相干扰。
- 内存分配方式
- 连续内存分配:将内存一整块连续分配给进程,适用于早期系统。容易造成外部碎片。
- 分区分配:将内存划分成若干个区域(分区),动态为进程分配分区。
- 静态分区:大小固定,适合批处理
- 动态分区:运行时动态划分,但仍会造成碎片
- 分段:程序按逻辑结构分段,如代码段、数据段、堆段、栈段。每段独立分配,可以提升安全性与灵活性。容易产生碎片,需要段表支持。
- 分页:将物理内存和逻辑地址空间都划分成固定大小的“页”。
- 页表记录逻辑页到物理页框的映射
- 消除外部碎片,提高内存利用率
- 增加了地址转换开销(用 TLB 缓解)
- 虚拟内存机制(核心):程序使用比物理内存更大的空间,按需加载内存(只加载正在使用的部分),内存不足时使用磁盘作为后备(Swap)。实现依赖:
- 页表:记录页与帧的映射
- TLB:缓存常用页表项,加快转换速度
- 缺页中断:访问未加载页时触发,操作系统会调入磁盘中的页面
- 页面置换算法(如 LRU、FIFO、Clock):决定淘汰哪一页
- 内存保护与隔离
- 操作系统保证:进程 A 无法访问进程 B 的地址空间
- 方法:
- 每个进程有自己的页表,页表中配置访问权限(读/写/执行)
- CPU 提供用户态/内核态隔离机制,防止非法访问内核地址
- 内存回收
- 当一个进程结束,操作系统负责清空该进程占用的内存页,释放页表、堆栈等相关资源。
- 对于暂时不活跃的页,操作系统可使用页置换算法将其换出到磁盘(Swap),后续访问再从磁盘换入。
- 分页 + 分段:现代系统一般同时结合分页和分段,段控制逻辑结构(比如用户/内核栈等),页控制物理存储映射(页表转换)。
4. 介绍下copy on write?
写时复制是一种操作系统内存优化机制,常用于
fork()
创建子进程时。fork 时,操作系统并不会立即复制父进程的整块物理内存,而是让子进程共享父进程的物理页面,并将这些页面标记为只读。这样可以节省大量内存开销和复制时间。但一旦父或子进程尝试写这些共享页面,CPU 会触发写保护异常,此时操作系统才真正复制对应的物理页面,并解除只读限制,从而保证两者互不干扰。这个“延迟复制”的过程就是写时复制。它的意义在于:大多数 fork 后的子进程会立即调用 exec 替换自己的地址空间,这样写时复制就避免了完全没必要的内存复制,大幅提升了资源利用率和系统性能。
fork() 之后父子进程通过独立页表共享同一块物理内存,只有在写操作发生时才复制内存,从而节省内存、提升性能。
若父或子进程尝试写入:
- 写入操作触发“写保护”异常(因为是只读)
- 操作系统捕获异常后,会:
- 分配新的物理内存块
- 复制旧内存的数据到新块
- 修改写入进程的页表指向新内存,并设置为可写
- 执行真正的写操作
这样,父子进程各自拥有了独立的内存副本,实现“写时复制”。