C言語でプロセスの生成と実行方法を教えて!
こういった悩みにお答えします.
本記事の信頼性
- リアルタイムシステムの研究歴12年.
- 東大教員の時に,英語でOS(Linuxカーネル)の授業.
- 2012年9月~2013年8月にアメリカのノースカロライナ大学チャペルヒル校(UNC)コンピュータサイエンス学部で客員研究員として勤務.C言語でリアルタイムLinuxの研究開発.
- プログラミング歴15年以上,習得している言語: C/C++,Python,Solidity/Vyper,Java,Ruby,Go,Rust,D,HTML/CSS/JS/PHP,MATLAB,Assembler (x64,ARM).
- 東大教員の時に,C++言語で開発した「LLVMコンパイラの拡張」,C言語で開発した独自のリアルタイムOS「Mcube Kernel」をGitHubにオープンソースとして公開.
- 2020年1月~現在はアメリカのノースカロライナ州チャペルヒルにあるGuarantee Happiness LLCのCTOとしてECサイト開発やWeb/SNSマーケティングの業務.2022年6月~現在はアメリカのノースカロライナ州チャペルヒルにあるJapanese Tar Heel, Inc.のCEO兼CTO.
- 最近は自然言語処理AIとイーサリアムに関する有益な情報発信に従事.
- (AI全般を含む)自然言語処理AIの論文の日本語訳や,AIチャットボット(ChatGPT,Auto-GPT,Gemini(旧Bard)など)の記事を50本以上執筆.アメリカのサンフランシスコ(広義のシリコンバレー)の会社でプロンプトエンジニア・マネージャー・Quality Assurance(QA)の業務委託の経験あり.
- (スマートコントラクトのプログラミングを含む)イーサリアムや仮想通貨全般の記事を200本以上執筆.イギリスのロンドンの会社で仮想通貨の英語の記事を日本語に翻訳する業務委託の経験あり.
こういった私から学べます.
C言語を独学で習得することは難しいです.
私にC言語の無料相談をしたいあなたは,公式LINE「ChishiroのC言語」の友だち追加をお願い致します.
私のキャパシティもあり,一定数に達したら終了しますので,今すぐ追加しましょう!
独学が難しいあなたは,元東大教員がおすすめするC言語を学べるオンラインプログラミングスクール5社で自分に合うスクールを見つけましょう.後悔はさせません!
目次
プロセス
プロセスとは,OSが管理するプログラムのインスタンスのことです.
プロセス間ではメモリ空間を共有しませんが,同じプロセス内のスレッド間ではメモリ空間を共有します.
スレッドと比較して,プロセスはメモリ保護できるメリットはありますが,プロセス通信のオーバーヘッドは大きくなります.
スレッドの生成と実行を知りたいあなたはこちらからどうぞ.
fork関数でプロセスの生成,wait関数で子プロセスの終了まで待機
1 |
pid_t fork(void); |
fork関数は,子プロセスを生成する関数です.
fork関数を呼び出すと,呼び出し元がプロセスを複製して新しいプロセスを生成します.
新しいプロセスは子プロセスと呼ばれ,呼び出し元のプロセスは親プロセスと呼ばれます.
fork関数の返り値は子プロセスの場合は0,親プロセスには子プロセスのプロセスID(PID)になります.
なので,fork関数の返り値で条件分岐すれば親プロセスと子プロセスで別々の処理ができます.
PIDは0以上の値になり,返り値が負の値の場合はエラーになります.
※PIDが0のプロセスはswapper(またはsched)プロセス,PIDが0のプロセスはinitプロセスと呼ばれます.
1 |
pid_t wait(int *wstatus); |
wait関数は,プロセスの状態変化を待つ関数です.
子プロセスのいずれかが終了するまで,呼び出し元のスレッドの実行を一時停止します.
wstatusがNULLでなければ,statusで指すint型のアドレスに状態情報を格納します.
返り値は終了した子プロセスのプロセスIDになります.
fork関数でプロセスを生成し,wait関数で子プロセスの終了まで待機するコードは以下になります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> int main(void) { pid_t pid; int status; int ret; if ((pid = fork()) < 0) { perror("fork"); exit(1); } else if (pid == 0) { sleep(1); printf("Child Process: %d\n", pid); } else { printf("Parent Process: Child PID = %d\n", pid); if ((ret = wait(&status)) < 0) { perror("wait"); exit(2); } printf("Parent Process end\n"); } return 0; } |
実行結果は以下になります.
1 2 3 4 5 |
$ gcc fork.c $ a.out Parent Process: Child PID = 6599 Child Process: 0 Parent Process end |
execve関数でコマンドの実行
1 |
int execve(const char *pathname, char *const argv[],char *const envp[]); |
execve関数は,引数pathnameで指定されたプログラムを実行する関数です.
呼び出したプロセスが現在実行しているプログラムは,新たに初期化されたスタック,ヒープ,(初期化された,または初期化されていない)データセグメントを持つ新しいプログラムに置き換えられます.
argvは新しいプログラムのコマンドライン引数として渡される文字列へのポインタの配列です.
これらの文字列の最初のもの(すなわち argv[0])には,実行されるファイルに関連するファイル名が格納されます.
envpは,新しいプログラムの環境として渡される文字列へのポインタの配列で,通常は key=value の形式をしています.
execve関数の返り値は成功すると返らない(返り値がない)ことに注意して下さい.エラーの場合のみ返ります.
execve関数でコマンドを実行するコードは以下になります.
execve関数は初見では結構使いづらいので注意して下さい.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> int main(int argc, char *argv[], char *envp[]) { pid_t pid; int status; int ret; if (argc < 3) { fprintf(stderr, "Error: argc %d must be more than or equal to 2.\n", argc); exit(1); } if ((pid = fork()) < 0) { perror("fork"); exit(2); } else if (pid == 0) { printf("Child Process: %d\n", pid); execve(argv[1], &argv[1], envp); /* return if execve() fails. */ perror("execve"); exit(3); } else { printf("Parent Process: Child PID = %d\n", pid); if ((ret = wait(&status)) < 0) { perror("wait"); exit(4); } printf("Parent Process end\n"); } return 0; } |
実行結果は以下になります.
execve関数にはパス検索機能はないので,/bin以下にあるechoコマンド等は絶対パスを指定しないと動作しないことに注意して下さい.
1 2 3 4 5 6 |
$ gcc execve.c $ a.out /bin/echo abc Parent Process: Child PID = 6609 Child Process: 0 abc Parent Process end |
execl関数でコマンドの実行
1 |
int execl(const char *pathname, const char *arg, ... /* (char *) NULL */); |
execl関数は,第1引数pathnameにパスを指定し,第2引数以降(arg...)に実行されるプログラムで利用可能な引数のリストを指定します.
引数のリストはNULLで終端された文字列へのポインタから構成されます.
慣習として,最初の引数は,実行されるファイル名へのポインタにします.
引数のリストは必ずNULLで終わらなければならず,これらの関数は可変長引数関数なので,このポインタは (char *) NULLとキャストしなければなりません.
execl関数でコマンドを実行するコードは以下になります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> #include <stdlib.h> #include <sys/wait.h> #include <unistd.h> int main(int argc, char *argv[]) { if (argc != 2) { fprintf(stderr, "Usage: %s command\n", argv[0]); return 1; } pid_t pid; int status; int ret; if ((pid = fork()) < 0) { perror("fork"); exit(1); } else if (pid == 0) { execl("/bin/sh", "myshell", "-c", argv[1], (char *) NULL); printf("Child Process: %d\n", pid); } else { printf("Parent Process: Child PID = %d\n", pid); if ((ret = wait(&status)) < 0) { perror("wait"); exit(2); } printf("Parent Process end\n"); } return 0; } |
実行結果は以下になります.
execve関数と比較すると,execl関数は使い方が少し異なりますので注意しましょう.
1 2 3 4 5 6 7 8 9 |
$ gcc execl.c $ a.out 'echo hoge' Parent Process: Child PID = 4298 hoge Parent Process end $ a.out 'echo $0' Parent Process: Child PID = 4300 myshell Parent Process end |
getpid/getppid関数でPIDを取得
1 2 |
pid_t getpid(void); pid_t getppid(void); |
getpid/getppid関数は,PIDを取得する関数です.
getpid関数は呼び出し元のプロセスのPID,getppid関数は呼び出し元のプロセスの親プロセスのPIDを返します.
getpid/getppid関数を利用するコードは以下になります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> int main(void) { pid_t pid; int status; int ret; if ((pid = fork()) < 0) { perror("fork"); exit(1); } else if (pid == 0) { sleep(1); printf("Child Process: %d\n", pid); printf("Child Process: getpid() = %d\n", getpid()); printf("Child Process: getppid() = %d\n", getppid()); } else { printf("Parent Process: Child PID = %d\n", pid); printf("Parent Process: getpid() = %d\n", getpid()); if ((ret = wait(&status)) < 0) { perror("wait"); exit(2); } printf("Parent Process end\n"); } return 0; } |
実行結果は以下になります.
3行目と6行目のPID,4行目と7行目のPIDが同じ値になることがわかります.
1 2 3 4 5 6 7 8 |
$ gcc getpid.c $ a.out Parent Process: Child PID = 6616 Parent Process: getpid() = 6615 Child Process: 0 Child Process: getpid() = 6616 Child Process: getppid() = 6615 Parent Process end |
まとめ
C言語でプロセスの生成と実行方法を紹介しました.
具体的には,fork/wait/execve/getpid/getppid関数の使い方を解説しました.
プロセスを生成するとマルチプロセスになりコードの書き方が難しくなりますので,基本的な使い方を習得しておきましょう.
プロセスの生成の応用としてデーモンの作り方があります.
興味があるあなたは是非読みましょう!
C言語を独学で習得することは難しいです.
私にC言語の無料相談をしたいあなたは,公式LINE「ChishiroのC言語」の友だち追加をお願い致します.
私のキャパシティもあり,一定数に達したら終了しますので,今すぐ追加しましょう!
独学が難しいあなたは,元東大教員がおすすめするC言語を学べるオンラインプログラミングスクール5社で自分に合うスクールを見つけましょう.後悔はさせません!