2019年7月20日土曜日

MochiOS@仮想ファイルシステムの作成part7

MochiOS@仮想ファイルシステムの作成part7

前提知識無しで作るなんちゃって仮想ファイルシステムpart7。

開発日記です。独り言です。

これまでのあらすじ

part 内容
0 仮想ファイルシステムを作りたい宣言をする。
1 ざっくり仮想ファイルシステムの機能を決める。
が、機能呼出し先の仮想ファイルシステムのタスクIDをどう取得するかという問題に気付く。
2 カーネルにタスク名登録機能を実装した。
これでタスク名からタスクIDを取得する事が出来る様になるハズ、なので試したいが試す方法が無い事に気付き、ログ出力タスクの開発を宣言する。
3 ログ出力の為に使うvsnprintf()の仕様を調べる。
4 ログ出力タスクを開発してタスク名からタスクIDの取得機能が正しく動いている事が確認できた。
5 mount機能を実装した。たぶんできてる。
6 FDどうやって管理しようかー。メッセージのやり取り制御を状態遷移で制御しようかー。とか考えて、open機能を実装した。たぶんできてる。

writeの仕様を考える

今回はwrite機能を作ります。
仮想ファイルサーバとしては、openもwriteもあんまりやること変わらないハズ。なので簡単にできるかな~と思っている。
あ、キャッシュなんて何にも考えていませんからね。

ユーザタスクmvfsシリアルポートドライバローカルFDテーブル参照write要求( グローバルFD, アドレス, データ, データサイズ )グローバルFDテーブル参照vfsWrite要求( グローバルFD, アドレス, データ, データサイズ )デバイス書込みvfsWrite応答write応答書込みアドレス更新ユーザタスクmvfsシリアルポートドライバ

シーケンスの順番は何も変わらず、渡すデータとちょっと間にやる事が変わったくらい。
(こうやってシーケンスにしてみると良く分かるのだけど、データのコピー回数の多さ。マイクロカーネルだとメッセージパッシングが主なデータのやり取りとなる(?)わけだけど、4回のデータコピー(ユーザタスク→カーネル→mvfs→カーネル→シリアルポートドライバ)が走るので性能が落ちる。)

以下、メッセージ仕様。

■ write要求メッセージ

offset size 内容 意味
0x00 4 機能ID 0x0000_0003 write
0x04 4 タイプ(要求/応答) 0x0000_0000 要求
0x08 4 グローバルFD 任意 -
0x0C 8 アドレス 任意 -
0x14 4 データサイズ 1~24,064 -
0x18 1~24,064 データ 任意 -

なぜ24,064かというと、現在のMochiKernelで送信可能なメッセージの最大長が24,576byteなので、そこから-512byte(ヘッダとかも含めるので)減らした。適当。
そもそも、なんで24,576にしたんだっけ…

■ write応答メッセージ

offset size 内容 意味
0x00 4 機能ID 0x0000_0003 write
0x04 4 タイプ(要求/応答) 0x0000_0001 応答
0x08 4 処理結果 0x0000_0000
0x0000_0001
成功
失敗
0x0C 4 書込み実施サイズ 0~24,064 -

■ vfsWrite要求メッセージ

offset size 内容 意味
0x00 4 機能ID 0x0000_0004 vfsWrite
0x04 4 タイプ(要求/応答) 0x0000_0000 要求
0x08 4 グローバルFD 任意 -
0x0C 8 アドレス 任意 -
0x14 4 データサイズ 1~24,064 -
0x18 1~24,064 データ 任意 -

■ vfsWrite応答メッセージ

offset size 内容 意味
0x00 4 機能ID 0x0000_0004 vfsWrite
0x04 4 タイプ(要求/応答) 0x0000_0001 応答
0x08 4 グローバルFD 任意 -
0x0C 4 処理結果 0x0000_0000
0x0000_0001
成功
失敗
0x10 4 書込み実施サイズ 0~24,064 -

実装してみた

大体、Openと同じような動きなので、あっさり実装。
(とはいえ、mountもopenもwriteもかなり適当に実装しているので、だいぶ改造やリファクタリングしていかなきゃならない。いずれそれをやる予定。)

とりあえず、実行結果をば。
write実装した画面

何をやっているかというと、

mttymvfsシリアルポートドライバwrite要求( "tes" )vfsWrite要求( "tes" )デバイス書込みvfsWrite応答write応答write要求( "tes" )vfsWrite要求( "tes" )デバイス書込みvfsWrite応答write応答mttymvfsシリアルポートドライバ

こんな感じ。
mttyはサーバタイプで、ラインディシプリンとかセッション管理とかそういうのを実装していこうと思っているプロセスです。今回は、このmttyが「/serial」ファイルをオープンして(上図では省略)二回「tes」という3バイトのデータをwriteしています。
シリアルポートドライバは、vfsWriteを受け取ったら、そのデータを1バイトづつ転送レジスタに書き込むようにしました。
(本当は、転送レジスタの空きがあるかチェックしたり、FIFOいっぱいだったら割込み待って書き込むとか必要なんだけど、ちょっと試したいだけだったので省略。真面目にやると、割込み待ちとメッセージ待ちの両方を行わせる事が現状できないので、カーネルの機能を追加しないといけない。)

qemu上で動かしているので「-serial telnet::4444,server」オプションを使う事で、telnet接続でシリアルポートに接続させることができるようにしています。

次!read。ほぼ、writeのコピペでいける気がする。テキトーに。

MochiOS@仮想ファイルシステムの作成part8

MochiOS@仮想ファイルシステムの作成part8

前提知識無しで作るなんちゃって仮想ファイルシステムpart8。

開発日記です。独り言です。

これまでのあらすじ

part 内容
0 仮想ファイルシステムを作りたい宣言をする。
1 ざっくり仮想ファイルシステムの機能を決める。
が、機能呼出し先の仮想ファイルシステムのタスクIDをどう取得するかという問題に気付く。
2 カーネルにタスク名登録機能を実装した。
これでタスク名からタスクIDを取得する事が出来る様になるハズ、なので試したいが試す方法が無い事に気付き、ログ出力タスクの開発を宣言する。
3 ログ出力の為に使うvsnprintf()の仕様を調べる。
4 ログ出力タスクを開発してタスク名からタスクIDの取得機能が正しく動いている事が確認できた。
5 mount機能を実装した。たぶんできてる。
6 FDどうやって管理しようかー。メッセージのやり取り制御を状態遷移で制御しようかー。とか考えて、open機能を実装した。たぶんできてる。
7 openと同じようなシーケンスなので、メッセージとちょっとやる事変えて簡単にwrite機能を実装できちゃった。

readの仕様を考える

今回はread機能を作ります。
仮想ファイルサーバとしては、openもwriteもreadもあんまりやること変わらないハズ。なので簡単にできるかな~と思っている。
あ、キャッシュなんて何にも考えていませんからね。
(ブログ記事までwriteのコピペ)

ユーザタスクmvfsシリアルポートドライバローカルFDテーブル参照read要求( グローバルFD, アドレス, データサイズ )グローバルFDテーブル参照vfsRead要求( グローバルFD, アドレス, データサイズ )デバイス読み込みvfsRead応答( グローバルFD, 処理結果, データ, データサイズ )read応答( 処理結果, データ, データサイズ )読込みアドレス更新ユーザタスクmvfsシリアルポートドライバ

シーケンスの順番は何も変わらず、渡すデータとちょっと間にやる事が変わったくらい。

以下、メッセージ仕様。

■ read要求メッセージ

offset size 内容 意味
0x00 4 機能ID 0x0000_0005 read
0x04 4 タイプ(要求/応答) 0x0000_0000 要求
0x08 4 グローバルFD 任意 -
0x0C 8 アドレス 任意 -
0x14 4 データサイズ 1~24,064 -

なぜ24,064かというと、現在のMochiKernelで送信可能なメッセージの最大長が24,576byteなので、そこから-512byte(ヘッダとかも含めるので)減らした。適当。
そもそも、なんで24,576にしたんだっけ…
(writeとおんなじ。)

■ read応答メッセージ

offset size 内容 意味
0x00 4 機能ID 0x0000_0005 read
0x04 4 タイプ(要求/応答) 0x0000_0001 応答
0x08 4 処理結果 0x0000_0000
0x0000_0001
成功
失敗
0x0C 4 読込み実施サイズ 0~24,064 -
0x10 0~24,064 データ 任意 -

■ vfsRead要求メッセージ

offset size 内容 意味
0x00 4 機能ID 0x0000_0006 vfsRead
0x04 4 タイプ(要求/応答) 0x0000_0000 要求
0x08 4 グローバルFD 任意 -
0x0C 8 アドレス 任意 -
0x14 4 データサイズ 1~24,064 -

■ vfsRead応答メッセージ

offset size 内容 意味
0x00 4 機能ID 0x0000_0006 vfsRead
0x04 4 タイプ(要求/応答) 0x0000_0001 応答
0x08 4 グローバルFD 任意 -
0x0C 4 処理結果 0x0000_0000
0x0000_0001
成功
失敗
0x10 4 読込み実施サイズ 0~24,064 -
0x14 0~24,064 データ 任意 -

実装してみた

大体、OpenやWriteと同じような動きなので、あっさり実装。

とりあえず、実行結果をば。
enter image description here

今回はちょっと動画でご紹介。
上は画面、下はシリアルポート入出力のウィンドウ。

最初にシリアルポート側で"testes"が出ていますが、前回のwrite機能実装でお試ししてみたもの。今回はその後に「aadekita!!!」を入力してやって、それをエコーしています。

ユーザタスクmvfsシリアルポートドライバ1秒ビジーウェイトread要求( 1バイト )vfsRead要求( 1バイト )デバイス読込みvfsRead応答()read応答()write要求( 読み込んだデータ )vfsWrite要求( 読み込んだデータ )デバイス書込みvfsWrite応答()write応答()loop[ 無限 ]ユーザタスクmvfsシリアルポートドライバ

1秒ビジーウェイトしているのは、ドライバ側で割込み待ちができない(現状、MochiKernelではメッセージ待ちと割込み待ちを同時に行えない。)ので仕方なく(CPUを労わる為に)。

write以上にあっさりreadを実装してしまった。
次はもっと簡単で、closeを実装する。キャッシングないからね。

2019年7月3日水曜日

MochiOS@仮想ファイルシステムの作成part6

MochiOS@仮想ファイルシステムの作成part6

前提知識無しで作るなんちゃって仮想ファイルシステムpart6。

開発日記です。独り言です。

これまでのあらすじ

part 内容
0 仮想ファイルシステムを作りたい宣言をする。
1 ざっくり仮想ファイルシステムの機能を決める。
が、機能呼出し先の仮想ファイルシステムのタスクIDをどう取得するかという問題に気付く。
2 カーネルにタスク名登録機能を実装した。
これでタスク名からタスクIDを取得する事が出来る様になるハズ、なので試したいが試す方法が無い事に気付き、ログ出力タスクの開発を宣言する。
3 ログ出力の為に使うvsnprintf()の仕様を調べる。
4 ログ出力タスクを開発してタスク名からタスクIDの取得機能が正しく動いている事が確認できた。
5 mount()機能を実装した。たぶんできてる。

openの仕様を考える

とりあえず、必要なのはパスよね。
あと、読書きフラグ。。。あるけど、今は忘れよ、うん。後ね後。
で、openしたらファイルディスクリプタ(FD)を払い出す。大体、FDはプロセス毎に1からなので、プロセス毎にFDを払い出す事にする。

あー、MochiKernelのメッセージパッシングはメッセージ送信元タスクIDしか取れないので、プロセスIDを取れるようにしないとだ。。
(どこかでMochiKernelの概要を書かねばと思っているのだけど、ここでさらっとご紹介。MochiKernelではカーネル上の1つのコンテキストをタスクという名前で扱う。つまるところ、タスクはスレッドと同じ意味。プロセスは複数のスレッド(タスク)から構成されるのは普通と一緒。スレッドIDはプロセス内ローカル値だがタスクIDはグローバル値という違いがある)

mountされたファイルをopenした時、そのファイルをmountしたドライバはopenされたタイミングで何か初期化をしたいかもしれない。
なので、openメッセージを横流しする。横流しといってもメッセージはそのままでなくて、新しい識別子を切ることとしよう。
パラメータはopenしてきたPIDと払い出されたFDを組み合わせた値とパスとしとく。

ユーザタスクmvfsシリアルポートドライバopen( "/serial" )FD払い出しvfsOpen( PID, FD, "/serial" )ユーザタスクmvfsシリアルポートドライバ

以下、メッセージ仕様。

■ open要求メッセージ

offset size 内容 意味
0x00 4 機能ID 0x0000_0001 open
0x04 4 タイプ(要求/応答) 0x0000_0000 要求
0x08 4 ローカルFD※ 任意 -
0x08 1024 絶対パス(\0含む) 任意 -

■ open応答メッセージ

offset size 内容 意味
0x00 4 機能ID 0x0000_0001 open
0x04 4 タイプ(要求/応答) 0x0000_0001 応答
0x08 4 処理結果 0x0000_0000
0x0000_0001
成功
失敗
0x0C 4 グローバルFD※ 任意 -

■ vfsOpen要求メッセージ

offset size 内容 意味
0x00 4 機能ID 0x0000_0002 vfsOpen
0x04 4 タイプ(要求/応答) 0x0000_0000 要求
0x08 4 PID 任意 -
0x0c 4 グローバルFD※ 任意 -
0x10 1024 絶対パス(\0含む) 任意 -

■ vfsOpen応答メッセージ

offset size 内容 意味
0x00 4 機能ID 0x0000_0002 vfsOpen
0x04 4 タイプ(要求/応答) 0x0000_0001 応答
0x08 4 処理結果 0x0000_0000
0x0000_0001
成功
失敗

※ 詳細は下記の「FD(ファイルディスクリプタ)の実装方法を考える」を参照

制御方法を考える

なんだかちょっとしたシーケンスが出来てしまったので、状態遷移表を用いた制御を行う。
その為に、MLib(自作の便利ライブラリ)に状態遷移管理機能を実装してそれを使うことにする。
詳細は割愛。ライブラリはここ(https://github.com/MasterMochi/MLib/commit/4135b1b0a1cd7d0617405e38e9b997b3bc3bf7fb)でコミットした。

状態遷移はこんな感じ。上のシーケンスから状態遷移記述に落としただけ。

状態 初期状態 vfsOpen待ち
イベント # 01 02
open要求 01 Task0101
→2
-
vfsOpen応答 02 - Task0202
→1

タスク概要はこんな感じ。エラー処理やその時の遷移はここでは割愛。

タスク名 処理概要
Task0101 1. FD払い出し
2. mountタスクへvfsOpen要求を送信
Task0202 1. oepn要求元タスクへopen応答を送信

FD(ファイルディスクリプタ)の実装方法を考える

ファイルディスクリプタって、各プロセス毎に一意の値じゃない?単純に考えると、仮想ファイルサーバは「プロセス(PID)」毎にFDテーブルを持たなきゃならない。PIDをキーにFDテーブルを持たなきゃならないわけだ。
PIDをインデックスとしたテーブルにすれば簡単にできそうだけど、PID分を全部用意しとかなきゃならないってのはなかなかメモリ容量を使いそう。

なので、各プロセス用のFDテーブルと仮想ファイルサーバ用のFDテーブルを持つこととする。
仮想ファイルサーバはプロセス毎にFDを管理するんじゃなくて、単純に一個のFDテーブルを持つ様にする。そうすればFDの払い出しは、0から順に空いているFD使えばいいので難しい事を考える必要がなくなる。
各プロセスは自分用のFDテーブルを管理しておいて、その用途は仮想ファイルサーバのFDを変換するだけにしておく。そうすれば、0からの各プロセス毎に一意なFDを使用できる。
ローカルFDテーブルの操作は、仮想ファイルサーバが提供するライブラリでやってしまえば、こんな仕組みはユーザに隠蔽することができる。

便宜上、各プロセス毎のFDをローカルFD、仮想ファイルサーバのFDをグローバルFDなんて名前にしておく。

ユーザタスクmvfsシリアルポートドライバローカルFD[0]払い出しopen( "/serial", ローカルFD=0 )グローバルFD[190]払い出しグローバルFD[190]=0vfsOpen( PID, グローバルFD=190, "/serial" )190ローカルFD[0]=190ユーザタスクmvfsシリアルポートドライバ

ユーザタスクがローカルFD=0のreadをしたければ、自身のFDテーブルを用いてローカルFDからグローバルFDに変換して、仮想ファイルサーバにグローバルFD=190のreadを出せばいい。

実装してみた

かなりやっつけだけど実装した。

ttyサーバ君を作って、シリアルポートドライバ君がMountする「/serial」を二回連続でOpenさせています。詳細な説明は割愛!

2019年6月8日土曜日

MochiOS@仮想ファイルシステムの作成part5

MochiOS@仮想ファイルシステムの作成part5

前提知識無しで作るなんちゃって仮想ファイルシステムpart5。

開発日記です。独り言です。

これまでのあらすじ

part 内容
0 仮想ファイルシステムを作りたい宣言をする。
1 ざっくり仮想ファイルシステムの機能を決める。
が、機能呼出し先の仮想ファイルシステムのタスクIDをどう取得するかという問題に気付く。
2 カーネルにタスク名登録機能を実装した。
これでタスク名からタスクIDを取得する事が出来る様になるハズ、なので試したいが試す方法が無い事に気付き、ログ出力タスクの開発を宣言する。
3 ログ出力の為に使うvsnprintf()の仕様を調べる。
4 ログ出力タスクを開発してタスク名からタスクIDの取得機能が正しく動いている事が確認できた。

共通メッセージフォーマットを決める

ようやっとタイトルに相応しい内容が書ける。メッセージフォーマットを決めます。

まぁ、mvfsライブラリでその他のプロセスにはメッセージを隠蔽するので適当に作ればいいのだけれど、設計して書き残しておかないと忘れちゃうよね~。

とりあえず、以下のようなフォーマットとしておく。

offset size 内容
0x00 4 機能ID
0x04 4 タイプ(要求/応答)
0x0c 可変 機能ID毎のデータ

ただ、このフォーマットだと後々に色々と問題が出てくる。要求と応答メッセージの対応が分からない、とか。一旦リリースした後に改変したくなった時にバージョン違いとの互換性をどうするか、とか。

でも、今は考えない。困ったら考えよう。行動力第一。

以下、機能IDとタイプの値を決めておく。

機能ID 意味
0x0000_0000 mount
0x0000_0001 open
0x0000_0002 read
0x0000_0003 write
0x0000_0004 close
0x0000_0005 unmount

タイプ 意味 備考
0x0000_0000 要求 mvfsへのメッセージ
0x0000_0001 応答 mvfsからのメッセージ

mount要求メッセージ

とりあえず、マウントするパスの場所が分かればいいので、次のようにした。

offset size 内容 意味
0x00 4 機能ID 0x0000_0000 mount
0x04 4 タイプ(要求/応答) 0x0000_0000 要求
0x08 1024 絶対パス(\0含む) - -

絶対パスは、例えば、"/serial"。
ルート以外のディレクトリ機能は後回しにするので、"/dev/serial"みたいに指定しちゃダメって事にする。

mount応答メッセージ

とりあえず、成功か失敗かあればいいっしょ!?

offset size 内容 意味
0x00 4 機能ID 0x0000_0000 mount
0x04 4 タイプ(要求/応答) 0x0000_0001 応答
0x08 4 処理結果 0x0000_0000
0x0000_0001
成功
失敗

ファイル・ディレクトリ管理方法

mountはじゃあ何させようかなぁ、とその前にファイルやディレクトリってどう管理しようかという事を決めてなかったので、テキトーに決める。

マウントするファイルやディレクトリはノードというオブジェクトで管理することにする。
ノードには、パス名タイプマウントタスクIDエントリリストへのポインタの情報を持たせ、C標準ライブラリのmalloc()を使って確保する。

今の段階では、タイプにマウントファイルディレクトリの2つを用意する。たぶん、これにマウントディレクトリを後で追加するかなぁ。

マウントファイルはマウントしてきたタスクIDと関連付けて、このファイルへのopen()やread()、write()、close()は全部そのタスクIDに横流しする。
ディレクトリは単純にディレクトリ。エントリリストを持たせて、配下のファイルやディレクトリのノードへの参照にする。(まだ決めないけど、マウントディレクトリの場合はマウントファイルと同じで、マウントしたタスクIDへの横流しかな。)
エントリリストは128個(数はテキトー)のノードへのポインタを持たせる。これもmalloc()で確保する。ディレクトリ内に128個のファイルやディレクトリしか置けないなんて制限は掛けたくないので、いっぱいになったら新しいエントリリストと双方向リンクさせる。チャンク構造って言えばいいのかな?

[0]
[127]
entry list 1
entry list 2
entry list 3
Node
path: /
type: directory
mount: -
Node
path: /serial1
type: mount file
mount: 3
Node
path: /serial2
type: mount file
mount: 3

mountを実装する

mountメッセージも管理方法も決めたので、とりあえず、mountを実装しようそうしよう。
mount要求メッセージを受信して、mount要求メッセージからマウントするファイルのパスがあるのでパスからノードを生成して、mount応答メッセージを送信する。

mvfsシリアルポートドライバmlog画面画面出力"mlog"ログ出力( "drv start!" )ログ出力( "mvfs start!" )「/」ノード生成ログ出力( "mount start!" )mount( "/serial" )ログ出力( "MountDo() start" )「/serial」ノード生成「/」ノードのエントリに「/serial」ノード追加mount応答ログ出力( "mount end! )ログ出力( "MountDo() end" )mvfsシリアルポートドライバmlog画面

とりあえず、こんな感じで実装した。
途中、前回作ったvsnprintfがしくってて上手く表示ができなくかなり時間かかってしまった。。トホホ。

mount

はぁー、次はopenか。

2019年4月7日日曜日

MochiOS@仮想ファイルシステムの作成part4

MochiOS@仮想ファイルシステムの作成part4

前提知識無しで作るなんちゃって仮想ファイルシステムpart4。

開発日記です。独り言です。

これまでのあらすじ

part 内容
0 仮想ファイルシステムを作りたい宣言をする。
1 ざっくり仮想ファイルシステムの機能を決める。
が、機能呼出し先の仮想ファイルシステムのタスクIDをどう取得するかという問題に気付く。
2 カーネルにタスク名登録機能を実装した。
これでタスク名からタスクIDを取得する事が出来る様になるハズ、なので試したいが試す方法が無い事に気付き、ログ出力タスクの開発を宣言する。
3 ログ出力の為に使うvsnprintf()の仕様を調べる。

とりあえず確認できた。

vsnprintf()の仕様調査や実装方法、リアルのお仕事の多忙さやハワイ旅行などで苦戦しつつ、漸くタスク名「LOG」からタスクIDを取得し、そのタスクIDへログ出力メッセージを送信する事ができた。
ログ出力を確認したところ
ちょっと洒落てmlog起動時にロゴを出している。
基本的な動きは以下の通り。
mvfsmlogカーネル物理メモリ領域割当( VRAMアドレス )仮想アドレスタスク名登録( "LOG" )mlogロゴ画面出力タスクID取得( "LOG" )タスクIDログ出力( "main.c:115 mvfs start!" )※ログ画面出力mvfsmlogカーネル
※ファイル名main.cと行数115はvsnprintf()により書式変換している。

仮想ファイルシステム開発に戻る

思い起こすと、私は仮想ファイルシステムの開発をしようとしていて、part1でこんなシーケンスを考えていた。
(ビデオは一旦ログ出力で占有する事にしたので、シーケンスの登場人物ビデオドライバをシリアルポートドライバに変更した)
ユーザプロセスmvfsシリアルポートドライバmount( "/serial" )open( "/serial" )open( "/serial" )read()read()レジスタ読込みwrite()write()レジスタ書込みclose()close()unmount( "/serial" )ユーザプロセスmvfsシリアルポートドライバ
無事、タスク名によってタスクIDを取得できるようになり、メッセージ通信ができるようになったので、次回からメッセージの設計をするよ!

2019年2月16日土曜日

MochiOS@仮想ファイルシステムの作成part3

MochiOS@仮想ファイルシステムの作成part3

前提知識無しで作るなんちゃって仮想ファイルシステムpart3。

開発日記です。独り言です。

これまでのあらすじ

part 内容
0 仮想ファイルシステムを作りたい宣言をする。
1 ざっくり仮想ファイルシステムの機能を決める。
が、機能呼出し先の仮想ファイルシステムのタスクIDをどう取得するかという問題に気付く。
2 カーネルにタスク名登録機能を実装した。
これでタスク名からタスクIDを取得する事が出来る様になるハズ、なので試したいが試す方法が無い事に気付き、ログ出力タスクの開発を宣言する。

%dとか使いたいじゃん?

part3では、ログ出力機能も実装して、試してみて、わはーうまく動いてる。って書きたかったんだけど、欲張りな私は、printfみたいに「%d」とかで書式指定して変数の内容をログ出力したいと考えたわけだ。
というわけで、C標準ライブラリのvsnprintf()を実装したい。
  • 何故vsnprintf()かというと、log( "へんすうのあたい=%d", value );みたいにログ出力関数の引数を可変長にしたいから。

vsnprintf()の仕様

wikipediaさんによるとこんな規則なんですよ。
%[引数順][フラグ][最小フィールド幅][.精度][長さ修飾子]変換指定子
仕様がよくわからんので、どっかから拾ってきた仕様を、英語できないけどがんばって訳してみる。

引数順

ISO/IEC9899:1999には無さそうだし、見なかったことにするよ。

フラグ(flag)

Zero or more flags (in any order) that modify the meaning of the conversion specification.
変換指定の意味を修飾する0個以上のフラグ(順不同)
フラグ 英語説明
適当翻訳
- The result of the conversion is left-justified within the field. (It is right-justified if this flag is not specified.)
フィールドの中で左寄せされるんだぜ。(これ指定されなかったら右寄せよ)
+ The result of a signed conversion always begins with a plus or minus sign. (It begins with a sign only when a negative value is converted if this flag is not specified.)
符号付き変換は常に+か-符号から始まるぜ。(これ指定されなかったら負の数の時だけ符号から始めちゃう。)
space If the first character of a signed conversion is not a sign, or if a signed conversion results in no characters, a space is prefixed to the result. If the space and + flags both appear, the space flag is ignored.
符号付き変換で最初の文字が符号じゃないか、符号付き変換が文字にならないんなら、空白が前につくぜ。空白と+フラグが両方あったら空白フラグは無視されちゃうんだな。
# The result is converted to an ‘‘alternative form’’. For o conversion, it increases the precision, if and only if necessary, to force the first digit of the result to be a zero (if the value and precision are both 0, a single 0 is printed). For x (or X) conversion, a nonzero result has 0x (or 0X) prefixed to it. For a, A, e, E, f, F, g, and G conversions, the result of converting a floating-point number always contains a decimal-point character, even if no digits follow it. (Normally, a decimal-point character appears in the result of these conversions only if a digit follows it.) For g and G conversions, trailing zeros are not removed from the result. For other conversions, the behavior is undefined.
"alternative form(選択方式、代替形態)"に変換されっぜ。o変換に対しては、必要な時に限って、最初の桁を0に強制するんで精度を拡張する(値と精度が両方0なら一つの0が出力される)。x(かX)変換に対しては、非0は0x(か0X)が前に付く。
(続きは、気が向いたら。。)
0 For d, i, o, u, x, X, a, A, e, E, f, F, g, and G conversions, leading zeros (following any indication of sign or base) are used to pad to the field width rather than performing space padding, except when converting an infinity or NaN. If the 0 and - flags both appear, the 0 flag is ignored. For d, i, o, u, x, and X conversions, if a precision is specified, the 0 flag is ignored. For other conversions, the behavior is undefined.
d,i,o,u,x,X,a,A,e,E,f,F,g,G変換に対して、無限大や非数に変換される時を除いて、最初に空白埋めが行われるんでなく(符号か基数表示の後に続いて)0がフィールド幅を埋める為に使われるぜ。0と-フラグ両方が使われると0フラグは無視されちゃうよ。d,i,o,u,x,X変換に対しては、精度が指定されちゃってると0フラグは無視されちゃうよ。他の変換では、どうなるか決めてないよ。

最小フィールド幅(minimum field width)

An optional minimum field width. If the converted value has fewer characters than the field width, it is padded with spaces (by default) on the left (or right, if the left adjustment flag, described later, has been given) to the field width. The field width takes the form of an asterisk * (described later) or a decimal integer.
任意のフィールド幅。変換値がフィールド幅より少ない文字数なら、フィールド幅まで(デフォルトで)空白を左に埋めるぜ(後で記述される(この記事では前だけど) 左寄せフラグがある場合は右に埋める)。フィールド幅はアスタリスク*(後で記述)の形式か10進数整数をとっちゃうよ。

.精度(precision)

An optional precision that gives the minimum number of digits to appear for the d, i, o, u, x, and X conversions, the number of digits to appear after the decimal-point character for a, A, e, E, f, and F conversions, the maximum number of significant digits for the g and G conversions, or the maximum number of bytes to be written for s conversions. The precision takes the form of a period (.) followed either by an asterisk * (described later) or by an optional decimal integer; if only the period is specified, the precision is taken as zero. If a precision appears with any other conversion specifier, the behavior is undefined.
d, i, o, u, x, X変換に対しては最小桁数、a, A, e, E, f, F変換に対しては小数点後の桁数、g, G変換に対しては最大有効桁数、s変換に対しては書かれるべき最大バイト数を表す任意の精度。精度はピリオド.とそれに続いてアスタリスク*(後で記述)か任意の10進数整数の形式をとるぜ。ピリオドだけ指定される場合は0として扱うんだぜ。精度がその他の変換指定子で表れる場合の動作は未定義だぜ。
  • 浮動小数点の桁数だけだと思ってた…

長さ修飾子(length modifier)

An optional length modifier that specifies the size of the argument.
引数のサイズを指定する任意の長さ修飾子。
長さ修飾子 英語説明
適当翻訳
hh Specifies that a following d, i, o, u, x, or X conversion specifier applies to a signed char or unsigned char argument (the argument will have been promoted according to the integer promotions, but its value shall be converted to signed char or unsigned char before printing); or that a following n conversion specifier applies to a pointer to a signed char argument.
後に続くd,i,o,u,x,X変換指定子がsigned charかunsigned char引数に適用されることを指定するぜ(引数は整数拡張に従って拡張されるよん。だけどその値は出力する前にsigned charかunsigned charに変換されるべきだよ。(詳しい人この意味教えて。。。))。または、後に続くn変換指定子がsigned char引数へのポインタに適用されることを指定するぜ。
h Specifies that a following d, i, o, u, x, or X conversion specifier applies to a short int or unsigned short int argument (the argument will have been promoted according to the integer promotions, but its value shall be converted to short int or unsigned short int before printing); or that a following n conversion specifier applies to a pointer to a short int argument.
後に続くd,i,o,u,x,X変換指定子がshort intかunsigned short int引数に適用されることを指定するぜ(引数は整数拡張に従って拡張されるよん。だけどその値は出力する前にshort intかunsigned short intに変換されるべきだよ。)または、後に続くn変換指定子がshort int引数へのポインタに適用されることを指定するぜ。
l Specifies that a following d, i, o, u, x, or X conversion specifier applies to a long int or unsigned long int argument; that a following n conversion specifier applies to a pointer to a long int argument; that a following c conversion specifier applies to a wint_t argument; that a following s conversion specifier applies to a pointer to a wchar_t argument; or has no effect on a following a, A, e, E, f, F, g, or G conversion specifier.
後に続くd,i,o,u,x,X変換指定子がlong intかunsigned long int引数に適用されることを指定するぜ。後に続くn変換指定子がlong int引数へのポインタに適用されることを指定するぜ。後に続くc変換指定子がwint_t引数に適用されることを指定するぜ。後に続くs変換指定子がwchar_t引数へのポインタに適用されることを指定するぜ。後に続くa,A,e,E,f,F,g,G変換指定子では何の効果もないよ。
ll 気が向いたらね。
j 気が向いたらね。。
z Specifies that a following d, i, o, u, x, or X conversion specifier applies to a size_t or the corresponding signed integer type argument; or that a following n conversion specifier applies to a pointer to a signed integer type corresponding to size_t argument.
後に続くd,i,o,u,x,X変換指定子がsize_tか同様のsigned integer型引数に適用されることを指定するぜ。または後に続くn変換指定子がsize_t引数に相当するsigned integer型へのポインタに適用されることを指定するぜ。
t Specifies that a following d, i, o, u, x, or X conversion specifier applies to a ptrdiff_t or the corresponding unsigned integer type argument; or that a following n conversion specifier applies to a pointer to a ptrdiff_t argument.
後に続くd,i,o,u,x,X変換指定子がptrdiff_tか同様のunsigned integer type引数に適用されることを指定するぜ。または後に続くn変換指定子がptrdiff_t引数へのポインタに適用されることを指定するぜ。
L 気が向いたらね。。。

変換指定子(conversion specifier)

A conversion specifier character that specifies the type of conversion to be applied.
適応される変換タイプを指定する変換指定子文字。
変換指定子 英語内容
適当翻訳
d
i
The int argument is converted to signed decimal in the style [−]dddd. The precision specifies the minimum number of digits to appear; if the value being converted can be represented in fewer digits, it is expanded with leading zeros. The default precision is 1. The result of converting a zero value with a precision of zero is no characters.
int引数が[-]ddddの形で符号付き10進数に変換されるぜ。精度は最小桁数を指定するぜ。変換される値が精度より少ない桁数なら、0で拡張される。デフォルトの精度は1だぜ。精度0で値0を変換したら文字は無くなっちゃうぜ。
o
u
x
X
The unsigned int argument is converted to unsigned octal (o), unsigned decimal (u), or unsigned hexadecimal notation (x or X) in the style dddd; the letters abcdef are used for x conversion and the letters ABCDEF for X conversion. The precision specifies the minimum number of digits to appear; if the value being converted can be represented in fewer digits, it is expanded with leading zeros. The default precision is 1. The result of converting a zero value with a precision of zero is no characters.
unsigned int引数が符号無し8進数表記に(o)、符号無し10進数表記に(u)、または符号無し16進数表記に(xかX)、dddd形式で変換されるぜ。文字abcdefがx変換指定子では使われて、文字ABCDEFがX変換指定子では使われるぞ。精度は最小桁数を指定するよ。変換される値がより少ない桁数で表されるならば、先頭0で拡張されるぜ。デフォルトの精度は1や。精度0で値0を変換したら文字は無いぜよ。
f
F
気が向いたらね。
e
E
気が向いたらね。。
g
G
気が向いたらね。。。
a
A
気が向いたらね。。。。
c If no l length modifier is present, the int argument is converted to an unsigned char, and the resulting character is written. If an l length modifier is present, the wint_t argument is converted as if by an ls conversion specification with no precision and an argument that points to the initial element of a two-element array of wchar_t, the first element containing the wint_t argument to the lc conversion specification and the second a null wide character.
長さ修飾子lが指定されないなら、int引数がunsigned charに変換されて、その結果の文字が書かれるよ。長さ修飾子lが指定されるなら、精度無しでwchar_tの2要素配列(最初の要素はlc変換指定へのwint_t引数を含み、二つ目の要素はnullワイド文字を含む)の先頭を指し示す引数を持つls変換指定のように、wint_t引数が変換されるぜ。
s If no l length modifier is present, the argument shall be a pointer to the initial element of an array of character type. Characters from the array are written up to (but not including) the terminating null character. If the precision is specified, no more than that many bytes are written. If the precision is not specified or is greater than the size of the array, the array shall contain a null character. If an l length modifier is present, the argument shall be a pointer to the initial element of an array of wchar_t type. Wide characters from the array are converted to multibyte characters (each as if by a call to the wcrtomb function, with the conversion state described by an mbstate_t object initialized to zero before the first wide character is converted) up to and including a terminating null wide character. The resulting multibyte characters are written up to (but not including) the terminating null character(byte). If no precision is specified, the array shall contain a null wide character. If a precision is specified, no more than that many bytes are written (including shift sequences, if any), and the array shall contain a null wide character if, to equal the multibyte character sequence length given by the precision, the function would need to access a wide character one past the end of the array. In no case is a partial multibyte character written.
長さ修飾子lが指定されないなら、引数はchar型配列の先頭ポインタでなければならない。配列から文字達は終端null文字まで書かれる(けどnull文字は書かれないよ)。精度が指定されるなら、精度以上のバイトは書かれない。精度が指定されないか精度が配列サイズより大きければ、配列はnull文字を含むべきだ。長さ修飾子が指定されるなら、引数は、wchar_t型配列の先頭ポインタであるべきだ。配列からワイド文字は終端nullワイド文字を含むまでマルチバイト文字に(それぞれ、wcrtomb関数呼び出しによる様に、mbstate_tオブジェクト(最初のワイド文字が変換される前に0に初期化される)によって記述される変換状態で)変換される。(以下略。。。疲れた。。。)
p The argument shall be a pointer to void. The value of the pointer is converted to a sequence of printing characters, in an implementation-defined manner.
引数はvoidポインタであるべきやね。ポインタ値は実装で定義される方法で一連の表示文字に変換されるわ。
n 気が向いたらね。。。。。
% A % character is written. No argument is converted. The complete conversion specification shall be %%.
%文字が書かれる。変換される引数はないっす。完ペキな変換指定は%%であるべきっす。

note
a field width, or precision, or both, may be indicated by an asterisk. In this case, an int argument supplies the field width or precision. The arguments specifying field width, or precision, or both, shall appear (in that order) before the argument (if any) to be converted. A negative field width argument is taken as a - flag followed by a positive field width. A negative precision argument is taken as if the precision were omitted.
最小フィールド幅と精度はアスタリスクによって示されることができるっす。この場合、int型引数がフィールド幅か精度になるっす。必ずフィールドアバか精度かその両方を指定する引数は変換されるための引数(もしあれば)より前に(順番に)表れるっす。負のフィールド幅引数は-フラグ(この後に正のフィールド幅が続く)として扱われるっす。負の精度引数は精度が省略された様に扱われるっす。っすっすっす。

実装方針を決める。

一言で言うと、「だるっ!」
こんな方針で実装しようそうしよう。
  • 引数順はちょっと見なかったことにしよう。
  • フラグはちょっとがんばろうかな。
  • 最小フィールド幅もがんばろう。
  • 精度はうーん浮動小数点演算以外も使えるの知らなかった。。。がんばろ。
  • 長さ修飾子は4バイトまでなら対応する。あとは知らない。32bitOSだし(そういうことじゃない
  • 変換指定子も浮動小数点数とよくわからないのは見なかったことにするか。
英語読むだけで疲れたわ!誤訳ばっかだわたぶん!恥ずかしい!ごめん!

Rust@naked関数

Rust@naked関数 multiboot仕様でブートローダを自作せずGRUBから自作カーネル(もどき)を起動できたので、起動情報も色々受け取りたい。multiboot準拠していれば、起動直前にブートローダがレジスタEAXにマジック値を...