プロセス1実行編⑨(システムコール) int exec(char *path, char **argv) (Xv6を読む~OSコードリーディング~)

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




exec.c
https://github.com/mit-pdos/xv6-public/blob/master/exec.c

int
exec(char *path, char **argv)
{
  char *s, *last;
  int i, off;
  uint argc, sz, sp, ustack[3+MAXARG+1];
  struct elfhdr elf;
  struct inode *ip;
  struct proghdr ph;
  pde_t *pgdir, *oldpgdir;
  struct proc *curproc = myproc();

  begin_op();

  if((ip = namei(path)) == 0){
    end_op();
    cprintf("exec: fail\n");
    return -1;
  }
  ilock(ip);
  pgdir = 0;

  // Check ELF header
  if(readi(ip, (char*)&elf, 0, sizeof(elf)) != sizeof(elf))
    goto bad;
  if(elf.magic != ELF_MAGIC)
    goto bad;

  if((pgdir = setupkvm()) == 0)
    goto bad;

  // Load program into memory.
  sz = 0;
  for(i=0, off=elf.phoff; i<elf.phnum; i++, off+=sizeof(ph)){
    if(readi(ip, (char*)&ph, off, sizeof(ph)) != sizeof(ph))
      goto bad;
    if(ph.type != ELF_PROG_LOAD)
      continue;
    if(ph.memsz < ph.filesz)
      goto bad;
    if(ph.vaddr + ph.memsz < ph.vaddr)
      goto bad;
    if((sz = allocuvm(pgdir, sz, ph.vaddr + ph.memsz)) == 0)
      goto bad;
    if(ph.vaddr % PGSIZE != 0)
      goto bad;
    if(loaduvm(pgdir, (char*)ph.vaddr, ip, ph.off, ph.filesz) < 0)
      goto bad;
  }
  iunlockput(ip);
  end_op();
  ip = 0;

  // Allocate two pages at the next page boundary.
  // Make the first inaccessible.  Use the second as the user stack.
  sz = PGROUNDUP(sz);
  if((sz = allocuvm(pgdir, sz, sz + 2*PGSIZE)) == 0)
    goto bad;
  clearpteu(pgdir, (char*)(sz - 2*PGSIZE));
  sp = sz;

  // Push argument strings, prepare rest of stack in ustack.
  for(argc = 0; argv[argc]; argc++) {
    if(argc >= MAXARG)
      goto bad;
    sp = (sp - (strlen(argv[argc]) + 1)) & ~3;
    if(copyout(pgdir, sp, argv[argc], strlen(argv[argc]) + 1) < 0)
      goto bad;
    ustack[3+argc] = sp;
  }
  ustack[3+argc] = 0;

  ustack[0] = 0xffffffff;  // fake return PC
  ustack[1] = argc;
  ustack[2] = sp - (argc+1)*4;  // argv pointer

  sp -= (3+argc+1) * 4;
  if(copyout(pgdir, sp, ustack, (3+argc+1)*4) < 0)
    goto bad;

  // Save program name for debugging.
  for(last=s=path; *s; s++)
    if(*s == '/')
      last = s+1;
  safestrcpy(curproc->name, last, sizeof(curproc->name));

  // Commit to the user image.
  oldpgdir = curproc->pgdir;
  curproc->pgdir = pgdir;
  curproc->sz = sz;
  curproc->tf->eip = elf.entry;  // main
  curproc->tf->esp = sp;
  switchuvm(curproc);
  freevm(oldpgdir);
  return 0;

 bad:
  if(pgdir)
    freevm(pgdir);
  if(ip){
    iunlockput(ip);
    end_op();
  }
  return -1;
}

exec関数は、execシステムコールを呼び出したプロセスが引数pathで指定されたELFファイルを実行するために必要となる処理を行います。
具体的には、ページディレクトリの作成、ELFファイルのロード、ユーザスタックの作成、ページディレクトリの切り替えなどを行います。


処理の内容

execシステムコールを呼び出したプロセスに対応しているプロセスディスクリプタを取得する

struct proc *curproc = myproc();

myproc関数を呼び出して、exitシステムコールを呼び出したプロセスに対応しているプロセスディスクリプタを取得します。

ロギングの開始処理を行う

begin_op();

begin_op関数を呼び出して、ロギングの開始処理を行います。

ファイルに対応したiノードを取得する

 if((ip = namei(path)) == 0){
    end_op();
    cprintf("exec: fail\n");
    return -1;
  }

namei関数を呼び出して、ファイルパスpathが指定するファイル資源に対応したiノードを取得します。
(ip = namei(path)) == 0 が真となる場合→namei関数の戻り値が0となる場合→ファイルパスpathが指定するファイル資源に対応したiノードを取得できなかった場合は、end_op関数を呼び出してロギングの終了処理を行い、cprintf関数を呼び出してメッセージを出力します。
最後にexecシステムコールの結果が失敗であることを示す-1を戻り値にして、処理を終了します。

ファイルに対応したiノードの内容をロードする

ilock(ip);

ilock関数を呼び出して、ハードディスクから、ファイルパスpathが指定するファイル資源に対応したiノードの内容をロードしておきます。

ファイルからsizeof(elf)バイトのデータを読み込む

if(readi(ip, (char*)&elf, 0, sizeof(elf)) != sizeof(elf))
    goto bad;

readi関数を呼び出して、ファイルパスpathが指定するファイル資源(に対応したiノード)から、ELFファイルのヘッダーを読み込み、elfhdr構造体の変数elfに読み込んだデータを保存します。
elfhdr構造体は、ELFファイルのヘッダーのデータ構造を表現します。
readi関数の第一引数には、読み込み対象となるiノード(inode構造体)のアドレスipを指定します。
第二引数には、読み込んだデータが保存されるメモリ領域の先頭アドレス&elfを指定します。
第三引数には、iノードにおいて読み込みを開始する位置0(ファイルの先頭)を指定します。
第四引数には、読み込まれるデータサイズsizeof(elf)(ELFファイルのヘッダーサイズ)をバイト単位で指定します。
戻り値は、readi関数が読み込んだデータサイズ(バイト単位)です。
readi(ip, (char*)&elf, 0, sizeof(elf)) != sizeof(elf) が真となる場合→readi関数によって読み込まれたデータサイズが、ELFファイルのヘッダーサイズと異なる場合は、badラベルにジャンプします。

読み込んだデータがELFファイルのヘッダーであることを確認する

if(elf.magic != ELF_MAGIC)
    goto bad;

読み込んだデータがELFファイルのヘッダーであることを確認します。
elf.magic != ELF_MAGIC が真となる場合→ELFファイルのヘッダー先頭4バイト(elf->magic)がELF形式固有のマジック・ナンバELF_MAGICではない場合は、badラベルにジャンプします。

ページディレクトリを作成する

if((pgdir = setupkvm()) == 0)
    goto bad;

setupkvm関数を呼び出して、カーネル空間のマッピングのみが設定されたページディレクトリを作成します。
(pgdir = setupkvm()) == 0 が真となる場合→setupkvm関数の戻り値が0になる場合→ページディレクトリの作成に失敗した場合は、badラベルにジャンプします。

ファイル内にあるセグメントをロードする

for(i=0, off=elf.phoff; i<elf.phnum; i++, off+=sizeof(ph)){
    if(readi(ip, (char*)&ph, off, sizeof(ph)) != sizeof(ph))
      goto bad;
    if(ph.type != ELF_PROG_LOAD)
      continue;
    if(ph.memsz < ph.filesz)
      goto bad;
    if(ph.vaddr + ph.memsz < ph.vaddr)
      goto bad;
    if((sz = allocuvm(pgdir, sz, ph.vaddr + ph.memsz)) == 0)
      goto bad;
    if(ph.vaddr % PGSIZE != 0)
      goto bad;
    if(loaduvm(pgdir, (char*)ph.vaddr, ip, ph.off, ph.filesz) < 0)
      goto bad;
  }

プログラムヘッダ・テーブルにあるエントリの情報に基づいて、ELFファイル内にあるセグメントをロードします。
for文のループ1回につき、プログラムヘッダ・テーブルのエントリ1つを取得して、1つのセグメントをロードしています。


初期設定式 i=0, off=elf.phoff について
elf.phoff(ELFファイルのphoffフィールド)には、プログラムヘッダ・テーブル(プログラムヘッダ・テーブルのエントリ0)の位置を、ELFファイル先頭からのバイトオフセットとして示した値が格納されています。
iは、プログラムヘッダ・テーブルのエントリを指定するインデックスです。


継続条件式 i < elf.phnum について
elf.phnum(ELFファイルのphnumフィールド)には、プログラムヘッダ・テーブルのエントリ数を示す値が格納されています。
プログラムヘッダ・テーブルのエントリを指定するインデックスiが、プログラムヘッダ・テーブルを越えない範囲で、for文の処理を行います。


再設定式 i++, off+=sizeof(ph) について
ph(proghdr構造体)は、プログラムヘッダ(プログラムヘッダ・テーブルのエントリ)を表現しています。
各ループが終わった後に、インデックスiをインクリメントし、offをプログラムヘッダのサイズ分(sizeof(ph))インクリメントすることで、次のプログラムヘッダ(プログラムヘッダ・テーブルのエントリ)の位置を指定することができます。

ELFファイルからプログラムヘッダを取得する

    if(readi(ip, (char*)&ph, off, sizeof(ph)) != sizeof(ph))
      goto bad;

readi関数を呼び出して、iノードip(ELFファイル)内においてoffの位置にあるプログラムヘッダを読み取り、読み取ったプログラムヘッダを構造体変数phに保存します。
readi関数の第一引数には、読み込み対象となるiノード(ELFファイル)のアドレスipを指定します。
第二引数には、読み込んだデータが保存されるメモリ領域の先頭アドレス&phを指定します。
第三引数には、ELFファイル(iノード)において読み込みを開始する位置off(プログラムヘッダがある位置)を指定します。
第四引数には、読み込まれるデータサイズsizeof(ph)(プログラムヘッダのサイズ)をバイト単位で指定します。
戻り値は、readi関数が読み込んだデータサイズ(バイト単位)です。
readi(ip, (char*)&ph, off, sizeof(ph)) != sizeof(ph) が真となる場合→readi関数によって読み込まれたデータサイズが、プログラムヘッダのサイズと異なる場合は、badラベルへジャンプします。

プログラムヘッダのtypeフィールドの値を確認する

    if(ph.type != ELF_PROG_LOAD)
      continue;

ph.type != ELF_PROG_LOAD が真となる場合→プログラムヘッダのtypeフィールドの値が、ELF_PROG_LOADではない場合→プログラムヘッダのtypeフィールドの値が、メモリ上にロードされるセグメントではないことを示している場合は、次のループに進みます。

プログラムヘッダのmemszフィールドの値とfileszフィールドの値を確認する

    if(ph.memsz < ph.filesz)
      goto bad;

ph.memsz < ph.fileszが真となる場合→メモリ上に展開される時のセグメントのサイズが、ELFファイル内にある時のセグメントのサイズよりも、小さい場合は、badラベルへジャンプします。

ラップアラウンドが生じていないか確認する

    if(ph.vaddr + ph.memsz < ph.vaddr)
      goto bad;

ph.vaddr + ph.memsz < ph.vaddr が真となる場合→セグメントのロード先の仮想アドレスの値とメモリ上に展開される時のセグメントのサイズの値を足して、ラップアラウンドが生じる場合は、badラベルへジャンプします。

セグメントのロード先となるページフレームを確保する

    if((sz = allocuvm(pgdir, sz, ph.vaddr + ph.memsz)) == 0)
      goto bad;

allocuvm関数を呼び出して、szからph.vaddr + ph.memsz未満の仮想アドレスの範囲に対し、ページフレームを必要な数だけマッピングさせます。
(sz = allocuvm(pgdir, sz, ph.vaddr + ph.memsz)) == 0 が真となる場合→allocuvm関数の戻り値が0となる場合→仮想アドレスのマッピングに失敗した場合は、badラベルへジャンプします。
(sz = allocuvm(pgdir, sz, ph.vaddr + ph.memsz)) == 0 が偽となる場合→allocuvm関数の戻り値が0ではない場合→仮想アドレスのマッピングに成功した場合は、ph.vaddr + ph.memszが戻り値となります。この戻り値で変数szの値を更新しておきます。

セグメントのロード先の仮想アドレスが4KB境界であることを確認する

    if(ph.vaddr % PGSIZE != 0)
      goto bad;

ph.vaddr % PGSIZE != 0 が真となる場合→セグメントのロード先の仮想アドレスが4KB境界(0x1000の倍数値)ではない場合は、badラベルへジャンプします。

ELFファイル内にあるセグメントをロードする

    if(loaduvm(pgdir, (char*)ph.vaddr, ip, ph.off, ph.filesz) < 0)
      goto bad;

loaduvm関数を呼び出して、ページディレクトリpgdirが指定する仮想アドレスのマッピング設定に基づいて、iノードip(ELFファイル)内にあるセグメントをロードします。
loaduvm関数の第一引数には、仮想アドレスaddrのマッピング設定に関わっているページディレクトリの先頭アドレスpgdirを指定します。
第二引数には、セグメントのロード先となるメモリ領域の先頭アドレスph.vaddrを指定します。
第三引数には、ロード元となるiノード(ELFファイル)の先頭アドレスipを指定します。
第四引数には、iノード(ELFファイル)内におけるセグメントの位置をバイトオフセットph.offで指定します。
第五引数には、ロードされるセグメントのサイズph.fileszを指定します。
loaduvm(pgdir, (char*)ph.vaddr, ip, ph.off, ph.filesz) が真となる場合→(loaduvm関数の戻り値が-1となる場合→セグメントのロードに失敗した場合は、badラベルへジャンプします。

ファイルに対応したiノードのスリープロックを解放する

iunlockput(ip);

iunlockput関数を呼び出して、ファイルパスpathが指定するファイル資源(ELFファイル)に対応したiノードのスリープロックを解放し、参照回数をデクリメントします(iノードの参照回数が0になった場合は、iノードを未使用状態に戻します)。

ロギングの終了処理を行う

end_op();

end_op関数を呼び出して、ロギングの終了処理を行います。

ユーザスタックのメモリ領域を確保する

sz = PGROUNDUP(sz);
  if((sz = allocuvm(pgdir, sz, sz + 2*PGSIZE)) == 0)
    goto bad;
  clearpteu(pgdir, (char*)(sz - 2*PGSIZE));

アドレスszを4KB境界にする

sz = PGROUNDUP(sz);

PGROUNDUPマクロを使って、前の処理でセグメントがロードされているアドレス範囲よりも後方(上位アドレス側)にあるアドレスszを4KB境界(0x1000の倍数値)にします。

ページフレーム2つを確保する

if((sz = allocuvm(pgdir, sz, sz + 2*PGSIZE)) == 0)
    goto bad;

allocuvm関数を呼び出して、アドレスszからページ2つ分の仮想アドレスの範囲に対して、ページフレーム2つをマッピングさせます。
(allocuvm関数は、ページディレクトリpgdirを使って、szからsz + 2*PGSIZE未満の仮想アドレスの範囲に対し、ページフレームを必要な数だけマッピングさせます。)

1つ目のページ(4KB)をユーザープロセスがアクセスできないようにする

  clearpteu(pgdir, (char*)(sz - 2*PGSIZE));

clearpteu関数を呼び出して、仮想アドレスの範囲にある最初のページ(4KB)を、ユーザープロセスがアクセスできないようにします。
clearpteu関数は、仮想アドレスsz - 2*PGSIZEに対応しているPTE(ページテーブルエントリ)を再設定し、ページテーブルエントリのU/Sフラグ(bit2)を0にすることで、ユーザープロセスがページフレームにアクセスできないようにします(スタックオーバーフロー対策と思われます)。

スタックポインタにユーザスタックのボトムアドレスを保存する

  sp = sz;

ユーザスタックのボトムアドレスとして扱います。

ロードされたファイル(プログラム)を実行するために必要となるユーザスタックを設定する

for(argc = 0; argv[argc]; argc++) {
    if(argc >= MAXARG)
      goto bad;
    sp = (sp - (strlen(argv[argc]) + 1)) & ~3;
    if(copyout(pgdir, sp, argv[argc], strlen(argv[argc]) + 1) < 0)
      goto bad;
    ustack[3+argc] = sp;
}
  ustack[3+argc] = 0;

  ustack[0] = 0xffffffff;  // fake return PC
  ustack[1] = argc;
  ustack[2] = sp - (argc+1)*4;  // argv pointer

  sp -= (3+argc+1) * 4;
  if(copyout(pgdir, sp, ustack, (3+argc+1)*4) < 0)
    goto bad;

execシステムコールによってロードされたファイル(プログラム)を実行するために必要となるユーザスタックを設定します。
ユーザースタックの前半には、リターンアドレス、コマンドライン引数の個数を示す値、コマンドライン引数配列の先頭アドレス、コマンドライン引数配列の0番目の要素(アドレス値)、コマンドライン引数配列の1番目の要素(アドレス値)、、、コマンドライン引数配列の最後の要素(0)を退避させます。
ユーザスタックの後半には、コマンドライン引数配列の最後-1の要素が指定する文字列文字列データ、、、コマンドライン引数配列の1番目の要素が指定する文字列データ、コマンドライン引数配列の0番目の要素が指定する文字列データ を退避させます。

作成するユーザスタックの構造

アドレス   配置されているデータ
下位アドレス側        
0xffff ffff(リターンアドレス)
argc(コマンドライン引数の個数を示す値)
コマンドライン引数配列の先頭アドレス値
コマンドライン引数配列の先頭アドレス アドレス値zzz(コマンドライン引数配列の0番目の要素)
       アドレス値yyy(コマンドライン引数配列の1番目の要素)
... ...
アドレス値bbb(コマンドライン引数配列の最後-1の要素)
        0(コマンドライン引数配列の最後の要素)
アドレスbbb        コマンドライン引数配列の最後-1の要素が指定する文字列データ
... ...
アドレスyyy        コマンドライン引数配列の1番目の要素が指定する文字列データ
アドレスzzz       コマンドライン引数配列の0番目の要素が指定する文字列データ
上位アドレス側

ユーザースタックの後半を設定する

for(argc = 0; argv[argc]; argc++) {
    if(argc >= MAXARG)
      goto bad;
    sp = (sp - (strlen(argv[argc]) + 1)) & ~3;
    if(copyout(pgdir, sp, argv[argc], strlen(argv[argc]) + 1) < 0)
      goto bad;
    ustack[3+argc] = sp;
  }

ユーザースタックの後半に、コマンドライン引数配列の要素が指定する文字列データを退避させていきます。


配列argvについて
配列argvは、「sys_exec関数で用意されたコマンドライン引数配列」です。
「sys_exec関数で用意されたコマンドライン引数配列argv」の各要素には、文字列データを指定するアドレスがそれぞれ格納されています。
この「sys_exec関数で用意されたコマンドライン引数配列argv」の各要素(アドレス値)を使って文字列データを取得し、取得した文字列データをこれから作成するユーザスタックに退避させます。


初期設定式 argc
argcは、「sys_exec関数で用意されたコマンドライン引数配列argv」と「ユーザスタックに退避させるコマンドライン引数配列」の両方におけるインデックスとして機能します。


継続条件式 argv[argc]
配列の要素argv[argc]が0と評価された場合 →「sys_exec関数で用意されたコマンドライン引数配列argv」の最後の要素に達した場合は、for文の処理を終了します。

インデックスargcが想定しているコマンドライン引数の数を越える場合
if(argc >= MAXARG)
      goto bad;

argc >= MAXARG が真となる場合→コマンドライン引数の数がMAXARGを越える場合は、badラベルへジャンプします。

スタックポインタを文字列データのサイズ分だけ下位アドレス方向に進める
sp = (sp - (strlen(argv[argc]) + 1)) & ~3;

文字列データをユーザスタックに退避させた時のspの値を求めます(argv[argc]が指定する文字列データをユーザスタックに退避させた時のスタックポインタの値を求めます)。そのために、spの値(スタックポインタの値)を文字列データのサイズ分だけ下位アドレス方向に進めます。
strlen関数は、argv[argc]が指定する文字列データのサイズを取得しますが、そのデータサイズにヌル終端文字の分は含まれていません。そのため、文字列データのサイズはstrlen(argv[argc]) + 1となります。
最後に、~3(0b 1111 1111 1111 1111 1111 1111 1111 1000)でマスク処理することにより、スタックポインタが1バイト境界(1バイトの倍数値)であることを確実にします。

ユーザスタックに文字列データを退避させる
if(copyout(pgdir, sp, argv[argc], strlen(argv[argc]) + 1) < 0)
      goto bad;

copyout関数を呼び出して、ページディレクトリpgdirに設定されたアドレスマッピングに基づき、ユーザスタックに(スタックポインタsp以降のメモリ領域に)、argv[argc]が指定する文字列データ(ヌル終端文字を含む)を書き込みます。
この処理により、文字列データ(ヌル終端文字を含む)がユーザスタックに退避されます。

スタックポインタの値をコマンドライン引数配列の要素として保存しておく(コマンドライン引数配列の用意)
ustack[3+argc] = sp;

スタックポインタの値(文字列データの先頭アドレス)を配列ustackに保存します。
ustackは、ユーザスタックの前半部分を一時的に保存しておくための配列です。
後ほど、ustack[0]にはリターンアドレスの値が、ustack[1]にはコマンドライン引数の個数を示す値(コマンドライン引数配列の要素数-1)が、ustack[2]にはコマンドライン引数配列の先頭アドレスの値が格納されます。ustack[3]以降がコマンドライン引数配列となります。
現在のspの値は、ユーザスタックに退避させた文字列データ(ヌル終端文字を含む) の先頭アドレスの値になっているので、このspの値をユーザスタックに退避させるコマンドライン引数配列の要素として、ustack[3+argc]に格納します。

ユーザースタックの前半を設定する

  ustack[3+argc] = 0;

  ustack[0] = 0xffffffff;  // fake return PC
  ustack[1] = argc;
  ustack[2] = sp - (argc+1)*4;  // argv pointer

  sp -= (3+argc+1) * 4;
  if(copyout(pgdir, sp, ustack, (3+argc+1)*4) < 0)
    goto bad;

配列ustackを使って、ユーザースタックの前半に、リターンアドレス、コマンドライン引数の個数(コマンドライン引数配列の要素数-1)、コマンドライン引数配列の先頭アドレス、コマンドライン引数配列の0番目の要素、コマンドライン引数配列の1番目の要素、、、を退避させていきます。

コマンドライン引数配列の終了を示す値を用意する
ustack[3+argc] = 0;

この時のargcは「ユーザスタックに退避させるコマンドライン引数配列」の最後の要素を指定するインデックスの値(コマンドライン引数配列の要素数-1)になっています。
ustack[3+0]が「ユーザスタックに退避させるコマンドライン引数配列」の0番目の要素なので、ustack[3+argc] は「ユーザスタックに退避させるコマンドライン引数配列」の最後の要素になります。

コマンドライン引数配列の先頭アドレス、コマンドライン引数の個数、リターンアドレスを用意する

  ustack[0] = 0xffffffff;  // fake return PC
  ustack[1] = argc;
  ustack[2] = sp - (argc+1)*4;  // argv pointer

ustack[0]にリターンアドレスの値を、ustack[1]にコマンドライン引数の個数の値(「ユーザスタックに退避させるコマンドライン引数配列」の要素数-1)を、ustack[2]にはコマンドライン引数配列の先頭アドレスの値を格納します。
リターンアドレスは形式的に用意しているだけなので、0xffff ffffの値を指定しています。
argcは、「ユーザスタックに退避させるコマンドライン引数配列」の最後の要素を指定するインデックスの値 = コマンドライン引数配列の要素数-1 = コマンドライン引数の個数を示しています。
この時のspの値は、コマンドライン引数の最後の要素が指定する文字列データ(ヌル終端文字を含む)の先頭アドレスの値です。
そのため、現在のスタックポインタの値spを、「ユーザスタックに退避させるコマンドライン引数配列」の要素数(argc+1)×4バイト分だけ下位アドレス方向に進めた値が、コマンドライン引数配列の先頭アドレスの値となります。

スタックポインタの値をスタックのトップアドレスにする
sp -= (3+argc+1) * 4;

spの値を、コマンドライン引数配列・コマンドライン引数配列の先頭アドレス・コマンドライン引数の個数を示す値・リターンアドレスの値をユーザスタックに退避させた時の値にします。
コマンドライン引数配列の先頭アドレス・コマンドライン引数の個数を示す値・リターンアドレスの値を退避させているメモリ領域の大きさは3×4バイト、コマンドライン引数配列を退避させているメモリ領域の大きさは(argc+1)×4バイトなので、合計で3×4+(argc+1)×4 = (3+argc+1) ×4バイト分、下位アドレスの方向へスタックのトップアドレスを進めます。

ユーザスタックにコマンドライン引数配列、コマンドライン引数配列の先頭アドレス、コマンドライン引数の個数、リターンアドレスを退避させる
if(copyout(pgdir, sp, ustack, (3+argc+1)*4) < 0)
    goto bad;

copyout関数を呼び出して、ページディレクトリpgdirに設定されたアドレスマッピングに基づき、ユーザスタックに(スタックポインタsp以降のメモリ領域に)、ustackが指定するデータ(リターンアドレス、コマンドライン引数の個数、コマンドライン引数配列の先頭アドレス、コマンドライン引数配列)を書き込みます。
この処理により、がユーザスタックに退避されます。

デバッグ用としてプロセスに名前をつける

for(last=s=path; *s; s++)
    if(*s == '/')
      last = s+1;
  safestrcpy(curproc->name, last, sizeof(curproc->name));

execシステムコールを呼び出したプロセスに対応しているプロセスディスクリプタにファイル名を記録します。
safestrcpy関数は、lastを先頭アドレスとするメモリ領域から、curproc->nameを先頭アドレスとするメモリ領域へ、文字列データ(ヌル終端文字を含む)をコピーします。
pathが指定する文字列データ内に '/'がある場合は、文字列データ内に'/'を含まないようするために、文字列データの先頭アドレスを1バイト進めて、更新し、その値をlastに格納します。

プロセスディスクリプタの情報を更新する

  oldpgdir = curproc->pgdir;
  curproc->pgdir = pgdir;
  curproc->sz = sz;
  curproc->tf->eip = elf.entry;  // main
  curproc->tf->esp = sp;

ファイルをロードしたプロセスの状態に合わせて、プロセスディスクリプタの情報を更新します。


oldpgdir = curproc->pgdir;
今まで使用していたページディレクトリを指定するアドレスをoldpgdirに保存しておきます。


curproc->pgdir = pgdir;
プロセスがこれから使用するページディレクトリを記録します。


curproc->sz = sz;
プロセスのユーザー空間の大きさを記録します。


curproc->tf->eip = elf.entry;
プロセスのカーネルスタックに退避させてあるeipレジスタの値を、ロードしたELFファイルの実行開始アドレスで書き換えます。


curproc->tf->esp = sp;
プロセスのカーネルスタックに退避させてあるespレジスタの値を、この関数で作成したユーザスタックのトップアドレス値spで書き換えます

ページディレクトリを切り替える

switchuvm(curproc);
freevm(oldpgdir);

switchuvm関数を呼び出してユーザープロセスがcurprocに記録されたページディレクトリを使用するようにし、freevm関数を呼び出してユーザープロセスが今まで使用していたページディレクトリのメモリ領域を解放します。

execシステムコールが成功したことを意味する0を戻り値としてリターン

return 0;

最後に、execシステムコールが成功したことを意味する0を戻り値としてリターンします。


次回
jupiteroak.hatenablog.com