为什么苹果程序不能在安卓设备上运行?
当你了解了关于计算机的基础知识之后,往往会出现更多的问题:
-
CPU是计算的的大脑,是具体执行指令的,对吧?对! -
程序就是写好的计算机指令,对吧?对! -
相同的CPU执行的指令也相同,对吧,例如intel的CPU。对! -
那为什么我朋友的Linux程序没法直接在我的Windows上面跑呢?
更一般的,常常有人问,为什么安卓程序不能在苹果上跑?
这是个好问题,但是不好回答。我们这次不直接回答问题,而是抽丝剥缕,看看到底能不能让不同系统的程序,在相同的CPU下面可以自然移植,直接运行。
从CPU如何执行指令开始
在一文中,我们解释了计算启动时像多米诺骨牌一样,从简单到复杂,逐级拉起的过程:
主板REST -> CPU启动 -> BIOS启动 -> OS加载器启动 -> OS启动
特别地,说到CPU是如何执行指令的:
-
从PC寄存器拿到下一条指令的地址 -
从地址提取指令 -
除非是停机指令,否则执行它 -
PC寄存器+1(移动到下一条指令)
以这个为起点,你跃跃欲试,想要设计出一个轻松跨操作系统的程序。展示一下你的功力!
仅仅依赖CPU指令的程序
于是你想,设计一个代码序列,完全符合这个要求。然后把起始地址植入到PC寄存器,岂不是就可以做到自然移植,操作系统不要瞎管,负责给我把PC寄存器设置上就行。这不可以吗?
相当可以!不过有个小问题需要澄清一下,我们肯定想要知道CPU执行后的结果,无论是输出到屏幕,还是产生其副作用。CPU只能处理数据和地址。这个咋办?
你迅速回想了一下的知识,给出了答案:把硬件寄存器映射到特定的地址段。为了简化,先不用硬件供应商的驱动了,我们自己阅读硬件手册,把寄存器映射到固定的地址区域,这样是不是就可以了呢?
可以可以,这样确实可以直接让CPU执行任何指令,而且完美控制硬件。理论上做到现有CPU能做到的一切。
事实上,BIOS(现在叫做UEFI,以下统一叫做BIOS)程序差不多就是这么执行的。
依赖CPU指令和BIOS驱动支持
更进一步,在Windows之前的DOS时代,有一种叫做COM的程序格式,几乎就是完全的CPU指令。不不过,它的设计和你的设计稍有不同,它不直接操作硬件(你可能是不知道直接操作硬件有多复杂),而是通过BIOS的中断系统。这个中断不是CPU的中断,而是BIOS提供的一种代码复用的办法,通过CPU中断的方式,调用一段BIOS写好的代码,比如输出到屏幕啦,读写磁盘了等等。你要不要?
你想了一下,BIOS基本的固定的,依赖这个不妨碍我们的自然移植的目标,也不需要讨厌的操作系统介入,而且你也不想自己看硬件文档,做无聊的寄存器映射,既然BIOS做了,使用一下也无妨。所以决定可以加入对BIOS的依赖。这样,我们就得到这样一个结果:纯净的CPU指令,给予BIOS的外设访问支持。
依赖CPU指令和外部驱动程序
BIOS的驱动支持很有限,几乎就是那种最简单的,能用就行的标准。想充分利用硬件的特性,必须要使用硬件商的驱动才行啊。你召集硬件商开会,宣读了你的伟大目标,要求他们配合。硬件商大眼瞪小眼看了半天,小心提出了一个问题:
我们的代码很多,需要复用,怎么调用那些可以复用的代码呢?
这不就是函数调用么!你仔细阅读了CPU的指令手册,给出了建议:
-
所有的参数都必须通过这几个寄存器传递,如果不够,从左往右,挨着放在内存中 -
函数的返回值放在这个寄存器里
硬件商看了下,说没问题,回去按照你的要求开发驱动去了。你设计了这个约定,也自然可以使用这个,终于可以避免大量的重复代码了。
有了这个函数的支持,你把硬件寄存器到地址的操作完全封在函数中了,这样每个程序只需要调用你的函数就可以设置好硬件映射了。你给它起了个名字,硬件驱动库。你自己可以用,其他人也可以用,真好。
但是你很快发现个问题,CPU几乎都是在闲着!
CPU的愤怒
从设计开始,CPU就被设定成极其快速地不断执行指令,直到停机或者断电。它每秒执行的指令以十亿计,这还是单个核。你的程序在CPU看来,须臾之间就完成了,CPU一直在空转。我们买了CPU不是让它来耗电的吧。
你想了一下,对!我们的设计需要让CPU尽可能的忙起来,最好是一直忙,那就好了。让CPU忙很简单,给它多多的程序让它跑!
于是你增加了规则:
-
每个程序都不能独占CPU啊,每次给你们0.01秒的时间。不要看这个时间短,这个足以执行百万条以上的指令了 -
我负责给你们切换,你们不用操行了,做好你们自己
你很快发现,给这些程序做切换太复杂了,一不小心就搞错。更糟糕的是,一个程序错了,还会殃及其他程序,这可不行。你再次翻了CPU的指令手册,很快得出结论,你需要用保护模式,把程序“保护”起来。
CPU指令手册说,你现在的使用方式叫做实模式。实模式只能使用1M的内存,可惜了你安装的16G大内存!而且,所有的代码都能操作硬件,这太容易出问题了。你需要保护模式才能解决这些问题:
-
可以使用全部的内存! -
有特权级,你的代码高特权,别人的代码低特权。低特权代码只能通过调用你的代码做搞特权的事情 -
硬件内存保护,谁敢越界访问,就触发异常掐死 -
最关键的,有虚拟内存支持。
你问,这个虚拟内存是干啥的?
CPU指令手册:虚拟内存是最伟大的设计,这可不是我胡说。你觉得你管理多个程序最大的困难是什么?
你想了一下说,应该是让每个程序设定好他的起始地址,这个地址很容易冲突,来回拉扯很费时间。
CPU指令手册:这就对了,虚拟内存就是解决这个问题的。每个程序都不想关心别人怎么布局,也没法关心。他想往哪里写就往哪里写,写的不对也是自己的问题,不应该影响别人。怎么做呢,来,我给你面授机宜……
你恍然大悟,这就是你想要的,瞌睡递给了枕头,不是么?
但是CPU手册又说了,使用保护模式,有些条件,首先就是BIOS的依赖不能用了,那是实模式的方式,不兼容。你自己实现吧。反正有了硬件的驱动。
依赖CPU指令和外部驱动程序(保护模式版)
你算了算,这个买卖挺划算,于是指定了如下规则:
-
之前的规则作废,以这一版为准 -
为了各自安好,我们要用保护模式,大家需要在低特权模式下跑,需要什么危险的操作,交给我做 -
我们要用虚拟内存,大家再也不会打架了。具体怎么安排,看我的 -
我给大家提供的硬件访问接口,不能用BIOS了。但是这个你们不用关心,该怎么干还是怎么干
程序纷纷举手欢迎,CPU使用率终于上来了,你可以一边听音乐,一边捣鼓你的不依赖的OS的纯CPU指令程序。
你分析了下现有的支持,发现有点乱。这是设计演化的正常现象。于是你花了一点时间,整理了下设计,这下看起来好多了:
-
程序管理。给每个程序分配合适的CPU时间,以及时间到了的时候切换它们 -
虚拟内存。按照CPU的要求,配合CPU实现虚拟内存。你偶尔发现,你分出去的内存远远超过实际拥有的内存,但是程序依然可以运行。空头支票开的好,你算是爱上虚拟内存了 -
硬件管理。在硬件商供应的驱动的支持下,你统一了硬件的使用 -
核心库和函数调用规则。你提供了必要核心库,这样程序就可以大大减少需要自己开发的代码量,而且不会破坏你的设计规则。
你对你的工作颇为得意,心想,这个肯定可以满足不依赖OS就可以执行CPU指令了,这个肯定是可以自然移植的,只要是和我的CPU一样就行。
为了标记的程序格式,你要求程序的最后四个字节必需是PURE,这样可以快速识别不是程序的数据。
古儒的评论
隔壁有个姓古的大哥,叫做古儒,有一天过来串门。你把你做的事情告诉了他,但是没有说你的目的,看他猜得出来猜不出来。
古儒说,这段时间不声不响,原来是搞操作系统啊!
什么?
古儒说,你不是实现了一个完整的操作系统么,虽然还未完备,但是核心的东西都齐备了啊。我给你说下。
-
程序管理,这不就是OS的进程管理么,怎么启动,终止,怎么调度,怎么分配时间 -
虚拟内存不就是OS的内存管理么,怎么给程序分配内存,怎么回收,怎么支持册程序执行的隔离 -
硬件管理是OS的设备管理,怎么初始化,怎么调用 -
核心库是你的系统调用和ABI,程序怎么使用你的工具 -
你还甚至设计一个独立的二进制格式PURE。这个不就是类似于Windows的PE和Linux的ELF么
你才意识到,为了实现不依赖OS可以直接执行纯CPU指令的目的,你不得不实现一个新的OS。更不幸的是,你的OS不和任何现有的OS兼容,为你的OS系的程序完全无法在Windows或者Linux上运行。
你终于明白,OS就是让你在CPU上执行指令的配套设施啊,程序不兼容,只是因为这些配套设施是由不同的人做的,导致这些配套设置本身不兼容罢了。
你小心翼翼地说,我就是想让我的程序可以在不同的平台上跑而已。
古儒说,啊?那你可以用虚拟机或者实现一个模拟层。
虚拟机或者模拟层,这又是什么?
夜雨聆风