← 计算与系统基础 · 第 2 篇

程序启动后:进程、虚拟内存与文件

区分进程草稿、虚拟内存与已保存内容,理解文件路径、保存失败、崩溃持久性和版本冲突。

学完这一篇,你能做到

区分内存编辑与持久化,设计保存失败和旧版本覆盖的检查。

编辑器里打开的文件、程序里的字符串和磁盘上保存的内容,可以在同一时刻处于三个不同状态。理解进程、内存与文件,能解释“界面已经变了,重启却恢复原样”,也能避免把内存变量当成可靠的任务记录。

这篇以一个笔记应用为例:用户编辑草稿,点击保存,然后关闭并重新启动。要让行为可预测,必须定义谁持有草稿、谁持有已保存版本,以及保存失败时显示什么。

从启动系统到启动程序

计算机启动通常经历固件、引导程序、内核和用户空间初始化,具体步骤随平台变化。内核管理执行、内存和设备等资源,用户程序通过系统调用请求操作。启动一个应用意味着建立可执行的进程环境,不只是“把代码文本复制到内存”。

进程拥有地址空间及一组资源。线程是进程内的执行单元,调度器决定何时让它们运行。线程通常共享所属进程的地址空间,因此修改同一对象需要协调;不同进程的普通变量不会因为变量名相同而自动共享。

进程间可以通过管道、套接字或明确的共享内存等方式通信。必须主动设计这些边界。启动第二个笔记进程时,第一个进程里未保存的字符串不会自动变成第二个进程的初始数据。

虚拟地址不等于一块固定 RAM

程序使用的虚拟地址通过页表等机制映射到物理资源。它让不同进程可以拥有隔离的地址空间,也支持文件映射等用途。一个虚拟地址存在,不代表相应数据此刻一定驻留在物理内存中。

页可能尚未建立实际映射,也可能对应文件内容或涉及交换机制。访问时可能触发缺页处理,结果取决于映射是否合法及系统能否满足请求。缺页并不总是程序错误,非法访问也不能通过“多等一会儿”解决。

应用排查内存问题时,要区分虚拟地址空间大小、实际驻留内存、分配器保留空间和业务上仍被引用的对象。某个数字下降或上升,单独不能证明泄漏。需要结合对象生命周期、进程指标和可重复工作负载判断。

文件名、打开的文件与内容

文件系统通过目录等结构把名称关联到文件对象和元信息。进程打开文件后取得文件描述符或平台相应句柄,用它执行读写等操作。路径是查找入口,句柄指向已打开的对象,这两个概念不能混用。

“文件被改名”与“已经打开的句柄不能继续读”之间没有跨平台的简单等式。权限、共享规则和文件系统行为需要根据实际系统检查。也不要把当前工作目录当成固定位置:相对路径会随着启动方式或目录改变而解析到不同地方。

笔记应用应在明确的数据目录保存内容,并记录格式版本。导出文件则由用户选择目的地。若让程序随意解释来自网页、模型输出或工具结果的路径,就把不可信数据变成了文件操作权限。

保存完成需要哪种保证

修改内存对象只改变进程内状态。调用写入接口后,也要区分数据进入用户空间缓冲区、内核缓冲区和持久设备的阶段。关闭窗口与保存成功不是同一个事件。

许多应用采用写入新文件再替换旧文件的方式减少半写入内容,但“替换是原子的”不能直接推导“断电后一定保留新内容”。刷写、目录元信息和底层文件系统的持久性保证都有关。需要崩溃一致性时,应依据实际平台的文档设计和测试。

界面可以先显示“有未保存修改”,收到明确保存结果后再显示“已保存”。保存失败时保留草稿并给出恢复办法。后台任务的状态也应显式持久化,不能把一段对话历史或一个全局变量当成恢复记录。

用状态模型观察重启前后

下面的 storage 是模拟已保存内容的共享对象。start 每次返回一个新的进程状态字典。第一份状态的草稿发生变化,第二次启动仍读取旧的已保存内容;只有明确更新 storage 后,第三次启动才读到新内容。

storage = {"note": "saved"}
def start():
    return {"draft": storage["note"]}
first = start()
first["draft"] = "unsaved"
second = start()
assert second["draft"] == "saved"
storage["note"] = first["draft"]
third = start()
assert third["draft"] == "unsaved"
print(second["draft"], third["draft"])

输出为 saved unsaved。各次 start 创建了不同的字典,但里面的数据类型是不可变字符串。它没有启动真实进程,也没有写磁盘;若整个 Python 解释器退出,这个模拟存储也会消失。

示例只检验“草稿变化”和“明确保存”的语义差别。它没有模拟文件锁、权限、磁盘写满、多个进程同时保存或断电。实际笔记程序还需要版本冲突处理,避免后保存的旧草稿覆盖较新的内容。

排查时先确认状态属于谁

遇到“重启丢内容”,先检查保存调用是否执行、返回状态是否成功、路径是否正确,再检查新进程是否从同一数据位置读取。不要先修改系统权限或删除目录来试运气。

环境变量通常由父进程传给子进程;子进程修改自己的环境不会反向修改父进程。标准输出和标准错误也有不同用途。记录运行目录、退出状态与错误类型,可以帮助复现问题,但日志不应包含凭据或用户笔记正文。

练习:加入保存失败与版本冲突

让模拟存储带一个版本号,启动时同时读取版本。保存要求提交读取到的版本;版本不匹配时返回冲突,草稿继续保留。再加入模拟写入失败,让界面状态能区分“未修改”“未保存”“保存中”“已保存”和“失败”。

验收时检查失败后草稿仍在、成功后再次启动读到新内容、两个草稿同时编辑时旧版本不能覆盖新版本。若进一步实现真实文件存储,在专用临时目录中测试,并把普通保存成功与崩溃后持久性测试分开记录。

资料来源与延伸阅读

资料核验:2026-11-10。本文为原创工程讲解,教学示例不代表线上实测。