プロセス1実行編⑧(システムコール) int sys_exec(void) (Xv6を読む~OSコードリーディング~)

前回
jupiteroak.hatenablog.com
トップページ
jupiteroak.hatenablog.com




sysfile.c
https://github.com/mit-pdos/xv6-public/blob/master/sysfile.c#L396

int
sys_exec(void)
{
  char *path, *argv[MAXARG];
  int i;
  uint uargv, uarg;

  if(argstr(0, &path) < 0 || argint(1, (int*)&uargv) < 0){
    return -1;
  }
  memset(argv, 0, sizeof(argv));
  for(i=0;; i++){
    if(i >= NELEM(argv))
      return -1;
    if(fetchint(uargv+4*i, (int*)&uarg) < 0)
      return -1;
    if(uarg == 0){
      argv[i] = 0;
      break;
    }
    if(fetchstr(uarg, &argv[i]) < 0)
      return -1;
  }
  return exec(path, argv);
}

sys_exec関数は、ユーザスタックにあるexecシステムコールの引数を、exec関数に渡す処理を行います(exec関数で使用するコマンドライン引数配列が不正なものでないことを確認します)。


処理の内容

execシステムコールの第1引数と第2引数を取得する

if(argstr(0, &path) < 0 || argint(1, (int*)&uargv) < 0){
    return -1;
  }


argstr(0, &path)
argstr関数を呼び出して、execシステムコールの第一引数(NULL終端文字列のファイルパス"/init\0"の先頭アドレス)をユーザスタックから取得します。
取得したexecシステムコールの第一引数は、変数path(&pathが指定するメモリ領域)に格納されます。
initcode.s内でexecシステムコール(int $T_SYSCALL)を呼び出す前に、execシステムコールの第一引数としてinitsiラベルのアドレス値(.string "/init\0"が格納されているメモリ領域の先頭アドレス)をユーザスタックに退避させています。
よって、argstr関数を呼び出すと、execシステムコールの第一引数であるinitラベルのアドレス値(.string "/init\0"が格納されているメモリ領域の先頭アドレス)が、変数path(&pathが指定するメモリ領域)に格納されます。
argstr関数の戻り値が-1となる場合→execシステムコールの第一引数をユーザスタックから取得できなかった場合は、システムコールの結果が失敗したことを示す-1を戻り値にして処理を終了します。


argint(1, (int*)&uargv)
argint関数を呼び出して、execシステムコール関数の第二引数(コマンドライン引数配列の先頭アドレス)をユーザスタックから取得します。
取得したexecシステムコールの第二引数は、変数uargv(&uargvが指定するメモリ領域)に格納されます。
initcode.s内でexecシステムコール(int $T_SYSCALL)を呼び出す前に、execシステムコールの第二引数としてargvラベルのアドレス値(.long init と .long 0 が格納されているメモリ領域の先頭アドレス)をユーザスタックに退避させています。
よって、argint関数を呼び出すと、execシステムコールの第二引数であるargvラベルのアドレス値(.long init と .long 0 が格納されているメモリ領域の先頭アドレス)が、変数uargv(&uargvが指定するメモリ領域)に格納されます。
argint関数の戻り値が-1となる場合→execシステムコールの第二引数をユーザスタックから取得できなかった場合は、システムコールの結果が失敗したことを示す-1を戻り値にして処理を終了します。

新規に用意したコマンドライン引数配列argvを0で初期化しておく

memset(argv, 0, sizeof(argv));

memset関数を呼び出して、新規に用意したコマンドライン引数配列argv(先頭アドレスargv・サイズがsizeof(argv)となるメモリ領域)を0で初期化します。このコマンドライン引数配列argv(の先頭アドレス)をexec関数に渡します。

ユーザ空間にあるコマンドライン引数配列から新規に用意したコマンドライン引数配列argvへ全ての要素をコピーする

  for(i=0;; i++){
    .....
  }

uargv(execシステムコールの第二引数として取得したargvラベルのアドレス値)を使って、ユーザ空間にあるコマンドライン引数配列 から 新規に用意したコマンドライン引数配列argv へ 全ての要素をコピーします。

argv配列の要素を指定するインデックスがarg配列の範囲を越える場合

 if(i >= NELEM(argv))
      return -1;

i >= NELEM(argv)が真となる場合→argv配列の要素を指定するインデックスiがarg配列の範囲を越える場合→ユーザー空間にあるコマンドライン引数配列が大きすぎる場合は、システムコールの結果が失敗したことを示す-1を戻り値にして処理を終了します。

ユーザ空間にあるコマンドライン引数配列の要素を取得する

if(fetchint(uargv+4*i, (int*)&uarg) < 0)
      return -1;

fetchint関数を呼び出して、ユーザ空間にあるコマンドライン引数配列のi番目の要素を取得します。
取得したコマンドライン引数配列のi番目の要素は、変数uarg(&uargが指定するメモリ領域)に格納されます。
uargvは、initcode.Sにあるargvラベルのアドレス値(ユーザ空間にあるコマンドライン引数配列の先頭アドレス→ユーザー空間にある .long init と .long 0 が格納されているメモリ領域の先頭アドレス)なので、アドレスuargv+4*iは、ユーザ空間にあるコマンドライン引数配列のi番目の要素が格納されているメモリ領域(4バイトサイズ)を指定します。
fetchint関数の戻り値が-1となる場合→ユーザ空間にあるコマンドライン引数配列のi番目の要素を取得できなかった場合は、システムコールの結果が失敗したことを示す-1を戻り値にして処理を終了します。

取得したコマンドライン引数配列の要素が最後の要素だった場合

if(uarg == 0){
      argv[i] = 0;
      break;
    }

uarg == 0 が真となる場合→取得したユーザ空間にあるコマンドライン引数配列の要素の値が0の場合→取得したユーザ空間にあるコマンドライン引数配列の要素が最後の要素だった場合は、argv配列にも0を格納して、for文から脱出します。

取得したコマンドライン引数配列の要素をargv配列に格納する

if(fetchstr(uarg, &argv[i]) < 0)
      return -1;

fetchstr関数を呼び出して、取得したコマンドライン引数配列のi番目の要素uargを、argv[i](アドレス&argv[i]が指定する変数)に格納します。
fetchstr関数の戻り値が-1となる場合→取得したコマンドライン引数配列の要素をargv配列に格納できない場合は、システムコールの結果が失敗したことを示す-1を戻り値にして処理を終了します。

引数pathと引数argvをexec関数に渡す

return exec(path, argv);

execシステムコールの第一引数path(NULL終端文字列のファイルパス"/init\0"の先頭アドレス)と第二引数argv(新規に用意したコマンドライン引数配列の先頭アドレス)を使って、exec関数を呼び出します。




次回
jupiteroak.hatenablog.com