古法编程秘籍(六):程序到底是怎么跑起来的?从 IO 到中断,一次讲明白

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。

事件驱动到底是什么

很多前端都听过"事件驱动"。

什么 clickkeydownresponseload......

但如果你继续往下追,会发现它的底层气质其实非常统一:

有件事发生了。

系统知道了。

程序收到了通知。

然后去执行对应的代码。

也就是说,事件驱动这四个字,本质上是在讲:

程序不是一直主动去问"出事了吗",而是等事情发生以后,再被通知去处理。

所以从底层一路串起来,能看到这样一条链:

这也是为什么现代前端、Node.js、GUI 程序都特别喜欢"事件"这套模型。

因为它们天天都在等:

  • 等点击
  • 等输入
  • 等网络返回
  • 等数据库结果
  • 等文件读完

操作系统在这里到底扮演什么角色

写到这里,你会发现一个很重要的人物一直在中间打工:操作系统。

因为程序员并不会直接拿着 JavaScript 去敲网卡,也不会直接让浏览器自己控制硬盘。

中间必须有一个中间人,负责管理和翻译。

这就是操作系统。

整个关系可以粗暴看成:

应用程序

操作系统

硬件设备

比如你写:

js 复制代码
fetch('/api/user')

在浏览器环境里,它背后更像是这条路:

JS → 浏览器 → 操作系统 → 网卡

数据回来时,则是反方向:

网卡 → 操作系统 → 浏览器 → JS

所以很多初学者感觉"浏览器很神奇",其实不是浏览器会魔法,而是浏览器一直在替你和操作系统打交道。

把整个闭环一次看懂

如果把上面所有东西收拢成一条完整闭环,现代软件运行最核心的流程就是这个:

这张图其实特别重要。

因为它把前端、后端、桌面程序、Node.js、浏览器、文件读取、网络请求这些东西,第一次放进了同一套世界观里。

你以后再看到:

  • 为什么点击按钮会触发函数
  • 为什么请求回来以后会执行 then
  • 为什么文件读完以后会进入回调
  • 为什么 Node 喜欢事件驱动

本质上都可以回到这一张图上来解释。

最后收个尾

前五篇讲的是数据、函数、模块、进程、线程。

而这一篇开始,我们终于碰到了真正会让"代码动起来"的那一层:

CPU、操作系统、硬件、IO、中断、事件。

从这一刻开始,你会慢慢意识到:

编程不再只是"写几行代码"。

你真正学的,其实是:

代码如何驱动整个计算机世界运转。

相关推荐
HYCS2 小时前
用pixi.js实现fabric.js(六):从线性代数的角度理解编辑器交互
前端·javascript·canvas
晨曦中的暮雨2 小时前
Golang速通(Javaer版)
java·开发语言·后端·golang
卷帘依旧2 小时前
useImperativeHandle的作用
前端
卷帘依旧2 小时前
Hooks在Fiber上的存储原理
前端
you45802 小时前
学成在线--day02 CMS前端开发(含Vue基础知识得回顾)
前端·javascript·vue.js
xiaofeichaichai2 小时前
虚拟 DOM
前端·javascript·vue.js
2401_878454532 小时前
前端高频得手写题
前端
初一初十3 小时前
vue3实现的纯前端护肤品商城网站
前端·javascript·vue.js·前端框架