おもちゃのOSを書いています(中断について)



この記事はブログ投稿として書かれています。 それがあなたにとって興味深いことが判明した場合、続編があります。



過去4か月間、x86_64用のおもちゃのOSを書くために自由時間を費やしてきました。 ソースコードはこちらです。



一般的な考え方(これまでのところ、実装からはほど遠い)は次のとおりです。永遠に生きているスレッド(Phantom OSなど)を備えた単一の64ビットアドレス空間 コード実行のセキュリティを提供する仮想マシン。 現在実装されています:



1.マルチブートブートローダー(GRUB)を使用してカーネルをロードします。

2.テキストVGAモード(16色、kprintf);

3.ページを表示するためのシンプルなインターフェイス。

4. Cで割り込みを処理する機能。

5.プロセッサトポロジ(ソケット、コア、スレッド)とその起動の識別。

6.優先度をサポートするプリエンプティブSMPスケジューラの動作プロトタイプ。



multiboot-loadingとVGAモードでの作業の説明をスキップしましょう(私はこれについては書きませんでしたが、遅延を除きます)。 ページの表示についても書きたくありません。退屈になるかもしれません(別の時間になるかもしれません)。 割り込み処理について詳しく説明しましょう。



通常、割り込みハンドラーは、他の重要なコードと同様に、アセンブラーで作成されます。 私はアセンブラがあまり好きではないので、Cでできるだけ多くのコードを書くことを好みます。したがって、Cで割り込みハンドラを書くのに便利なマクロをいくつか作成しました。リアルタイム)。



ロングモードでの割り込み時に、プロセッサは保存されたレジスタを含むハンドラースタック(カスタムスタックまたは個別に選択されたスタックのいずれか)にフレームを形成します。







実際、この写真はプロテクトモードに対応しています(ロングモード用の高品質な写真は見つかりませんでした)が、細かい部分を除けば、原理はまったく同じです。 ユーザーストリームの残りのレジスタはそのまま保持されるため、ハンドラーはそれらをスタックに保存する必要があります。 ハンドラーはCで記述されているため、FPU / MMX / SSEの512バイトを含むレジスターの完全なセットを保存する必要があります。 もちろん、コンパイラーがカーネル全体のSIMDコードを生成しないようにすることも、割り込み内で機能する関数のみを生成することもできます。 前者の場合、多くの最適化が失われます。後者の場合、標準関数を使用できないため、Cでハンドラを記述する利点を一般的に無効にします。 そのため、fxsaveおよびfxrstor命令を使用して、FPU / MMX / SSEレジスタをすばやく保存/復元します。



スタックフレームの構造は次のとおりです。



struct int_stack_frame { uint64_t r15, r14, r13, r12, r11, r10, r9, r8; uint64_t rdi, rsi, rbp, rdx, rcx, rbx, rax; uint8_t fxdata[512]; uint32_t error_code; uint64_t rip; uint16_t cs; uint64_t rflags, rsp; uint16_t ss; };
      
      





error_codeの前のフィールドの最初の部分は手動で保存されたレジスタで、2番目はプロセッサによって自動的に保存されたレジスタです。 逆の順序は、スタックが上から下に成長するという事実によるものです。 ハンドラーを簡単に作成するためのマクロを定義します。



 #define DEFINE_INT_HANDLER(name) \ static NOINLINE \ void handle_##name##_int(UNUSED struct int_stack_frame *stack_frame, \ UNUSED uint64_t data) #define DEFINE_ISR_WRAPPER(name, handler_name, data) \ static NOINLINE void *get_##name##_isr(void) { \ ASMV("jmp 2f\n.align 16\n1: andq $(~0xF), %rsp"); \ ASMV("subq $512, %rsp\nfxsave (%rsp)"); \ ASMV("push %rax\npush %rbx\npush %rcx\npush %rdx\npush %rbp\n"); \ ASMV("push %rsi\npush %rdi\npush %r8\npush %r9\npush %r10"); \ ASMV("push %r11\npush %r12\npush %r13\npush %r14\npush %r15"); \ ASMV("movq %%rsp, %%rdi\nmovabsq $%P0, %%rsi" : : "i"(data)); \ ASMV("callq %P0" : : "i"(handle_##handler_name##_int)); \ ASMV("pop %r15\npop %r14\npop %r13\npop %r12\npop %r11"); \ ASMV("pop %r10\npop %r9\npop %r8\npop %rdi\npop %rsi"); \ ASMV("pop %rbp\npop %rdx\npop %rcx\npop %rbx\npop %rax"); \ ASMV("fxrstor (%rsp)\naddq $(512 + 8), %rsp"); \ void *isr; \ ASMV("iretq\n2: movq $1b, %0" : "=m"(isr)); \ return isr; \ } #define DEFINE_ISR(name, data) \ DEFINE_INT_HANDLER(name); \ DEFINE_ISR_WRAPPER(name, name, data) \ DEFINE_INT_HANDLER(name)
      
      





最初のマクロは、ハンドラー関数の署名を定義します。 2つ目は、レジスタを保存および復元するラッパーです。 このようなスキームにより、複数の割り込みに対して1つの関数ハンドラーを呼び出すことができます。 複数の割り込みがスタックフレームをダンプするとき、標準エラーにこれを使用します。 コードからわかるように、ハンドラーは追加の引数データを受け取ります;したがって、異なる割り込みが1つのハンドラーにデータを渡すことができます。 最後に、短いペアリングの最後のマクロ:ハンドラー+ラッパー(ハンドラーが1つの割り込みによってシャープ化される場合)。



ラッパーは、独自の本体にある処理コードの先頭へのポインターを返す関数です。 このトリックの詳細については、こちらをご覧ください



その結果、ハンドラーを作成してそれを割り込みにバインドするのは簡単なタスクになります。



 DEFINE_ISR(foo) { //  C-   //  struct int_stack_frame *stack_frame  uint64_t data } set_isr(INT_FOO_VECTOR, get_foo_isr());
      
      





ベトナムが割り込みを処理することについて伝えたかったのはそれだけです。



All Articles