在这里插入图片描述

为什么程序必须先加载到内存?操作系统到底在管什么?一篇讲透计算机核心架构


前言

你有没有想过这些问题?

  • 为什么你双击一个程序,它要先“加载”才能运行?
  • CPU 那么快,为什么不直接从硬盘读取数据?
  • 操作系统到底在忙什么?

如果你对这些问题的答案模模糊糊,那这篇文章就是为你准备的。

我们把 冯诺依曼体系结构(计算机的骨架)和 操作系统(计算机的大脑)这两个计算机基石,一次性讲清楚。

🏛️ 比喻:冯诺依曼体系结构就像工厂的流水线设计,操作系统就像工厂的管理层


第一部分:冯诺依曼体系结构 —— 计算机的骨架

一、什么是冯诺依曼体系结构?

冯诺依曼体系结构是现代计算机的基本设计蓝图,由数学家冯·诺依曼在 1945 年提出。几乎所有的通用计算机都遵循这个结构。

1.1 核心组成部分

部件 作用 比喻
运算器 做数学运算(加减乘除、与或非) 工厂里的工人
控制器 指挥协调各个部件工作 工厂里的车间主任
存储器 存储数据和指令(就是内存) 工厂里的工作台
输入设备 把数据送进计算机(键盘、鼠标) 工厂的原材料入口
输出设备 把结果展示出来(显示器、打印机) 工厂的成品出口

💡 CPU = 运算器 + 控制器(工人 + 车间主任)

1.2 体系结构图

在这里插入图片描述
这里的存储器就是内存


二、核心规则:CPU 只能和内存打交道

2.1 为什么程序必须先加载到内存?

规则:CPU 获取指令、读写数据,只能从内存中进行,不能直接从硬盘、键盘、网卡读取。

你以为是 实际是
CPU 直接从硬盘读文件 ❌ 不行
CPU 直接从键盘读输入 ❌ 不行
CPU 从内存读取 ✅ 对的

所以:一个程序要想运行,必须先被加载到内存中。

🍔 比喻:CPU 像一个只认工作台的厨师。食材(数据)必须先放到工作台(内存)上,厨师才能处理。你不可能把菜直接扔给厨师,让他边切边炒。

2.2 数据流动的本质

数据在计算机里流动,本质上是从一个设备拷贝到另一个设备

场景 数据流动路径 拷贝次数
键盘打字 键盘 → 内存 → CPU → 内存 → 显示器 多次
读取文件 硬盘 → 内存 → CPU → 内存 多次
网络下载 网卡 → 内存 → CPU → 内存 → 硬盘 多次

🚗 比喻:数据流动就像快递运输。每经过一个节点,就是一次“拷贝”。整个系统的效率,取决于最慢的那段“运输”。

2.3 外设只和内存打交道

设备 直接和谁打交道 说明
硬盘 内存 硬盘把数据读到内存,CPU 再从内存取
键盘 内存 键盘输入存到内存,CPU 再从内存读
网卡 内存 网卡收到数据放到内存,CPU 再处理

💡 结论:CPU 和外设之间不直接通信,全部通过内存中转。内存是整个系统的数据交换中心


三、体系结构的效率瓶颈

3.1 木桶效应

整个计算机的效率,不取决于最快的部件(CPU),而取决于最慢的部件

部件 速度级别 比喻
CPU 缓存 纳秒级 超级跑车
内存 纳秒-微秒级 小轿车
固态硬盘 SSD 微秒级 自行车
机械硬盘 HDD 毫秒级 步行
网络 毫秒-秒级 蜗牛

就像一队人走路,最快的人跑得再快,也要等最慢的人。

3.2 为什么要有缓存?

存储层级 速度 容量
CPU 寄存器 0.3 纳秒 几十字节
L1 缓存 1 纳秒 几十 KB
L2 缓存 3-5 纳秒 几百 KB
L3 缓存 10-20 纳秒 几 MB
内存(RAM) 50-100 纳秒 几 GB
硬盘 几毫秒 几百 GB

问题:CPU 太快,内存太慢。CPU 等数据的时间,足够做几百次运算。

🏃 比喻:CPU 是博尔特,内存是普通人。让博尔特等普通人,是巨大浪费。

解决方案:在 CPU 和内存之间加一层更小但更快的存储器——缓存。

3.2 缓存的核心思想:局部性原理

类型 含义 例子
时间局部性 刚用过的数据,很可能马上再用 循环计数器 i
空间局部性 用了一个数据,旁边的也可能被用 数组遍历

📖 比喻:你看书会把当前页和附近几页放在手边,不用每次翻页都去书架找。


3.3 缓存的工作流程

CPU 想读数据
    │
    ▼
1. 先查 L1 缓存
   ├─ 命中 → 直接返回(最快)
   └─ 未命中 → 去 L2
    │
    ▼
2. 查 L2 缓存
   ├─ 命中 → 返回
   └─ 未命中 → 去 L3
    │
    ▼
3. 查 L3 缓存
   ├─ 命中 → 返回
   └─ 未命中 → 去内存
    │
    ▼
4. 从内存读取(慢几十倍)
   并把数据加载到 L3→L2→L1

一个生活比喻

层级 比喻
L1 缓存 你手里的书(当前页)
L2 缓存 桌面上的书
L3 缓存 书包里的书
内存 家里的书架
硬盘 图书馆

找书逻辑:先看手里 → 再看桌面 → 翻书包 → 回家找 → 去图书馆

🎯 越靠近 CPU,越快但越小;越远离,越慢但越大。


3.4 缓存一致性(多核 CPU)

问题:核1 改了缓存里的数据,核2 的缓存里还是旧的。

解决方案:MESI 协议

状态 含义
M 已修改,和内存不一致
E 独占,和内存一致
S 共享,多个核心只读
I 无效

3.5 按行遍历 VS按列遍历

3.5.1 了解数组在内存中的存储方式

在 C/C++ 中,二维数组是按行优先存储的:

int arr[3][4] = {
    {1, 2, 3, 4},   // 第0行
    {5, 6, 7, 8},   // 第1行
    {9, 10, 11, 12} // 第2行
};

数组在内存中的实际排列
在这里插入图片描述
🎯 关键:同一行的元素在内存中是连续挨着的。不同行之间也是连续的,行与行是首尾相接。

3.5.2 缓存的工作方式

CPU 从内存读数据时,不是只读一个变量,而是一次读一整块(叫缓存行,Cache Line,通常是 64 字节)。

假设一个 int 是 4 字节,一个缓存行 64 字节,一次能装下 16 个 int

你只想要 CPU 实际读入
arr[0][0] arr[0][0]arr[0][15](整整 16 个元素)

🚌 比喻:就像学校的校车,接送一个学生需要跑一趟,接送一车的学生也需要跑一趟,那就还不如每次都接送一车的学生,这样可以减少总的趟数,更加高效。

3.5.3 结合具体场景来看

场景一:按行遍历(缓存友好)

for (int i = 0; i < N; i++)
    for (int j = 0; j < N; j++)
        sum += arr[i][j];

访问顺序:arr[0][0] → arr[0][1] → arr[0][2] → arr[0][3] …

内存访问模式:

访问 arr[0][0]:CPU 把 arr[0][0]~arr[0][15] 加载到缓存
    ↓ 接着访问 arr[0][1]
    ✔ 已经在缓存里了(命中!)
    ↓ 接着访问 arr[0][2]
    ✔ 已经在缓存里了(命中!)
    ↓ ... 一直到 arr[0][15]
    ✔ 全部命中!
    ↓ 访问 arr[0][16]
    ✘ 缓存没命中 → 加载下一批 arr[0][16]~arr[0][31]

操作 缓存命中率
第 1 次访问(加载整行) 未命中
后面 15 次访问 命中
命中率 ≈ 93.75% 极高

🚀 结论:一次缓存未命中,换来 15 次命中,效率极高。


场景二:按列遍历(缓存不友好)

for (int j = 0; j < N; j++)      // 列在外层
    for (int i = 0; i < N; i++)  // 行在内层
        sum += arr[i][j];

访问顺序:arr[0][0] → arr[1][0] → arr[2][0] → arr[3][0] …

内存访问模式(假设 N 很大,一列元素不在同一个缓存行里):

访问 arr[0][0]:CPU 把 arr[0][0]~arr[0][15] 加载到缓存
    ↓ 接着访问 arr[1][0]
    ✘ arr[1][0] 不在刚才的缓存行里!(因为它在第1行,相隔很远)
    → 又加载 arr[1][0]~arr[1][15] 到缓存
    ↓ 接着访问 arr[2][0]
    ✘ 又不在!加载 arr[2][0]~arr[2][15]
    ↓ ... 每次访问都是未命中!

操作 缓存命中率
访问 arr[0][0] 未命中
访问 arr[1][0] 未命中
访问 arr[2][0] 未命中
访问 arr[3][0] 未命中
命中率 ≈ 0% 极低

🐌 结论:每次访问一个元素,都要重新从内存加载,缓存完全没用上。

一个直观类比

遍历方式 比喻
按行遍历 你站在一排货架前,从左往右拿货,一次拿一排。手边的货拿完,旁边就是下一排。
按列遍历 你拿完第一排的第 1 个,然后跑到第二排的第 1 个,再跑到第三排的第 1 个…每次都要跑到不同位置,永远拿不到"附近的货"

🎯 一句话:缓存利用的是空间局部性——访问了某个地址,它旁边的地址很可能也会被访问。按列遍历完全破坏了这种连续性。

3.5.4 总结
问题 答案
数组怎么存的? 按行优先,同一行连续
缓存怎么读的? 一次读一整行(64 字节)
按行为什么快? 访问 arr[i][j] 后,arr[i][j+1] 已经在缓存里了
按列为什么慢? 访问 arr[i][j] 后,arr[i+1][j] 在内存中相隔很远,缓存里没有
本质原因 按列遍历破坏了空间局部性

🎯 一句话记住:因为数组是按行存的,所以按行遍历更充分利用缓存。按列遍历等于每次都访问“远房亲戚”,缓存帮不上忙。


第二部分:操作系统 —— 计算机的大脑

一、操作系统是什么?

概念 说明
操作系统 一款纯正的“搞管理”的软件
内核 操作系统的核心部分
其他程序 图形界面、驱动程序、系统工具等

🏢 比喻:操作系统就像一家公司的管理层

  • 管理层不直接搬砖(不直接处理硬件)
  • 管理层负责管理资源(CPU、内存、硬盘、进程)
  • 管理层给员工提供工作规范(系统调用)

1.1 操作系统的定位

操作系统的核心职责:

职责 说明 比喻
管理 CPU 决定哪个进程用 CPU 排班经理
管理内存 分配、回收内存 仓库管理员
管理文件 读写、权限控制 档案管理员
管理设备 驱动、输入输出 设备调度员
管理进程 创建、销毁、调度 人事经理

二、管理的本质:先描述,再组织

2.1 管理者不需要和被管理者见面

💡 这是一个非常重要的思想:管理者和被管理者不需要见面,管理者根据数据进行管理,数据由中间层获取

现实例子 管理者 数据 被管理者
学校管理学生 教务处 学籍档案、成绩单 学生
公司管理员工 HR 员工档案、考勤记录 员工
操作系统管理进程 操作系统 PCB(进程控制块) 进程

🎯 核心:管理就是基于数据做决策。操作系统不需要“看到”进程,只需要看进程的“档案”(PCB)。

2.2 先描述,再组织

操作系统管理资源的方法:

  1. 描述:用数据结构描述一个资源(比如用 struct task_struct 描述一个进程)
  2. 组织:把这些数据结构用链表、队列等方式组织起来
// 操作系统眼中的“进程”就是一个结构体
struct task_struct {
    int pid;           // 进程ID
    int state;         // 进程状态
    int priority;      // 优先级
    // ... 还有很多字段
};

// 所有进程的组织:一个链表
struct list_head task_list;

🍔 比喻:就像学校的学生管理系统。每个学生有一张档案卡(描述),所有档案卡放在一个柜子里(组织)。教务处不需要见学生本人,看档案卡就行。


三、系统调用 —— 访问操作系统的唯一入口

3.1 什么是系统调用?

系统调用是操作系统提供的函数接口,用户程序通过它来请求操作系统提供服务。

服务 系统调用示例 作用
文件操作 open()read()write() 读写文件
进程管理 fork()exec()wait() 创建、执行进程
内存管理 malloc()free()mmap() 分配、释放内存
设备管理 ioctl()read()write() 操作设备

🏦 比喻:操作系统就像一个银行。你不能直接进金库拿钱(不能直接访问硬件)。你必须通过柜台窗口(系统调用)来办理业务。银行不相信任何人,必须走正规流程。


3.2 为什么不能直接访问硬件?

原因 说明
安全性 防止用户程序破坏系统
稳定性 防止程序崩溃导致整个系统崩溃
抽象性 用户不需要知道硬件细节
公平性 操作系统可以调度、分配资源

🔒 核心原则:操作系统不相信任何用户。所有对硬件的访问,必须经过操作系统。

3.3 软硬件体系结构层次图

在这里插入图片描述


四、重要结论

4.1 访问硬件必须贯穿整个体系

任何程序,只要访问硬件,它的请求必须贯穿整个软硬件体系结构。

例如:你在程序中调用 printf(“hello”):

printf() 
    → 库函数 
    → 系统调用(write)
    → 操作系统内核
    → 显卡/显示器驱动
    → 硬件(显示器)

4.2 库函数底层封装了系统调用

我们常用的库函数,很多在底层封装了系统调用:

库函数 底层系统调用 说明
printf() write() 输出到屏幕
scanf() read() 从键盘读取
fopen() open() 打开文件
malloc() brk()mmap() 分配内存

💡 所以:库函数提供了更方便的接口,但底层还是操作系统在干活。

4.3 操作系统是“搞管理”的软件

操作系统不直接“干活”(不直接算 1+1,不直接显示像素),它的工作是:

· 分配:谁用 CPU、谁用内存
· 调度:谁先运行、谁后运行
· 保护:不让程序互相干扰
· 抽象:让程序员不用关心硬件细节

🎯 一句话:操作系统是资源的管理者,不是资源的使用者。


五、总结速查表

知识点 核心内容
冯诺依曼体系 CPU、内存、输入、输出四大部件
CPU 只和内存打交道 程序必须先加载到内存才能运行
数据流动本质 从一个设备拷贝到另一个设备
效率瓶颈 由最慢的部件决定(木桶效应)
操作系统 一款"搞管理"的软件
管理本质 先描述(数据结构),再组织(链表等)
系统调用 访问操作系统的唯一入口
操作系统不相信用户 所有硬件访问必须经过操作系统
库函数 底层可能封装了系统调用

最后

冯诺依曼体系结构给了计算机骨架,操作系统给了计算机灵魂。

理解这两者,你就理解了:

· 为什么程序要“加载”
· 为什么电脑会“卡”
· 为什么程序崩溃不会让整个系统崩溃
· 为什么写代码时不能直接访问硬盘

这是计算机科学的基石,值得你反复品味。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐