FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

RISC-V SpikeシミュレータでC/C++のprintfを実現する仕組み (7. RISC-Vのシステムコールとfesvr)

UCBが開発しているRISC-VのシミュレータSpikeや、Rocket-ChipのRTLデザインは通常はシステムコールを持っていない。 つまり、当然ながらC言語printf("Hello World\n");などと書いても動作しないのだが、そこはコンパイラとフロントエンドサーバfesvr、pk(Proxy Kernel)によって肩代わりすることでこれらのシステムコールを実現している。

Proxy KernelというのはRISC-Vのシステムコールにつながる処理をフロントエンドサーバに受け渡すインタフェース、そしてフロントエンドサーバはPKからのシステムコールリクエストを受け取ってRISC-Vコアの代わりに実現する機能を持っている。

f:id:msyksphinz:20180727001523p:plain

例えば、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に渡され、コマンドに応じて処理される。

  • riscv-fesvr/fesvr/syscall.cc (ホストのx86のコードとしてコンパイルされる)
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);
}