Skip to content
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

[実装例]システムコールの実装 #14

Open
wants to merge 2 commits into
base: user_program
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 23 additions & 4 deletions os/kernel.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@ struct process *idle_proc; // アイドルプロセス

void putchar(char);
void kernel_entry(void);
void handle_trap(void);
void handle_trap(struct trap_frame *);
paddr_t alloc_pages(uint32_t);
void map_page(uint32_t *, uint32_t, paddr_t, uint32_t);
struct process *create_process(const void *, size_t);
void switch_context(uint32_t *, uint32_t *);
void yield(void);
void user_entry(void);
void handle_syscall(struct trap_frame *);

struct process *proc_a;
struct process *proc_b;
Expand Down Expand Up @@ -133,6 +134,7 @@ __attribute__((aligned(4))) __attribute__((naked)) void kernel_entry(void) {
"addi a0, sp, 4 * 31\n"
"csrw sscratch, a0\n"

"mv a0, sp\n" // handle_trap関数の引数としてコンテキストを保存したスタック領域のポインタを渡す
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spの値をa0にコピーする。このspの値はaddi sp, sp, -4 * 31された後の値であり、汎用レジスタの値が格納されているスタック領域の先頭アドレスである。a0にコピーしているのは、次に呼び出すhandle_trap関数の引数とするためである。スタックに保存された汎用レジスタの値はtrap_frame構造体と同じ構造になっているため、handle_trap関数にはtrap_frame構造体の型を持つ引数が渡されることになる。

"call handle_trap\n"

"lw ra, 4 * 0(sp)\n"
Expand Down Expand Up @@ -170,12 +172,19 @@ __attribute__((aligned(4))) __attribute__((naked)) void kernel_entry(void) {
"sret\n");
}

void handle_trap(void) {
void handle_trap(struct trap_frame *f) {
uint32_t scause = READ_CSR(scause);
uint32_t stval = READ_CSR(stval);
uint32_t sepc = READ_CSR(sepc);
PANIC("unexpected trap ocurred; scause=%x, stval=%x, sepc=%x", scause, stval,
sepc);
if (scause == SCAUSE_ECALL) {
Copy link
Owner Author

@rihib rihib Aug 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ecall 命令が呼ばれたのかどうかは、scause の値を確認することで判定できる。handle_syscall関数を呼び出す以外にも、sepcの値に4を加えている。これは、sepcはトラップを引き起こしたプログラムカウンタ、つまりecall命令を指している。変えないままだと、ecall命令を無限に繰り返し実行してしまうので、命令のサイズ分 (4バイト) だけ加算することで、ユーザーモードに戻る際に次の命令から実行を再開するようにしている。

handle_syscall(f);
sepc += 4;
} else {
PANIC("unexpected trap occured; scause=%x, stval=%x, sepc=%x\n", scause,
stval, sepc);
}

WRITE_CSR(sepc, sepc);
}

paddr_t alloc_pages(uint32_t n) {
Expand Down Expand Up @@ -327,3 +336,13 @@ void user_entry(void) {
WRITE_CSR(sepc, USER_BASE);
__asm__ __volatile__("sret\n");
}

void handle_syscall(struct trap_frame *f) {
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

トラップハンドラから呼ばれるのがシステムコールハンドラである。システムコールの種類に応じて処理を分岐する。今回は、SYS_PUTCHAR に対応する処理を実装する。単にa0レジスタに入っている文字を出力するだけである。

switch (f->a3) {
case SYS_PUTCHAR:
putchar(f->a0);
break;
default:
PANIC("unexpected syscall a3=%x\n", f->a3);
}
}
5 changes: 1 addition & 4 deletions os/shell.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
#include "user.h"

void main(void) {
*((volatile int *)0x80200000) = 0x1234;
for (;;);
}
void main(void) { printf("shell > "); }
16 changes: 15 additions & 1 deletion os/user.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,21 @@ extern char __stack_top[];

__attribute__((noreturn)) void exit(void) { for (;;); }

void putchar(char c) { /* 後で実装する */ }
int syscall(int sysno, int arg0, int arg1, int arg2) {
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

syscall関数は、a3にシステムコール番号、a0〜a2レジスタにシステムコールの引数を設定して ecall 命令を実行する。ecall 命令は、カーネルに処理を委譲するための特殊な命令である。ecall 命令を実行すると、トラップハンドラが呼び出され、カーネルに処理が移る。カーネルからの戻り値はa0レジスタに設定される。

register int a0 __asm__("a0") = arg0;
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

変数a0をレジスタa0に対応させている。これにより変数a0を使用すると、実際にはレジスタa0を操作することになる。そのため、変数a0にarg0を代入しているので、実際はレジスタa0にarg0を格納している。

register int a1 __asm__("a1") = arg1;
register int a2 __asm__("a2") = arg2;
register int a3 __asm__("a3") = sysno;

__asm__ __volatile__("ecall"
: "=r"(a0)
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

出力オペランドを指定する部分である。ecall命令が実行された結果をa0レジスタに格納するように指定している。

: "r"(a0), "r"(a1), "r"(a2), "r"(a3)
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

入力オペランドを指定する部分である。rはレジスタに格納された値を使用することを意味し、a0からa3の各レジスタに格納された値がecall命令に渡される。

: "memory");
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

コンパイラレベルのメモリバリアーを作り、最適化によってメモリアクセスの順序を変更しないようにする。
https://stackoverflow.com/questions/14950614/working-of-asm-volatile-memory


return a0;
}

void putchar(char ch) { syscall(SYS_PUTCHAR, ch, 0, 0); }
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

putcharシステムコールを呼び出す。このシステムコールでは、第1引数として文字を渡す。第2引数以降は、未使用なので0を渡すことにする。


__attribute__((section(".text.start"))) __attribute__((naked)) void start(
void) {
Expand Down