
首先,先知道一个概念: 运行环境 = 操作系统 + 硬件
那么,先提出一个问题:你写了一个程序,为什么它不能在任何地方运行
程序运行需要 操作系统 和 硬件 同时满足条件
想象一个场景:
目标: 把这道菜放进去烤箱烤10分钟
场景一: 厨房有烤箱 -> 顺利执行
场景二: 厨房没有烤箱,只有高压锅 -> 出现问题 -> 无法执行
那么这个问题会出现什么后果?
写一个软件,要针对每种平台都写一遍,开发成本极高
核心概念:不让程序直接去操作硬件,否则必须为每一种硬件组合写不同的代码
解法逻辑:
程序(只调用 windows api) -> windows 系统调用层 -> 驱动程序层 -> 实际硬件
windows 系统调用层:所有程序看到的接口都一样
驱动程序层(NVIDIA驱动 AMD驱动):通常由硬件厂商提供,对上统一接口
实际硬件:如显卡 声卡 网卡
windows 就像翻译官,程序说要 “播放声音”,windows就把这句话翻译成不同硬件能够理解的方式,再交给下面的驱动程序层
我们先来看一个问题,windows 确实在很大程度上解决了 “同一 OS 下的硬件差异”,但 OS 本身之间的差异 仍然存在
比如:Windows 和 Linux
目标:打开一个文件
Windows:
HANDLE h = CreateFile("data.txt", GENERIC_READ, ...);
Linux:
int fd = open("data.txt", O_RDONLY);
可以看出,即使 CPU 相同,程序也不一定能在不同的 OS 下直接运行,因为它依赖的系统调用接口、可执行文件格式、运行时环境都可能不同
那么 运行环境变得更清晰了
运行环境 = CPU 架构 + 操作系统
那在 Linux 生态里,一种很常见的解决办法是什么?就是 源代码分发
不分发编译好的程序,直接分发源代码,让用户在自己的机器上编译
这种方式不太好,使用门槛变高,会变相地流失很多用户,那么 Java 虚拟机就出现了
Java 虚拟机:一次编写,处处运行
它的做法是这样的:创造一个 "中间层",再由中间层去适配各种环境
这和上面的 windows 做法有点像,本质上也是在程序和真实运行环境之间再加一层适配。不过这里适配的对象,已经不只是同一 OS 下的硬件差异,而是更广义的平台差异
传统编译: 源代码 -> 编译器 -> x86机器码 / ARM机器码
java: 源代码 -> java编译器 -> 字节码 -> 不同平台上的 JVM 执行
这么说有点抽象,
那代价就是,会多一层运行时开销
原本执行机器码的流程:CPU → 指令 → 执行
JVM执行字节码:CPU → JVM处理字节码 → 转成当前平台可执行的形式 → 执行
云计算:按需租用运行环境
核心概念:不只是搬程序,而是把“运行环境”本身也变成一种可以出租的服务
可分为:SaaS、PaaS、IaaS
SaaS就像是去饭店吃饭。PaaS是在厨房,工具给你配好了。IaaS则更像是只给你厨房场地和基础设施,剩下很多东西都要自己来配
最后,还有个最根本的问题:电脑刚通电,操作系统还没有加载到内存中来,谁来启动系统?
这有点像“先有鸡还是先有蛋”的问题
没有运行操作系统我怎么写进内存?操作系统没在内存我怎么运行?
这时,BIOS 就出现了。这里可以先把它理解成机器通电后最先运行的一小段固化程序。
它是这样做的
引导并装入程序(Bootloader):比如 windows Boot Manager 这一类启动程序
这个过程被叫做 自举(Bootstrap):用一个小程序拉起一个更大的程序,一次又一次,最终把操作系统拉起来
打个比方:就像学习,你不会一上来就什么都会,而是有许许多多的基础知识,来理解更高级的知识
最后收一下,如果只用一句话去理解 运行环境,那就是: 程序不是写完就能跑,而是要放到一个合适的环境里,它才能真正跑起来。
这个环境不只是电脑这么简单,它背后还包括 CPU 架构、操作系统、驱动、运行时,甚至平台本身提供的接口。前面讲 Windows、Linux、JVM,本质上都在解决同一个问题: 怎么让程序和运行环境对得上。
所以理解运行环境,其实就是在理解一件事: 写程序不能只盯着代码本身,还要看它最后是在哪跑、靠什么跑、换个地方还能不能跑。而最后讲到 BIOS、Bootloader 和 Bootstrap,其实又把这个问题往前推了一步: 不只是程序要有运行环境,连操作系统自己,也得先被一步一步拉起来,才能让后面的东西开始运行。