likes
comments
collection
share

日本的 PHPer 竟用 PHP 写了一个“操作系统”(上)

作者站长头像
站长
· 阅读数 23

7 月 13 日,日本的 PHPer めもりー(@m3m0r7) 发推,展示了他用 PHP 实现的“操作系统”。之所以要加引号,是因为这个“操作系统”现阶段仅能在模拟器或虚拟机中输出“Hello World!”,除此以外,没有任何功能。

日本的 PHPer 竟用 PHP 写了一个“操作系统”(上)

めもりー(@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

如果你懒着动手操作或者只想看看这个“操作系统”的运行效果,那么可以直接观看这个视频:

🎬 用 PHP 实现的”操作系统“

另外,还可以通过以下命令将 ./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)转换为机器代码才能执行。

机器语言中的每一条指令(本质上是一个二进制数字)都对应计算机处理器的一项操作,如加载数据、存储数据、加法、减法、跳转等。而汇编语言使用了简短的助记符(如 MOVADDSUB)来代替机器指令。这些助记符(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 文档的示例代码中并没有直接使用 InstructionRegister 这样的类或枚举(enum),而是先创建了 Code 类的实例,然后调用其上的方法,如 registerService() 等,最后通过 Bundler 类生成 Makefile。

我想这体现出了作者的设计思路——使用 Facade 设计模式隐藏底层组件的复杂性、简化接口,并通过一系列 Service 类来组织汇编语言的代码

为什么说 Code 类是在采用 Facade 设计模式来简化组件之间的交互呢?示例代码的时序图能直观地解释这一点:

日本的 PHPer 竟用 PHP 写了一个“操作系统”(上)

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
评论
请登录