技術

[xv6 #27] Chapter 2 – Traps, interrupts, and drivers – Code: System calls

テキストの36〜37ページ

本文

システムコールの場合、trap関数はsyscall関数を呼ぶ。
syscall関数は、トラップフレームからシステムコール番号を読み込む。
トラップフレームは保存された%eaxを含み、システムコールテーブルの特定のエントリを指す。
最初のシステムコールの場合、%eaxはSYS_execという値を含んでいる。(#define SYS_exec 7)
そしてsyscallは、システムコールテーブルのSYS_exec番目のエントリを呼び出す。
そのエントリは、sys_exec関数の呼び出しに対応している。

syscall.cのsyscall関数

void
syscall(void)
{
  int num;

  num = proc->tf->eax;
  if(num >= 0 && num < SYS_open && syscalls[num]) {
    proc->tf->eax = syscalls[num]();
  } else if (num >= SYS_open && num < NELEM(syscalls) && syscalls[num]) {
    proc->tf->eax = syscalls[num]();
  } else {
    cprintf("%d %s: unknown sys call %d\n",
            proc->pid, proc->name, num);
    proc->tf->eax = -1;
  }
}

syscall関数は、%eaxが指すシステムコール関数の戻り値を記録する。
トラップからユーザスペースに戻ったとき、それはcp->tfからマシンのレジスタにロードされるだろう。
このように、execから戻ったとき、システムコールハンドラから返った値を返すだろう。(syscall関数中の最初のproc−>tf−>eax = syscalls[num]();の部分)
システムコールは、慣習的にエラーを通知するために負の値を返し、成功したときは正の値を返す。
もしシステムコール番号が異常なら、syscall関数はエラーを印字し-1を返す。

後の章で、個別のシステムコールの実装について説明する。
この章は、システムコールのメカニズムに関係している。
メカニズムが後一つ残っている。
それはシステムコールの引数を見つける仕組みについてである。
argintとargptrとargstrというヘルパ関数は、n番目のシステムコールの引数を取り出す。
それぞれ順番に、整数、ポインタ、文字列に対応する。
argintは、n番目の引数を特定するために、ユーザ空間の%espレジスタを使う。
%espは、システムコールのスタブのための戻り先アドレスを指している。
引数は、まさにその(戻り先アドレス)のすぐ上位にあり、それは%esp+4で導き出せる。
だからn番目の引数は、%esp+4+4*nで導き出せる。

syscall.cのargint, argptr, argstr関連の部分

// User code makes a system call with INT T_SYSCALL.
// System call number in %eax.
// Arguments on the stack, from the user call to the C
// library system call function. The saved user %esp points
// to a saved program counter, and then the first argument.

// Fetch the int at addr from process p.
int
fetchint(struct proc *p, uint addr, int *ip)
{
  if(addr >= p->sz || addr+4 > p->sz)
    return -1;
  *ip = *(int*)(addr);
  return 0;
}

// Fetch the nul-terminated string at addr from process p.
// Doesn't actually copy the string - just sets *pp to point at it.
// Returns length of string, not including nul.
int
fetchstr(struct proc *p, uint addr, char **pp)
{
  char *s, *ep;

  if(addr >= p->sz)
    return -1;
  *pp = (char*)addr;
  ep = (char*)p->sz;
  for(s = *pp; s < ep; s++)
    if(*s == 0)
      return s - *pp;
  return -1;
}

// Fetch the nth 32-bit system call argument.
int
argint(int n, int *ip)
{
  return fetchint(proc, proc->tf->esp + 4 + 4*n, ip);
}

// Fetch the nth word-sized system call argument as a pointer
// to a block of memory of size n bytes.  Check that the pointer
// lies within the process address space.
int
argptr(int n, char **pp, int size)
{
  int i;
  
  if(argint(n, &i) < 0)
    return -1;
  if((uint)i >= proc->sz || (uint)i+size > proc->sz)
    return -1;
  *pp = (char*)i;
  return 0;
}

// Fetch the nth word-sized system call argument as a string pointer.
// Check that the pointer is valid and the string is nul-terminated.
// (There is no shared writable memory, so the string can't change
// between this check and being used by the kernel.)
int
argstr(int n, char **pp)
{
  int addr;
  if(argint(n, &addr) < 0)
    return -1;
  return fetchstr(proc, addr, pp);
}

argint関数は、ユーザメモリにあるアドレスから値を読み込み、*ipにそれを書き込むためにfetchint関数を呼ぶ。
fetchintは、そのアドレスを簡単にポインタにキャストすることができる。
なぜなら、ユーザとカーネルは同じページテーブルを共有しているからである。
しかし、カーネルは、ユーザによるそのポインタがアドレス空間のユーザの部分にあるポインタであることを確認しなければならない。
カーネルは、プロセスがそれ自身のローカルでプライベートなメモリの外にアクセス出来ないことを確実にするために、ページテーブルハードウェアをセットアップした。
もし、ユーザプログラムがp->szより上のアドレスを読み書きしようとしたら、プロセッサはセグメンテーショントラップを引き起こし、そしてそのトラップはプロセスを殺す。
我々がいままで見てきたように。
今、カーネルは実行中であり、ユーザが渡したかもしれないどのアドレスも逆参照可能である、とはいえ、そのアドレスがp->sz未満であることをカーネルは確認しなければならない。

argptr関数は、argintの用途と似ている。
argptr関数は、n番目のシステムコールの引数を解釈する。
argptr関数は、整数として引数を取ってくるためにargint関数を使い、そしてそのユーザポインタとしての整数がアドレス空間のユーザの部分を指すかどうかをチェックする。
argptrを呼ぶと2回のチェックが走ることに注意。
最初に、ユーザのスタックポインタは引数を取り出すときにチェックされる。
そして引数、ユーザポインタそれ自身はチェックされる。

argstr関数は、システムコール引数トリオの最後のメンバーである。
それは、ポインタとしてn番目の引数を解釈する。
それは、ポインタがヌル終端文字列を指すことと、その文字列全体がアドレス空間のユーザの部分の終わりより書いに配置されていることを確認する。

システムコールの実装(例えばsysproc.cやsysfile.c)は、典型的なラッパ関数である。
それらは、argint, argptr, argstrを使って引数をデコードしたあと、実際の実装を呼び出す。
第1章で、sys_execはそれらの関数を使って引数を解釈した。

sysproc.cで実装されてる関数の最初の3つ

int
sys_fork(void)
{
  return fork();
}

int
sys_exit(void)
{
  exit();
  return 0;  // not reached
}

int
sys_wait(void)
{
  return wait();
}

感想

たいして難しいところじゃないと思うんですが、今回は凄く本文が読みづらかったです。
見慣れない文法や間違ってるっぽい単語や回りくどい書き方がいくつかありました。
なので、いつもそうではあるのですが、今回は特に訳があやしいです。
書いた人が違うのかな。

コメントを残す

メールアドレスが公開されることはありません。



※画像をクリックして別の画像を表示