7日目の記事でもあります。~初めてのユーザプロセス~の続きです。前回の内容プロセスとは?x86リングプロテクション(ユーザモードとカーネルモード)マルチタスク割り込みコンテキストスイッチを一気に説明しました。多少知識が無いとわからないよなあって感じになっているのでその点は反省しております。今回はコンテキストスイッチの実装ユーザプロセス起動を見て行きたいと思います。前回はタイマ割り込みによってコンテキストスイッチが起きる、というところで終わったと思います。そこの流れが大事ですので再掲します。↓ユーザモードに復帰はい、こんな感じです。(※カーネルスタックではわかりづらいのでカーネルモードスタックに変更します。)では、早速ですがもぷりんOSのコンテキストスイッチ実装を見て行きましょう。この部分は主にLinuxの実装を真似ています。言っても、x86上で同じことをやろうとしたら大体こうなると思います。リポジトリはこちらmopp/Axel上記の流れに沿ってコードを掲載していきます。はじめに呼ばれる割り込みコードを見てみましょう。以下のこれは、割り込みエントリ関数で、タイマー割り込み以外のキーボード割り込みや一般保護例外などでも呼ばれる部分です。上記コードはsrc/interrupt_asm.asmにあります。Gistに貼った方は見やすさのためにマクロなど全て展開済みです。レジスタ操作があるのでC言語では書けません。アセンブラを使用しています。asm_interrupt_timerという関数がカーネル初期化時にCPUのタイマ割り込みハンドラに登録されます。タイマ割り込みが発生したらCPUが勝手にasm_interrupt_timerを呼び出してくれます。ここで大切なのは、割り込みが発生した際、CPUが自動的にカーネルモードに切り替えるということです。これは前回も書いたかもしれないですが、きちんと書いていないので、ちゃんと説明します。下巻:システム・プログラミング・ガイドの5.12.1を参照してください。同じことを書いています。割り込まれる状況というのは、カーネルモード時割り込みとユーザモード時割り込みが有ります。前回、スタックも切り替わると書きましたが、これはユーザモード時割り込みの時だけです。そもそもとして、カーネルモードだったら、カーネルモードスタックになっているに決まってるんだから切り替える必要はないですよね。以下の図を見てください。ユーザモード時割り込みのスタック切り替え以前動作していたコンテキストの一部がカーネルモードスタックに保存されます。割り込みから戻るときにこれらを使って戻ります。csとssを説明する気力がないのでWikipediaに投げます...ここは、混乱しがちですので注意してください。割り込まれた時のスタックでは無く、カーネルモードになってからのスタック(カーネルモードスタック)です。codeからeflagsまでがカーネルモードスタックに保存されます。asm_interrupt_timerに戻りましょう。タイマ割り込みにはエラーコードが無いので擬似的に設定します。これは後々出てくる構造体のためです。次に、割り込み前の状態を維持し、正しく復帰するために汎用レジスタなどを保存します。タイマ割り込みハンドラ本体を呼び出します。この時に、pushしているespを、"後々出てくる構造体のポインタ"として、関数に渡しています。本体の処理が終わったら、レジスタ状態の復元と擬似エラーコードの削除をします。最後のiret命令これが、割り込み復帰処理です。実行していたところへ帰ってくれます。その際、以前のespやeipが必須になるわけです。次にinterrupt_timer関数ですが、これはsrc/time.cにあります。実際、下記の関数を呼び出しているだけなので省略。上記コードはsrc/proc.cにあります。このコードでは、プロセスの切り替えを行っています。スレッド実装したかったので、プロセス構造体無いのスレッド構造体に色々入っています。currentが現在実行中のプロセスで、nextに実行可能なプロセスを持ってきます。ここでキモなのは、インラインアセンブラの部分です。31行目で現在プロセスが復帰した時のeipとespを保存しています。次に実行されるときは、next_turnから実行されることになります。次に、切り替え先プロセスの情報を復元します。34行目でespを切り替えています。これはまあ普通ですね。次はeipを切り替えます。eipに対してmov命令を実行することは出来ません。スタックに入れて、やります。call命令ではなくjmp命令で関数に移動します。すると、関数の終了時にはret命令が呼ばれるはずです。この、ret命令はスタックから戻り先アドレスをeipに復元します。これを利用しています。change_contextから戻った時点で、既にプロセスは切り替わったことになります。ここでchange_contextに渡す引数はレジスタ渡し、fastcall規約で渡しています。(Linuxのを真似してみたんですが、現状fastcallである必要はあまりない...)今の流れを延々と繰り返して、マルチタスクが実現されているわけですね。コンテキストスイッチの説明が終わりました。(長すぎた)初めてのユーザプロセス起動を見て行きましょう。上記コードはsrc/proc.cにあります。この関数はプロセス初期化中に呼ばれます。プロセスを一つ確保します。textセグメントとstackセグメントを確保します。(dataセグメント忘れてた...)この次の15行目、これがポイントです!thread.ipにinterrupt_returnを設定しています。先のswitch_contextではthread.ipを次の復帰先として、使っていました。このプロセスは次にswitch_contextによって切り替えられた時に、interrupt_returnから実行が始まるのです。interrupt_returnでは汎用レジスタの復元、そして、iret命令を実行しています。iret命令では上図(ユーザモード時割り込みのスタック切り替え)のようなスタックを読み取ってユーザプロセスを再開しています。割り込みから戻ったようにカーネルモードスタックを調節してやればいいのです!18〜32行目でそれを行っています。ここで使っているInterrupt_frameが後に出てくる構造体です。これを使って、割り込み時のスタックをいい感じに参照、変更することが出来ます。prev_espが上図の前の〜〜に対応するものです。これをプログラムの実行開始アドレスにしてやればいいわけです。ぷりんOSの現在のinitプログラムでは実行開始アドレスはDEFAULT_TEXT_ADDRです。この辺は実行プログラムによります。48行目で、switch_context内のnext_procで拾えるように、実行可能状態にします。あとは、普通にスケジューラが動き出すのを待つだけです。初期ユーザプロセスの起動はこれで完了になります。時間があれば、forkとかexecの話ももっとちゃんと書きたいなと思っています。Extension】"マイム"と読むらしい直訳してみれば、"多目的インターネットメール拡張機能"かな?ネットワーク上で動画、音声、画像などのファイルを扱うための規格RFC2045~2049で規定されている。タイプ名/サブタイプ名"のように区切られて表現される例としては、text/htmltext/plainimage/jpgapplication/pdfなどなどさて、例えば、AndroidでSDカードにアクセスし適当なファイルを開いた際どんなファイルか判別したいとするこの時にMIMEを読み取り画像か音声かテキストか判別し他のアプリにIntentを投げようと思います※12/25追記:以下の方法だと動作しない場合があるので更に下に追記しました。}引数のパスは"/sdcard/Music/xxx.mp3"のように渡しています。その感想をメモを兼ねて時系列順にざっと思うままに書いてみます。Festは過去に何度か開催されていたようですが、自分は今回が初参加でした。場所は永田町で、10時開場、10:30セッション開始というスケジュールで、10:10くらいに入場しました。会場は縦に長く、中央にプロジェクターのスクリーンが1枚という構成でした。後ろの方に座るとスライドがよく見えないだろうと思ったので前の方の席を探すと中央の前から2番目というかなりのいい席に座ることができてセッション中に不自由しなかったのがとてもよかったです。発表資料を公開します。2019一つ目の発表はHastegaというElixirのコードをOpenCLに変換して分散処理をするライブラリ(でいいのだろうか)についての発表でした。名前の由来はFFの仲間を強化する魔法だそうです。現状はdefhastegaというマクロで囲んだ中にあるEnum.mapなどが変換され、並列処理されるようになるそうです。将来はdefhastegaマクロを使わずに、できそうなところは変換するようにしたいとも話していました。スライドに性能評価はありましたが、コード生成の仕組みに悩んでいるとのことでリリースはされていないそうです。Hastegaを使ったコード例を見た限り、使う分にはシンプルそうでよさそうでした。22からはHiPEが非推奨になるのでそこも悩みどころ、と話しており、それについてツイートしたところ、記事を教えていただきました。Joseから紹介された記事です。