exec関数


exec関数ファミリー
多くの読者がこのシリーズの文章を発売してから読んでいるかもしれませんが、ここまで大きな疑問があります.すべての新しいプロセスがforkによって生成され、forkによって生成されたサブプロセスが親プロセスとほぼ同じである以上、システムのすべてのプロセスがそっくりであることを意味しているのではないでしょうか.また、私たちの常識では、プログラムを実行するとき、新しく生成されたプロセスの内容はプログラムの内容であるべきです.私たちの理解が間違っていますか?明らかにそうではありません.これらの疑問を解決するには、以下に説明するexecシステム呼び出しに言及する必要があります.
1.10.1概要
execシステム呼び出しといえば、実際にLinuxでは、1つのexec()の関数形式は存在しません.execは1組の関数を指し、合計6つあります.それぞれ:
#include <unistd.h> 
int execl(const char *path, const char *arg, ...); 
int execlp(const char *file, const char *arg, ...); 
int execle(const char *path, const char *arg, ..., char *const envp[]);
int execv(const char *path, char *const argv[]); 
int execvp(const char *file, char *const argv[]); 
int execve(const char *path, char *const argv[], char *const envp[]);

ここでexecveだけが本当の意味でのシステム呼び出しであり、その他はこれに基づいてパッケージされたライブラリ関数である.
exec関数ファミリーの役割は、指定したファイル名に基づいて実行可能ファイルを見つけ、呼び出しプロセスの内容の代わりに使用することです.すなわち、呼び出しプロセス内で実行可能ファイルを実行します.ここでの実行可能ファイルは、バイナリファイルであってもよいし、Linuxで実行可能なスクリプトファイルであってもよい.
一般的な状況とは異なり、exec関数ファミリーの関数は成功した後も返されない.呼び出しプロセスのエンティティ、コードセグメント、データセグメント、スタックなどが新しい内容に取って代わられ、プロセスIDなどの表面的な情報だけが残っているため、「36計」の「金蝉脱殻」に似ている.古い体のように見えるが、新しい魂が注入されている.呼び出しに失敗した場合にのみ、元のプログラムの呼び出しポイントから下に実行される-1が返されます.
Linuxの下でどのように新しいプログラムを実行しているのか、プロセスが自分がシステムと擁護に貢献できないと思っているたびに、彼は最後の余熱を発揮して、execを呼び出して、自分を新しい姿で再生させることができます.あるいは、あるプロセスが別のプログラムを実行したい場合、forkは新しいプロセスを出し、任意のexecを呼び出すことができ、アプリケーションを実行することによって新しいプロセスが生成されたように見えます.
実際には2つ目の状況がこのように一般的に適用されており、Linuxはそれを最適化しています.forkは呼び出しプロセスのすべての内容をそのまま新しく生成されたサブプロセスにコピーします.これらのコピーの動作には時間がかかりますが、forkが終わったらすぐにexecを呼び出します.これらの苦労してコピーしたものはすぐに消去され、これは非常にお得ではないように見えます.そこで、forkが終わったらすぐに親プロセスの内容をコピーするのではなく、本当に実用的な時にコピーする「書き込みコピー(copy-on-write)」技術を設計しました.そうすれば、次の文がexecであれば、無駄に勉強することはありません.効率が向上します.
1.10.2やや深く
上の6つの関数は複雑そうに見えますが、実際には作用も使い方も非常に似ていて、わずかな違いしかありません.それらを学ぶ前に、私たちが慣れているmain関数を理解してみましょう.
次のmain関数の形式は予想外かもしれません.
int main(int argc, char *argv[], char *envp[])

ほとんどの教科書で説明されているのとは異なるかもしれませんが、実際にはmain関数の本当の完全な形式です.
パラメータargcは、プログラムを実行するときのコマンドラインパラメータの個数を示し、配列argvはすべてのコマンドラインパラメータを格納し、配列envpはすべての環境変数を格納します.環境変数とは、ユーザーがログインしてからずっと存在する値のセットを指し、多くのアプリケーションはそれに頼ってシステムの詳細を決定する必要があります.私たちが最も一般的な環境変数はPATHで、/binなどのアプリケーションを検索する場所を指摘しています.HOMEも比較的一般的な環境変数であり,システム内の個人ディレクトリを指摘している.環境変数は一般に文字列「XXX=xxx」として存在し、xxxは変数名、xxxは変数の値を表す.
特筆すべきはargv配列とenvp配列が格納されているのは、いずれも配列の末尾を1つのNULL要素で表す文字列を指すポインタである.
argc、argv、envpに伝わるものを以下のプログラムで見ることができます.
int main(int argc, char *argv[], char *envp[]) 
{ 
printf("
### ARGC ###
%d
", argc);  printf("
### ARGV ###
");  while(*argv)  printf("%s
", *(argv++));  printf("
### ENVP ###
");  while(*envp)  printf("%s
", *(envp++));  return 0;  }

コンパイル:
$ cc main.c -o main

実行時に、何の役にも立たないコマンドラインパラメータをいくつか追加しました.
$ ./main
 -xx 000 ### ARGC ### 3 ### ARGV ### 
./main -xx 000 ### ENVP ### 
PWD=/home/lei 
REMOTEHOST=dt.laser.com 
HOSTNAME=localhost.localdomain 
QTDIR=/usr/lib/qt-2.3.1 
LESSOPEN=|/usr/bin/lesspipe.sh %s 
KDEDIR=/usr USER=lei
 LS_COLORS= MACHTYPE=i386-redhat-linux-gnu 
MAIL=/var/spool/mail/lei INPUTRC=/etc/inputrc 
LANG=en_US 
LOGNAME=lei 
SHLVL=1 SHELL=/bin/bash
 HOSTTYPE=i386 
OSTYPE=linux-gnu 
HISTSIZE=1000 
TERM=ansi
 HOME=/home/lei P
ATH=/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin:/home/lei/bin _=./main

プログラムは「./main」を1番目のコマンドラインパラメータとしているので、合計3つのコマンドラインパラメータがあります.これは皆さんが普段慣れている言い方とは少し違うかもしれませんが、間違えないように気をつけてください.
振り返ってexec関数ファミリーを見て、まずexecveに集中します.
int execve(const char *path, char *const argv[], char *const envp[]);

main関数の完全な形式を比較して、問題を見ましたか?はい、この2つの関数のargvとenvpは完全に1つ1つの対応関係です.execveの1番目のパラメータpathは被実行アプリケーションの完全なパスであり、2番目のパラメータargvは被実行アプリケーションに伝達されるコマンドラインパラメータであり、3番目のパラメータenvpは被実行アプリケーションに伝達される環境変数である.
この6つの関数を注意してみると、最初の3つの関数はすべてexeclで始まり、後の3つはすべてexecvで始まり、それらの違いは、execvで始まる関数は「char*argv[]」のような形式でコマンドラインパラメータを伝達し、execlで始まる関数は私たちがもっと慣れやすい方法でパラメータを1つ1つ列挙していることです.そして1つのNULLで終わります.ここでのNULLの役割はargv配列のNULLの役割と同じである.
すべての6つの関数のうち、execleとexecveだけがchar*envp[]を使用して環境変数を伝達し、他の4つの関数にはこのパラメータがありません.これは、環境変数を伝達しないことを意味しません.この4つの関数は、デフォルトの環境変数を変更せずに実行されるアプリケーションに伝達します.execleとexecveは、デフォルトの代わりに指定した環境変数を使用します.
pで終わる2つの関数execlpとexecvpがあります.どのように見えますか.それらとexeclとexecvの違いは小さく、事実もそうです.execlpとexecvp以外の4つの関数は、1番目のパラメータpathが「/bin/ls」などの完全なパスである必要があります.一方、execlpとexecvpの1番目のパラメータfileは、「ls」のようなファイル名だけに簡単にできます.この2つの関数は、環境変数PATHが作成したディレクトリに自動的に検索できます.
1.10.3実戦
知識の紹介はあまり悪くありません.次に、実際の応用を見てみましょう.
 #include <unistd.h> 
main()
 {
char *envp[]={"PATH=/tmp", "USER=lei", "STATUS=testing", NULL}; 
char *argv_execv[]={"echo", "excuted by execv", NULL}; 
char *argv_execvp[]={"echo", "executed by execvp", NULL}; 
char *argv_execve[]={"env", NULL}; 
if(fork()==0) 
if(execl("/bin/echo", "echo", "executed by execl", NULL)<0) 
perror("Err on execl"); 
if(fork()==0)
 if(execlp("echo", "echo", "executed by execlp", NULL)<0) 
perror("Err on execlp");
 if(fork()==0) 
if(execle("/usr/bin/env", "env", NULL, envp)<0) 
perror("Err on execle");
 if(fork()==0) 
if(execv("/bin/echo", argv_execv)<0) 
perror("Err on execv"); 
if(fork()==0) 
if(execvp("echo", argv_execvp)<0) 
perror("Err on execvp");
 if(fork()==0)
 if(execve("/usr/bin/env", argv_execve, envp)<0) 
perror("Err on execve");
}

プログラムではLinuxでよく使われるシステムコマンド,echo,envを2つ呼び出した.echoは、後述するコマンドラインパラメータをそのまま印刷し、envはすべての環境変数をリストするために使用します.
各サブプロセスの実行順序が制御できないため、プログラムにリストされた順序に厳密に従うのではなく、各サブプロセスの印刷結果が混ざり合うという比較的混乱した出力が発生する可能性があります.
コンパイルおよび実行:
$ cc exec.c -o exec 
$ ./exec 
executed by execl
 PATH=/tmp 
USER=lei 
STATUS=testing executed by execlp excuted by execv executed by execvp 
PATH=/tmp
 USER=lei 
STATUS=testing

案の定、execle出力の結果はexeclpの前に走った.
皆さんは普段のプログラミングでexec関数ファミリーを使っている場合は、間違い判断文をつけることを忘れないでください.他のシステム呼び出しに比べてexecは傷つきやすいため、実行されるファイルの場所、権限など多くの要因で呼び出しの失敗を招くことができます.最も一般的なエラーは次のとおりです.
ファイルまたはパスが見つかりません.errnoがENOENTに設定されます.
配列argvとenvpはNULLで終わるのを忘れ、errnoはEFAULTに設定される.
実行するファイルに対する実行権限がありません.errnoはEACCESに設定されます.