故事大纲(45集微故事版)
核心设定
- 作品信息:书名《1990:种下那棵不落叶的树》,文艺副标题《代码山河》;
- 核心意象:以"树"【Linux】为核心,根系对应内核、枝干对应子系统、叶对应应用,"不落叶"寓意开源精神的永续;
- 主角设定:叶知秋,32岁,名字取"一叶知秋"之意,暗含"从微小预知变革"的内涵;
- 关键节点:穿越至1990年春天玉兰花刚开的北京;
- 个人特质:随身携带《庄子》,以"庖丁解牛"喻内核优化;调试代码间隙爱吹口琴,最爱《莫斯科郊外的晚上》;厨艺精湛,常以"算法如做菜,火候即时机"教导学生;口头禅为"代码要写得像散文,机器能懂,人也能读"。
第6集 圆明园的对话
散步时讨论"永恒的系统"。叶知秋说内核应如石础,沈书影说文档应如碑文。
本集播客 : 播客地址
下面是我个定制:
《1990:种下那棵不落叶的树 》两个版本的主题曲(大家评选一下):


第6集:圆明园的对话
一、开篇:虚拟内存的第一次"窒息"
场景:1990年4月18日,清晨。北大机房。
屏幕上的错误信息像一记闷棍:
[内核恐慌]
原因:内存耗尽 (Out of Memory)
位置:kmalloc() @ memory.c:127
可用物理页:0
请求大小:4096 字节
叶知秋盯着这行字,已经二十分钟没动。
新来的两个实习生------清华的吴锐和中科院的李静------站在他身后,大气不敢出。
吴锐,21岁,瘦高,眼镜片厚得像瓶底,昨天刚报到就改了三处算法,把内存拷贝速度提升了15%。李静,22岁,马尾辫,说话轻声细语,但昨晚独自搞定了打印机的并口驱动。
此刻,连最聪明的吴锐也束手无策。
"问题在编辑器。"吴锐说,"用户打开大文件时,我们一次性把整个文件读进内存。沈叔叔打的诗只有两行,但如果有人想编辑《红楼梦》......"
"不可能。"叶知秋打断,"20MB硬盘装不下《红楼梦》。"
"但问题本质一样。"李静轻声说,"物理内存只有2MB可用(去除内核占用),一个大点的程序就占满了。剩下的程序怎么办?"
这正是"内存耗尽"的根源:叶知秋写了一个简单的命令行解释器(shell),用户可以在里面启动多个程序。但程序退出后,内存并未完全释放------他还没有实现完善的内存回收机制。
更本质的问题是:物理内存就是不够用。
"Unix的解决方案是虚拟内存。"吴锐翻着那本Unix源码注释,"把暂时不用的内存页面换出到磁盘,需要时再换入。"
"我们需要硬盘交换分区。"李静说,"但我们的硬盘只有20MB,系统占5MB,用户数据占5MB,剩下10MB能做交换空间------够用吗?"
叶知秋没回答。他看向窗外,梧桐树新叶在风中翻动,像无数页被风快速翻阅的书。
"今天不写代码。"他忽然站起来,"去圆明园。"
两个实习生愣住。沈书影刚从医院过来,也愣住了。
"去......圆明园?现在?"
"对。"叶知秋已经关掉显示器,"有些问题,在机房想不明白。"
二、废墟上的隐喻
圆明园遗址,下午两点。春日暖阳照在残破的大水法石柱上,游人不算多。
五个人------叶知秋、沈书影、吴锐、李静,还有坚持要来的沈国栋(坐在轮椅上,由沈书影推着)------走在荒草与石砾之间。
"为什么来这儿?"吴锐忍不住问,"我们的问题很急......"
"因为这儿有答案。"叶知秋停在一片废墟前。这里曾是一座楼阁,现在只剩地基的轮廓和几块雕花石阶。
沈国栋让女儿停住轮椅,他伸手抚摸一块汉白玉残石,上面有精美的莲花纹样。
"这是'海晏堂'的基址。"老人缓缓说,"当年,这里有十二生肖喷泉,每个时辰一个生肖喷水。西洋机械,中国意象。"
他指向远处:"那边是'方外观',曾经的回族礼拜堂。再那边是'万花阵',西洋迷宫。"
"都烧了。"吴锐说,"1860年,英法联军。"
"对。"沈国栋说,"但你看,地基还在。柱子倒了,梁烧了,瓦碎了------但地基的网格还在。"
叶知秋眼睛一亮。
他蹲下来,用手指在泥土上画图:
假设这是一块物理内存:
[柱础][柱础][柱础][柱础]...
每个柱础代表一个物理页面(比如4KB)
当年,建筑是这样布局的:
柱础A → 支撑"海晏堂"的东柱
柱础B → 支撑"海晏堂"的西柱
柱础C → 支撑"方外观"的主柱
"但建筑烧毁后,"叶知秋继续画,"我们站在这里,看着这些柱础,还能在脑子里重建当时的布局------哪些柱子属于哪座建筑,哪条走廊连接哪个厅堂。"
沈书影明白了:"你的意思是......物理内存就像这些柱础,是实际存在的。而建筑的'布局',是虚拟的,存在人脑子里的?"
"不。"叶知秋站起来,"存在图纸里。图纸记录着每个柱础属于哪座建筑,每面墙在哪里。有了图纸,即使建筑倒了,也能按原样重建。"
他看向两个实习生:"页表(Page Table)就是图纸。它记录着:虚拟地址0x1000 → 物理页面#3,虚拟地址0x2000 → 物理页面#7......"
第一个知识点:虚拟内存的核心思想
吴锐迅速理解:"所以程序以为自己拥有连续的、从0开始的地址空间,实际上它的页面可能分散在物理内存各处?甚至有些页面暂时被'换出'到磁盘------就像有些建筑构件被搬到仓库保存?"
"对。"叶知秋说,"而且更关键的是:每张图纸(每个进程的页表)是独立的。程序A的虚拟地址0x1000可能映射到物理页面#3,程序B的虚拟地址0x1000可能映射到物理页面#9。它们互不知晓。"
李静补充:"这解决了两个问题:内存隔离(一个程序崩溃不会影响其他程序),和地址空间碎片化(程序看到的是连续空间)。"
沈国栋忽然说:"这就像......印刷厂的版样库。"
所有人都看向他。
"铅字有限,但书稿无穷。"老人解释,"老师傅会把常用版样(比如'第一章''第一节'的固定排版)做成模板,存在库房。需要时取出来,填上具体内容。一本大书可能要几十个模板轮换使用。"
他顿了顿:"库房大小有限(物理内存),但通过轮换模板,能排印比库房大得多的书(虚拟地址空间)。"
叶知秋怔住了。这个比喻比任何教科书都精准。
模板轮换 = 页面置换
版样库 = 物理内存
书的排版计划 = 页表
沈书影看着父亲,眼睛湿润。这个只读过小学的印刷工人,用最朴素的语言,点破了计算机科学最核心的抽象之一。
三、第二个知识点:缺页中断------当记忆需要被唤起
他们走到"大水法"遗址前。巨大的石门矗立着,但顶部的雕塑早已不见。
"这里原本有鹰嘴喷泉。"沈书影读过资料,"水流从鹰嘴喷出,落入下面水池。但现在,只有石头。"
吴锐忽然问:"如果我们要重建这个喷泉,但发现鹰嘴雕塑不在现场------它可能存放在某个仓库,或者已经损毁------怎么办?"
"去仓库找。"李静说,"或者重新雕刻一个。"
"对。"叶知秋接口,"在虚拟内存里,这叫做缺页中断(Page Fault)。"
他详细解释:
- 程序访问虚拟地址V
- CPU查页表,发现该地址对应的页表项显示"不在内存中"(Present位=0)
- CPU触发缺页中断,操作系统接管
- 操作系统检查:这个页面被换出到磁盘了?还是程序第一次访问?
- 从磁盘读取页面到空闲物理页框
- 更新页表,标记为"在内存中"
- 重新执行那条指令
"这会产生性能开销。"吴锐说,"就像重建喷泉需要时间。"
"所以需要优化。"叶知秋说,"比如,预读------程序访问第5页时,操作系统推测它很快会访问第6页,提前加载。就像看到游客在研究大水法图纸,就提前把旁边的'观水法'遗址资料也准备好。"
沈国栋听着,忽然说:"铅字排版也有类似情况。排一部长篇小说,师傅会让徒弟提前把下一章可能用到的生僻字从高架上取下来,放在手边。"
预取(Prefetching)------这个现代处理器的重要优化,在印刷厂里已经实践了几百年。
李静认真记笔记,然后问:"但如果物理内存真的满了,没有空闲页框装新页面呢?"
"就要淘汰一个旧页面。"叶知秋说,"选择哪个淘汰?这就是页面置换算法。"
他讲了几种经典算法:
- FIFO(先进先出):最早进来的页面先淘汰。简单但可能淘汰常用页面。
- LRU(最近最少使用):淘汰最久没被访问的页面。效果更好但需要硬件支持记录访问时间。
- Clock(时钟算法):近似LRU的实现,像钟表指针循环检查。
吴锐立刻想到优化:"我们的80386没有硬件记录访问时间,但MMU(内存管理单元)的页表项里有个'访问位'(Accessed bit)------每次读/写页面,CPU自动置位。我们可以用Clock算法!"
沈书影虽然不懂技术细节,但她抓住了本质:"所以,这其实是一种......记忆的选择性保存?哪些记忆要留在身边,哪些可以暂时封存,哪些可以遗忘?"
所有人都沉默了。
废墟的风吹过,石柱的影子在地上缓慢移动。
四、生活的启示:故宫的"记忆宫殿"
第二天,沈书影带大家去了故宫------不是游客常走的三大殿,而是西北角的"英华殿"区域,那里正在举办"故宫文物南迁史料展"。
1933年,日军侵华前夕,故宫将一万三千多箱文物南迁,辗转上海、南京、重庆、乐山......抗战胜利后又运回北京。
"这里展出的不是文物本身,"沈书影说,"是文物搬迁的记录。"
展柜里,泛黄的清单上写着:
箱号:京字第1372号
内容:宋徽宗《听琴图》轴
1937年11月3日离京
1938年2月抵达长沙
1944年6月转移至四川巴县
1947年9月返京
现状:完好,藏故宫绘画库
还有手绘的装箱图:每件文物如何包裹、如何固定、箱子内部如何分隔。
吴锐看着这些,忽然说:"这......这不就是页表吗?虚拟地址(文物在故宫的原位置)到物理位置(文物在迁徙途中的实际位置)的映射记录!"
李静补充:"而且有'状态位'------'在运输中''在重庆仓库''已返京'。就像页面的'在内存/在磁盘/无效'状态。"
叶知秋站在一张巨大的迁徙路线图前,久久不语。
地图上,红色箭头从北京出发,分三条路线南下,在战火中蜿蜒,最终汇聚重庆,战后北归。箭头旁边标注着时间、负责人、运输工具(火车、汽车、轮船、甚至人挑肩扛)。
这就是一个民族文化的"虚拟内存系统"。
物理载体(文物)在战乱中颠沛流离,但记录系统(清单、图纸、档案) 始终保持完整。正是靠这套"元数据系统",上万箱文物散而复聚,文明记忆得以存续。
"我明白了。"叶知秋轻声说,"虚拟内存技术,不只是为了解决物理内存不够用。它是一种记忆的存续策略------如何在有限的、易失的物理载体上,保存远超载体容量的记忆,并在灾难后完整重建。"
沈书影看着他:"就像圆明园。建筑烧了,但如果当年的设计图纸还在,如果每个构件的尺寸、材质、位置都有记录......理论上可以重建。"
"但图纸也不在了。"吴锐说,"大部分被烧了,小部分流散海外。"
"所以,"沈国栋缓缓说,"图纸本身也需要备份。就像故宫文物,清单本身也是文物,也要保护。"
这句话点醒了叶知秋。
页表本身也需要内存存储! 如果一个进程有4GB虚拟地址空间(2^20个4KB页面),每个页表项4字节,页表就要占4MB内存------这比很多程序本身还大!
"多级页表。"他脱口而出,"就像......图书馆的目录不在一本大书里,而是分层:总目录→分馆目录→书架目录→层架目录。"
80386正是用二级页表:页目录(Page Directory)指向页表(Page Table),页表指向物理页面。这样,不需要为整个地址空间分配页表,只需要为实际使用的区域分配。
"又回到了'目录树'。"沈书影微笑,"文件系统是目录树,虚拟内存也是目录树。计算机科学,就是不断发明新的树来整理世界。"
五、第三个知识点:实现简单的虚拟内存
回到机房,叶知秋带领两个实习生开始实现。
第一步:启用80386的分页机制。
c
// 设置页目录和页表
void init_paging() {
// 1. 创建页目录(4KB对齐)
uint32_t *page_dir = (uint32_t *)alloc_aligned(4096, 4096);
// 2. 创建第一个页表(映射低4MB物理内存)
uint32_t *page_table = (uint32_t *)alloc_aligned(4096, 4096);
// 填充页表:每个页表项控制4KB页面
for (int i = 0; i < 1024; i++) {
// 页表项格式:物理地址(20位) | 标志位(12位)
// 标志位:P(存在)=1, RW(可读写)=1, US(用户模式)=0
page_table[i] = (i * 0x1000) | 0x03; // 0x03 = 011b (P=1,RW=1,US=0)
}
// 3. 页目录第0项指向这个页表
page_dir[0] = (uint32_t)page_table | 0x03;
// 4. 其余页目录项暂时标记为不存在
for (int i = 1; i < 1024; i++) {
page_dir[i] = 0x02; // 0x02 = 010b (P=0,RW=1,US=0)
}
// 5. 加载页目录地址到CR3寄存器
asm volatile("mov %0, %%cr3" : : "r"(page_dir));
// 6. 启用分页(设置CR0的PG位)
uint32_t cr0;
asm volatile("mov %%cr0, %0" : "=r"(cr0));
cr0 |= 0x80000000; // PG位(第31位)=1
asm volatile("mov %0, %%cr0" : : "r"(cr0));
}
但这就够了吗?不。这只是"恒等映射"------虚拟地址=物理地址,没有解决内存不足问题。
需要实现页面置换。叶知秋设计了一个简单的交换分区机制:
- 在硬盘末尾划出4MB作为交换空间
- 维护一个"物理页面状态表",记录每个物理页框:
- 被哪个进程占用
- 对应哪个虚拟页面
- 是否被修改过(脏位)
- 是否可换出
- 当需要分配新页面但无空闲页框时:
- 用Clock算法选择一个"牺牲页面"
- 如果页面被修改过,写回交换分区
- 标记该页表项为"不在内存中",记录交换分区位置
- 清空物理页框,分配给新页面
吴锐负责Clock算法的实现,李静负责交换分区的磁盘读写。
三天后,基础框架完成。
测试:运行一个"内存吞噬者"程序,它不断申请内存,填写数据。
# 运行前:可用物理内存 1.5MB
# 运行吞噬者,申请 2MB
[系统] 物理内存不足,开始页面置换...
[系统] 换出页面: 进程1, 虚拟页0x1000 -> 交换分区扇区 0x200
[系统] 换出页面: 进程1, 虚拟页0x2000 -> 交换分区扇区 0x208
...
[系统] 吞噬者程序运行完成,释放内存
[系统] 页面自动换回
成功了!系统第一次突破了物理内存的限制。
虽然速度慢------每次缺页都要读硬盘,机械硬盘寻道时间就需10毫秒。但能运行了,这比什么都重要。
六、第四个知识点:共享内存------共同的记忆
但沈书影提出一个问题:"如果我父亲在编辑器里打了一首诗,我想在旁边开一个'朗读程序'把诗读出来,朗读程序需要重新把诗文件读进内存吗?"
"理论上要。"叶知秋说,"两个进程的内存空间是隔离的。"
"但这样浪费内存。"沈书影说,"而且......不'共享'。父亲打的字,应该能被多个程序看到,就像一首诗可以被多人同时阅读。"
共享内存------这是虚拟内存系统的高级特性。
实现原理:两个进程的页表项指向同一个物理页框。这样,一个进程修改内容,另一个进程立刻能看到。
但这带来同步问题:如果两个进程同时修改怎么办?
"需要锁。"吴锐说,"就像图书馆,一本书只能一个人借出修改,其他人只能阅读。"
"或者,"李静想到另一种方案,"采用写时复制(Copy-on-Write)。最初共享,当某个进程要修改时,才复制一份给它单独修改。这样节省内存,又保证安全。"
叶知秋惊讶地看着两个实习生。他们正在快速成长。
他决定先实现简单的共享内存:只读共享。用于系统库(如C标准库的代码段),多个进程可以共享同一份代码副本,节省大量内存。
实现方法:
- 标记某些页面为"共享"
- 这些页面永远不会被换出(常驻内存)
- 多个进程的页表项指向同一个物理页框
- 页表项标记为只读,如果进程尝试写入,触发保护异常
测试时,他们让编辑器和简单的"文件查看器"同时打开同一首诗。用内存查看工具确认:两个进程的页表项确实指向同一个物理页框。
沈国栋看着屏幕,忽然说:"这像不像......同一个铅字,可以印在无数张纸上?字模是共享的,但印出的每个字是独立的。"
"不完全一样。"叶知秋解释,"如果是只读共享,就像多人传阅同一本书。如果是写时复制,就像复印机------平时不复印,只有当有人要在书上做笔记时,才给他复印一本单独的。"
老人点点头,眼里有光:"机器里的字,比铅字更......活。"
七、圆明园的黄昏
项目阶段性成功的那天傍晚,叶知秋和沈书影又去了圆明园。
这次只有他们两人。
夕阳把废墟染成金色。他们站在"海晏堂"基址上,脚下的青砖缝隙里长出嫩草。
"虚拟内存实现了。"叶知秋说,"但我们又欠了债------页面置换算法还不够聪明,共享内存还没做读写支持,还有......"
"叶知秋。"沈书影打断他,"看着我。"
他转头。
"你还记得我们来这里的第一天吗?"沈书影轻声说,"你在地上画柱础图,讲页表。那时你眼睛里有光,就像现在。"
她顿了顿:"但那时候,你是在创造 。现在,你只是在修补。"
叶知秋愣住了。
"我父亲常说,"沈书影继续,"最好的印刷师傅,不是能把字排得最整齐的,是能看出'这页应该空一行''这个字应该大一号'的人。他懂得留白 ,懂得节奏。"
她指向废墟:"你看这些石头。它们当年被设计在这里,不只是为了承重。柱子之间的距离,门窗的比例,台阶的级数------都有韵律。那是建筑师心里的'虚拟建筑',比石头本身更重要。"
叶知秋忽然懂了。
"你是说......操作系统设计,不应该只关注'功能是否实现',还要关注'是否优雅'?是否......有韵律?"
"对。"沈书影微笑,"就像虚拟内存。它不只是让大程序能运行的技术。它是一种承诺------承诺每个程序都有自己完整的世界,承诺记忆可以被安全地保存和传递,承诺有限可以创造无限。"
她捡起一块碎瓦,上面有蓝色的琉璃釉:"就像这片瓦。它曾经是屋顶的一部分,为下面的人遮风挡雨。现在碎了,但釉色还在阳光下发光。"
"虚拟内存......"叶知秋喃喃道,"其实是给每个程序一个'完整的屋顶',即使物理上不可能。"
"也给我们自己。"沈书影轻声说,"给我们这些创造者,一个可以安放理想的'虚拟空间'------在那里,技术不必完美,但必须温柔;不必强大,但必须公正。"
夕阳完全沉下,废墟陷入深蓝的暮色。远处的路灯一盏盏亮起。
"该回去了。"叶知秋说,"吴锐和李静还在等代码审查。"
"等等。"沈书影从包里拿出一个笔记本,撕下一页,用钢笔写下几行字,折好递给他,"回去再看。"
八、笔记本上的话
回到机房,已是深夜。
吴锐和李静还在调试一个页面置换的边界条件bug。叶知秋让他们先回去休息。
等机房里只剩他一人,他打开那张纸。
沈书影的字迹工整清秀:
叶:
今天在圆明园,你说"页表是废墟的重建图纸"。
我想说的是:图纸固然重要,但比图纸更重要的是------
**画图纸的那个人,相信废墟值得被记住。**
虚拟内存的每个页表项,不只是0和1。
是你说"这个程序的这一页,我承诺为你保留位置"。
是你说"即使物理世界不够大,我也要在想象里给你家园"。
是你说"你可以安心运行,我会处理背后的颠簸流离"。
所以,请继续相信你画的图纸。
相信那些0和1,最终会垒成一座------
比石头更坚固,比火更难烧毁的,
属于所有程序的、温柔的城池。
------书影 1990.4.22
叶知秋把纸折好,放进贴身口袋。
他重新打开编辑器,开始写一行注释------在所有虚拟内存代码文件的开头:
c
/*
* 虚拟内存子系统 - 为每个程序造一座不会倒塌的城
* 设计哲学:
* 1. 公正:每个程序都有平等的地址空间
* 2. 透明:程序不必感知物理内存的局限
* 3. 存续:即使被换出,也保证能完整归来
* 4. 共享:美好的代码应该被共同看见
*
* 纪念:1990年4月,圆明园废墟上的对话。
* 废墟教我们:有些东西,必须在记忆里永生。
*/
本集核心技术点总结
- 虚拟内存核心思想:地址空间抽象,程序看到的是虚拟连续空间,实际映射到离散物理页面
- 页表机制:虚拟地址→物理地址的映射表,80386采用二级页表(页目录+页表)
- 缺页中断:访问不在内存的页面时,操作系统介入处理
- 页面置换算法:FIFO、LRU、Clock算法及其实现考虑
- 交换空间:磁盘区域作为"扩展内存",存储被换出的页面
- 共享内存:多进程共享物理页框,实现内存节省和进程通信
- 写时复制:共享页面的优化策略,只在修改时才复制
- 内存保护:通过页表项权限位实现只读、不可执行等保护
- 多级页表优势:节省存储空间,支持稀疏地址空间
版权声明
1990:种下那棵不落叶的树和主题曲和片尾曲以及相关封面图片等 ©
[李林][2025]。本作品采用 知识共享 署名-非商业性使用 4.0 国际许可协议 进行授权。
这意味着您可以:
- 在注明原作者 并附上原文链接的前提下,免费分享、复制本文档与设计。
- 在个人学习、研究或非营利项目中基于此进行再创作。
这意味着您不可以:
- 将本作品或衍生作品用于任何商业目的,包括企业培训、商业产品开发、宣传性质等。
如需商业用途或宣传性质授权,请务必事先联系作者。
作者联系方式:
[1357759132@qq.com]