借り初めのひみつきち

仮ブログです。

最近の myos

最後にこの記事書いたの何ヶ月前だったかな?な感じなのでタイトルも変更です。

綺麗なウィンドウシステムがそれなりに動くようになって逆に見た目の進化が乏しくなってしまった myos ですが、今も中身は進化しています。

外部アセンブラの廃止

高級言語で OS を作るにあたり、アセンブラに一切頼らないで作るのはおそらく不可能でしょう。

myos も 99% の処理は rust で作っていますが、一部にアセンブラが必要な処理があります。

myos を作り始めた頃の rust でインラインアセンブラを使うには、汚名高い AT&T 表記と gcc っぽいレジスタ指定が必要で正直使いやすいとは言えなかったので、外部アセンブラで作ったモジュールを build.rs を使ってビルド時にリンクするというやり方で対応していました。

しかし、この方法は解消したいと思っていました。

やがて rust 側のインラインアセンブラの文法が整理されてだいぶ使いやすくなったので、アセンブラのモジュールを廃止してインラインアセンブラに統一することにしました。

ここで問題となるのが、 SMP 初期化部分です。

x64 の SMP の初期化ではリアルモードからロングモードに遷移するコードを実行するためインラインアセンブラで記述できるかどうかも微妙で対応が難しそうです。 一方、コードとしてはカーネルと独立しているのでインライン化する意味も薄く、外部アセンブラモジュールをリンクする必要もあまりありません。

SMP 初期化部分は頻繁に更新しないしサイズも小さいので、別途外部アセンブラでバイナリファイルを作って include_bytes! マクロで読み込み実行時に配置して関数ポインタを使って呼び出すように変えました。 フルビルド時には全て更新しますが、通常のビルドではレポジトリにある smpinit.bin をそのまま使います。

ファイルシステムUNIX 要素

myos の設計目標のひとつに「UNIX 互換にしない」というのがあります。

汎用 OS は UNIX 系かそれ以外かに大別できるほど UNIX の影響力が強いです。

myos は UNIX 互換ではないので UNIX 系 OS の有名なシステムコールである fork には対応していません。 その他の UNIX の代表的な要素もほとんどはサポートされていないし、サポートする予定もありません。

しかし、 UNIXファイルシステムの仕組みはよくできています。 あの MS-DOS でさえ、ファイルシステムUNIX の影響を強く受けているほどです。 *1

というわけで、裏で動いてる仕組みは UNIX とだいぶ違いますが、 マウントによるファイルシステム拡張とデバイスファイルをサポートしました。

現状デバイスの種類によってインターフェースがバラバラになっていますが、デバイスファイルで整理できるものは整理したいです。

今後の予定

現在の API はとりあえず動けばいいという感じであまり洗練されてない部分も多いです。 ファイルシステムの改修が落ち着いたら API を整理したいです。

*1:MS-DOS version 2 最大の特徴のひとつが、 CP/MファイルシステムUNIX(XENIX) のファイルシステムを当時の技術限界でハイブリッドに融合させたことだと思ってます。

Kano PC

教育用パソコンとかいう奴らしいです

元は5万円くらいしたようですが、現在は2万円くらいで手に入るので購入してみました。 結論から言うと、ハードウェアはゴミです。これを買うくらいなら mouse の E10 の方がはるかにマシです。

windows がギリギリ動くスペックで実用性はありません。 見た目は 2in1 の形に見えますが、分厚く重いのでタブレットとして使うのは無理があります。

独特なアプリが付属しているのは興味深いですが、 CPU の説明に嘘があったり色々最適化が甘いです。

以降は自作 OS (myos) の観点から

Landscape 液晶です (超重要)
Portrait 液晶のような小細工しなくてもちゃんと画面描画できます。
しかし、同じ CPU で Portrait 液晶の mouse E10 の方が gbench が高速に動作します。謎です。

キーボードはボゴピンで接続する USB HID となっています。 独自コネクタで作りが甘いせいか、接続不良で Address Device コマンド発行時に USB Transaction Error になりやすいです。

この HID デバイスには Interface が2個あって、1個目は HID Boot Keyboard となっていて概ね普通のキーボードとして振る舞います。

顔文字の刻印された謎のキーを押すと 08 00 37 00 00 00 00 00 という謎のデータが送られてきます。 これは Left GUI + "." と解釈され、 windows 上では絵文字パットが起動します。 *1

問題は2番目の Interface で、クラスコードは HID Boot Mouse になっているのでトラックパッドのように見えますが、 実際の内容をみるとそれ以外のデータ (キーボードのマルチメディア制御キーなど) も送られてきます。

ファームウェアのバグなのか、特定のキーを押したときに 02 00 00 という謎のデータが一緒に送られてくることがあります。 これはレポートの定義と矛盾した謎データに見えます。

なお、執筆時点の myos では肝心のトラックパッドのデータを取得できていません。

windowsバイスマネージャの状況証拠から USB HID 接続の可能性が高く、 chromeOS でも普通に使えたので特殊なドライバは必要ないと思います。

HID デバイスに Usage 000D_000E (Device Configuration) というものがあって、そこで何か設定しないとダメな感じがしますが、レポートのパースに失敗しているのか有効なデータを設定することができていません。

繰り返しになりますが、これを買うくらいなら mouse の E10 の方がマシです。

*1:厳密には最初に 08 00 00 00 00 00 00 00 が送られて、 Left GUI を押したと認識させた一瞬後に "." のデータが来ます

raspberry pi ベアメタルでマルチコアを利用する方法

入手困難とうわさの raspberry pi を偶然にも入手たので最近触っています。

raspi の CPU は4コアで動作しますが、現在のファームウェアは最初のコア (ID 0) のみ起動します。*1

他のコアがどうしているかというと、ファームウェア物理アドレス 0x0000_00E0, 0x0000_00E8, 0x0000_00F0 の値を監視しているので、ここを書き換えてやれば起動できます。

例えば 0x0000_00E0 に 0x0008_0000 (_start のアドレス) を書き込むと ID 1 のコアが _start から実行開始します。

実際に書き込む場合、マルチコアでメモリ書き込みを保証するために Release のメモリーオーダーで書き込み、 sev 命令でイベントを通知します。

Rust だとこのような感じになります。

    for p in [0xE0, 0xE8, 0xF0] {
        let p = &*(p as *const AtomicUsize);
        p.store(_start as usize, Ordering::Release);
        asm!("sev");
    }

このコードを実行すると各コアが _start から実行開始するので、それぞれ mpidr_el1 レジスタを読み出して下位2ビットからコア ID を取得し、シフトした決め打ちのスタックポインタを設定し、 bl 命令で初期化関数を呼び出します。

_start:
    mrs     x1, mpidr_el1
    and     x1, x1, #3
    cbz     x1, 2f

    lsl     x2, x1, #16
    add     x2, x2, #0x10000
    mov     sp, x2

    bl      _smp_main

1:  wfe
    b       1b
2:
    (省略)

これだけでマルチコアが起動できました。

APIC の設定を弄ったりロングモードの用意が必要な x86-64 に比べると、 Arm64 のマルチコア起動はスッキリしていて簡単にできますね!

と、言いたいところですが、このままでは atomic 変数の read-modify-write や CAS 操作に必要な命令 (stxrなど) が正しく動作しません。 CAS 操作ができないと spinlock を実装できないのでコア間で同期を取る事ができません。 つまり、コアごとに完全に役割分離しているような特殊な用途を除いてマルチコアを活用する事ができません。 CAS 操作を正しく実行するためには、 MMU とキャッシュの設定が必要になります。

この辺り x86 は適当に書いてもなんとなく動きますが、 arm では真面目にちゃんと設定しないと意図した通りに動いてくれないので難しいです。

なお、 Arm 系 CPU は基本的に同様のプロトコルで起動できるようで、各コアの監視アドレス 0xE0, 0xE8, 0xF0 は DeviceTree の各 CPU にある cpu-release-addr で確認できます。

  cpus {
        #address-cells = <0x01>;
        #size-cells = <0x00>;
        enable-method = "brcm,bcm2836-smp";
        phandle = <0xdb>;

        cpu@0 {
            device_type = "cpu";
            compatible = "arm,cortex-a72";
            reg = <0x00>;
            enable-method = "spin-table";
            cpu-release-addr = <0x00 0xd8>;
            phandle = <0x28>;
        };

        cpu@1 {
            device_type = "cpu";
            compatible = "arm,cortex-a72";
            reg = <0x01>;
            enable-method = "spin-table";
            cpu-release-addr = <0x00 0xe0>;
            phandle = <0x29>;
        };

        cpu@2 {
            device_type = "cpu";
            compatible = "arm,cortex-a72";
            reg = <0x02>;
            enable-method = "spin-table";
            cpu-release-addr = <0x00 0xe8>;
            phandle = <0x2a>;
        };

        cpu@3 {
            device_type = "cpu";
            compatible = "arm,cortex-a72";
            reg = <0x03>;
            enable-method = "spin-table";
            cpu-release-addr = <0x00 0xf0>;
            phandle = <0x2b>;
        };
    };

*1:過去のファームウェアは全てのコアが一斉に起動していた時代があるようで、ネットで raspi ベアメタルのやり方を調べるとスタートアップ時にコアを判別して ID 0 以外のコアで無限ループするやり方がよく紹介されています

私の OS のご紹介

ここ数年 Rust で meg-os という名前の自作 OS を作っています。

github.com

過去に何度か0から作り直しているので、似たような名前の別のものを昔見たり聞いたりしたことがあるかもしれません。

現在のコードベースのコードネームは「Maystorm」となっていて、これは2020年5月に正式に開発を開始したのが由来です。 なお、初期のコードネームが「myos」だったのでこのブログでは「myos」と表記されてることも多いです。

主要なターゲットは 2020年前後の x64 PC です。 一時期古い PC 用に機能縮小した x86 版も並行で開発していましたが、しばらく放置している間に現在では互換性がほとんどなくなってしまいました。 いずれ時間ができたら x86 版の開発も再開したいと考えています。

主な特徴として以下のような特徴を備えています。

  • POSIX との互換性は目指していません。
  • アプリケーション・カーネルの両方で Rust でのプログラミングを前提にしています。
  • 自作の WebAssembly ランタイムを搭載し、標準アプリケーションの形式として WebAssembly をサポートしています。
  • マルチコアや HyperThreading に対応したスケジューラーを備えています。
  • 半透明や角丸のウィンドウを表示可能なウィンドウシステムを搭載しています。
  • はりぼて OS のアプリケーションをエミュレーション実行できます。

謎の SSE 例外

先週、 myos で hd audio に対応しましたが、波形メモリに直接波形を書き込むことで beep 音を生成していました。

この方法だと波形生成後のフィルター処理などが困難で、複数のアプリケーションで同時に音を鳴らすこともできませんでした。

ということで、複数のオーディオノードを接続してフィルタ処理をかけるスケジューラーを実装します。

デジタルオーディオデータは 16bit がよく使われますが、フィルタ処理の最中には 16bit の精度を超える計算をすることもあったりクリッピングの手間があるのでフィルタなどの計算は浮動小数演算で処理し、最終的に波形メモリに書き込む際に 16bit 値に丸めるようにします。

実は myos では結構前に SSE を解禁していますが、浮動小数演算を実際に使ったのは今回がはじめてでした。

QEMU で動いたので実機で試したところ、 Inexact-Result (Precision) Exception という謎の例外が発生しました。

f:id:neriring16:20220220203409p:plain

調べてみると SIMD 例外の一種で、割り切れない演算をした際など演算結果に誤差が生じたときに発生する例外のようです。 多くの場合そこまで厳密な計算したいわけではないので、ほとんどのケースでこの例外はマスクされる想定のようです。

SSE では MXCSR というレジスタを使って浮動小数演算の動作を制御することができます。

f:id:neriring16:20220220205827p:plain

このレジスタの下位 6bit は SSE 例外が派生した場合にどの例外が発生したかを示していて、 7bit 〜 12bit では例外を種類ごとにマスクするかどうか選択することができます。また丸め処理なども制御することができます。

ということで、スレッドの初期化時に LDMXCSR 命令を実行して 12bit 目の Precision Mask を 1 にセットすることでこの例外を抑制することができました。

今月の myos

久しぶりの更新になります。

現在のバージョンの myos には明確な内部目標がありました。 Intel HD Audio の対応です。 *1

HD Audio は近年の PC にはかなりの割合で搭載されており、仕様も公開されていてそれなりに情報が出揃っているので実装可能だろうという目論見で実装開始しました。

QEMU で音を鳴らすだけであればネットによくある情報だけで十分実装可能ですが、実機で鳴らす場合にはいくつかの注意点があります。

実機の HD Audio は QEMU よりも多くのノードが繋がっており、実際に音を鳴らすことが可能なノードを特定する確実なロジックは今のところよくわかりませんでした。 HD Audio で音を再生する際に登場する主要なノードとして Pin と DAC があり QEMU では DAC と Pin が直結されていますが、実機では間に Mixer が挟まっているケースが多いです。

現状の実装では音が鳴る機種と鳴らない機種があります。

実際に実装してみると XHCI に比べればだいぶ単純ですが、それでも必要な構造体がかなり多くなってしまいました。

f:id:neriring16:20220213134343p:plain

まだネイティブアプリは未対応で、 haribote OS の MML player (mmlplay.hrb) くらいしか音が鳴りませんが、 myos でも音が出るようになりました。

BEEP 音ジェネレーターはかなり適当な実装なので、将来的にはオーディオデータのスケジューラーやミキサーも実装したいです。

*1:余談ですが HD Audio の開発時のコードネームは Azalia という名前で、現在の myos のコードネームの Azalea と偶然にも非常によく似ています。

QOI 画像ビューワーつくった

最近 QOI (Quite OK Image Format) という新しい画像形式が登場しました。

QOI — The Quite OK Image Format

PDF1 ページに収まる単純な仕様でエンコーダーデコーダーもC言語で数百行と単純で高速ですが、PNGよりやや圧縮率が悪い程度というトレードオフがなかなか優秀なフォーマットです。

アルゴリズム上、写真などの自然画よりもイラスト画像に効果があり、デコーダーの軽さも考慮するとアセット画像に向いてそうです。

さて、新しい画像形式が生まれた時に問題となるのが、サポートするソフトが少ない問題です。

QOI 形式の画像は本格的な画像編集はあまり求められておらず、内容の確認とフォーマットの変換ができれば十分だと思います。 公式サイトにはいくつかツールが紹介されていますが、動作環境が微妙だったりビルドがうまく通らなかったりしてぼくの環境では簡単に使えるツールがありませんでした。

幸い Rust のライブラリはすでにいくつか提供されているようなので WebAssembly でラッピングしてウェブアプリにしてみました。

https://nerry.jp/image-viewer/

github.com

構造としては IMG タグで読み込んだ画像を Canvas に描画する簡単なウェブアプリに QOI フォーマットの入出力機能を追加した感じです。 この方法だと PNG の入出力はブラウザに任せればいいので相互変換が簡単に実装できます。

これで QOI 画像の表示や変換が簡単にできるようになりました。

ちなみに myos の最新版でも QOI 形式に対応していて一部の画像リソースは QOI 形式になっています。