乐于分享
好东西不私藏

为什么苹果程序不能在安卓设备上运行?

为什么苹果程序不能在安卓设备上运行?

当你了解了关于计算机的基础知识之后,往往会出现更多的问题:

  1. CPU是计算的的大脑,是具体执行指令的,对吧?对!
  2. 程序就是写好的计算机指令,对吧?对!
  3. 相同的CPU执行的指令也相同,对吧,例如intel的CPU。对!
  4. 那为什么我朋友的Linux程序没法直接在我的Windows上面跑呢?

更一般的,常常有人问,为什么安卓程序不能在苹果上跑?

这是个好问题,但是不好回答。我们这次不直接回答问题,而是抽丝剥缕,看看到底能不能让不同系统的程序,在相同的CPU下面可以自然移植,直接运行。

从CPU如何执行指令开始

在一文中,我们解释了计算启动时像多米诺骨牌一样,从简单到复杂,逐级拉起的过程:

主板REST -> CPU启动 -> BIOS启动 -> OS加载器启动 -> OS启动

特别地,说到CPU是如何执行指令的:

  1. 从PC寄存器拿到下一条指令的地址
  2. 从地址提取指令
  3. 除非是停机指令,否则执行它
  4. 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的指令手册,给出了建议:

  1. 所有的参数都必须通过这几个寄存器传递,如果不够,从左往右,挨着放在内存中
  2. 函数的返回值放在这个寄存器里

硬件商看了下,说没问题,回去按照你的要求开发驱动去了。你设计了这个约定,也自然可以使用这个,终于可以避免大量的重复代码了。

有了这个函数的支持,你把硬件寄存器到地址的操作完全封在函数中了,这样每个程序只需要调用你的函数就可以设置好硬件映射了。你给它起了个名字,硬件驱动库。你自己可以用,其他人也可以用,真好。

但是你很快发现个问题,CPU几乎都是在闲着!

CPU的愤怒

从设计开始,CPU就被设定成极其快速地不断执行指令,直到停机或者断电。它每秒执行的指令以十亿计,这还是单个核。你的程序在CPU看来,须臾之间就完成了,CPU一直在空转。我们买了CPU不是让它来耗电的吧。

你想了一下,对!我们的设计需要让CPU尽可能的忙起来,最好是一直忙,那就好了。让CPU忙很简单,给它多多的程序让它跑!

于是你增加了规则:

  1. 每个程序都不能独占CPU啊,每次给你们0.01秒的时间。不要看这个时间短,这个足以执行百万条以上的指令了
  2. 我负责给你们切换,你们不用操行了,做好你们自己

你很快发现,给这些程序做切换太复杂了,一不小心就搞错。更糟糕的是,一个程序错了,还会殃及其他程序,这可不行。你再次翻了CPU的指令手册,很快得出结论,你需要用保护模式,把程序“保护”起来。

CPU指令手册说,你现在的使用方式叫做实模式。实模式只能使用1M的内存,可惜了你安装的16G大内存!而且,所有的代码都能操作硬件,这太容易出问题了。你需要保护模式才能解决这些问题:

  1. 可以使用全部的内存!
  2. 有特权级,你的代码高特权,别人的代码低特权。低特权代码只能通过调用你的代码做搞特权的事情
  3. 硬件内存保护,谁敢越界访问,就触发异常掐死
  4. 最关键的,有虚拟内存支持。

你问,这个虚拟内存是干啥的?

CPU指令手册:虚拟内存是最伟大的设计,这可不是我胡说。你觉得你管理多个程序最大的困难是什么?

你想了一下说,应该是让每个程序设定好他的起始地址,这个地址很容易冲突,来回拉扯很费时间。

CPU指令手册:这就对了,虚拟内存就是解决这个问题的。每个程序都不想关心别人怎么布局,也没法关心。他想往哪里写就往哪里写,写的不对也是自己的问题,不应该影响别人。怎么做呢,来,我给你面授机宜……

你恍然大悟,这就是你想要的,瞌睡递给了枕头,不是么?

但是CPU手册又说了,使用保护模式,有些条件,首先就是BIOS的依赖不能用了,那是实模式的方式,不兼容。你自己实现吧。反正有了硬件的驱动。

依赖CPU指令和外部驱动程序(保护模式版)

你算了算,这个买卖挺划算,于是指定了如下规则:

  1. 之前的规则作废,以这一版为准
  2. 为了各自安好,我们要用保护模式,大家需要在低特权模式下跑,需要什么危险的操作,交给我做
  3. 我们要用虚拟内存,大家再也不会打架了。具体怎么安排,看我的
  4. 我给大家提供的硬件访问接口,不能用BIOS了。但是这个你们不用关心,该怎么干还是怎么干

程序纷纷举手欢迎,CPU使用率终于上来了,你可以一边听音乐,一边捣鼓你的不依赖的OS的纯CPU指令程序。

你分析了下现有的支持,发现有点乱。这是设计演化的正常现象。于是你花了一点时间,整理了下设计,这下看起来好多了:

  • 程序管理。给每个程序分配合适的CPU时间,以及时间到了的时候切换它们
  • 虚拟内存。按照CPU的要求,配合CPU实现虚拟内存。你偶尔发现,你分出去的内存远远超过实际拥有的内存,但是程序依然可以运行。空头支票开的好,你算是爱上虚拟内存了
  • 硬件管理。在硬件商供应的驱动的支持下,你统一了硬件的使用
  • 核心库和函数调用规则。你提供了必要核心库,这样程序就可以大大减少需要自己开发的代码量,而且不会破坏你的设计规则。

你对你的工作颇为得意,心想,这个肯定可以满足不依赖OS就可以执行CPU指令了,这个肯定是可以自然移植的,只要是和我的CPU一样就行。

为了标记的程序格式,你要求程序的最后四个字节必需是PURE,这样可以快速识别不是程序的数据。

古儒的评论

隔壁有个姓古的大哥,叫做古儒,有一天过来串门。你把你做的事情告诉了他,但是没有说你的目的,看他猜得出来猜不出来。

古儒说,这段时间不声不响,原来是搞操作系统啊!

什么?

古儒说,你不是实现了一个完整的操作系统么,虽然还未完备,但是核心的东西都齐备了啊。我给你说下。

  1. 程序管理,这不就是OS的进程管理么,怎么启动,终止,怎么调度,怎么分配时间
  2. 虚拟内存不就是OS的内存管理么,怎么给程序分配内存,怎么回收,怎么支持册程序执行的隔离
  3. 硬件管理是OS的设备管理,怎么初始化,怎么调用
  4. 核心库是你的系统调用和ABI,程序怎么使用你的工具
  5. 你还甚至设计一个独立的二进制格式PURE。这个不就是类似于Windows的PE和Linux的ELF么

你才意识到,为了实现不依赖OS可以直接执行纯CPU指令的目的,你不得不实现一个新的OS。更不幸的是,你的OS不和任何现有的OS兼容,为你的OS系的程序完全无法在Windows或者Linux上运行。

你终于明白,OS就是让你在CPU上执行指令的配套设施啊,程序不兼容,只是因为这些配套设施是由不同的人做的,导致这些配套设置本身不兼容罢了。

你小心翼翼地说,我就是想让我的程序可以在不同的平台上跑而已。

古儒说,啊?那你可以用虚拟机或者实现一个模拟层。

虚拟机或者模拟层,这又是什么?