作者介绍:本文作者 CodeStats,资深底层技术爱好者,专注计算机体系结构、操作系统内核与编程语言实现原理。长期在 CSDN 分享硬核技术文章,致力于用通俗语言讲透计算机背后的运行逻辑。
参考文章:本文核心思想基于作者的两篇前置文章,强烈建议配合阅读:
📖 目录
-
思考一个问题:你真的理解"程序运行"吗?
-
程序执行的终极真相:CPU只做一件"蠢事"
-
操作系统与CPU权限:用户态与内核态的"游戏规则"
-
编译型 vs. 解释型:从CPU视角看透本质区别(重点:预制程序)
-
浏览器如何让HTML/CSS/JS协同运行?
-
总结:打通底层到应用层的统一模型
一、思考一个问题:你真的理解"程序运行"吗?
打开一个网页,双击一个游戏,运行一段Python脚本------这些日常操作背后,你是否想过:
-
为什么C语言程序运行飞快,而Python相对慢?
-
操作系统是怎么"保护"自己不被普通程序搞崩溃的?
-
浏览器凭什么能把HTML、CSS、JS三种不同"语言"融为一体,展示出动态页面?
如果你无法清晰回答这些问题,说明你对程序运行的本质还存在认知空白。
这篇文章将从CPU的最底层指令执行开始,一路讲到操作系统权限控制,再到编译型/解释型语言的本质差异,最后揭开浏览器渲染的神秘面纱。读完它,你会建立一个从单条机器指令到各种应用程序执行原理的完整认知框架。
二、程序执行的终极真相:CPU只做一件"蠢事"
2.1 剥离所有软件外衣
我们日常接触的代码、IDE、框架、操作系统,全部是上层建筑。剥离所有软件层,直达硬件,你会发现CPU的工作逻辑简单到令人惊讶。
CPU没有理解能力,没有思维,不会"读懂"任何高级语言。 它只会机械地重复一个无限循环:
-
查看程序计数器(PC)------一个记录下一条指令内存地址的寄存器;
-
从内存中取出该地址的指令;
-
执行指令(算术运算、数据搬移、地址跳转等);
-
更新程序计数器,指向下一条指令;
-
回到第1步。
这就是冯·诺依曼架构的核心:程序就是内存中连续排列的二进制指令;程序运行就是CPU顺着程序计数器这条"锁链",逐条取指执行的过程。
2.2 重要推论:CPU根本听不懂你的代码
CPU只能执行它所对应的机器码(x86、ARM、RISC-V的指令集各不相同)。你写的任何高级语言------无论是C、Java、Python还是JavaScript------最终都必须被翻译成CPU能识别的二进制机器语言。
这个"翻译"过程,正是不同语言执行模型的本质分水岭。
三、操作系统与CPU权限:用户态与内核态的"游戏规则"
3.1 为什么需要权限分级?
如果所有程序都能随意访问所有内存和硬件,一个普通的计算器程序就能把你的硬盘格式化。为了防止这种灾难,CPU设计了特权级别。
在x86架构中,有Ring 0到Ring 3四个级别。我们只需要关注最常用的两个:
| 模式 | 通俗称呼 | 能做什么 | 谁运行在这里 |
|---|---|---|---|
| Ring 0 | 内核态 | 执行任何CPU指令、访问所有内存、直接控制硬件 | 操作系统内核 |
| Ring 3 | 用户态 | 只能访问自己的内存空间,不能直接操作硬件 | 你的应用程序(微信、浏览器、游戏) |
核心规则 :用户态程序如果想做"特权操作"(读文件、发网络包、申请大块内存),必须通过系统调用(System Call)陷入内核,由内核代码代为完成。内核在完成操作后,再切换回用户态。
这就是为什么你的普通程序崩溃不会导致整个电脑蓝屏------它被关在"用户态笼子"里,根本影响不了内核。
3.2 从CPU权限看操作系统内核架构
两种权限态的存在,直接决定了操作系统的设计哲学:
-
宏内核(Linux、旧Unix):将进程调度、内存管理、文件系统、设备驱动等几乎所有服务都放在内核态运行。优点:模块间直接调用,性能高。缺点:一个驱动bug就可能让整个内核崩溃。
-
微内核(QNX、L4、鸿蒙部分设计):内核态只保留最基础的功能(调度、IPC、内存管理)。其他服务(驱动、文件系统)都作为普通用户态进程运行。优点:极致稳定,一个驱动挂了最多重启该进程。缺点:频繁的内核-用户态切换带来性能开销。
-
混合内核(Windows、现代Linux也吸收部分微内核思想):将关键功能(如图形驱动)放入内核态保证性能,非关键服务放在用户态。
无论哪种架构,底层都依赖CPU提供的用户态/内核态切换指令。理解了这个,你就看懂了所有操作系统的本质。
四、编译型 vs. 解释型:从CPU视角看透本质区别(重点:预制程序)
4.1 两种"翻译"策略
将人类写的代码转化为CPU能执行的机器码,需要翻译器。翻译方式有两种:
-
编译型(C、C++、Go、Rust) :在程序运行之前 ,一次性将整个源代码翻译成目标平台的机器码,生成一个独立可执行文件。可以理解为:做一桌宴席,全部提前做好,然后一次性端上来。
-
解释型(传统Python、Ruby、早期JavaScript) :在程序运行过程中 ,一边读取源代码,一边翻译一小段,然后立即执行,再读取下一段。可以理解为:吃火锅,边涮边吃,边吃边涮。
4.2 从CPU执行角度看本质区别(重点)
现在是最核心的部分------从CPU指令执行视角来区分两者:
编译型语言:
-
源码经过编译器,直接生成了完整的可执行机器码文件。
-
运行时,操作系统将程序计数器直接指向这个文件的入口地址。
-
CPU从头到尾执行的就是这个程序自己的机器码。没有中间商,没有额外翻译开销。
-
所以它快。
解释型语言:
-
源码以普通文本文件存在,里面没有任何机器码。
-
运行时,操作系统启动的是一个解释器程序 (比如
python.exe或node.exe)。这个解释器本身是一个已经编译好的可执行文件。 -
CPU执行的从头到尾都是这个解释器程序的机器码 。你的源代码呢?它只是被解释器读取的输入数据,就像文本编辑器打开的txt文件一样。
-
解释器的机器码内部有一个主循环:读一行你的源码 → 解析这一行 → 找到对应的预编译功能函数 → 调用该函数 → 继续读下一行。
-
所以它慢,因为多了"边读边解析"的额外开销。
4.3 核心概念:预制程序
为什么解释器能执行你的源代码?因为解释器是一个预制程序------它内部已经提前(编译阶段)实现好了所有你可能需要的操作:变量赋值、算术运算、条件判断、循环、函数调用......每个操作都对应一段已经编译成机器码的函数。
当你写a = 1 + 2时,CPU并不是直接计算1+2。CPU执行的是解释器的机器码,这段机器码会:
-
解析文本"a = 1 + 2",识别出"赋值"和"加法"
-
跳转到解释器内部预制的"加法函数"机器码去执行真正计算
-
跳转到解释器内部预制的"变量存储函数"去记录结果
总结:解释型语言的本质 = 预制程序(解释器) + 你的源代码(控制数据)
4.4 Java和现代JS的"中间路线"
Java和Chrome V8引擎打破了纯解释的局限。它们引入字节码 和JIT编译:
-
源代码先被编译成平台无关的字节码(比文本源码更紧凑、更易翻译)
-
运行时,解释器执行字节码
-
同时监控哪些代码段执行频繁("热点代码"),在运行时动态将其编译成真正的本地机器码
-
之后CPU直接执行这些动态生成的机器码,不再经过解释器
这使得Java和现代JS的性能接近编译型语言,同时保留了跨平台能力。
五、浏览器如何让HTML/CSS/JS协同运行?
5.1 本质:CPU执行的是浏览器的机器码
当你打开一个网页时,不要被"HTML是标记语言、CSS是样式表、JS是脚本"这些概念迷惑。从CPU视角看,很简单:
浏览器本身是一个庞大的、预先编译好的可执行程序(Chrome.exe、Safari等)。操作系统将它加载到内存,CPU开始逐条执行浏览器的机器码。
你的HTML文件、CSS文件、JS文件,对CPU来说都是输入数据。浏览器程序的机器码内部包含多个模块:
-
HTML解析器(将文本数据转成内部DOM树结构)
-
CSS解析器(将样式文本转成CSSOM树)
-
JS引擎(V8等,负责执行JS代码,但V8本身也是浏览器程序的一部分)
-
渲染引擎(根据DOM+CSSOM生成屏幕像素)
5.2 渲染管线的核心步骤
浏览器的主线程会依次做这几件事(简化版):
-
解析HTML → 构建DOM树(内存中的一个对象模型,表示网页结构)
-
解析CSS → 构建CSSOM树(表示每个元素应该长什么样)
-
合并 → 生成渲染树(只包含可见元素及其最终样式)
-
布局 → 计算每个元素在屏幕上的精确位置和大小
-
绘制 → 生成绘制指令("在(x,y)处画一个红色矩形")
-
合成 → 将分层内容交给GPU,显示到屏幕
5.3 JS如何修改页面?------通过操作DOM/CSSOM
JS代码本身也是输入数据,被浏览器内置的JS引擎(V8)执行。关键在于:浏览器给JS引擎提供了一个宿主对象 ------document。
当你的JS执行 document.getElementById('box').style.backgroundColor = 'blue' 时:
-
JS引擎执行这段JS的机器码(自己解释或JIT编译后执行)
-
JS引擎调用浏览器提供的原生函数(
getElementById、style的setter) -
这些原生函数直接修改DOM树中对应节点的样式属性
-
然后标记该节点为"需要重新计算样式"
-
下一轮渲染时,浏览器会重新执行样式计算、布局、绘制,最终屏幕变蓝
关键机制:事件循环(Event Loop)
浏览器主线程不断重复:执行JS任务 → 执行微任务 → 必要时渲染。JS执行和渲染是交替进行的,不能同时并行。如果一个JS任务执行时间太长,页面就会卡住------因为渲染一直没机会发生。
5.4 一个生活比喻
-
浏览器 = 一个智能工厂(整体程序)
-
HTML = 产品设计图纸(定义结构和组件)
-
CSS = 涂色和装饰说明(定义外观)
-
JS = 自动化控制脚本(定义交互逻辑)
-
工厂内部的机器 = 浏览器的各个模块(解析器、渲染引擎、JS引擎)
-
CPU = 工厂的电力系统------它只负责让所有机器运转,不关心图纸内容
六、总结:打通底层到应用层的统一模型
回顾整篇文章,我们其实只讲了一件事:
CPU只会机械执行内存中的机器码。所有软件------操作系统、编译器、解释器、浏览器------都是不同形态的机器码程序。你的高级语言代码,最终要么直接变成机器码(编译型),要么作为数据被另一个"预制程序"(解释器/浏览器)处理。
这个统一模型可以帮助你理解:
-
从指令到应用程序:任何应用程序(微信、游戏、浏览器)的底层,都只是一堆CPU指令在按照程序计数器顺序执行。所谓的"应用程序执行原理",本质就是"CPU执行机器码 + 操作系统权限管理"。
-
为什么C比Python快? 因为C的机器码是直接执行,Python的机器码是解释器的预制程序在执行,多了解析开销。
-
为什么操作系统崩溃比应用程序崩溃严重得多? 因为操作系统运行在内核态(最高权限),应用程序在用户态(受限权限)。
-
为什么浏览器能同时处理HTML、CSS、JS? 因为浏览器是一个预制好的大程序,内部包含了解析/执行这三种数据的不同模块。
打通了这个认知,你再去看任何新技术、新框架、新语言,都不会再感到神秘------它们统统逃不出"CPU执行机器码 + 操作系统权限管理 + 预制程序处理输入数据"的基本框架。
作者寄语:计算机科学没有魔法。所有看似神奇的效果,底层都是简单的规则层层组合。希望本文能帮你搭建起从硬件到软件的认知桥梁,让你在今后的学习和工作中"胸有成竹"。
如果觉得有收获,欢迎点赞👍、关注🔔、收藏⭐,让更多人看到。有任何疑问或想深入探讨的方向,评论区告诉我!