Hi!这里是 JustHappy 这是专为编程初学者准备的专栏。你以为程序在"跑代码",其实大部分时间在"等 IO"。这篇把链路一次串通:CPU只会取指令/执行/下一条;遇到读文件、网络、键盘这些 IO,就交给硬件干活;干完用中断通知操作系统;操作系统再把它变成事件,唤醒回调继续执行。看懂这套闭环,按钮、请求、输入为何触发代码就不再玄学。

前面几篇我们其实一直在回答几件事:
对象是什么?
函数怎么复用代码?
模块化怎么组织代码?
进程和线程怎么让程序跑起来?
写到这里,很多初学者还是有很多疑问:
我写的代码,到底是怎么跑起来的?
为什么点击按钮会触发代码?
为什么网络请求会返回数据?
为什么键盘按下会执行逻辑?
为什么文件读完之后,程序又能继续往下跑?
如果你把这些问题一路往下追,最后一定会撞上几个词:
IO、中断、事件、调度、执行流。
这些词单独看都不难,但一旦混在一起,很多人就会开始头晕。
所以这一篇我们不拆开讲,而是把它们放回同一条链路里,一次讲清楚。
先说结论:程序不是"自己跑起来"的
很多人写代码写久了,会产生一种错觉:好像我写完,程序自己就懂了,自己就开始跑了。
其实不是。
程序能运行起来,本质上靠的是一整套协作:
- CPU 负责执行指令
- 操作系统负责管理和调度
- 硬件设备负责和外部世界交换数据
- 你的代码只是告诉计算机:接下来该做什么
所以理解"程序怎么跑起来",本质上是在理解:
代码是怎么一步步驱动操作系统和硬件一起工作的。
CPU 其实只会干一件事
很多人会把计算机想得很聪明。
但如果你从最底层看,CPU 其实像一个非常老实的流水线工人,它每天就在重复三件事:
- 取指令
- 执行指令
- 继续下一条指令
比如这段代码:
js
const a = 1
const b = 2
const c = a + b
你看到的是"声明变量"和"相加"。
CPU 看到的其实更像是:
把数据拿出来。
做一次计算。
把结果写回去。
然后继续下一条。
这就是为什么我一直说:CPU 天生不是一个"会思考的家伙",它更像一个只会老老实实执行命令的工人。
更准确一点,这条最原始的工作节奏长这样:

什么叫执行流
既然 CPU 只能一条一条往下执行,那程序运行时一定会有一条"路径"。
这条路径,就是执行流。
例如:
js
function main() {
login()
getUserInfo()
render()
}
main()
你写代码的时候感觉像是在"调用函数"。
但如果换成运行时视角,它其实是在走一条路:
先进入 main(),再进入 login(),执行完回来,再去 getUserInfo(),执行完再回来,最后再去 render()。
这就是执行流。
所以函数调用这件事,表面上是"写名字",本质上是在改变程序当前的执行路径。
问题来了:CPU 为什么老是在等
如果程序永远只是计算,那事情其实很简单。
但真实软件几乎都不是这样。
比如你读一个文件:
js
readFile()
或者发一个请求:
js
fetch('/api/user')
这时候 CPU 真正做的事情通常只是:
发出一个命令。
告诉硬盘去读。
或者告诉网卡去发。
然后呢?
然后 CPU 就开始等。
你会发现一个非常反直觉的事实:
现代程序里,很多时候 CPU 根本没有在"拼命计算",它更常见的状态是"在等外部世界"。
而这些"和外部世界交换数据"的事情,统称为 IO。
什么是 IO
IO 就是 Input / Output,输入 / 输出。
凡是 CPU 和外部世界交换数据,基本都可以归到 IO 里。
比如:
- 键盘输入
- 鼠标点击
- 硬盘读写
- 网络收发
- 显示器输出
- 摄像头 / 麦克风数据
程序员平时嘴里说的 IO,通常就是这些场景:
- 读文件
- 发网络请求
- 查数据库
- 接收用户输入
别看表面不一样,本质都一样:
CPU 让某个外部设备帮它拿数据,或者把数据送出去。
同步 IO:最朴素,也最浪费
最原始的模式很好理解:
CPU 发起读取。
然后一直等。
等文件读完。
再继续执行。

这就是同步 IO。
它的问题也非常直接:干等。
CPU 这么贵,你却让它傻站在原地等硬盘慢慢转、等网卡慢慢收,这显然不划算。
异步 IO:先把活交出去,自己去干别的
后来人们发现,不能这么浪费 CPU。
于是思路变成了:
CPU 发起读取。
然后别在原地傻等,继续去干别的。
等文件真读完了,再回来处理结果。

这就是异步 IO。
比如你在 JavaScript 里写:
js
fetch('/api/user')
console.log('继续执行')
fetch() 发出去以后,后面那句不会傻等网络返回,它会先继续往下跑。
所以异步 IO 的核心不是"更高级的语法",而是:
别让 CPU 在等待外设时白白浪费时间。
关键问题:设备做完了,谁来通知 CPU
看到这里,真正关键的问题才冒出来。
如果 CPU 没有一直傻等,那文件读完、网络回来了、键盘按下了,谁来告诉它?
答案就是:中断(Interrupt)。
你可以把 CPU 理解成一个埋头工作的员工。
它正在处理手头这份工作,突然旁边有人"叮"一下:
有新情况了。
文件读完了。
键盘有输入了。
网卡收到数据了。
CPU 就会先停一下,看看发生了什么,处理完再回去继续原来的工作。
这就是中断。
所以中断一句话解释就是:
硬件通知 CPU:别光埋头干活了,有事发生了。
生活里你每天都在用中断,只是没意识到
你按下键盘上的一个键。
键盘不是"自己把字塞进代码里"的,它会通过硬件机制告诉 CPU:有人按键了。
网卡收到数据包,也不是自己凭空飘进浏览器的,它会通知 CPU:数据到了。
硬盘读完文件,同样会通过这套机制告诉 CPU:事情办完了。
所以你看到的这些"事件"背后,底层几乎都能追到这句话:
硬件完成某件事,然后通过中断通知 CPU。
事件驱动到底是什么
很多前端都听过"事件驱动"。
什么 click、keydown、response、load......
但如果你继续往下追,会发现它的底层气质其实非常统一:
有件事发生了。
系统知道了。
程序收到了通知。
然后去执行对应的代码。

也就是说,事件驱动这四个字,本质上是在讲:
程序不是一直主动去问"出事了吗",而是等事情发生以后,再被通知去处理。
所以从底层一路串起来,能看到这样一条链:

这也是为什么现代前端、Node.js、GUI 程序都特别喜欢"事件"这套模型。
因为它们天天都在等:
- 等点击
- 等输入
- 等网络返回
- 等数据库结果
- 等文件读完
操作系统在这里到底扮演什么角色
写到这里,你会发现一个很重要的人物一直在中间打工:操作系统。
因为程序员并不会直接拿着 JavaScript 去敲网卡,也不会直接让浏览器自己控制硬盘。
中间必须有一个中间人,负责管理和翻译。
这就是操作系统。
整个关系可以粗暴看成:
应用程序
↓
操作系统
↓
硬件设备
比如你写:
js
fetch('/api/user')
在浏览器环境里,它背后更像是这条路:
JS → 浏览器 → 操作系统 → 网卡
数据回来时,则是反方向:
网卡 → 操作系统 → 浏览器 → JS

所以很多初学者感觉"浏览器很神奇",其实不是浏览器会魔法,而是浏览器一直在替你和操作系统打交道。
把整个闭环一次看懂
如果把上面所有东西收拢成一条完整闭环,现代软件运行最核心的流程就是这个:
这张图其实特别重要。
因为它把前端、后端、桌面程序、Node.js、浏览器、文件读取、网络请求这些东西,第一次放进了同一套世界观里。
你以后再看到:
- 为什么点击按钮会触发函数
- 为什么请求回来以后会执行
then - 为什么文件读完以后会进入回调
- 为什么 Node 喜欢事件驱动
本质上都可以回到这一张图上来解释。
最后收个尾
前五篇讲的是数据、函数、模块、进程、线程。
而这一篇开始,我们终于碰到了真正会让"代码动起来"的那一层:
CPU、操作系统、硬件、IO、中断、事件。
从这一刻开始,你会慢慢意识到:
编程不再只是"写几行代码"。
你真正学的,其实是:
代码如何驱动整个计算机世界运转。