はい、miku_JK_Jbです
今回は自作OSをフォントの描画に対応させた作業について解説していくよ。
このブログはかなり読み応えがあるからね。
フォントの描画をできるようにする前に前回のブログで話した
カーネル空間とユーザー空間の分離について解説しよう
最初にカーネル空間とユーザー空間は何なのかについて解説する
まずはカーネル空間について解説する
カーネル空間が行う処理はこんな感じだ
メモリ管理(どのプロセスがどの領域を使うかを割り当て、不要になった領域を解放する)
プロセス管理(複数のプログラムが同時に動けるようにスケジューリングを行う)
ドライバ管理(キーボードやディスクなどをアプリで使えるようにする)
全体的にOS全体を包括する様な感じだ。
カーネルはOSの心臓で、ユーザー空間を支える基盤といったものだ
次にユーザー空間について解説する
ユーザー空間が行う処理はこんな感じだ
アプリが動作する領域
プログラムは直接ハードウェアにアクセスできないのでシステムコールなどを使用する
セキュリティや安定性のため制限が掛かっている
今見ているアメブロやYouTubeなどはユーザー空間で動作しているのだ
では何故カーネル空間とユーザー空間を分離するのかについて解説する
分離する理由は主に安定性だ。
もしユーザー空間が暴走したとしても分離されているとカーネルにまで影響が起こらない。また、カーネルが守られているのでOS全体のクラッシュを防ぐことができるのだ。
他にも効率性が上がるのだ
これはカーネルとユーザー空間側の役割が分担されるんでそれぞれを最適化をしやすいのだ。
それでは作業について解説していこう
まずはユーザー空間側での画面描画だ
これはユーザー空間でUIの描画を行う準備だ
ユーザー空間で画面描画を行うためにカーネル空間とユーザー空間のソースコードの分離などを行った。
また、カーネル空間では描画の高速化を図るバックバッファの処理だけを残した。
ユーザー空間での画面描画は前回のブログのソースコードをそのまま使えるので使った。
で、ユーザー空間で描いたものはこんな感じだ

この画面はUIの開発と何らかの理由でデスクトップ画面の背景が映せない場合のみに代わりとなる背景だ

この本を使って背景カラーの色を選んだ
色は江戸切子だ
これでユーザー空間側での画面描画の処理とUIを作る準備は終了
次はフォントの描画が処理をできるようにしたことについて解説する
やっと本題に入ったってことだ
今は設計もPCなどでできるが私は紙で行った方が考えが浮かびやすい
フォントの描画つまり文字表示で問題になるのが多言語化だ
これは設計図にもある通り各言語(β版では主にメジャー)の.langファイルを使用して多言語化を行う
自作OSの多言語化は最初にテストで10言語で「こんにちは 世界」と表示できるようにする

まずは.langファイルの作成だ
写真にあるようにスプレッドシートで各言語を書き、JSONファイルに変換、
その後Pythonを使用してJSONファイルを読み込ませてKey配列を取得し、各言語ごとに.langファイルを作成するようにした
スプレッドシートから.langファイルの変換はこのサイトを利用した
↓
Excelから貼り付けてJSONに変換するツール。入れ子にも対応 | Hep Hep!
ExcelデータからJSONに変換するためのツール。エクセル、またはGoogleスプレッドシートでJSON形式に対応したデータをコピーして貼り付けるだけで簡単にJSONデータを作成できる便利ツール。
hep.eiz.jp
次は自作OSが言語ファイルを取り扱えるようにした
さっきは言語ファイルを作っただけだからね😅
なので言語ファイルの読み込み、初期化、終了を行えるようにした。
後にかなり面倒なことになるんだけどね😓
ただ、修正した後もやっていることは変わらないので解説していく
まずは、言語ファイルの読み込みだ
これは仮想のファイルシステムから言語ファイルを読み込み、メモリに展開する処理だ。また、言語ファイルにある各言語の翻訳も読み込んでいる
これを行うことによって自作OSが言語ファイルに触れるようになるのだ
次は初期化だ
これは内部テーブルをリセットし、日本語ファイルを読み込む処理だ。
これで自作OSが多言語に対応する感じだ👍
また、今は日本語ファイルを読み込むようにしているが、今後は初期設定で設定された言語を読み込むように変更する
次は終了だ
これは言語ファイルがメモリに展開されているので、リセットしてキャッシュを破棄、次回起動時にクリーンな状態から再読み込みできる処理だ。
これで次に起動するときに前の翻訳された文字などが出なくなるのだ😉
これで言語ファイルを自作OSが取り扱えるようになった
次はフォントの読み込みと描画だ
これも言語ファイルと一緒で後でトラブルが起こるんだけどね😉
フォントはかなり前のブログでNotoSansとNotoSansJPを使うことにしたんだよね。
https://qiita.com/miku_JK_Jb/items/78ea814d1a58

使い分けとしては、日本語はNotoSansJPをそれ以外の言語はNotoSansで分けている
本当は日本語、韓国語、中国語を一緒にしたNotoSansCJKを使いたかったが何故か見つけることができなかったので、NotoSansJPで日本語専門、それ以外の言語はNotoSansと言う使い分けになってしまった。
また、フォントの描画にはFreeTypeを使用する
FreeTypeとは、NotoSans達はビットマップフォントではなく、OTFやTTFフォントだ
それら(OTF、TTFフォント)を読み込んで画面に描画するためのフォントエンジンだ。
MacやiOS、Android、ChromeOSにも使われている。有名な奴だ
ちなみにFreeTypeのラインセンスは2つあってGPLv2とFTLだ。
勿論私はFTLを選択した。
それじゃあフォントの読み込みと描画処理について解説する
まずはフォントの読み込みだ
これは言語ファイルみたいに仮想のファイルシステムからフォントを探して読み込み、メモリ上に展開する。また、フォントは日本語はNotoSansJPをそれ以外の言語はNotoSansをメモリ上に展開する。
これを行うことによって描画するためのフォントの準備が整う👍
次はフォントの描画だ
これは後ほど解説する動的リンカーからFreeTypeを読み込み、UTF-8,16,32をデコードして、描画する。フォントの描画も図形の描画と一緒でバックバッファ(画面の裏)で先に描画し、画面の幅や内部幅が一致したなら描画した物を一気にまとめてコピーし、表示するようにしている
これを行うことで自作OSがフォントを描画することができるのだ😁
次はフォントの各種設定だ
これはサイズの変更、太字、下線、イタリック、取り消し線、簡易的な色覚補正だ。
まあ、フォントの大きさだったり、色だったり、書き方を変更する感じだね
これで様々な文字に対応することができるね🥹
これで自作OSはフォントを描画できるようになるはずだ
ただ、自作OSはFreeTypeを動的リンクで動かしている。
そしてglibcも動的リンクだ。
だからカーネルに動的リンクのライブラリーを読み込ませる必要がある。
そのために動的リンカーを作成した
それじゃあ動的リンカーの処理について解説する
まずはライブラリの管理だ
これは指定されたファイルパスにある共有ライブラリをメモリに読み込み、実行可能にする処理だ。
この処理を行うことで動的ローダーの内部状態を初期化して、ライブラリをロードする準備が整うのだ😁
次にライブラリのロード
これは指定されたパスのファイルを読み込み、ファイルサイズを取得してメモリに割り当て、ファイルが有効なELF形式で共有オブジェクトかを検証、ELFのプログラムヘッダーを解析し、ライブラリのロードに必要なメモリ領域を計算・確保し、ファイルの内容をそのメモリ領域にコピー、共有ライブラリを任意のメモリ位置にロードできるようにし、ロードされたライブラリの情報を格納する処理だ。
これでカーネルや他のアプリが、そのライブラリの機能を利用するための基盤が構築されるのだ。
次にシンボルの解決だ
これはロードされたライブラリハンドルとシンボル名を受け取り、ライブラリ内の ダイナミックシンボルテーブルと文字列テーブルを走査。シンボル名が一致した場合、シンボルの値にライブラリのベースアドレスを加えてその関数/データの実アドレスを計算して返す処理だ。
この処理を行うことでアプリは名前ではなくアドレスを通じて、そのライブラリの機能を直接呼び出すことができるようになるのだ。
次にライブラリのアンロード
これは指定されたライブラリハンドルに関連付けられたメモリを解放する処理だ。
この処理を行うことでアプリがライブラリの使用を終了した後も、メモリが効率的に再利用されるようになるのだ。
最後にシステムライブラリのロード
これはOSの動作に不可欠な以下のライブラリをロードし、特定の関数のポインタをグローバル変数に解決する処理だ。
この処理を行うことで標準的なC/C++の機能や、フォント描画などの高度な機能にアクセスできるようになるのだ。
これが動的リンカーの処理と実装だ!!
かなりてんこ盛りな処理でしょ😁
くっそ大変だったわ🤪
ってあれ??
フォントが表示されない...
シリアルログを見てみると


言語ファイルとFreeTypeの読み込みに失敗している!!
原因は?
ブートローダーが受け渡すexFAT情報を使っていなかった
Rootディレクトリを開くことができていなかった(こいつが中々の難敵だった)
ってことで直していこうか
まずはブートローダーの修正だ
これはexFAT の BPB を解析し、FAT/クラスタヒープ/ルートクラスタなどのメタ情報をブートのボリュームに追加。EFIのBlock-IOからボリューム全体をメモリへスナップショットし、その物理アドレスとサイズを格納してカーネルへ渡すようにした
これを行うことによってメタデータ提供: exFATの構造情報をブートボリュームに詰めることでカーネルの構造解析時間を短縮。ディスク全体をスナップショットすることで、ディスクファイルシステムは物理I/Oコードを必要とせず、単なるメモリアクセスとして動作できるになるのだ😁
次に共有ヘッダーの拡張だ
これはブートローダーとカーネルで共通に使う構造体を拡張させた
これを行うことによってブートローダー側とカーネル側の開発を完全に分離でき、構造体を拡張することにより、カーネルとブートローダーの整合性を保証できるのだ
次にRootディレクトリをカーネル側でも開けられるように修正
これはスナップショットメモリを使ってセクタ読み込みを行い、exFAT の FAT チェイン・ディレクトリを解析して Rootディレクトリを開けるようにした。
また、私の自作OSはRootを仮想化しているので、仮想ファイルシステムがインメモリだけでなく、ヒットしなかったパスをディスクファイルシステムに任せる仕組みを作成した
これを行うことによってメモリ上のアドレス指定でセクタの読み書きをエミュレートできるから、非常に高速なファイルアクセスが可能になる。また、Rootの仮想化ができ、インメモリファイルと物理ディスクファイルを統合することができるのだ
これが修正の中で一番大変だった🥺
最後にカーネルで
これはカーネルが起動したら仮想ファイルシステムを起動させてディスクファイルシステムを有効にしてからユーザー空間に移るようにした
これを行うことによってカーネルが起動に必要なファイルを読み込むことが可能になるのだ。
これが行った修正だ!!
これも中々大変だったわ
それじゃあ多言語化が可能になったか見ていこう!!
ダメだ
起こっている問題はstart_image() returned Out of Resourcesだね
これはUEFIでメモリ不足が起こっていたり、スタック不足でする際に起こるエラーだ
原因はブートローダーのスナップショットをボリューム全体にしていたのだが
実機検証などで使っているASUS P8Z77-Vのマザボは初期のUEFIを搭載していてメモリマップがかなりシビアなものだから、メモリ不足などが発生してしまい、結果的にstart_jmage() returned Out of Resourcesが出てしまったというわけだ
それじゃあ直していこう!!
修正する場所はブートローダーだ
まずはスナップショット数をボリューム全体から1GBにまでサイズをダウンして見た
結果は案の定start_image() returned Out of Resourcesだった

それならば極端ではあるがスナップショットのサイズを256MBに下げて見た
結果は

FreeType経由でフォントや文字の描画がされていないが起動はした
FreeTypeが動作しない原因はスナップショットを256MBにしたせいでFreeTypeがいる475MBの場所が範囲外であったため
動作がしなかった。うん。みんなは1GBから急に256MBに下げたりしないで段階的に量を減らしていくんだぞ。ダイエットと同じだ
ってことでFreeTypeを動かせるようにするために
スナップショットのサイズを512MBにするのではなく、1GBがダメなら512GB、384MB、256MBにする段階フォールバックを実装した。また、FreeTypeなどが範囲外の場合は仮想ファイルシステムから読み込んで動作させるというバックアップ方式に修正した。これでちゃんとユーザー空間でFreeTypeが動作し、文字の描画などができるようになるはずだ!!
結果は
動wかwなwいw
あー、うぜ〜
でもねフォントの初期化のコードを一時的にコメントアウトして無効化すると

デスクトップの表示はできる
デスクトップUIは前回のブログで書いたけど、実はこのフォント描画や動的リンカーの作成の方が長いのである
時系列バグっててすまんこ
ん?ここでようやく気づいた
FreeTypeに必要なライブラリをリンクしてなくね?FreeTypeちゃんずっと寂しい寂しい🥲ってなってたのでは?と
ってことでFreeTypeが必要としているライブラリを探すと

libpng16.so.16、libz.so.1、libharfbuzz.so.0、libbrotlidec.so.1、libc.so.6を欲しているらしい
今の所ロードしているのはlibcとlibmとFreeTypeなので残りのlibpngとlibzとlibharfbuzzとlibbrotlidecを読み込めばいいね
ってことで修正します💪
修正は単純でFreeTypeやglibcなどと一緒でブートローダーのスナップショットの範囲内であればそのまま動的リンカーを使って読み込み、範囲外であれば仮想ファイルシステムから探して動的リンカーを使って呼び出す方式なのでパスを仮想ファイルシステムとブートローダーでカーネルとライブラリを一緒に読み込ませるファイルに設定した
これを行うことにより、スナップショット内にライブラリがあるとディスクI/Oを発生させずに、すでにメモリ上にあるライブラリをそのまま利用することができ、速度が向上する。また、スナップショット内にライブラリが無い場合でも起動直後に必要になったライブラリを仮想ファイルシステム経由で持ってくるので拡張性は損なわない構造になる。
従来のOSの起動プロセスでは初期の実行環境から本格的な運用環境に一気に切り替わるが、自分の設計ではその境界をわざと曖昧にしている。
要するにスナップショットをキャッシュみたいにも扱うという感じ
さあこれで動くようになるか!?
結果は
クラッシュ!!
あーうぜー
シリアルログを見てみると
原因が判明
今度はFreeTypeなどの例外関数が未実装だったためクラッシュしている
なので未実装であった関数を実装するのみ
実装したので再度動くのか検証!!
さあ、結果は?
なぜかUIだけ描画されたw
なんか色ずれが直ってるんだけどw
多分メモリマッピング関係も少しいじったのでその影響だな
FreeTypeが動かない理由は?
brkが-1を返していた
-1を返す理由は自作の動的リンカーでbrkをカーネル実装にバインドしていなかった😅
これで動くかな?と思いきや
結果は動かなかったわ
原因は
FreeTypeの初期化を2回行っていた。
初期化を2回行ったことでリソースリークが起き、FreeTypeの内部状態で不整合になってしまっていた。
なので間違えて追加されていた2回目のFreeTypeの初期化の処理を削除した
これで動くはずだ!!
多言語デモで10か国でHello Wrold!! (こんにちは 世界)が表示されてねぇ!!
というかフォントが描画されてねぇぇぇぇぇ!!
ってことでちゃんとフォントが描画されるように修正するか
フォントが描画されていない原因は?
単純にNotoSans NotoSans-JPのフォントファイルを読み込めていなかった。
それじゃあ直していこう
これはFreeTypeやglibcなどと一緒でブートローダーのスナップショットの範囲内であればそのまま読み込み、スナップショットの範囲外であれば仮想ファイルシステムを経由してフォントを読み込むように修正した。これで確実性も上がる
ダメでしたw
シリアルログを見ると?
FreeTypeが複数回ロードされている!!
何が問題なのか?
FreeTypeが複数回ロードされることで、最初はリロケーションされていた関数が、上書きされてしまい、未リロケーションとして処理されてしまい、FreeTypeが必要としている関数にアクセスすることができなくなり、クラッシュしたり、ちゃんと動かなくなってしまう
それじゃあ修正していく😁
行った修正は、多重ロード防止のキャッシュ機構を追加だ
これを追加することで、既に同じファイル名のライブラリがロード済みかどうかをチェックし、
ロード済みなら新たにロードせず、既存のハンドルを返すようにした。
また、ファイル名(leaf名)だけで比較しているので、絶対パスや区切り文字(/や\)の違いも吸収できる。
これにより、同じライブラリは一度だけロードされ、以降はキャッシュされたハンドルが返るようになった。
んで、フォントが描画されようになったのか?
結果は?
😊😊
あーーうぜー。
まだFreeTypeに必要な関数が足りない!!
と言うことで必要な関数を追加していく
まあ追加したのはいいのだが私は気づいてしまった。
メモリマッピングの不足と違反を
ってことでそこも修正する
修正した点はこの3つ
・FreeTypeから見える位置に関数がなかったので、見えるようにした
・シンボルのマッピングミスとメモリアドレス空間のマッピングミス
・フォントファイルのマッピングミス
だ
まずはFreeTypeから見える位置に関数がなかったので、見えるようにしたことについて解説する
これは動的ロードされたユーザー空間にとって必要なのは、
・メモリ管理係、
・FreeType初期化や face 操作用の関数
・glyph 読み込み周辺の関数
のような外部シンボルだが、自作した動的リンカーで十分に解決されていなかったので、呼び出しがNULLになったり、変な所にすっ飛んでたりしていたので、修正した。
次に、シンボルのマッピングミスとメモリアドレス空間のマッピングミスの修正について解説する
これは動的リンカーのリロケーション処理で、JUMP_SLOT だけに当てるべき外部上書きを GLOB_DAT にまで広げていて、ユーザー空間自身の内部参照まで、動的リンカーのアドレスに置き換わり、本来は関数アドレスであるべき場所に、変数のアドレスが入る状態になっていたため、修正した。
最後にフォントファイルのマッピングミスの修正について解説する
これはフォントファイルの実体はブートイメージ由来で、最初はカーネルの仮想アドレスのまま、FT_New_Memory_Faceに渡していた。だが、FreeTypeが動いているのはユーザー空間なので、ユーザー空間にそのままカーネル空間の仮想アドレスとユーザー空間からの保証されていないバッファを渡すのは非常に危険なので、フォントデータをユーザー空間のヒープと安全なバッファコピーをし、そのポインタをFreeTypeに渡すように修正した。
結果は!?
フォントの描画ができた!!
けど
原因は?
原因は使用しているフォントのNotoSans-JPとNotoSansは韓国語(ハングル文字)には完全には対応していないとの事、なので韓国語専用のNotoSans-KRを追加することにした

フォントの追加は少し前に解説した時と一緒で、仮想のファイルシステムからフォントを探して読み込み、メモリ上に展開するようにした。
ちゃんとフォントが映った!!
これで自作OSが多言語とフォントの描画に対応した!!
これが高校生が自作OSをライブラリの改変なしでFreeType経由で文字を描画できるようになった話でした✨
ということでmiku_JK_Jbでした
次回は高校生の間に行った自作OSの作業について解説しようかな












