ホームページへ戻る
 

上へ戻る
 

   C言語入門  その3 プロセスとシグナル
by H.Y 3月 4 16:54:04 JST 2002

目次
1。プロセスの置換
1ー1。システムコール
1ー2。プログラムとプロセス
1ー3。プロセスの置換
1ー4。exec ファミリー
1) l(エル)系 exec の書き方
2) l 系 exec の実例
3) v 系 exec の書き方
4) v 系 exec の実例
5) execlp(), execvp() パスの検索
6) execle(), execve()
2。プロセスの複製
2ー1。プロセス ID の取得 getid()
2ー2。プロセスの複製を作る。 fork()
2ー3。親子の識別
2ー4。wait 子プロセスの終了を待つ。
1) wait()
2)子プロセスの終了ステータス
2ー5。新規プロセスの作成
3。シグナル
3ー1。シグナルの種類
3ー2。割込みキイとシグナル
3ー3。stty による、割込みキイの変更
3ー4。signal 関数
1) signal() シグナルを補足する関数。
2)raise(), kill(), alarm()
3) sigaction() 信頼性の高いシグナルインターフェイス

---------------------------------

1。プロセスの置換
1ー1。システムコール
OSがユーザープログラムに提供するサービルーチンの集合のこと。
標準のライブリ関数でも、Linuxが提供しているサービスを受ける場合
は内部でシステムコールを実行している。
プロセスの生成やシグナルの発行には、システムコールを実行する。
1ー2。プログラムとプロセス
プロセスはプログラムの1形態である。実行されているカーネルに
管理されているプログラムがプロセス。ディスク上のプログラムは
カーネルにより、プロセス情報を追加してメモリー読み込まれプロセス
になる。
1ー3。プロセスの置換
exec システムコールはプログラムの中から別のプログラムを起動させ
る。 新しく起動したプログラムは、元のプログラムを置き換える。
元のプログラムは消滅して、起動したプログラム(プロセス)に変身する。
これが「 プロセスの置換」である。
1ー4。exec ファミリー
exec システムコールの種類
----------------------------------------------------------
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 *filename, char *const argv [],
char *const envp[]);
--------------------------------------------------------------
最初の5種類は 内部で execve() システムコールを呼び出す。
(1) v 系 exec は第2引数を配列のポインタで指定できる。
(2) p 形式の exec は、第1引数に指定するコマンド名をフルパス
で指定しなくてもよい。
(3) e 形式の exec は 第3引数に環境変数を指定できる。
--------------------------------------------------------------
1) l(エル)系 exec の書き方
execl, execlp, execle のシステムコールが l 系 exec。
(1)第1引数:実行するプログラムのパス名をフルパスで指定する。
(2)第2引数:コマンドラインへ渡す引数を指定する。
(3)最後に :コマンドライン引数の最後を示す文字 0 (ヌル)を書く。
例)
execl("/usr/bin/less", "less", "/etc/lilo.conf", "NULL");

l 系 exec では、プロセスに渡す引数を、コマンド名も含めてすべ
て並べて指定する。
NULL は (char *)0 と同じ。
2) l 系 exec の実例
------ execl_1.c -------------------------------------------
#include <unistd.h>
#include <stdio.h>

int main(){
printf("何か、キイをおしなされ.........。\n");
getchar();

if(execl("/usr/bin/less", "less", "/etc/lilo.conf",
"NULL") == -1){
perror("execl");
return 1;
}
return 0;
}
-----------------------------------------------------------
実行方法
(1) ./execl_1
(2) Ctl_z を押す。 プロセスがサスペンドする。
(3) ps | grep execl_1
(4) execl_1 のプロセス ID を確認する。
(5) fg で exec1_l に戻る。
(6) キイを入力する。
(7) 別のターミナルで ps -ax を実行する
(8) execl_1 のプロセス ID が無く、less の ID がある。
即ち execl() の実行で execl_1 は less に置換された。
3) v 系 exec の書き方
execv, execvp, execve が v 系 exec。
(1)第1引数:実行するプログラムのパス名をフルパスで指定する。
(2)第2引数: char *[] の形式でポインタを指定する。
例)
char *const cmdline[]{
"wc", "/etc/lilo.conf", NULL
};
execv("/usr/bin/wc", cmdline);
4) v 系 exec の実例
------------ execv_1.c -------------------------------
#include <unistd.h>
#include <stdio.h>

int main(){
char *const cmdline[] = {
"wc", "/etc/lilo.conf", NULL
};

if(execv("/usr/bin/wc", cmdline) == -1){
perror("execv");
return 1;
}
retrun 0;
}

実行: ./execv_1
--------------- execv_2.c ----------------------------
#include <unistd.h>
#include <stdio.h>

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

if(argc < 2){
fprintf(stderr, "usage %s command args...\n",
basename( argv[0] );
return 1;
}
if(execv(argv[1], &argv[1]) == -1){
perror("execv");
return 1;
}
return 0;
}
-------------------------------------------------------
basename():ファイル名からディレクトリと拡張子を取り去る関数。

実行例 ./execv_2 /bin/ls -l
main()の第2引数を利用している。

5) execlp(), execvp() パスの検索
execlp(), execvp() は、第1引数に指定するコマンド名をフルパス
で指定しなくてもよい。
execlp("less",....);
execvp("less",,,,,);
この形式の exec は第1引数で指定されたコマンドを PATH の定義に
したがって検索する。
例題) execv_2.c の中の execv() を execvp() に変更してテスト
せよ。

6) execle(), execve()
e 形式の exec は第3引数に環境変数の配列を指定できる。
例題)
--------------- envp.c ---------------------------------------
#include <stdio.h>

int main(int argc, char *argv[], char *envp[]){
int i;

for( i = 0; envp[i]; i++)
printf("%s \n", envp[i]);
return 0;
}
-------------- execle_1.c ------------------------------------
#include <unistd.h>
#include <stdio.h>

int main(){
char * const envp[] = {
"FUJI = fuji",
"TAKA = taka",
"NASUBI = nasubi",
NULL
};

if(execle("./envp", "env", NULL, envp) == -1){
perror("execle");
return 1;
}
return 0;
}
------------------------------------------------------------
実行)
(1) ./execle
(2) プログラムで疑似的に作成した環境変数の配列 envp を
exec の最後の引数として指定している。

2。プロセスの複製
プロセスは fork() システムコールで、自分の分身を作成できる。
また、exec でプロセスを置換することができる。
プロセスA------------------------------------->
|
| fork()
|
プロセスA ------------>プロセスB---->
exec

2ー1。プロセス ID の取得 getid()
----------------------------------------
#include <unistd.h>

pid_t getpid(void);
-----------------------------------------
プログラムの中で getpid() を実行すると、自分自身のプロセス
ID を取得する。
---------- getpid_1.c --------------------
#include <unistd.h>

int main(){
printf("何かキイをおしなはれ!......\n");
getchar();
printf("あんさんの PID は %d でおます。 \n", getpid());

return 0;
}
-------------------------------------------
2ー2。プロセスの複製を作る。 fork()
-----------------------------
#include <unistd.h>

pid_t fork(void);
-----------------------------
成功したときは
(1) 親プロセスの場合: 子プロセスのプロセス ID
(2) 子プロセスの場合: 0
失敗したときは -1 をかえす。errno がセットされる。

(1) fork を実行すると同じプロセスの複製が走りだす。
(2) 複製されたプロセスの実行位置は fork の次の文から。
親プロセス--------------------------->
|
| fork()
|
子プロセス---------------->
1)例題
-------------------- fork_1.c ----------------------
#include <unistd.h>

int main(){
int i;

printf("親プロセススタート \n");
printf("親プロセス PID = %d \n", getpid());

printf("fork() start \n");
fork();
printf("fork() finish \n"); //子プロセスはここから
実行する。
for(i = 0; i < 4; i++){
printf("%02d: PID = %d running....\n", i,
getpid());
sleep(1);
}
return 0;
}
---------------------------------------------------
実行結果)
親プロセススタート
親プロセス PID = 1253
fork() start
fork() finish 親プロセスの表示
fork() finish 子プロセスの表示
00: PID = 1254 running....
00: PID = 1253 running....
01: PID = 1254 running....
01: PID = 1253 running....
02: PID = 1254 running....
02: PID = 1253 running....
03: PID = 1253 running....
03: PID = 1254 running....

2ー3。親子の識別
fork() の帰り値により、親子の識別をする。
----------------- fork_2.c ----------------------
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>

int main(){
pid_t pid;

printf("fork_2 PID = %d /n", getpid() );
printf("fork() start \n");

if( (pid = fork() == -1){
perror("fork");
return 1;
}
else if(pid > 0){
printf("親 : PID = %d \n", getpid() );
printf("my child PID = %d \n", pid );
return 0; //親プロセスはここで終了
}
else{
printf("子 : PID =%d \n", getpid() );
return 0; //子プロセスはここで終了
}
}

2ー4。wait 子プロセスの終了を待つ。
親プロセスが子プロセスの終了を待つには、wait システムコール
を用いる。現在のLinuxでは wait ファリーのなかで wati4 のみが
システムコールになっている。
ここでは wait() のみを説明する。他は man を参照のこと。
1) wait()
--------------------------------------------------
#include <sys/wait.h>

pid_t wait(int *status);
---------------------------------------------------
status: 子プロセスの終了状態を保存する領域へのポインタ
NULL の場合は保存されない。
戻り値: 終了した子プロセスのプロセス ID
エラーの場合は -1 値がerrno にセットされる。

親プロセスで wait() を記述すると、子プロセスの終了ませ次の文
は実行されない。
--------------- wait_1.c -----------------------------------
#include <sys/wait.h>
#include <stdio.h>
#include <sys/types.h>

int main(){
int i = 0;
pid_t pid;

printf("fork() start \n");
if( (pid = fork()) ==-1){
perror("fork()");
return 1;
}
else if(pid > 0){
printf("親プロセス: 待っています。.....\n");
wait(NULL);
printf("親プロセス: 子プロセスは今終了しました。\n");
}
else{
for(i = 0; i < 4; i++){
printf("%2d: 子プロセスが走っています。....\n");
sleep(1);
}
}
return 0;
}
------------------------------------------------------------
親プロセスは子プロセスが終了するまで wait(NULL) で待つ。
子プロセスが終了すると、次の文を実行して return 0 で終了する。
2)子プロセスの終了ステータス
wait(int *status); の status の内容
(1)子プロセスが正常終了の場合
(exit または main()でのreturn)
上位8ビット : 終了ステータス
下位8ビット : 0
(2)子プロセスがシグナルで終了した場合
上位8ビット : 0
下位8ビット : 終了の原因のシグナル番号
(3)status の値操作のマクロ
WIFEXITED(): Wait IF EXITED.
子プロセスが exit や return で終了した時に真になる。
WEXITSTATUS() : Wait EXIT STATUS
終了ステータスを得る。
WIFSINALED(): Wait IF SIGNALED
子プロセスがsignalで終了した場合に真になる。
WTERMSIG(): Wait TERMinated SIGNAL
子プロセスが終了したシグナルの値を得る。
---------------- wait_2.c ---------------------------
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <stdio.h>

int main(){
int i = 0;
pid_t pid;
int status;

printf("fork() start \n");
if ( (pid = fork()) == -1){
perror("fork()");
return 1;
}
else if (pid > 0){
printf("親プロセス: 待機中......\n");
wait(&status);
if ( WIFEXITED(status) ){
printf("子プロセスは今終了した。\n");
printf("終了ステータス = %d\n",
WEXITSTATUS(status) );
}
else if ( WIFSIGNALED(status) )
printf("子プロセスの受信したシグナル番号
= %d \n",WTERMSIG(status) );
}
else{
for( i = 0; i < 4; i++){
printf("%2d: 子プロセス走行中......\n", i);
sleep(1);
}
return 123;
}
return 0;
}
-------------------------------------------------------
実行1) ./wait_2
終了ステータスは 123
実行2)
(1) ./wait_2
(2) Ctrl+Z を押す
(3) ps で子プロセスのPIDをしれべる。
(4) kill 子プロセスのPID
(5) fg
終了ステータスは 15

2ー5。新規プロセスの作成
fork exec で新規プロセスを作成する。
即ち、fork でプロセスの複製を作成して、この複製したプロセスを
exec で別のプロセスに変更する。
----------- newprocess.c -------------------------------------
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>

int main(){
pid_t pid;

printf("newprocess start \n");
printf("親プロセスの PID = %d \n",getpid() );

if( (pid = fork()) == -1){
perror("fork(()");
return 1;
}
else if ( pid > 0 ){
printf("親プロセス: 待機中......\n");
wait(NULL);
printf("子プロセスは今終了しました。\n");
}
else {
printf("子プロセススタート \n");
printf("子プロセス PID = %d \n", getpid() );
printf("子プロセス:キイを押してくらはい....\n");
getchar();

printf("子プロセス:execlp start ...\n");
if( execlp("bc", "bc", NULL) == -1){
perror("execlp");
return 1;
}
return 0;
}
}
-----------------------------------------------------------
実行)
(1) ./newprocess
(2)「 キイを押して... 」 で Ctrl+Z で中断する。
(3) ps で子プロセスのPIDを確認
(4) fg
(5) bc で計算する。
2 ^ 10
(6) Ctrl+Z で中断する。
(7) ps で bc が作成されていることを確認する。
(8) fg
(9) quit で bc を終了する。

3。シグナル
シグナルはシステムによって生成されるイベントのことである。
イベントを受け取ったプロセスはなんらかの処理を起こすことがある。
この処理を行うルーチンをシグナルハンドラーと呼ぶ。シグナルは
プロセスに対するソフトウェア割込みである。シグナルはプロセス間の
通信のひとつでもある。
シグナルの週類は /usr/include/asm/signal.h で参照できる。
シグナルを追加するには、 /usr/include/bits/signum.h に追加すれば、
プログラムで使用できるようになる。
3ー1。シグナルの種類
------ signals /usr/include/bits/signum -------------------
#define SIGHUP 1 Hangup (POSIX)
#define SIGINT 2 Interrupt (ANSI)
#define SIGQUIT 3 Quit (POSIX)
#define SIGILL 4 Illegal instruction (ANSI)
#define SIGTRAP 5 Trace trap (POSIX)
#define SIGABRT 6 Abort (ANSI)
#define SIGIOT 6 IOT trap (42 BSD)
#define SIGBUS 7 BUS error (42 BSD)
#define SIGFPE 8 Floating-point exception (ANSI)
#define SIGKILL 9 Kill, unblockable (POSIX)
#define SIGUSR1 10 User-defined signal 1 (POSIX)
#define SIGSEGV 11 Segmentation violation (ANSI)
#define SIGUSR2 12 User-defined signal 2 (POSIX)
#define SIGPIPE 13 Broken pipe (POSIX)
#define SIGALRM 14 Alarm clock (POSIX)
#define SIGTERM 15 Termination (ANSI)
#define SIGSTKFLT 16 Stack fault
#define SIGCLD SIGCHLD Same as SIGCHLD (System V)
#define SIGCHLD 17 Child status has changed (POSIX)
#define SIGCONT 18 Continue (POSIX)
#define SIGSTOP 19 Stop, unblockable (POSIX)
#define SIGTSTP 20 Keyboard stop (POSIX)
#define SIGTTIN 21 Background read from tty (POSIX)
#define SIGTTOU 22 Background write to tty (POSIX)
#define SIGURG 23 Urgent condition on socket (42 BSD)
#define SIGXCPU 24 CPU limit exceeded (42 BSD)
#define SIGXFSZ 25 File size limit exceeded (42 BSD)
#define SIGVTALRM 26 Virtual alarm clock (42 BSD)
#define SIGPROF 27 Profiling alarm clock (42 BSD)
#define SIGWINCH 28 Window size change (43 BSD, Sun)
#define SIGPOLL SIGIO Pollable event occurred (System V)
#define SIGIO 29 I/O now possible (42 BSD)
#define SIGPWR 30 Power failure restart (System V)
#define SIGSYS 31 Bad system call
#define SIGUNUSED 31

#define _NSIG 64 Biggest signal number + 1
(including real-time signals)
-----------------------------------------------------------------
シェルで
kill -HUP PID または
kill -1 PID を実行すると
SIGHUP signal が PID を持つプロセスに送信される。

kill -l でシグナルとシグナル番号の対応が表示される。
シグナル番号はシグナルを区別する整数である。
3ー2。割込みキイとシグナル
シグナル名 stty 割込みキイ
----------------------------------
SIGINT intr C-c Interrupt
SIGQUIT quit C-\ Quit (core が作られる)
SIGCONT star C-q Continue
SIGSTOP stop C-s Stop
SUGTSTP susp C-z Terminal Stop(Back Ground へ)

3ー3。stty による、割込みキイの変更
(1) stty -a で現在の設定を表示
(2) stty -g save_stty 現在の設定をセーブする。
(3) stty intr ^? SIGINT -> DEL キイに
(4) stty intr ^C SIGINT -> Ctrl+C

3ー4。signal 関数
1) signal() シグナルを補足する関数。
-------------------------------------------------------
#include <signal.h>

void signal(*signal(int sig, void (*func)(int)))(int);
-------------------------------------------------------
signal() は sig と func() をパラメータにとる関数。
sig には signal を指定。
func には シグナルハンドラを指定する。
fucn は int型の引数(受け取ったシグナル)を1つ取る void型の関数。

例題1 Ctrl+C を受信した処理を変更してみる。]
----------- ctrlc1.c -------------------------------
#include <signal.h>
#include <stdio.h>
#include <unistd.h>

void ouch(int sig){
printf("OUCH! - I got signal %d\n", sig);
(void) signal(SIGINT, SIG_DFL);
// SIG_DFL で default のハンドラに戻す。
}

int main(){
(void) signal(SIGINT, ouch);
// SIGINT のハンドラを ouch() に設定
while(1) {
printf("Hello World!\n");
sleep(1);
}
}
実行
(1) ./ctrlc1
(2) Ctrl+C
(3) メッセージを確認
(4) Ctrl+1 で終了
2度目の Ctrl+C で default のハンドラが呼び出されて、
終了する。

2)raise(), kill(), alarm()
(1) raise() プロセスが自分自身にシグナルを送る。
---------------------------------------------
#include <signal.h>

int raise(int sig);
---------------------------------------------
成功すると 0 を、失敗すると 0 以外の値を返す。
(2) kill() 他のプロセスにシグナルを送る。
-----------------------------------------
#include <signal.h>
#include <sys/types.h>

int kill(pid_t pid, int sig);
----------------------------------------
pid で指定された プロセスIDをもつプロセスに対して、
sig に指定されたシグナルを送信する。
失敗すると -1 をかえし、errno をセットする。
(3) alarm() 一定時間後にSIGALARMシグナルを生成する。
-------------------------------------------
#include <unistd.h>

unsigned int alarm(unsigned int seconds);
-------------------------------------------
seconds に指定した秒数を経過すると、SIGALARM を配送する。
seconds に 0 を指定すると、すでにセットした alarm を
キャンセルする。
alarm は登録済みのアラームが送信されるまでの秒数を返す。
例題------------- alarm.c---------------------------
#include <signal.h>
#include <stdio.h>
#include <unistd.h>

static int alarm_fired = 0;

void ding(int sig){
alarm_fired = 1;
}

int main(){
int pid;

printf("alarm application starting\n");

if((pid = fork()) == 0) { //子プロセスの場合
sleep(5);
kill(getppid(), SIGALRM);
//getppid()は現在のプロセスの親プロセスのPIDを得る
//即ち親プロセスにSIGALARMを送る。
exit(0);
}

printf("waiting for alarm to go off\n");
(void) signal(SIGALRM, ding);

pause(); //シグナルを受け取るまで中止する。
if (alarm_fired)
printf("Ding!\n");

printf("done\n");
exit(0);
}
-----------------------------------------------------
実行
(1) ./alarm
(2) alarm application starting
waiting for alarm to go off (5秒待つ)
Ding!
done
3) sigaction() 信頼性の高いシグナルインターフェイス
----------------------------------------------------
#include <signal.h>

int sigaction(int sig, const struct action *act,
struct sigactio *oact);
-----------------------------------------------------
siaction関数は、シグナル sig に関連づけられた動作を設定する。
oact が NULL でなければ、oact で指定する場所に以前のシグナル
の動作が書き込まれる。act が NULLの場合、sigaction が行う動作
はこれだけである。
act が NULLで無い場合、sigaction は sigに対する指定した動作
を設定する。
成功すると 0 を、祖例外の場合は -1 を返す。
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
}
(1) sa_handler
sig を受け取った時に呼び出される関数へのポインタ。
SIGIGN(シグナルを無視せよ)を指定できる。
SIGDEF(default の動作にもどせ)も指定できる。
(2) sa_mask;
sa_handler関数を呼び出す前にプロセスのシグナルマスクに追加
するシグナルの集合を指定。
(3) sa_flag
sigaction で設定されたハンドラによって補足されるシグナルの
動作は、デフォルトではリセットされない。
default に戻す場合は、SA_RESETHANDを含む値を sa_flag に
設定する。
例題 ------------ ctrlc2.c --------------------------
#include <signal.h>
#include <stdio.h>
#include <unistd.h>

void ouch(int sig){
printf("OUCH! - I got signal %d\n", sig);
}

int main(){
struct sigaction act;

act.sa_handler = ouch;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;

sigaction(SIGINT, &act, 0);

while(1) {
printf("Hello World!\n");
sleep(1);
}
}
-------------------------------------------------
実行
(1) ./ctrlc2
(2) Ctrl+C シグナルを送る
(3) メッセージを確認
(4) Ctrl+C シグナルを送る
(5) メッセージを確認
(6) Ctrl+\ SIGQUITを送る
(7) 終了