UCBが開発しているRISC-VのシミュレータSpikeや、Rocket-ChipのRTLデザインは通常はシステムコールを持っていない。
つまり、当然ながらC言語でprintf("Hello World\n");
などと書いても動作しないのだが、そこはコンパイラとフロントエンドサーバfesvr、pk(Proxy Kernel)によって肩代わりすることでこれらのシステムコールを実現している。
Proxy KernelというのはRISC-Vのシステムコールにつながる処理をフロントエンドサーバに受け渡すインタフェース、そしてフロントエンドサーバはPKからのシステムコールリクエストを受け取ってRISC-Vコアの代わりに実現する機能を持っている。
例えば、RISC-V Fesvrは以下のシステムコールをサポートしている。
syscall_t::syscall_t(htif_t* htif) : htif(htif), memif(&htif->memif()), table(2048) { table[17] = &syscall_t::sys_getcwd; table[25] = &syscall_t::sys_fcntl; table[34] = &syscall_t::sys_mkdirat; table[35] = &syscall_t::sys_unlinkat; ... table[2011] = &syscall_t::sys_getmainvars; }
ID | Function | |
---|---|---|
17 | getcwd | sys_exit ( reg_t code) |
25 | fcntl | sys_fcntl ( reg_t fd, reg_t cmd, reg_t arg) |
34 | mkdirat | sys_mkdirat ( reg_t dirfd, reg_t pname, reg_t len, reg_t mode) |
35 | unlinkat | sys_unlinkat ( reg_t dirfd, reg_t pname, reg_t len, reg_t flags) |
37 | linkat | sys_linkat (reg_t odirfd, reg_t poname, reg_t olen, reg_t ndirfd, reg_t pnname, reg_t nlen, reg_t flags) |
38 | renameat | sys_renameat ( reg_t odirfd, reg_t popath, reg_t olen, reg_t ndirfd, reg_t pnpath, reg_t nlen) |
46 | ftruncate | sys_ftruncate ( reg_t fd, reg_t len) |
48 | faccessat | sys_faccessat ( reg_t dirfd, reg_t pname, reg_t len, reg_t mode) |
49 | chdir | sys_chdir ( reg_t path) |
56 | openat | sys_openat ( reg_t dirfd, reg_t pname, reg_t len, reg_t flags, reg_t mode) |
57 | close | sys_close ( reg_t fd) |
62 | lseek | sys_lseek ( reg_t fd, reg_t ptr, reg_t dir) |
63 | read | sys_read ( reg_t fd, reg_t pbuf, reg_t len) |
64 | write | sys_write ( reg_t fd, reg_t pbuf, reg_t len) |
67 | pread | sys_pread ( reg_t fd, reg_t pbuf, reg_t len, reg_t off) |
68 | pwrite | sys_pwrite ( reg_t fd, reg_t pbuf, reg_t len, reg_t off) |
79 | fstatat | sys_fstatat ( reg_t dirfd, reg_t pname, reg_t len, reg_t pbuf, reg_t flags) |
80 | fstat | sys_fstat ( reg_t fd, reg_t pbuf) |
93 | exit | sys_exit ( reg_t code) |
1039 | lstat | sys_lstat ( reg_t pname, reg_t len, reg_t pbuf) |
2011 | getmainvars | sys_getmainvars ( reg_t pbuf, reg_t limit) |
例えば、sys_openat
はファイルのオープンを処理しているものと思われるが、実装は以下のようになっている。AT_SYSCALL
はフロントエンド(シミュレータの場合はx86上で動くLinux)のopenat
システムコールを呼び出して肩代わりする。
ファイル名は、RISC-Vシミュレータ上のメモリの特定の場所に格納されているため、memif->read()
で呼び出して実際のファイル名をシステムコールに渡している。
reg_t syscall_t::sys_openat(reg_t dirfd, reg_t pname, reg_t len, reg_t flags, reg_t mode, reg_t a5, reg_t a6) { std::vector<char> name(len); memif->read(pname, len, &name[0]); int fd = sysret_errno(AT_SYSCALL(openat, dirfd, &name[0], flags, mode)); if (fd < 0) return sysret_errno(-1); return fds.alloc(fd); }
システムコールはどのようにして呼ばれるのか?
これは、プログラムのコンパイル結果を見ていったほうが早い。printf()
などと記述すると、それはlibraryを経由してputc()
の塊となり、最終的にシステムコールとなりProxy Kernelがホストにシステムコールとして伝える。
file_t* file_open(const char* fn, int flags, int mode) { return file_openat(AT_FDCWD, fn, flags, mode); } file_t* file_openat(int dirfd, const char* fn, int flags, int mode) { file_t* f = file_get_free(); if (f == NULL) return ERR_PTR(-ENOMEM); size_t fn_size = strlen(fn)+1; long ret = frontend_syscall(SYS_openat, dirfd, va2pa(fn), fn_size, flags, mode, 0, 0); if (ret >= 0) { f->kfd = ret; return f; } else { file_decref(f); return ERR_PTR(ret); } }
long frontend_syscall(long n, uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5, uint64_t a6) { static volatile uint64_t magic_mem[8]; static spinlock_t lock = SPINLOCK_INIT; spinlock_lock(&lock); magic_mem[0] = n; magic_mem[1] = a0; magic_mem[2] = a1; magic_mem[3] = a2; magic_mem[4] = a3; magic_mem[5] = a4; magic_mem[6] = a5; magic_mem[7] = a6; htif_syscall((uintptr_t)magic_mem); long ret = magic_mem[0]; spinlock_unlock(&lock); return ret; }
最終的に、HTIF(Host Interface)によりホストにこのシステムコールは渡される。
riscv-pk/machine/htif.c
void htif_syscall(uintptr_t arg) { do_tohost_fromhost(0, 0, arg); } static void do_tohost_fromhost(uintptr_t dev, uintptr_t cmd, uintptr_t data) { spinlock_lock(&htif_lock); __set_tohost(dev, cmd, data); while (1) { uint64_t fh = fromhost; if (fh) { if (FROMHOST_DEV(fh) == dev && FROMHOST_CMD(fh) == cmd) { fromhost = 0; break; } __check_fromhost(); } } spinlock_unlock(&htif_lock); }
HTIF経由で渡されたコマンドはriscv-fesvrに渡され、コマンドに応じて処理される。
void syscall_t::handle_syscall(command_t cmd) { if (cmd.payload() & 1) // test pass/fail { htif->exitcode = cmd.payload(); if (htif->exit_code()) std::cerr << "*** FAILED *** (tohost = " << htif->exit_code() << ")" << std::endl; return; } else // proxied system call dispatch(cmd.payload()); // cmd.respond(1); }