操作系统内存管理详解:分页、分段、页面置换、Swap 与 OOM
内存管理的本质是把程序看到的虚拟地址和真实物理内存解耦:分页与页表负责映射和权限,TLB 负责给地址翻译提速,置换算法决定内存紧张时换谁出去,伙伴系统和 slab 则在内核里分管物理页的批发与零售。本文按”为什么需要、怎么映射、怎么置换、怎么分配”的顺序把这条链路讲透。 它解决什么问题早期系统里程序直接操作物理地址,单程序还能凑合,多道程序一并发立刻暴露三个硬伤。 隔离失效:程序 A 往物理地址 1000 写数据,程序 B 恰好也把变量放在 1000,互相覆盖;更危险的是普通程序可能踩到内核自己的内存,系统稳定性无从谈起。 无法重定位:程序编译时不知道会被装进物理内存哪一段,地址一旦写死,换个装载位置就跑不了,更别说同时跑多个实例。 无法超售:物理内存就那么大,但程序总想要一大片连续空间,还希望”用到的才真的占内存”。 解法是引入虚拟地址空间:每个进程独占一套连续地址,落到哪些物理页由操作系统与 MMU 共同决定。隔离、重定位、超售三个目标,全部建立在这层映射之上。 核心原理:分段与分页怎么切地址空间分段(Segmentation)按程序逻辑切:代码段、数据段、栈段各成一...
进程间通信(IPC)详解:管道、消息队列、共享内存、Socket 与 Binder
每个进程拥有独立的虚拟地址空间,进程之间要交换数据或协调动作,必须借助内核提供的 IPC 机制。本文对比管道、FIFO、消息队列、共享内存、信号量、信号和 Socket 七种方式的差异,并解释共享内存与信号量这对经典组合的分工。 为什么进程间通信必须借助内核虚拟内存让每个进程活在自己的地址空间里:两个进程中相同的虚拟地址,映射的是不同的物理页。A 进程写的变量,B 进程根本看不见。隔离保证了安全与稳定,但也意味着进程要协作时只能靠内核出面:要么内核代收代发数据(管道、消息队列、Socket),要么内核建立一份双方都能访问的共享对象(共享内存)。 七种 IPC 方式逐个看管道与命名管道管道是内核里的一段缓冲区,表现为单向字节流。pipe() 返回读、写两个文件描述符,父进程 fork() 后子进程继承描述符,因此匿名管道天然适合父子等有亲缘关系的进程,Shell 里的 ps aux | grep java 走的就是它。命名管道(FIFO)用 mkfifo 在文件系统里建一个特殊文件,没有亲缘关系的进程按路径打开即可通信——路径只负责命名,数据仍在内存缓冲区,并不落盘。两者都不保...
中断、异常与系统调用详解:从内核入口到缺页异常
中断、异常、系统调用都会把 CPU 从用户态送进内核,但触发来源完全不同:一个是外设发来的异步通知,一个是当前指令闯的祸,一个是程序主动请求特权服务。这篇把三者放在同一张地图上对比,再以 read() 为例走完系统调用的完整路径,说清它为什么比普通函数调用贵,以及 vDSO 这类优化省掉了什么。 它解决什么问题CPU 平时的职责是按程序计数器顺序执行用户指令,但有三类事情必须打断这条指令流:外设有进展要汇报(网卡收到包、磁盘读写完成、定时器到点);当前指令执行不下去(除零、访问未映射的地址、执行非法指令);程序要办自己没权限办的事(读文件、发网络包、创建进程——这些都涉及硬件与全局状态)。 三类事情对应三个内核入口:中断、异常、系统调用。没有这套机制,要么用户程序用不了任何硬件,要么谁都能直接操作硬件,系统谈不上任何隔离与稳定。 核心原理三者的本质区别在触发来源与同步性: 中断是异步的:来自 CPU 之外,与当前正在执行哪条指令毫无关系。你的 Java 线程跑得好好的,网卡随时可能收到一个包。 异常是同步的:当前指令自己引发的。除零、缺页、非法地址,问题就出在这条指令身上,...
操作系统文件系统详解:inode、VFS、Page Cache 与日志机制
文件系统把块设备组织成目录和文件,回答”名字怎么找到数据、数据存在哪、崩溃后怎么恢复”。本文讲清 inode 与 dentry 的分工、硬链接与软链接的本质差异、VFS 抽象层、Page Cache 与 mmap 的关系,以及 ext4、XFS、Btrfs 三种常见文件系统的定位。 文件系统在管什么对应用来说,文件是路径加字节流;对磁盘来说,只有一堆编号的数据块。文件系统夹在中间,负责四件事:命名(路径到对象的映射)、组织(目录树)、定位(文件第 N 个字节对应哪个块)、保护(权限与所有者)。理解这四件事,inode、dentry、Page Cache 这些名词就各就各位了。 inode 与 dentry:名字和实体分离inode(索引节点)是文件系统对象的身份证,记录类型、权限、所有者、大小、时间戳和数据块位置,唯独不记文件名。文件名存在目录里——目录本质上是一种特殊文件,内容就是”文件名 → inode 号”的映射表。 dentry(目录项)则是内核在内存里建的查找缓存:路径 /var/log/app.log 每解析一级就生成一个 dentry,指向对应 inode,下次...
死锁详解:四个必要条件、Java 死锁排查与数据库死锁处理
死锁是一组执行单元互相等待对方手里的资源,等待关系绕成一个环,环上谁也走不下去。它最阴险的地方是程序没错、CPU 不忙,线程只是安静地卡住。本文把四个必要条件、三类应对策略和线上排查手段一次讲透。 它解决什么问题严格说死锁不是被”解决”的问题,而是需要被”理解和管理”的状态。典型画面:线程 A 握着订单锁去申请库存锁,线程 B 握着库存锁去申请订单锁,两边都在等对方先放手,而对方也在等自己——没有异常、没有超时、没有高 CPU,只有接口永远不返回。 普通阻塞和死锁的分水岭在于有没有环。普通阻塞等的是一个终将到来的事件:锁会释放、I/O 会返回。死锁里每个等待者本身就是环上另一方的绊脚石,靠时间永远解不开。进程、线程、数据库事务、分布式锁,只要资源独占且允许持有后继续等待,都会踩进同一个形状。 核心原理:四个必要条件缺一不可死锁发生必须同时满足 Coffman 提出的四个条件,少任何一个,环就闭不上: 互斥:资源在同一时刻只能被一个持有者占用。库存行、对象监视器、文件写锁天然如此,这是资源本身的属性。 占有且等待:已经拿着至少一份资源,同时还在申请新的资源。只拿一把...
CPU 调度与系统负载详解
CPU 调度回答的是”有限的核心上,下一个让谁跑”的问题,load average 回答的是”此刻有多少任务在等”的问题。这篇从调度目标的取舍讲起,过一遍经典算法与 Linux CFS 的公平思想,最后落到线上最常见的那类怪象:负载很高,CPU 却闲着。 它解决什么问题一台机器的逻辑 CPU 数量是固定的,想跑的任务却远不止这个数:业务线程、GC 线程、JIT 线程、网络事件循环、日志采集 Agent……8 核机器同一时刻最多只有约 8 个任务真正占用 CPU,其余全部排队。调度器就是决定”下一个谁上”的角色。 难办的是,调度追求的目标互相打架: 吞吐量:单位时间完成更多任务,希望少切换、单个任务一次跑久一点; 响应延迟:交互任务要尽快被看到,希望切换勤快、能及时抢占长任务; 公平性:不能让低优先级任务永远挨饿。 时间片拉长,吞吐上去了,交互卡顿;时间片缩短,响应好了,CPU 却把时间浪费在切换上。不存在三者同时拉满的策略,所有调度算法本质上都是在这个三角里选位置。 核心原理任务离开 CPU 只有两大类原因。一类是主动让出:调 read 等数据、等锁、等定时器,线程进入睡...
Shell 编程基础知识总结
Shell 是用户与内核之间的命令解释器,也是把小程序拼成数据流的脚本语言。本文讲清变量与环境变量、管道与重定向背后的 fork/exec/dup2 机制、find+xargs 与 grep+awk 两组经典组合,以及脚本调试的基本功。 Shell 的定位内核不认识 ls -l | grep log 这样的字符串。Shell 站在用户与内核之间:读入命令行,做变量替换、通配符展开、参数切分,然后调用 fork、exec 等系统调用把程序跑起来,再把结果交回终端。交互时它是命令解释器,写进文件就是脚本语言——变量、条件、循环、函数一样不缺。常见实现有 bash(主流默认)、sh(POSIX 标准)、zsh、dash 等;写脚本第一行用 #!/bin/bash 声明解释器。 变量与环境变量Shell 变量分两类。普通变量 name="value" 只活在当前 Shell 进程里,子进程看不到;export PATH="$HOME/bin:$PATH" 导出的环境变量才会随 fork 被子进程继承——PATH、HOME 都...
进程与线程详解:区别、状态、通信、上下文切换与虚拟线程
一句话概括这对概念:进程是操作系统分配资源、隔离故障的单位,线程才是真正被调度器放到 CPU 上跑的单位。本文顺着”为什么需要这两层抽象、切换贵在哪、协程解决了什么、工程上怎么选”这条线展开。 它解决什么问题一台机器只有几颗 CPU,却要同时跑几十上百个程序。操作系统引入进程,本质是给每个运行中的程序圈出一块独立领地:各自的虚拟内存、文件句柄、用户权限互不可见,一个程序崩溃不会拖垮邻居。 但光有进程还不够。一个 Web 服务器内部要同时处理成千上万的连接,如果每个连接都开一个进程,内存和切换开销都扛不住,进程之间共享一份缓存还得走 IPC。于是有了线程:在同一个进程的领地里跑多条执行流,共享全部资源,协作成本大幅降低。进程解决”程序之间”的隔离与多路复用,线程解决”程序内部”的并发。 核心原理:资源归进程,执行归线程判断一样东西属于哪一层,只需问一句:它是资源,还是执行现场? 进程持有资源:虚拟地址空间(代码段、数据段、堆、映射区)、打开的文件描述符与 Socket、用户 ID 与权限、信号处理表、环境变量。 线程私有执行现场:独立的栈(函数调用链、局部变量)、寄存器快照、...
操作系统专题:进程线程、内存管理、文件系统、I/O 多路复用、Linux 与 Shell
操作系统知识点多而散,这张学习地图把它收成四组主题:程序运行、内存管理、持久化与 I/O、系统实践。每组标注依赖关系和推荐顺序,每个主题配一句话说明它解决什么问题。 操作系统在学什么操作系统夹在应用与硬件之间:向上提供进程、虚拟内存、文件、Socket 这些抽象,向下管理 CPU、内存、磁盘和设备。它的核心目标不是让资源看起来无限,而是在有限硬件上做好共享、隔离、保护与可观测。学习它的主线不是背定义,而是回答一个问题——一条请求从代码到硬件,中间经过哪些状态转换、各花什么代价。所有主题都是这条主线上的站点。 主题分组第一组:程序如何运行 进程与线程:进程是资源与隔离的边界,线程是 CPU 调度的执行流。 中断、异常与系统调用:用户态进入内核态的三扇门,理解一次 read() 到底经历了什么。 CPU 调度与负载:调度器决定谁先跑,load average 告诉你队伍排了多长。 锁与同步:临界区、互斥锁、信号量、条件变量,解决并发访问的秩序问题。 死锁:循环等待的成因、四个必要条件与排查方法。 进程间通信:地址空间彼此隔离之下,数据怎么过墙。 第二组:内存如何管理 ...
操作系统常见面试题总结(上)
操作系统面试上篇聚焦四条主线:CPU 特权级与系统调用、进程与线程的边界、调度与负载、死锁。这组题几乎是每场后端面试的开场题,答好的关键不是背定义,而是讲清机制、触发条件和代价。 考察地图 特权级与系统调用:用户态/内核态为何存在、系统调用的完整过程、系统调用与上下文切换的关系。考察对 CPU 特权级和内核入口机制的理解。 进程与线程:二者区别、进程状态转换、上下文切换成本。考察对「资源边界」与「执行现场」的划分是否清晰。 调度与负载:经典调度算法的取舍、load average 与 CPU 使用率的分辨。考察调度器权衡思路和线上指标解读能力。 死锁:定义、四个必要条件、四类处理策略。考察并发推理能力。 建议自行查阅:操作系统的定义与六大功能、有了进程为什么还需要线程、PCB/TCB 字段清单、线程同步方式(互斥锁/读写锁/信号量/条件变量)、IPC 选型、fork/exec/wait 语义、手写死锁 Demo——多为记忆或编程练习,面试中通常只作铺垫。 特权级与系统调用什么是用户态和内核态?为什么要区分...