exec() と fork() の違い

July 03, 2017

システムコンソールからログインする際の流れ

例として、システムコンソールに「login:」と表示され、実際に bash が起動するまでの流れは以下のようになっている。

  1. login: プロンプトを表示し、ユーザー名の入力を受け付ける処理を mingetty プロセス が行う
  2. ユーザー名を受け付けた migetty プロセスは login プロセスexec() する
  3. login プロセスがパスワードによりユーザーを認証したのち、プロセスを fork() する
  4. fork () された子プロセスは bash プロセスexec() する

図に表すとこんな感じ。

f:id:shiro_kochi:2018××××××××:plain:w100:left

要するに、

exec(): PID は変えずにプロセスのみを変える
fork(): 異なる PID で同一のプロセスを作成する

実際に、mingetty.c を見てみる。

mingetty.c
while ((logname = get_logname ()) == 0);

execl (_PATH_LOGIN, _PATH_LOGIN, "--", logname, NULL);
error ("%s: can't exec " _PATH_LOGIN ": %s", tty, sys_errlist[errno]);
exit (0);

execl() で exec() システムコールを用いて、PATHLOGIN_ で指定されるプログラムを実行している。 次は、login.c を見てみよう。

login.c
static void fork_session(struct login_context *cxt) {

  //...中略...
  
  child_pid = fork();
  //親プロセスの child_pid には子プロセスの PID が入る
  //子プロセスの child_pid には 0 が入る
  if (child_pid < 0) { warn(_("fork failed")); pam_setcred(cxt->pamh, PAM_DELETE_CRED);
    pam_end(cxt->pamh, pam_close_session(cxt->pamh, 0));
    sleepexit(EXIT_FAILURE);
  }

  if (child_pid) {
    /*
     * parent - wait for child to finish, then clean up session
     */
    close(0);
    close(1);
    close(2);
    sa.sa_handler = SIG_IGN;
    sigaction(SIGQUIT, &sa, NULL);
    sigaction(SIGINT, &sa, NULL);

    /* wait as long as any child is there */
    //子プロセスが終了するのを待って、自分も終了する
    while (wait(NULL) == -1 && errno == EINTR) ;
    openlog("login", LOG_ODELAY, LOG_AUTHPRIV);

    pam_setcred(cxt->pamh, PAM_DELETE_CRED);
    pam_end(cxt->pamh, pam_close_session(cxt->pamh, 0));
    exit(EXIT_SUCCESS);
  }

  //子プロセスの実行
  /*
   * child
   */
  sigaction(SIGHUP, &oldsa_hup, NULL);		/* restore old state */
  sigaction(SIGTERM, &oldsa_term, NULL);
  if (got_sig)
    exit(EXIT_FAILURE);

  /*
   * Problem: if the user's shell is a shell like ash that doesn't do
   * setsid() or setpgrp(), then a ctrl-\, sending SIGQUIT to every
   * process in the pgrp, will kill us.
   */

  /* start new session */
  setsid();

  /* make sure we have a controlling tty */
  open_tty(cxt->tty_path);
  openlog("login", LOG_ODELAY, LOG_AUTHPRIV);	/* reopen */

  /*
   * TIOCSCTTY: steal tty from other process group.
   */
  if (ioctl(0, TIOCSCTTY, 1))
    syslog(LOG_ERR, _("TIOCSCTTY failed: %m"));
  signal(SIGINT, SIG_DFL);

}

//...中略...

int main(int argc, char **argv) {

  //...中略...
  
  fork_session(&cxt);

  //...中略...  

  //子プロセスは exec で bash に変化する
  execvp(childArgv[0], childArgv + 1);  

}

fork() でプロセスを複製し、子プロセスを exec() で bash に変化させている。


 © 2023, Dealing with Ambiguity