一、冯·诺依曼体系:所有设备都只能和内存打交道
我们常见的笔记本、不常见的服务器,绝大多数都遵守冯·诺依曼体系结构。它把计算机拆成五类部件:
| 部件 | 举例 | 职责 |
|---|---|---|
| 输入单元 | 键盘、鼠标、扫描仪、手写板 | 把外界信息送进机器 |
| 输出单元 | 显示器、打印机 | 把结果送出机器 |
| 中央处理器 CPU | 运算器 + 控制器 | 执行指令、做运算、发号施令 |
| 存储器 | 内存(注意:不是硬盘) | 存放正在运行的程序和数据 |
| (外存)硬盘 | 磁盘 | 长期保存,不直接参与运算 |
画成图大概是这样:
┌─────────────┐
输入设备 → │ │ → 输出设备
(键盘/鼠标) │ 内 存 │ (显示器/打印机)
│ │
└──────┬──────┘
│ 总线
┌──────┴──────┐
│ CPU │
│ 运算器+控制器 │
└─────────────┘
三条必须记住的"硬规矩"
-
这里的存储器指内存,不是硬盘。 硬盘是外存,它要参与运算,必须先把数据搬进内存。
-
CPU 只能对内存读写,不能直接访问外设。 不考虑缓存的前提下,CPU 的数据来源和去向只有内存一个。CPU 要"读键盘",实际上是键盘先把数据写进内存,CPU 再从内存读。
-
外设也只能和内存打交道。 键盘输入 → 写内存;显示器输出 → 从内存读。它不会绕过内存直接和 CPU 对话。
一句话总结:
内存是整台机器的"中转站",任何两个部件想交换数据,都得经过内存。
为什么非要这么设计?
因为速度差得太离谱:
| 部件 | 大致速度 |
|---|---|
| CPU 一次运算 | 纳秒级(0.x ns) |
| 内存一次访问 | 几十纳秒 |
| 硬盘一次随机读 | 毫秒级(比内存慢几十万倍) |
| 键盘/网络 | 毫秒到秒级,且人类输入更慢 |
如果让 CPU 直接等硬盘、等键盘,CPU 99.99% 的时间都在发呆。所以设计上让内存做缓冲区:慢设备把数据攒到内存里,CPU 一口气从内存搬走;CPU 算完先写内存,慢设备再去慢慢取。这样"慢"被内存吸收掉了。
📌 这也解释了一个常见现象:程序"卡"的时候,往往不是 CPU 慢,而是在等 IO (等磁盘、等网络)。这就是后面进程状态里 S(睡眠)和 D(不可中断睡眠)的由来。
二、把"数据流动"讲透:QQ 发一条消息经历了什么
课件里有一道经典思考题:从你登录 QQ,给朋友发一条消息,到他收到,数据是怎么流动的?如果是发文件呢?
很多人能背"输入→CPU→输出",但说不清细节。我们一步步拆。
场景 1:发一条文字消息
你的电脑这侧:
- 你敲键盘,键盘控制器把按键扫描码写进内存中的输入缓冲区;
- CPU 从内存读到按键,交给 QQ 进程处理,你在聊天框里看到的字,是 CPU 把字符写回内存,显卡再从内存读出来画到屏幕上;
- 你点"发送",QQ 进程把这段文字组装成网络数据包,写进内存;
- 网卡(也是一种外设)从内存里把数据包读走,通过网线/无线发出去。
对方的电脑这侧:
- 对方的网卡收到数据包,写进内存;
- 网卡发中断通知 CPU:"有数据了";
- CPU 从内存取出数据,交给对方的 QQ 进程解析,再把文字写进内存;
- 对方的显卡从内存读数据,显示到屏幕上。
注意每一步:CPU 只碰内存,网卡只碰内存,显卡只碰内存。它们之间从不直接握手。
场景 2:发一个文件
差别在"数据从哪来":
- QQ 先从硬盘把文件读进内存(这一步是磁盘 DMA + 内核参与);
- 把文件切成分片、加协议头,仍放在内存里;
- 网卡从内存读走,发出去;
- 对方网卡写入内存 → CPU 处理 → 写入对方硬盘(落盘才算真的收到)。
所以发文件时你会看到磁盘和网络同时忙:磁盘 → 内存 → 网络,内存还是那个中转站。
📌 面试常问变体:"为什么大文件传输时内存占用会飙高?"------因为要经过内存缓冲;"为什么零拷贝技术能提速?"------因为它在想办法少搬几次内存。
三、操作系统:一个纯正的"搞管理"的软件
3.1 什么是操作系统
任何计算机系统都包含一个基础程序集合,称为操作系统(Operating System,OS)。笼统地说,它包括:
- 内核(kernel):进程管理、内存管理、文件管理、驱动管理;
- 其他程序:函数库、shell 程序等。
也就是说,OS ≠ 内核。我们平时说的"Linux",严格讲是内核 ;你敲命令用的 bash、链接用的 libc,是内核之外的"其他程序"。
3.2 设计 OS 的目的:两头讨好
┌──────────────────────────────┐
│ 用户 / 应用程序 │
└──────────────┬───────────────┘
│ 对上:提供良好的执行环境
┌──────────────┴───────────────┐
│ 操作系统 │
│ (纯正的"搞管理"的软件) │
└──────────────┬───────────────┘
│ 对下:管理所有软硬件资源
┌──────────────┴───────────────┐
│ 硬件(CPU/内存/磁盘/网卡) │
└──────────────────────────────┘
- 对下:与硬件交互,管理所有软硬件资源;
- 对上:为用户程序提供一个良好的执行环境。
3.3 核心功能:管理
在整个软硬件架构里,操作系统的定位就是------一款纯正的"搞管理"的软件。
它不亲自算 1+1,也不亲自把字节写到磁盘磁道上。它做的事是:决定谁能用 CPU、用多久、用哪块内存、能不能打开这个文件。
四、如何理解"管理":先描述,再组织
这是整个操作系统课最重要的一句话,请务必吃透。
4.1 一个生活例子:学生、辅导员、校长
假设你是校长,要管理 2 万个学生。你会怎么做?
- 描述每个学生:姓名、学号、班级、成绩、奖惩记录......(存成一张表/一个结构体)
- 组织这些学生:按学院分组、按班级排队、按成绩排序......(用链表、数组、树)
校长从不直接接触学生 。他要"管理",只需要操作那两张东西:描述信息 + 组织结构。辅导员也是这么干的。
4.2 操作系统管硬件,同理
-
描述 :给每个硬件/被管理对象定义一个
struct,把所有属性塞进去; -
组织 :用链表(或其他高效数据结构)把这些
struct串起来。计算机管理硬件的套路:
1. 描述起来 ------ 用 struct 结构体
2. 组织起来 ------ 用链表或其他高效的数据结构
📌 记住这个套路,后面看到的一切都顺了:
| 被管理对象 | 描述它的结构体 | 怎么组织 |
|---|---|---|
| 进程 | task_struct(PCB) |
双链表串在内核里 |
| 虚拟内存区域 | vm_area_struct |
单链表 / 红黑树 |
| 打开的文件 | struct file |
文件描述符表 |
| 文件系统 | struct super_block 等 |
VFS 树 |
所以"管理进程"= 用一个结构体描述进程 + 把结构体链起来。 这也直接回答了下一篇的问题:进程在操作系统眼里到底是什么?------一个
task_struct。
五、系统调用和库函数:OS 的"门面"
操作系统对外表现为一个整体,但它必须给上层留出口子,否则应用程序什么也干不了。
5.1 系统调用(system call)
操作系统提供的接口 ,叫做系统调用。比如:fork、execve、wait4、open、read、write......
特点:
- 功能比较基础(就是一次内核能力的直接暴露);
- 使用门槛相对高(要自己处理参数、错误码、平台差异,甚至要写汇编/内联汇编触发软中断);
- 是唯一能进入内核的合法通道。
5.2 库函数(library function)
有心的开发者对部分系统调用做了适度封装 ,形成库(最典型的就是 libc)。有了库,上层开发者就能更方便地二次开发。
比如:
| 你写的 | 背后其实是 |
|---|---|
printf |
write(系统调用)+ 用户态缓冲 + 格式化 |
malloc |
brk / mmap(系统调用)+ 用户态内存池 |
fopen/fread |
open/read(系统调用)+ 用户态 FILE 缓冲 |
分层的直观图:
你的程序
│ 调用 printf / malloc / fopen ← 库函数(用户态)
▼
libc
│ 调用 write / brk / open ← 系统调用(陷入内核)
▼
内核(特权态)
│ 操作硬件寄存器、页表、磁盘
▼
硬件
📌 为什么要有这一层?因为系统调用**太"素"**了:write(1, buf, n) 每次只写几个字节,一次一次调,性能极差;printf 在用户态攒够一块再一次性 write,快得多。这也是第 7 篇里 exit 和 _exit 行为不同的根本原因(一个会刷缓冲区,一个直接走)。
六、承上启下:操作系统怎么管进程?
到这里,套路已经很清楚了:
操作系统管理进程,也是三步: 1. 先把进程描述起来(PCB / task_struct); 2. 再把进程组织起来(双链表); 3. 然后在需要的时候,读写这些结构体的字段(调度、切换、销毁)。
下一篇我们就去内核里,把 task_struct 拆开看:它到底记了哪些东西?以及那个让所有初学者困惑的 fork()------为什么一个函数会有两个返回值?
本篇小结
- 冯·诺依曼体系下,所有设备只和内存打交道,内存是数据中转站;CPU 不能直接访问外设。
- 操作系统 = 内核 + 其他程序(函数库、shell);核心定位是管理。
- 管理的本质:先描述(struct),再组织(数据结构)。
- 系统调用是 OS 暴露的基础接口,库函数是对它的封装;这是"用户态 / 内核态"分层的由来。
- 进程管理的答案,就藏在
task_struct这个结构体里。