-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[実装例]コンテキストスイッチの実装 #11
base: pcb
Are you sure you want to change the base?
Conversation
if (!proc) PANIC("no free process slots"); | ||
|
||
// switch_context() で復帰できるように、スタックに呼び出し先保存レジスタを積む | ||
uint32_t *sp = (uint32_t *)&proc->stack[sizeof(proc->stack)]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
スタックは下に成長していくため、何も入っていない状態だとspの値は配列stackの先頭アドレスと等しい。
|
||
// switch_context() で復帰できるように、スタックに呼び出し先保存レジスタを積む | ||
uint32_t *sp = (uint32_t *)&proc->stack[sizeof(proc->stack)]; | ||
*--sp = 0; // s11 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
--spはスタックポインタの値を1つ(4バイト)減らし、次に*sp = 0となるので、新しいspが指すメモリ位置に0が格納される。
*--sp = 0; // s2 | ||
*--sp = 0; // s1 | ||
*--sp = 0; // s0 | ||
*--sp = (uint32_t)pc; // ra |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
create_process関数はpc(実行開始アドレス)を引数として受け取る。つまりそのプロセスで実行される関数の先頭アドレスを引数として受け取る。それをスタックに保存することでコンテキストスイッチ時にリターンアドレスレジスタにセットし、retを実行することで、プロセスで実行したい関数に制御が移すことができる。
@@ -168,3 +196,83 @@ void map_page(uint32_t *table1, uint32_t vaddr, paddr_t paddr, uint32_t flags) { | |||
uint32_t *table0 = (uint32_t *)((table1[vpn1] >> 10) * PAGE_SIZE); | |||
table0[vpn0] = ((paddr / PAGE_SIZE) << 10) | flags | PAGE_V; | |||
} | |||
|
|||
struct process *create_process(uint32_t pc) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
create_process関数で行っているのはスタックの初期化である。そのため、プロセスの実行が進むにつれて汎用レジスタの値は変わってくる。なのでコンテキストスイッチをする際には現在の汎用レジスタの値を保存し直す必要がある。
|
||
uint32_t *page_table = (uint32_t *)alloc_pages(1); | ||
|
||
// カーネルのページをマッピングする |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
各プロセスに対して、カーネル空間のページ(__kernel_baseから__free_ram_endまでの範囲)をプロセスのページテーブルにマッピングすることで、そのプロセスがカーネルモードに移行した際に、カーネルコードやデータに適切にアクセスできるようにしている。
すでにSv32が有効化されているのでマップしないとアクセスできなくなるのと、カーネルページについては同じ値の仮想アドレスと物理アドレスをマップすることで、今までと同様に物理アドレスでアクセスできるかのようにしている。
return proc; | ||
} | ||
|
||
__attribute__((naked)) void switch_context(uint32_t *prev_sp, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
この関数はprev_spにコンテキストスイッチする前のspの値を格納し、next_spに格納されているspの値から新しいコンテキストにスイッチする。
スタックに汎用レジスタの値が格納されているので、それを使って汎用レジスタの値を切り替えることでコンテキストスイッチを実現する。上記はスタックポインタの値を拡張し、汎用レジスタの値を格納した後、a0(prev_sp)にスタック領域の先頭(スタックトップ)を格納している。
次に、a1(next_sp)から新しいスタックトップのアドレス(スタックポインタの値)をスタックポインタに格納し、そこに格納されている汎用レジスタの値を復元することで新しいプロセスのコンテキストに切り替えている。
最後にretを実行することで、切り替えたプロセスが持っていたリターンアドレスに制御を移す。
思考プロセス
コンテキストスイッチを実装することで、複数のプロセスを交互に切り替えて実行していくことができるようになる。
create_process
switch_context
switch_context(&proc_prev->sp, &proc_next->sp);
と呼び出せるようにしたいテスト
switch_context(&proc_a->sp, &proc_b->sp);
proc_a_entry関数が実行されると、switch_context関数によってプロセスAの現在の状態が保存され、そのスタックトップがproc_a->spに格納され、proc_b->spに格納されているspからプロセスBの状態が復元される。これによってプロセスBを作るときに、スタックを初期化した際にraにセットしたproc_b_entry関数の先頭アドレスがraレジスタにセットされるため、proc_b_entry関数に制御が移る。
asm volatile("nop");
nop命令は「何もしない」命令で、これをしばらく繰り返すループを入れることで、文字での出力が速すぎてターミナルを操作できなくなるのを防いでいる。
起動時のメッセージが1回ずつ表示され、その後は「ABABAB...」と交互に表示される。