从内存到存储:一次把计算机系统的关键概念串起来

2 阅读6分钟

本文是一篇个人系统学习笔记的系统化整理。起点并不是“高深理论”,而是一个非常生活化、却足以引出整套计算机体系的问题:

“我们在代码里申请的内存,最后到底对应什么实物?”

在不断追问这个问题的过程中,我逐渐把 CPU、内存、虚拟内存、操作系统、文件系统、Android 存储模型等零散知识,串成了一条相对完整的认知链路(BY GPT)。


序言:从一个“return 局部对象”的疑问说起

起因是室友(C/C++ 背景)的一次提问:

“为什么不能在函数里 return 一个局部对象?”

我当时的第一反应是:当然不行啊,出了作用域就没了。

于是给她简单讲了一下 Java 里最基础的内存模型:

  • 栈(Stack)
    • 线程私有
    • 函数调用创建,返回即销毁
    • 局部变量、参数、返回地址
  • 堆(Heap)
    • new 出来的对象
    • 通过“引用”建立联系
    • Java 中由 JVM 回收,C/C++ 需要手动 free / delete
  • 方法区(或元数据区)
    • 类信息、常量、静态变量
    • 从抽象意义上更接近“特殊的堆”

在这个层面,我们很快达成了共识。

但紧接着,她问了一个更本质的问题:

“那我们代码里申请的这些内存,对应的实物到底是什么?是 RAM 吗?”

我当时正好在补操作系统基础,自以为理解了虚拟内存,于是回答:

“严格来说是虚拟内存;如果非要对应实物,那是主存 + 磁盘,由操作系统调度。”

她一脸懵逼。

后来我意识到:不是她没懂,而是我自己的理解还没有真正“打通”。

于是,我决定从最基础的计算机组成开始,重新梳理一遍。


一、计算机硬件组成:我们眼睛能看到的那一层

1.1 基本硬件模块

一台计算机,从硬件角度看,通常包括:

  • CPU
    • 算术逻辑单元(ALU)
    • 控制单元(CU)
    • 寄存器
    • Cache(多级缓存)
    • 内存管理单元(MMU)
  • 内存储器
    • RAM(主存):断电易失,与 CPU 直接交互
    • ROM:只读存储,存放 BIOS / Bootloader
  • 外存储器
    • 硬盘、SSD 等
  • 输入 / 输出设备
    • 键盘、显示器、网络设备、打印机等

这些都是物理存在、肉眼可见的组件。


1.2 CPU 如何与内存交互

CPU 与 RAM 之间,通过三类总线通信:

  • 地址总线:指定访问位置
  • 数据总线:传输数据
  • 控制总线:读写、时序等控制信号

在数据层面,CPU 访问的内容只有两类:

  • 指令
  • 数据

而“指令”,就引出了指令集与语言的问题。


二、指令、汇编与高级语言

2.1 汇编语言:人类能理解的指令集表示

早期的汇编语言,本质上是:

机器指令的文本化表示

例如 8086 的指令集。

需要澄清的一点是:

  • 汇编代码确实是文本文件(ASCII / UTF-8 编码)
  • 字符编码只是存储形式
  • 指令的语义由:
    • 汇编器
    • CPU 架构 共同决定,与 ASCII 本身无关

正如《CSAPP》中所说:

源代码是文本文件,其他一切都是可执行文件。


2.2 编译型与解释型语言(传统视角)

传统上,我们会这样分类:

  • 编译型语言(C / C++)
    • 源码 → 编译 → 可执行文件(ELF / exe)
  • 解释型语言(Python)
    • 源码 → 解释器 → 执行

这在理解早期语言模型时非常有用,但在 Java、Android、JavaScript 出现后,已经不够准确。

我曾看到一个对“解释型语言”的定义,很有启发性:

不能被 CPU 直接执行,必须依赖运行时环境(一个程序来运行另一个程序)

这也解释了为什么:

  • JVM、Python 解释器、ART
  • 本身都大量使用 C/C++ 编写

因为:

解释器本身,必须足够快。


2.3 Java:编译 + 虚拟机执行的混合模型

Java 的执行路径是:

.java
  ↓ javac
.class(字节码)
  ↓
JVM(解释 / JIT / AOT)
  ↓
CPU

因此更准确的说法是:

Java 是“面向虚拟机的编译语言”

它既不是“不编译”,也不是“直接编译成机器码”。


三、操作系统:连接软件与硬件的中枢

3.1 从功能角度看 OS

  • 对应用程序而言
    • 提供执行环境
    • 提供 IO、网络等服务
    • 管理和终止进程
  • 对内核而言
    • CPU 调度
    • 内存管理
    • 设备管理
    • 文件系统

一句我非常喜欢的总结是:

OS 将 CPU 虚拟化为进程,将内存虚拟化为地址空间,将磁盘虚拟化为文件。


3.2 虚拟内存:回答“内存对应什么实物”的关键

虚拟内存的目标是:

让每个进程都“以为”自己拥有连续、完整的内存空间。

实际上:

  • 虚拟地址 → MMU → 物理地址
  • 数据可能在:
    • RAM
    • 磁盘(swap / 文件映射)

因此:

我们在代码中申请的“内存”,并不直接等价于一块固定的 RAM 芯片区域。


四、内存层次结构:为什么有 Cache

从 CPU 视角看,存储是分层的:

  • 寄存器(最快,最小)
  • L1 / L2 / L3 Cache
  • 主存(RAM)
  • 磁盘(最慢,最大)

这是用空间换时间的经典设计。


五、Java 内存与并发中的“可见性”问题

Java 的内存模型,可以粗略理解为:

  • 主内存
  • 每个线程的工作内存(缓存)

示例代码中:

static boolean run = true;

子线程不退出的原因,并不是“线程没看到修改”,而是:

  • JIT / CPU 缓存导致读取的是旧值

这正是 volatilesynchronized 存在的意义。


六、磁盘、分区、挂载与文件系统

6.1 为什么要 mount

磁盘在未挂载前:

  • 只是“原始二进制数据”

挂载之后:

  • 通过文件系统
  • 才能看到目录、文件、inode 等概念

6.2 inode 与 fd

  • inode:文件系统内部的数据结构
  • fd:VFS 层暴露给应用的操作句柄

应用程序:

只和 fd 打交道,不关心底层文件系统实现。

这正是 OS 的抽象能力。


七、Android 存储模型(概览)

从 Android 4.4 到 11+:

  • internal / external 的语义不断变化
  • Scoped Storage 成为默认模型
  • 权限控制通过 mount namespace + FUSE 实现

这些变化,本质上仍然是:

在 Linux 文件系统抽象之上,进一步做安全与隔离。


八、抽象与缓存:贯穿整个系统的思想

《CSAPP》中有一句非常经典的总结:

  • 文件:I/O 设备的抽象
  • 虚拟内存:程序存储器的抽象
  • 进程:正在运行程序的抽象
  • 虚拟机:整个计算机的抽象

而缓存:

  • 小到一个 HashMap
  • 大到 CPU Cache、页缓存

都是同一种思想的不同尺度体现。


结语

回到最初的问题:

我们写代码时申请的内存,对应什么实物?

答案已经不再是某一块具体的 RAM,而是:

操作系统 + 硬件 + 抽象机制共同构建的一套“看起来像内存”的系统。

如果这篇文章能让你对“内存”“存储”“操作系统”的边界稍微清晰一点,那它的目的就达到了。


有任何错误或不严谨之处,欢迎指正 🙏