コマンドの実行 exec

提供: C言語入門
移動: 案内検索
スポンサーリンク

Unixでプログラムを実行するには、exec系システムコールを利用します。exec系システムコールを実行するとプログラムが置き換わります。そのため、元のプログラムに戻ることはありません。

読み方

exec
いくぜっく

概要

exec系システムコール execl, execlp, execle, exect, execv, execvp, execvP は、ファイルを実行します。 関数によって渡せるパラメータが違います。

  • サーチパスから自動的にコマンドを探し、実行する関数
  • 環境変数まで指定できる関数

システム関数

system関数は、プログラムを実行できる関数です。 この実装は、以下のような流れになっています。

  1. fork
    1. 子プロセス
    2. signal 設定
    3. execl (_PATH_BSHELL "sh", "-c", command, NULL)
  2. wait4

FreeBSD の libc の system.c の実装の抜粋です。

int
__system(const char *command)
{
        pid_t pid, savedpid;
        int pstat;
        struct sigaction ign, intact, quitact;
        sigset_t newsigblock, oldsigblock;
 
        if (!command)           /* just checking... */
                return(1);
 
        /*
         * Ignore SIGINT and SIGQUIT, block SIGCHLD. Remember to save
         * existing signal dispositions.
         */
        ign.sa_handler = SIG_IGN;
        (void)sigemptyset(&ign.sa_mask);
        ign.sa_flags = 0;
        (void)_sigaction(SIGINT, &ign, &intact);
        (void)_sigaction(SIGQUIT, &ign, &quitact);
        (void)sigemptyset(&newsigblock);
        (void)sigaddset(&newsigblock, SIGCHLD);
        (void)_sigprocmask(SIG_BLOCK, &newsigblock, &oldsigblock);
        switch(pid = fork()) {
        case -1:                        /* error */
                break;
        case 0:                         /* child */
                /*
                 * Restore original signal dispositions and exec the command.
                 */
                (void)_sigaction(SIGINT, &intact, NULL);
                (void)_sigaction(SIGQUIT,  &quitact, NULL);
                (void)_sigprocmask(SIG_SETMASK, &oldsigblock, NULL);
                execl(_PATH_BSHELL, "sh", "-c", command, (char *)NULL);
                _exit(127);
        default:                        /* parent */
                savedpid = pid;
                do {
                        pid = _wait4(savedpid, &pstat, 0, (struct rusage *)0);
                } while (pid == -1 && errno == EINTR);
                break;
        }
        (void)_sigaction(SIGINT, &intact, NULL);
        (void)_sigaction(SIGQUIT,  &quitact, NULL);
        (void)_sigprocmask(SIG_SETMASK, &oldsigblock, NULL);
        return(pid == -1 ? -1 : pstat);
}

ヘッダファイル

     #include <unistd.h>
 
     extern char **environ;
 
     int
     execl(const char *path, const char *arg, ..., /*, (char *)0, */);
 
     int
     execlp(const char *file, const char *arg, ..., /*, (char *)0, */);
 
     int
     execle(const char *path, const char *arg, ...,
             /*, (char *)0, char *const envp[], */);
 
     int
     exect(const char *path, char *const argv[], char *const envp[]);
 
     int
     execv(const char *path, char *const argv[]);
 
     int
     execvp(const char *file, char *const argv[]);
 
     int
     execvP(const char *file, const char *search_path, char *const argv[]);

execvの簡単な例

execv1.c

#include <stdlib.h>
#include <unistd.h>
 
int
main(int argc, char *argv[])
{
        char *path = "/usr/bin/whoami";
        char *_argv[] = {
                "whoami",
                NULL
        };
        execv(path, _argv);
        exit(EXIT_SUCCESS);
}

コンパイル

$ cc execv1.c

実行例

ここでは、あなたのユーザ名が表示されます。

$ ./a.out
kaoru

exeveで引数を渡す簡単な例

hello.c

#include <stdlib.h>
#include <unistd.h>
#include <err.h>
 
int
main(int argc, char *argv[])
{
        char *path = "/bin/echo";
        char *_argv[] = {
                "echo",
                "hello",
                "world",
                NULL
        };
        int r = execv(path, _argv);
        if (-1 == r) {
                err(EXIT_FAILURE, "can not execv(%s)", path);
        }
        // NOTREACHED
        exit(EXIT_SUCCESS);
}

コンパイル

$ cc execv1.c

実行例

ここでは、あなたのユーザ名が表示されます。

$ ./a.out
kaoru

forkしてechoする例

fork_echo.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <err.h>
 
#include <sys/types.h>
#include <sys/wait.h>
 
 
int
main(int argc, char *argv[])
{
        pid_t pid = fork();
        if (-1 == pid) {
                err(EXIT_FAILURE, "can not fork");
        } else if (0 == pid ) {
                char *path = "/bin/echo";
                char *_argv[] = {
                        "echo",
                        "hello",
                        "world",
                        NULL
                };
                execv(path, _argv);
                _Exit(EXIT_FAILURE);
                // NOTREACHED
        }
        int status;
        pid_t wpid = waitpid(pid, &status, WCONTINUED);
        if (-1 == wpid) {
                err(EXIT_FAILURE, "waitpid error");
        }
        (void) printf ("status=%d\n", WEXITSTATUS(status));
        exit(EXIT_SUCCESS);
}

コンパイル

$ cc fork_echo.c

実行例

$ ./a.out
hello world
status=0

exitと_Exit

  • _Exit と _exit は等価です。
  • exit は、 ANSI C で規定されています。
  • _exit は、POSIX.1 で規定されています。

exit()の動作は、以下の通りです。

  • exit()は、標準入出力ライブラリの後始末を常に行います。
  • 開いているすべてのストリームに対してfclose()を呼びます。
  • バッファされたデータは、フラッシュされます(ファイルへ書き出されます)。

_Exit の特徴は、以下の通りです。

  • _Exit は、atexit で登録された関数、 signalで登録されたシグナルハンドラが呼び出されません。
  • 出力がフラッシュされません。

実装に依存する処理は以下のものがあります。

  • 標準I/Oのバッファのフラッシュ
  • tmpfileで作成されたテンポラリファイルの削除

fork()したときにバッファのデータも親プロセスから子プロセスにコピーされます。これだけが理由ではありませんが、同じデータがflushされることを回避するために、子プロセスでは、_exit()で終了するのがお作法です。

#include <stdlib.h>
 
void
exit(int status);
 
void
_Exit(int status);
 
#include <unistd.h>
 
void
_exit(int status);

FreeBSDの環境では、 hoge は、表示されずにプログラムが終了されます。

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
 
int
main(int argc, char *argv[])
{
        printf("hoge");
        _exit(EXIT_SUCCESS);
}

関連項目




スポンサーリンク