日本的 PHPer 竟用 PHP 写了一个“操作系统”(上)
7 月 13 日,日本的 PHPer めもりー(@m3m0r7) 发推,展示了他用 PHP 实现的“操作系统”。之所以要加引号,是因为这个“操作系统”现阶段仅能在模拟器或虚拟机中输出“Hello World!”,除此以外,没有任何功能。
めもりー(@m3m0r7)开源了这个 PHP 实现的“操作系统”:github.com/m3m0r7/php-…。
既然有了源代码,我们就先来玩一玩这个“操作系统”,然后读一读背后的 PHP 源代码,再学一学底层的知识,最后试着改一改,基于 めもりー(@m3m0r7)提供的框架,用另一种写法输出“Hello World!“。
玩一玩
m3m0r7/php-os
项目的 ReadMe 文档写得比较详细,我们只需要按照里面列出的步骤一步步去做就可以体验这个“操作系统”了。
该项目依赖 8.3 以上的 PHP 版本,在 macOS 上可通过如下命令安装高版本的 PHP
$ brew tap shivammathur/php $ brew tap shivammathur/extensions $ brew install shivammathur/php/php@8.4
若执行
composer require m3m0r7/php-os
报错,可先git clone
该项目,然后进入项目的根目录,执行composer update
。
如果你懒着动手操作或者只想看看这个“操作系统”的运行效果,那么可以直接观看这个视频:
另外,还可以通过以下命令将
./dist/build/php-os.img
(见 ReadMe 文档第 4 步)作为引导文件,生成一个.iso
的光盘镜像。这样就可以在用 VirtualBox 或 VMware Workstation / Fusion 系列等创建出的虚拟机中,通过加载该.iso
镜像来体验这个“操作系统”了。# `cdrtools` 中含有 `mkisofs` 这个命令 $ brew install cdrtools $ mkisofs -o output.iso -b ./dist/build/php-os.img -no-emul-boot ./dist/build/php-os.img
读一读
我们知道,计算机唯一能够理解和执行的就是机器语言(机器代码),就连同属于低级语言的汇编语言都必须先通过汇编器(assembler)转换为机器代码才能执行。
机器语言中的每一条指令(本质上是一个二进制数字)都对应计算机处理器的一项操作,如加载数据、存储数据、加法、减法、跳转等。而汇编语言使用了简短的助记符(如 MOV
、ADD
、SUB
)来代替机器指令。这些助记符(mnemonic)比二进制数字更容易理解和记忆。
所以,裸机是不可能直接执行 PHP 代码的。所谓“用 PHP 实现操作系统”,极有可能要么使用 PHP 实现了某种编译器(转换器),将 PHP 代码转换为汇编语言的代码;要么使用 PHP 实现出代码生成器,通过调用 PHP 的函数生成汇编语言的代码,就好像利用 PHP 框架中的 SQLBuilder
构造 SQL 语句一样。
阅读源码后会发现,めもりー(@m3m0r7)采用了后者,打造了一个生成汇编语言代码的框架,例如通过如下代码即可生成 2 条汇编语言的指令:
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use \PHPOS\Architecture\Architecture;
use \PHPOS\Architecture\ArchitectureType;
use \PHPOS\OS\Code;
use \PHPOS\OS\Instruction;
use \PHPOS\Architecture\Register\DataRegister32Bits;
use \PHPOS\Architecture\Register\x86_64\Register;
use \PHPOS\Operation\Mov;
use \PHPOS\Operation\Xor_;
$architecture = new Architecture(ArchitectureType::x86_64);
$code = new Code($architecture);
$instruction = new Instruction($code);
$axRegister = new DataRegister32Bits(Register::EAX);
$instruction->append(Xor_::class, $axRegister->value(), $axRegister->value());
$instruction->append(Mov::class, $axRegister->value(), 42);
echo $instruction->assemble();
// 输出的汇编语言的代码
// xor eax, eax
// mov eax, 42
然而,ReadMe 文档的示例代码中并没有直接使用 Instruction
、Register
这样的类或枚举(enum
),而是先创建了 Code
类的实例,然后调用其上的方法,如 registerService()
等,最后通过 Bundler
类生成 Makefile。
我想这体现出了作者的设计思路——使用 Facade 设计模式隐藏底层组件的复杂性、简化接口,并通过一系列 Service
类来组织汇编语言的代码。
为什么说 Code
类是在采用 Facade 设计模式来简化组件之间的交互呢?示例代码的时序图能直观地解释这一点:
在 Code
类和 Bundler
类的背后(灰色区域),竟然还有那么多组件!
“那通过一系列 Service
类来组织汇编语言的代码”这一点又是怎么体现的呢?我们以最简单的 Service
之一 DefineOrigin
类为例,
use \PHPOS\Service\BIOS\Standard\DefineOrigin;
$simpleService = new DefineOrigin($code, null, \PHPOS\OS\OSInfo::MBR->value);
$instruction = $simpleService->process();
echo $instruction->assemble();
// 输出
// [org 0x7c00]
这个 Service
封装了 [org 0x7c00]
这样一条汇编语言的指令(Instruction
类的对象):
class DefineOrigin implements ServiceInterface
{
// ...
new Instruction($this->code))
->append(
fn () => $this
->code
->architecture()
->runtime()
->callRaw(
sprintf(
<<< __ASM__
[org 0x%04x]
__ASM__,
$origin
),
),
);
而对于
cli
xor ax, ax
xor bx, bx
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 31744
这一系列汇编指令,则封装在 \PHPOS\Service\BIOS\Standard\Segment
这个 Service
中,
class SetupSegments implements ServiceInterface
{
// ...
new Instruction($this->code))
->append(Cli::class)
->append(Xor_::class, $ac->value(), $ac->value())
->append(Xor_::class, $base->value(), $base->value())
->append(Mov::class, $ds->segment(), $ac->value())
->append(Mov::class, $es->segment(), $ac->value())
->append(Mov::class, $ss->segment(), $ac->value())
->append(Mov::class, $sp->pointer(), $this->code->origin());
按照作者的设计,要想为操作系统加入新功能,就要把相应的指令(Instruction
类的实例)封装到 Service
中。虽然这样组织代码有些复杂,不如一行汇编指令就对应一个 $instruction->append()
调用来得直观,但当代码规模逐渐扩大,就算不定义 Service
,也势必需要一种将相关代码分门别类组织起来的方法,只不过作者想出的办法是定义 Service
。
相信大家现在都明白用 PHP 实现操作系统的原理了吧,那为什么由 ReadMe 文档中的 PHP 代码生成的汇编语言程序就能在裸机上运行并输出“Hello World!”呢?
下一篇文章将分析这段汇编语言的程序,并自定义一个 Service
,用另一种写法输出“Hello World!”。
转载自:https://juejin.cn/post/7392513610948493350