计科幼幼班
今天的分享可以(粗浅)解答以下几个问题:
- 如何制作一台计算机
- 苹果的 Rosetta 是怎么把 x86 的程序运行在 arm 上的
- Steam Deck 是怎么把 Windows 的游戏运行在 Linux 上的
如何制作一台计算机
逻辑门是什么?
是数字逻辑电路中的基本单元,可以用真值表定义一个逻辑门,以 NAND(Not AND)逻辑门为例:
Input A | Input B | Output |
---|---|---|
0 | 0 | 1 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
Q:它有什么用?
A:我们只用它就可以制作出计算机
先来制作 NAND 本身,下面是用于制作 NAND 的原材料:
三个圆是元器件的引脚,上面的引脚是输出,下面是控制(c)和输入(in)。
控制端的原理很简单,就是电磁铁,c 通电,则 relay 的舌片被磁铁吸住,控制 relay 开或关。
in 是电源,始终有电,我们把有电的状态叫做 1,没电叫做 0
relay(default on):
- c 没电,则电路接通,输出 1
- c 有电,则电路断开,输出 0
relay(default off):
- c 没电,则电路断开,输出 0
- c 有电,则电路接通,输出 1
实践时间,不提供解法。跟着 nandgame 一路把第一部份所有逻辑门写完。
到这里对数字电路应该有了初步的认识,但这个课程后面还有很多东西,有条件的话我们后面再分享:
- ALU(算术逻辑单元)
- 如何实现存储(bit, byte, 寄存器,内存)
- 机器语言与汇编语言,汇编器
- 高级编程语言,编译器
- 操作系统
- 应用程序
苹果的 Rosetta 是怎么把 x86 的程序运行在 arm 上的
https://support.apple.com/zh-tw/HT211861
Rosetta 2 可讓配備 Apple 晶片的 Mac 使用專為配備 Intel 處理器的 Mac 所開發的 App。
比如我们制作了一个 CPU,它支持一系列指令(只是举例哈):
- 写入寄存器a
- 读取寄存器a
- 写入寄存器b
- 读取寄存器b
- 将第一个、第二个数相加,结果写入寄存器a
指令集就是规定了这样一系列指令,如果某个 CPU 实现了这些指令,就可以说它支持某个指令集。
然后我们写了一段代码1x3+2=?
,并以这个 CPU 为目标,编译出来的二进制形如:
- 将 1 写入寄存器a
- 将 1 写入寄存器b
- 相加
- 将 1 写入寄存器b
- 相加
- 将 2 写入寄存器b
- 相加
- 读取寄存器a
这个二进制在我们的 CPU 上执行完,寄存器a中的数字就是代码的执行结果。但是如果我们拿为另一个 CPU 编译的二进制,在我们的 CPU 上执行:
- 将 1 写入寄存器a
- 将 3 写入寄存器b
- 相乘 <<<<
- 将 3 写入寄存器b
- 相加
- 读取寄存器a
执行就会失败,原因是我们的 CPU 并不支持 相乘 这个指令。简单来说,X86 和 ARM 就是这样两个互不相容的指令集,所以为 X86 编译的二进制不能运行在 ARM 上。
那 Rosseta 是怎么做到的呢?还是以上面的相乘操作举例,我们可以把 1x3
改成 1+1+1
,将不支持的 x
改成 +
来实现,这样就能在我们的 CPU 中运行为其他 CPU 编译的二进制了。
Steam Deck 是怎么把 Windows 的游戏运行在 Linux 上的
Steam Deck 的 CPU 是 X86 的,Windows 上的游戏(大多)也是为 X86 编译的。既然它们的 CPU 都是相同的指令集,为什么二进制不能共用呢?
因为 Steam Deck 运行的 Steam OS 是 Linux 的,所以问题其实是,应用程序为什么不能跑在不同的操作系统上。
这里牵扯到两个概念:
- 可执行文件格式,比如
- 操作系统 API,比如
- POSIX
- Windows APIs
- Java 虚拟机提供给字节码的 API 也算是一种 System API
首先可执行文件格式是啥?exe?但 exe 在 linux 中是运行不了的。
举个可能不太恰当的例子,我们编写了一个图片查看器,支持 jpg 格式。这时如果要求它打开一个 png 图片,它就会说“未知文件格式,无法打开”。
是我们的屏幕无法显示 png 中保存的图片么?不是的,是我们的图片查看器不知道如何提取出 png 中的图片信息,它不认识 png 这种图片格式。
在这个例子中:
- 图片查看器 -> Window
- jpg -> exe(PE)
- png -> ELF
Window 认识 exe(PE) 这种可执行文件格式,可以解读并且让 CPU 执行其中的指令。
但不认识 ELF 这种可执行文件格式,所以无法解读出其中的指令。所以不是 CPU 不能执行指令,而是操作系统不知道怎么提取出指令,进而交给 CPU 执行。
PE 可执行文件格式:
所以 wine 是怎么在 linux 中执行 exe 的呢?既然操作系统不认识 exe,wine 自己去解析就行了嘛。然后把解析出的结果交给 linux 去执行就可以了。
但实际上还是不行,原因是不同的操作系统提供的“操作系统 API”不一样。比如 Windows API 中包含 GUI 相关的 API,创建窗口之类的,但 POSIX 中没有。
假设有这样一个 OS A,他的窗口必须“有,且只有一个标题”,当二进制想要创建一个自己的窗口,可以调用这个 OS 提供的一个 API:
// 创建一个标题为 Hello world 的窗口
create_window("Hello world")
但还有另一个 OS B,它规定窗口必须有主标题和副标题,它提供的创建窗口 API 可能就是这样:
// 创建一个标题为 Hello World,副标题为 subtitle 的窗口
create_window("Hello world", "subtitle")
如果我们尝试在 OS B 上执行为 OS A 编写的应用程序,当调用到 create_window
这个操作系统 API 时,就会出错,因为少传了副标题。
所以 wine 还得为 window 的应用程序提供兼容的操作系统 API,当应用程序调用到某个 Windows API 的时候,转为帮它调用对应的 linux 下的 API。
这样一来,对应用程序来说就像是在 Windows 中运行一样。
注意:文中的例子都是超级简化版本,只为了方便理解不保证正确全面,不要当真