プロセス間通信 (IPC) の 8 つの主要な方法 (パイプ、名前付きパイプ、シグナル、セマフォ、メッセージ キュー、共有メモリ + メモリ マッピング、ソケット) を解決する 1 つの記事

目次

プロセス間通信 (IPC)

UNIX IPC

パイプ

名前付きパイプ (FIFO)

信号

システム V IPC

セマフォ

メッセージキュー

共有メモリ

IPCエクストラモード

メモリマップ

ソケット


プロセス間通信 (IPC)

参考/引用:

オリジナルの UNIX プロセス間通信 (IPC: InterProcess Communication) 方式: パイプ (PIPE)、名前付きパイプ (FIFO)、シグナル (Signal) を含む、System V プロセス通信方式: セマフォ (Semaphore)、メッセージ キュー (Message Queue) を含む) )、および共有メモリ (Shared Memory)。これらはどちらも初期の UNIX IPC であり、ソケットとメモリ マッピングも同様であり、Linux はこれら 8 つの基本的なメソッドを継承しています。

要約すると、パイプ、名前付きパイプ、シグナル、セマフォ、メッセージ キュー、共有メモリ、メモリ マップ、ソケットです。

UNIX IPC

パイプ

パイプは半二重通信方法です。これはカーネル内のバッファです。データは一方向にのみ流れることができ、一方の端で書き込み、もう一方の端で読み取ります。関係のあるプロセス間でのみ使用できます (親-子プロセス)。さらに、パイプはフォーマットされていないバイト ストリームを送信し、パイプ バッファのサイズは制限されています (パイプ バッファはメモリ内に存在し、パイプの作成時にバッファにページ サイズが割り当てられます)。

半二重(双方向通信、一方のみが送信でき、もう一方が同時に受信できる)、父、息子、兄弟プロセス間の通信に使用されます(この家父長制社会における命名方法がわかりますか? 「親と子」プロセス、「ピア プロセス」 (そう呼びましょう!) を使用すると、パイプは名前なしパイプまたは匿名パイプとも呼ばれ、2 つのプロセス間の通信に使用されるパイプは名前付きパイプと呼ばれます。

Pipe() 関数を使用してパイプ通信用のバッファを作成します。この関数は 2 つのファイル記述子、つまり「読み取りファイル記述子」と「書き込みファイル記述子」を返します。これらはそれぞれバッファの入力端を指します。/reading end および出力終了/書き込み終了、そして、関連する 2 つのプロセスは write() を使用して「ファイル記述子の書き込み」とデータの書き込みを行い、もう 1 つは read() を使用して「ファイル記述子の読み取り」を行い、データを読み取るだけです。

#include <unistd.h> 
int Pipe(int Pipefd[2]); 
/* パラメータ Pipefd 配列、2 つのファイル記述子を渡す必要があります、pipefd[0] は読み取り用、pipefd[1] は書き込み用です*/

パイプ関数を呼び出すと、通信のためにカーネル内で読み取り側と書き込み側のバッファが開かれます。Pipefd[0] はパイプの読み取り端を指し、pipefd[1] はパイプの書き込み端を指します。したがって、パイプはユーザー プログラム内で開いているファイルのように見えますが、read(pipefd[0]) または write(pipefd[1]) によるこのファイルへのデータの読み書きは、実際にはカーネル バッファーの読み書きとなります。

作業手順

  1. 親プロセス (作成者注: ここでの名前は「親プロセス」に変更する必要があり、ツリー構造の「親ノード」は「親ノード」に変更する必要があります!!!) は、pipe を呼び出してパイプラインを開き、パイプラインの両端を指す 2 つのファイル記述子を取得します。

  2. 親プロセス (作成者の注: ああああ) が子プロセスを作成するために fork を呼び出した場合、子プロセスには同じパイプを指す 2 つのファイル記述子もあります。

  3. 子プロセスにデータを送信する例として、親プロセス (作成者注: 後ほど気にしません) を取り上げます。親プロセスはパイプの読み取り側を閉じ、子プロセスはパイプの書き込み側を閉じます。 。親プロセスはパイプに書き込むことができ、子プロセスはパイプから読み取ることができます。パイプラインはリングキューを用いて実装されており、書き込み側からデータが流入し、読み出し側からデータが流出することでプロセス間通信が実現される。

パイプ通信の例:

Pipe() 関数はファイル記述子を返すため、基礎となる read() および write() システム コールを使用してのみアクセスできます。

パイプの読み取りおよび書き込みルール

読み込むデータがない場合

  • O_NONBLOCK 無効: 読み取り呼び出しはブロックされます。つまり、データが到着するまでプロセスは実行を一時停止します。

  • O_NONBLOCK 有効: 読み取り呼び出しは -1 を返し、errno 値は EAGAIN です。

パイプがいっぱいになったとき

  • O_NONBLOCK 無効: プロセスがデータを読み取るまで、書き込み呼び出しはブロックされます。

  • O_NONBLOCK 有効: 呼び出しは -1 を返し、errno 値は EAGAIN です。

パイプラインにおける 4 つの特殊な状況

  1. 書き込み側は閉じられていますが、読み取り側は閉じられていません。パイプ内の残りのデータがすべて読み取られた後、再度読み取ると、ファイルの最後まで読み取った場合と同様に 0 が返されます。

    すべてのパイプの書き込み端に対応するファイル記述子が閉じている場合、read は 0 を返します。

  2. 書き込み側は閉じられていませんが、データは書き込まれず、読み取り側も閉じられていません。この時点で、パイプ内の残りのデータがすべて読み取られた後、読み取りは再びブロックされ、データは再取得されません。 -read を実行し、パイプ内に読み取るデータが存在するまで返します。

  3. 読み取り側は閉じられていますが、書き込み側は閉じられていません。このとき、プロセスはシグナル SIGPIPE を受信します。これにより、通常はプロセスが異常終了します。

    すべてのパイプ リーダーに対応するファイル記述子が閉じられている場合、書き込み操作により信号 SIGPIPE が生成されます。

  4. 読み取り端は閉じられていませんが、データは読み取られず、書き込み端も閉じられていません。このとき、書き込み端がいっぱいになると、再度の書き込みはブロックされ、空の位置ができるまでデータは書き込まれません。パイプに戻って再び戻ります。

書き込まれたデータ量

  • 書き込まれるデータの量が PIPE_BUF 以下の場合 (Posix.1 では、PIPE_BUF が少なくとも 512 バイトである必要があります)、Linux は書き込みのアトミック性を保証します。

  • 書き込まれるデータの量が PIPE_BUF よりも大きい場合、Linux は書き込みのアトミック性を保証しなくなります。

パイプを使用するデメリット

  1. 2 つのプロセスはパイプを介して一方向の通信しか実現できません。双方向の通信が必要な場合は、新しいパイプを作成するか、sockpair を使用してこの種の問題を解決する必要があります。

  2. 親子プロセス、兄弟プロセスなど、アフィニティ関係のあるプロセス間の通信にのみ使用できます。

名前付きパイプ (FIFO)

名前付きパイプも半二重通信を提供しますが、パイプ/名前なしパイプ/匿名パイプと比較すると、無関係なプロセス間の通信が可能になります名前付きパイプは、名前付きパイプまたは実名パイプとも呼ばれます (中国語の文脈では、私の知る限り、パイプと FIFO の名前は合計 6 つあります)。

参考: Linux プロセス間通信パイプ (pipe)、名前付きパイプ (FIFO)、およびシグナル (Signal) - as_ - Blog Park (cnblogs.com)、Linux プロセス間通信: 名前付きパイプ FIFO の概要_Mr_John_Liang のブログ - CSDNブログ

名前付きパイプ FIFO の詳細なリファレンス: [Linux] プロセス間通信 - 名前付きパイプ FIFO プログラミング学習ガイド ブログ - CSDN ブログの名前付きパイプ fifo

FIFO はファイル システム (名前付きパイプは特別な種類のファイルです。Linux ではすべてがファイルであり、ファイル システム内にファイル名として存在するため) を借用してパイプに名前を付けるだけです。書き込みモードのプロセスは FIFO ファイルに書き込みますが、読み取りモードのプロセスは FIFO ファイルから読み取ります。FIFO ファイルを削除するとパイプ接続が消えます。FIFO の利点は、ファイルのパスによってパイプを識別できるため、無関係なプロセス間で接続を確立できることです。

#include <sys/types.h> 
#include <sys/stat.h> 
int mkfifo(const char *ファイル名, mode_t モード); 
int mknode(const char *ファイル名, mode_t モード | S_IFIFO, (dev_t) 0 ); 
/ * pathname は作成されるファイルの名前 (ファイルは存在してはなりません)、mode はファイルに設定される許可ビットと作成されるファイルの種類 (読み取りおよび書き込み許可を示します) を表します。 dev はデバイス特殊ファイルの作成時に使用する値です。したがって、先入れ先出しファイルの値は 0 になります。*/

作業手順

  1. まず、access() を使用して、ターゲットの名前付きパイプ FIFO ファイルが存在するかどうかを確認します。存在する場合はステップ 3 にジャンプし、存在しない場合はステップ 2 にジャンプできます。

  2. mkfifo() を使用して、モード パラメーターを使用して名前付きパイプ FIFO ファイルを作成します0777作成された FIFO ファイルが の場合は、/tmp/my_fifoコマンド ラインを使用してls -lF /tmp/my_fifoファイルを確認できます。

  3. 次に、open() (または fopen() の高度なパッケージ) を使用して FIFO ファイルを開きます (受信フラグは O_RDONLY、O_WRONLY、O_NONBLOCK であり、個別または組み合わせで使用できます。受信フラグについては後で詳しく説明します) 关于 FIFO 读写时候的阻塞问题FIFO はファイルであるため、使用する前に開く必要があります。

  4. 次に、read/write (または fread/fwrite) を使用して読み取りと書き込みを行います。

  5. 最後に close() を使用してファイルを閉じます。

参考例

wirte_fifo.c の例を作成します。

O_WRONLY); 
     if(fd == -1){ perror("オープンエラー"); 終了(1); }
 char *p = "こんにちは、世界"; 
 int len = write(fd, p, strlen(p)+1); 
 閉じる(fd); 
 0を返します。
}

例を読む read_fifo.c

O_RDONLY); 
     if(fd == -1){ perror("オープンエラー"); 終了(1); } 
 buf[512];
 int len = read(fd, buf, sizeof(buf)); 
 buf[len] = 0; 
 printf("buf = %s\n, len = %d", buf, len); 
 閉じる(fd); 
 0を返します。
}

FIFO読み書き時のブロッキング問題について

詳細なリファレンス:

open() を使用してブロック方式で FIFO ファイルを開く場合 (つまり、O_NONBLOCK フラグが渡されない場合)、read() はブロックされます (FIFO が空であるか、他のプロセスが読み取りを行っている場合、ブロックされます)。ブロックされている(ブロックが解除されるまで)、write() についても同様です。

FIFO ファイルを開くための受信フラグは、以下に詳述するように、個別または組み合わせて、O_RDONLY、O_WRONLY、および O_NONBLOCK です。

FIFO を開く際の主な制限は、プログラムが読み取りおよび書き込み操作のために O_RDWR モードで FIFO ファイルを開くことができないことですが、これを行った場合の結果は明確に定義されていません。シングルトンでデータを渡すためだけに FIFO を使用しているため、この制限は意味があり、O_RDWR モードを使用する必要はありません。パイプが読み取り/書き込み用に FIFO を開くと、プロセスはパイプから独自の出力を読み取ります。プログラム間でデータを両方向に渡す必要がある場合は、各方向に 1 つずつ、ペアの FIFO を使用するのが最善です。

Linux プロセスがブロックされても CPU リソースを消費しないため、このプロセス同期方法は CPU にとって非常に効率的です。

したがって、読み取り/書き込みに加えて、最大の影響は O_NONBLOCK フラグであることがわかります。

  • flags = O_RDONLY: open はブロッキングを呼び出し、別のプロセスが書き込み用に同じ FIFO を開くまで待機します。

  • flags = O_WRONLY: open はブロッキングを呼び出し、別のプロセスが読み取り用に同じ FIFO を開くまで待機します。

  • flags = O_RDONLY | O_NONBLOCK: この時点で他のプロセスが書き込み用に FIFO を開いていない場合、open も正常に戻ります。このとき、FIFO はエラーを返さずに読み取り用にオープンされます。

  • flags = O_WRONLY | O_NONBLOCK: すぐに戻ります。この時点で他のプロセスが読み取りのためにオープンされていない場合、オープンは失敗します。このとき、FIFO はオープンされず、-1 が返されます。

FIFO ファイルの読み取りおよび書き込み操作 (open() 時に O_NONBLOCK フラグを渡すことの影響):

open 関数呼び出しのパラメータ フラグ O_NONBLOCK は、FIFO の読み取りおよび書き込み操作に影響します。

ルールは次のとおりです。

  • 空のブロッキング FIFO への読み取り呼び出しは、読み取るデータが存在するまで待機してから続行します。

  • 空の非ブロッキング FIFO への読み取り呼び出しは、すぐに 0 バイトを返します。

  • 完全にブロックされた FIFO への書き込み呼び出しは、データが書き込まれるまで待機してから実行されます。

一度に書き込まれるデータのサイズに関する規則:

システム規定: 書き込まれるデータの長さが PIPE_BUF バイト以下の場合、すべてのバイトが書き込まれるか、1 バイトも書き込まれません。この制限の影響に注意してください。

FIF を 1 つだけ使用し、複数の異なるプログラムが FIFO リーダー プロセスにリクエストを送信できるようにする場合、この制限は、異なるプログラムからのデータ ブロックが相互にインターリーブしないようにするために重要です。つまり、各操作がアトミックであることを保証します。すべての書き込みリクエストがブロッキング FIFO に送信され、各書き込みリクエストのデータ長が PIPE_BUF バイト以下の場合、システムはデータが決してインターリーブされないことを保証できます。通常、FIFO を介して渡されるデータの長さを毎回 PIPE_BUF に制限することをお勧めします。

ノンブロッキング書き込み呼び出しの場合、FIFO が書き込まれたデータをすべて受信できない場合は、次のルールに従います。

  • 書き込み要求されたデータの長さが PIPE_BUF バイト未満であるため、呼び出しは失敗し、データを書き込むことができません。

  • 書き込みが要求されたデータの長さが PIPE_BUF バイトよりも大きいです。部分的なデータが書き込まれ、実際に書き込まれたバイト数が返されます。戻り値は 0 である場合もあります。

で。PIPE_BUF は FIFO の長さであり、ヘッダー ファイルlimits.h で定義されます。Linux またはその他の UNIX 系システムでは、その値は通常 4096 バイトです。

FIFOファイルの削除

ジャンクファイルの作成を避けるために、FIFO ファイルは使用後に削除する必要があります。

#include <unistd.h> 
int unlink(const char *パス名);

unlink の詳細については、unlink(2) - Linux のマニュアル ページを参照してください。

信号

その他の引用/参考文献: Chapter 10 Signal - as_ - Blog Garden (cnblogs.com) Linux-アプリケーション プログラミング-学習まとめ (4): プロセス間通信 (パート 2)_ブロッコリーのブログの学習-CSDN ブログLinux プロセス間通信パイプ (pipe)、名前付きパイプ (FIFO)、およびシグナル (Signal) - as_ - Blog Park (cnblogs.com)

シグナルは、イベントが発生したことを受信側プロセスに通知するために使用されます。シグナルは非同期通知のために配信されます (シグナルは非同期であり、プロセスは操作を通じてシグナルの到着を待つ必要はありません。実際、プロセスは待機します)シグナル (シグナル) プロセス間通信機構の中で唯一の非同期通信機構であり、非同期通知とみなすことができます) であり、データを送信することはできません。信号はソフトウェア レベルでの一種の割り込みメカニズムであり、シミュレーションの効果は割り込みメカニズムと似ています。

ただし、信号と割り込みは異なります。割り込みの応答と処理はカーネル空間で発生し、信号の応答はカーネル空間で発生し、シグナル ハンドラーの実行はユーザー空間で発生します。

では、いつ信号を検出して応答するのでしょうか? 通常、次の 2 つの状況で発生します。

  • システムコール、割り込み、または例外により現在のプロセスがカーネル空間に入った後、カーネル空間からユーザー空間に戻る前、つまりプロセスがカーネル状態からユーザー状態に戻ろうとしているとき(つまり、プロセスがカーネル状態からユーザー状態に戻ろうとしているとき)つまり、ソフト割り込み信号はカーネル状態では機能せず、ユーザー モードに戻るまで処理されません)。

  • 現在のプロセスがカーネルでスリープに入った直後にウェイクアップされたとき、つまり、プロセスが適切な低優先度のスリープ状態に入るか、または適切なスリープ状態から抜けようとしているとき、シグナルの検出により、プロセスは早期にユーザー空間に戻ります。

基準信号 (LINUX 信号メカニズム)_Baidu Encyclopedia (baidu.com)

コンピューター サイエンスにおいて、シグナルは、Unix、Unix 類似のオペレーティング システム、およびその他の POSIX 準拠のオペレーティング システムにおけるプロセス間通信の制限された方法です。これは、イベントが発生したことをプロセスに通知するために使用される非同期通知メカニズムです。シグナルがプロセスに送信されると、オペレーティング システムはプロセスの通常の制御フローを中断し、このとき、アトミックでない操作はすべて中断されます。プロセスでシグナル ハンドラーが定義されている場合はそれが実行され、そうでない場合はデフォルトのハンドラーが実行されます。

プロセスは、システム コール kill を通じて相互にソフト割り込み信号を送信できます (kill コマンドはシェルで使用され、kill() 関数はアプリケーション プログラミングで使用されます)。カーネルは、内部イベントによりプロセスにシグナルを送信し、イベントが発生したことをプロセスに通知することもできます。シグナルは、どのようなイベントが発生したかをプロセスに通知するためにのみ使用され、プロセスにデータは渡されないことに注意してください。シェルはシグナルを使用してジョブ制御コマンドを子プロセスに渡すこともできます。

信号の種類:

一般的な信号:

信号名 信号番号 処理内容
署名 2 Ctrl+C を押すと、OS はフォアグラウンド プロセス グループ内の各プロセスにメッセージを送信します。
フォローします 3 終了キー (CTRL+/) を入力すると、すべてのフォアグラウンド グループ プロセスに送信されます。
シガブト 6 abort関数が呼び出され、プロセスが異常終了します。
シギキル 9 プロセスを中止します。無視して捕らえることはできません。
対象期間 15 プロセスを終了するリクエスト。デフォルトでは kill コマンドが送信されます。kill コマンドによって送信される OS のデフォルトの終了シグナル
SIGTSTP 20 一時停止キー (通常は Ctrl+Z)。すべてのフォアグラウンド グループ プロセスに送信されます
シグストップ 19 プロセスを中止します。無視して捕らえることはできません。
シグコント 18 停止したプロセスが動作を再開すると、自動的に送信されます。
シグセグブ 11 無効なストレージ アクセスが発生した場合、OS はこのシグナルを発行します。
シグパイプ 13 パイプとソケットが含まれます。リーダーが終了した後にパイプに書き込むときに送信されます。誰も読んでいないパイプに書き込むときに発生します。
シガルム 14 アラーム機能で設定したタイマータイムアウト、またはsetitimer機能で設定したインターバルタイマータイムアウト
シグヒルド 17 OS は、子プロセスが終了または停止したときに、このシグナルを親プロセスに送信します。プロセスが終了または停止すると、SIGCHLD がその親プロセスに送信されます。デフォルトでは、このシグナルは無視されます
シグポール / シジオ 8 「高度な IO」で説明されている非同期 IO イベントを示します。
シグSR1 10 ユーザー定義信号。その機能と意味はアプリケーション自体によって定義されます。
シグスル2 12 ユーザー定義信号。その機能と意味はアプリケーション自体によって定義されます。
シグティン 21 バックグラウンドプロセスが読み取りを要求しています
シットトゥ 22 バックグラウンドプロセスが書き込みを要求している

シェル内のすべての信号と対応する番号を表示しますkill -l

無視できないシグナル

  • SIGKILL を実行するとプロセスが終了します。

  • ジョブ制御メカニズムの一部である SIGSTOP は、ジョブの実行を一時停止します。捕まえることも無視することもできません。

これは、プロセスを停止または強制終了する確実な方法を提供するためです。

ジョブ制御信号

SIGCHILD: 子プロセスは停止または終了しました。

SIGCONT: プロセスが停止した場合は、実行を継続します。

SIGSTOP: 停止信号 (捕捉または無視はできません);

SIGTSTP: インタラクションのための停止信号。

SIGTTIN: バックグラウンド プロセス グループのメンバーが制御端末を読み取ります。

SIGTTOU: バックグラウンド プロセス グループのメンバーは、制御端末に書き込みます。

シェルでのプロセスジョブ制御: Linux タスク制御 bg、fg、jobs、kill、wait、suspend... - Baidu Experience (baidu.com)bg、fg、job、kill、wait、suspendがあります。

信号伝送:

  • ドライバーとアプリケーション間: シグナルは、ユーザー空間プロセスとカーネル プロセス (ドライバーなど) の間で直接対話できます。カーネル プロセスは、シグナルを使用して、どのようなイベントが発生したかをユーザー空間プロセスに通知できます (たとえば、ドライバーは通常、SIGIO シグナルを使用して非同期に通知します)アプリケーション)。

  • アプリケーション間: あるプロセスから別のプロセス (またはそれ自身) に明示的に送信される、プロセス間通信または動作変更の方法としても使用できます。信号の生成をジェネレーション、信号の受信をキャプチャといいます。

シグナルを生成する状況:

  • Shell ユーザーによって発行されました。たとえば、フォアグラウンドのプロセスで CTRL+C を押すと、SIGINT シグナルが生成され、フォアグラウンド プロセスに送信されます。

  • ユーザーモードプロセスのシステムコールAPI(kill()、raise()、alarm()、pause()など)。

  • ドライバーは信号を送信して、アプリケーション (SIGIO などの一般的なもの) またはハードウェア エラーを非同期的に通知します。

キャプチャされた信号の 3 種類の処理:

  1. プロセスはシグナルを無視します。ほとんどの信号は無視できます。さらに、一部のハードウェア例外によって生成されるシグナルを無視すると、プロセスの動作は未定義になります。実際、個々のシグナルに対するデフォルトのアクションは無視することです。

  2. 信号を捕捉します。プロセスはシグナルを受信した後、シグナル/sigaction システムを呼び出すためにユーザーが設定した関数を実行します (ユーザーはシグナルのコールバック関数を設定できます)。

  3. デフォルトのアクションを実行します。処理が実行されない場合は、デフォルトのアクションが実行されます。ほとんどのシグナルのデフォルトの動作は、プロセスを中止することです。

一部のシグナルのデフォルト動作では、プロセスが終了するだけでなく、コア ダンプも生成されます。つまり、core という名前のファイルが生成され、終了時にプロセス メモリのイメージが保存され、デバッグに使用できます。次の状況では、コア ファイルは生成されません。

  • 現在のプロセスは現在のユーザーに属していません。

  • 現在のプロセスは現在のグループに属していません。

  • ユーザーには現在のディレクトリへの書き込み権限がありません。

  • コア ファイルはすでに存在しており、ユーザーには書き込み権限がありません。

  • ファイルが大きすぎるため、RLIMIT_CORE を超えています。

本質は、シグナルがシグナルを受信するプロセスにイベントが発生したことを非同期に通知し、オペレーティング システムがシグナルを受信したプロセスの実行を中断し、代わりに対応するシグナル ハンドラーを実行することです(無視、キャプチャに従って実行)。またはデフォルトの操作) )。

#include <sys/types.h> 
#include <signal.h> 
#include <unistd.h> 
void
(*signal(int sig,void (*func)(int)))(int); 
/*
バインディング特定のシグナルを受信した後のコールバック関数の最初のパラメーターはシグナルであり、2 番目のパラメーターはシグナルに対するユーザー独自の処理関数ポインターです。
戻り値は、前のシグナル ハンドラーへのポインターです。
: int ret = signal(SIGSTOP, sig_handle); 
*/ 
/
* シグナルは十分に堅牢ではないため、sigaction 関数を使用することをお勧めします. sigaction 関数はシグナル関数を再実装します*/ int sigaction(intsignum 
, const struct sigaction *act,struct sigaction *oldact); 
/* sigaction の使用については、使用時間を確認してください */ 
//
kill 関数はプロセス番号 pid のプロセスにシグナルを送信し、シグナル値は sig です。pid が 0 の場合、シグナル sig は現在のシステム内のすべてのプロセスに送信されます。
int kill(pid_t pid,int sig); 
/* 
    kill の pid パラメータには 4 つの状況があります。 
 1).pid > 0、シグナルはプロセス ID が pid のプロセスに送信されます。
 2).pid == 0、シグナルは、送信プロセスと同じプロセス グループに属するすべてのプロセスに送信されます (これらのプロセスのプロセス グループ ID は、送信プロセスのプロセス グループ ID と同じです)。プロセスには、これらのプロセスにシグナルを送信する権限があります。「すべてのプロセス」という用語には、実装定義のシステム プロセスのセットは含まれないことに注意してください。ほとんどの UNIX システムの場合、このシステム プロセス セットにはカーネル プロセスと init (pid 1) が含まれます。3 
 ).pid < 0、シグナルは ID が pid の絶対値と等しいすべてのユーザーに送信され、送信者は許可を持っています。信号を送信するためのプロセス。上記の通り、全プロセスセットにはシステムプロセスは含まれません。
 4).pid == -1、送信側プロセスが設定を送信する権限を持っているシステム上のすべてのプロセスにこのシグナルを送信します。以前と同様に、特定のシステム プロセスは含まれていません。
 : 親プロセスを終了します kill(getppid(), SIGKILL); 
*/ 
//
現在のプロセスにシグナル sig をブートストラップします、つまり、現在のプロセスにシグナルを送信します。kill(getpid(),sig); 
int raise(int sig); と同等; 
//
alarm() は、パラメータ秒で指定された秒数の後にシグナル SIGALRM が現在のプロセスに送信されるように設定するために使用されます。秒パラメータが 0 の場合、以前に設定されたアラームはキャンセルされ、残り時間が返されます。アラーム機能を使用する場合は、プロセス内で一度アラーム機能を使用すると、それまでのプロセスのアラーム機能は無効になるため、アラーム機能の適用範囲に注意してください。
//数秒後、SIGALRM シグナルをプロセス自体に送信します。
unsigned int アラーム (unsigned int 秒); 
/*
 設定した時間を超えるとSIGALRM信号が発生します。このシグナルが無視されない、または捕捉されない場合、アクションはプロセスを終了します。
 アラーム呼び出し時にプロセスにアラーム時間が事前に設定されており、タイムアウトしていない場合は、アラーム時間の残りの値がこのアラーム関数呼び出しの値として返されます。以前に登録されたアラーム時刻は、新しい値に置き換えられます。以前に登録されたまだ経過していないアラーム時刻があり、秒の値が 0 の場合、前のアラーム時刻はキャンセルされ、残りの値が関数の戻り値として使用されます。
*/ 
//
遅延/スリープ 秒秒
unsigned int sleep(unsigned int 秒); 
/* 
 0 またはスリープなしの秒数を返します。
 この関数により、 (1) 秒で指定された実時間が経過するまで、
 (2) プロセスがシグナルをキャプチャしてシグナル ハンドラーから戻るまで、
 呼び出しプロセスが一時停止されます。
 アラーム信号と同様に、何らかのシステムアクティビティにより、実際の復帰時間は必要な時間よりも遅くなります
*/ 
//
呼び出しプロセス (またはスレッド) を、シグナルが受信されるまでスリープ状態にするか、終了するか、シグナルを呼び出すようにします。キャプチャ機能。
// 一時停止関数により、シグナルが捕捉されるまで呼び出しプロセスが一時停止されます。
int stop(void); 
/*
 シグナル ハンドラーが実行され、シグナル ハンドラーから返された場合にのみ一時停止が返されます。この場合、pause は -1 を返し、errno は EINTR に設定されます。
* 
/
//abort 関数の機能は、プログラムを異常終了させることです。この関数は、呼び出しプロセスに SIGABRT シグナルを送信します。プロセスはこの信号を無視してはなりません。
// アボート関数は決して戻りません。
ボイド中止(ボイド);

シグナルの送信とキャッチの例をいくつか示します。

システム V IPC

System V IPC は、AT&T が System V.2 リリースで導入した 3 つのプロセス間通信ツールを指します。 (1) 共有リソースへのアクセスを管理するために使用されるセマフォ (2) プロセスを効率的に実装するために使用される共有メモリ プロセス間のデータ共有(3) メッセージキュー。プロセス間のデータ転送を実現するために使用されます。これら 3 つのツールを総称して System V IPC オブジェクトと呼び、各オブジェクトには一意の IPC 識別子があります。異なるプロセスが同じ IPC オブジェクトを確実に取得できるようにするには、IPC キーを提供する必要があり、カーネルは IPC キーを IPC 識別子に変換する役割を果たします。

System V IPC は、API 命名に関して同様の構文を持ちます。セマフォの場合は semxxx()、共有メモリの場合は shmxxx()、メッセージ キューの場合は msgxxx() です。

System V IPC は通常、次のように動作します。

1. IPC キーワード (API の仮パラメータ key_t キー) を選択します。次の 3 つの方法を使用できます。

  • IPC_PRIVATE。カーネルは、キーワードを選択してから IPC オブジェクトを生成し、IPC 識別子を別のプロセスに直接渡す責任があります。

  • 既存のキーワードを選択するだけです。

  • ftok() 関数を使用してキーワードを生成します。

2. semget()/shmget()/msgget() 関数を使用して、IPC キーワード キーとフラグに基づいて IPC オブジェクトを作成またはアクセスします。

キーが IPC_PRIVATE である場合、またはキーが既存の IPC オブジェクトに関連付けられておらず、フラグに IPC_CREAT フラグが含まれている場合、新しい IPC オブジェクトが作成されます。

3. semctl()/shmctl()/msgctl() 関数を使用して、IPC オブジェクトのプロパティを変更します。

4. semctl()/shmctl()/msgctl() 関数と IPC_RMID フラグを使用して、IPC オブジェクトを破棄します。

System V IPC は、IPC オブジェクトごとに ipc_perm 構造体を設定し、IPC オブジェクトの作成時に初期化します。この構造は、IPC オブジェクトのアクセス許可と所有者を定義します。

struct ipc_perm{ 
   uid_t uid; //所有者のユーザーID 
   gid_t gid; //所有者のグループID 
   uid_t cuid; //作成者のユーザーID 
   gid_t cgid; //作成者のグループID 
   mode_t mode; //アクセスモード
   … 
};

メッセージ キュー、セマフォ、共有メモリは総称して XSI IPC と呼ばれ、カーネル内に同様の IPC 構造 (メッセージ キューの場合は msgid_ds、セマフォの場合は semid_ds、共有メモリの場合は shmid_ds) があり、すべて非負の整数で識別されます。識別子は参照されます (メッセージ キューの msg_id、セマフォの sem_id、共有メモリの shm_id、それぞれ msgget、semget、shmget を通じて取得されます)。識別子は IPC オブジェクトの内部名です。各 IPC オブジェクトには、 key (key_t key) このキーをオブジェクトの外部名として使用する関連付け。

XSI IPC の IPC 構造はシステム全体で動作し、参照カウントを使用しません。プロセスがメッセージ キューを作成し、そのメッセージ キューに複数のメッセージを置いた場合、プロセスの終了後、メッセージ キューを使用しているプログラムがなくても、メッセージ キューとその内容は残ります。PIPE がパイプを参照した最後のプロセスを終了すると、パイプは完全に削除されます。FIFO を参照する最後のプロセスが終了すると、FIFO はまだシステム内にありますが、その内容は削除されます。PIPE や FIFO とは異なり、XSI IPC はファイル記述子を使用しないため、ls を使用して IPC オブジェクトを表示したり、rm コマンドを使用してオブジェクトを削除したり、chmod コマンドを使用してそのアクセス権を削除したりすることはできません。ipcs と ipcrm を使用して、それらを削除できるかどうかを確認することしかできません。

シェルで IPC オブジェクトを管理するためのコマンドは、ipcs、ipcmk、および ipcrm です。

のように:

  • ipcs -s作成されたセマフォセットの数を確認し、ipcrm -s <semid>semid 番号のセマフォセットを削除します。

  • ipcs -m作成された共有メモリの数を確認し、ipcrm -m shm_id共有メモリを削除します。

セマフォ

追加の参照/引用: IPC オブジェクト セマフォのブログ - CSDN ブログ ipc セマフォIPC セマフォの詳細な説明 Qiuoooooo のブログ - CSDN ブログ ipc セマフォ

セマフォは、特定のリソースに対する各プロセスのアクセス状況を記録するために使用されるカウンターであり、共有リソースへの複数のプロセスのアクセスを制御するために使用できます (たとえば、セマフォは後続の共有メモリで使用されます)。これは、プロセスがリソースにアクセスしているときに、他のプロセスが共有リソースにアクセスできないようにするロック メカニズムとしてよく使用されます。クリティカル リソース (クリティカル リソース: 特定の時点で 1 つのプロセスまたはスレッドでのみ操作できるリソース) のアクセス同期の問題に対処するためによく使用されます。重要なリソースにアクセスできるスレッドは常に 1 つだけです。

セマフォのワークフロー:

(1) リソースを制御するためのセマフォを作成します。

(2) このセマフォの値が正の場合、リソースの使用が許可されます。このプロセスにより、アドバンス数が 1 減算されます。

(3) セマフォが 0 の場合、リソースは現在利用できず、セマフォ値が 0 より大きくなるまでプロセスはスリープ状態に入り、プロセスは起動されてステップ (1) に進みます。

(4) プロセスがセマフォによって制御されるリソースを使用しなくなると、セマフォの値は 1 増加します。この時点でこのセマフォを待ってスリープしているプロセスがある場合は、このプロセスを起動します。

プロセスがリソースを使用しなくなった場合、セマフォは +1 (対応する操作を V 操作と呼びます)、逆にプロセスがリソースを使用した場合、セマフォは -1 (対応する操作を P 操作と呼びます) 。セマフォに対する値の操作はすべてアトミックな操作です。

P 操作、読み取りと書き込みの開始準備、P(sv): sv の値が 0 より大きい場合は 1 減算し、その値が 0 の場合はプロセスの実行を一時停止し、操作を待ちます。

V の動作は読み書き後に解放可能 V(sv): 他のプロセスが sv 待ちで停止している場合は実行を再開する sv で停止しているプロセスがない場合は 1 を加算

簡単に理解すると、P はリソースの申請に相当し、V はリソースの解放に相当します。

たとえば、2 つのプロセスが初期値 1 のセマフォ sv を共有します。プロセスの 1 つが P(sv) 操作を実行すると、セマフォを取得してクリティカル セクションに入ることができ、sv が 1 ずつ減ります。また、2 番目のプロセスは、P(sv) を実行しようとすると、クリティカル セクションに入ることがブロックされます。sv は 0 で、最初のプロセスがクリティカル セクションを出て、V(sv) を実行して解放するのを待ってハングアップします。セマフォが完了すると、2 番目のプロセスが実行を再開できます。

バイナリ セマフォ: 値は 0 または 1 です。ミューテックス ロックと同様に、リソースが使用可能な場合は値が 1、使用できない場合は 0 になります。つまり、P 操作はロックと同等、V 操作はロック解除と同等です。

カウンティングセマフォ: 0 から n までの値。統計リソースと同様に、その値は利用可能なリソースの数を表します。

Linux システムでセマフォを使用するには、通常、セマフォの作成、セマフォの初期化、セマフォの PV 操作、およびセマフォの削除の 4 つの操作が必要です。

セマフォ コレクションを作成/取得します。

#include <sys/types.h> 
#include <sys/ipc.h> 
#include <sys/sem.h> 
int semget(key_t key, int nsems, int semflg); 
/* 
    key: セマフォ コレクション番号/キー値は
        関数 key_t ftok(c​​onst char *pathname, int proj_id) を使用して取得できます;
        キー値が同じである限り、異なるプロセスでも同じセマフォ セットにアクセスできます。
        特別な値 IPC_PRIVATE があります。これは、プライベート セマフォの作成を意味します。現在のプロセスのセマフォ
    nsems: このパラメータは、作成するセマフォ セット内のセマフォの数を示します。セマフォはコレクションとしてのみ作成できます。
        作成されるセマフォの数。通常は 1 です。複数のセマフォが作成される場合、それはセマフォ セットと呼ばれます。
        新しいコレクションを作成する場合は、nsems を指定する必要があります。
        既存のセマフォ コレクションを参照する場合は、nsems を 0 として指定します。(新しいセマフォ セットを作成するのではなく) セマフォ セットの識別子を取得するだけの場合は、nsems を 0 にすることができます。
    semflg: 
        IPC_CREAT|IPC_EXCL は、キーに対応するセマフォが存在しない場合は作成され、存在する場合はエラーが報告されることを意味します。つまり、新しいセマフォ コレクションが作成されるか、すでに存在する場合は -1 が返されます。
        IPC_CREAT は、キーに対応するセマフォが存在しない場合は作成し、存在する場合はセマフォの識別子を直接返すことを意味します。新規または既存のセマフォ コレクションを返します。
        フラグの下位 8 ビットは、ファイルのアクセス許可と同様に、セマフォのアクセス許可ビットとして使用され、
            フラグの下位 8 ビットが許可ビットとなります。一般に、0666 が使用されます (6 の 2 進数は 110 で、読み取り可能、書き込み可能、​​実行不可能を意味します。3 つの 6 はそれぞれ現在のユーザー、グループ ユーザー、およびその他のユーザーに対応します)。たとえば、フラグは IPC_CREAT になります。 |0666。戻り値は次のとおりです: セマフォは
        正常に返されまし
    た。コレクションの半分 (非負の整数)、失敗した場合は -1 を返します。
    
    たとえば、プロセス A とプロセス B の間で同じセマフォが使用されている場合、デザイン A が最初にセマフォを生成/作成し、次に B がセマフォを参照/バインドします。 したがって、A が semget() を呼び出すときは、semflg パラメーターを渡す必要があります
        。 IPC_CREAT|IPC_EXCL |0666 で、nsems は作成されるセマフォの数です。B は
        IPC_CREAT|0666 または IPC_CREAT で渡す必要があり、nsems は 0 です。
*/ 
key_t
ftok( const char * fname, int id); 
/* IPC キー値の形式変換関数。システムは、IPC 通信 (メッセージ キュー、セマフォ、共有メモリ) を確立するときに ID 値を指定する必要があります。通常、id 値は ftok 関数を通じて取得されます。
    fname は指定したファイル名 (既存のファイル名) 通常、現在のディレクトリ
    ID はサブシリアル番号です。int型ですが、8bit(1~255)しか使われません。
    計算処理: 指定したファイルのインデックスノード番号が65538、16進数に換算すると0x010002、指定したID値が38、16進数に換算すると0x26の場合、最終的なkey_tの戻り値は0x26010002となります。
    これは、同じプログラム、および 2 人の異なるユーザーの下にある 2 つの同一のプログラムが、相互に干渉しない IPC キー値を確実に取得するために使用されます。
    例 key_t key = ftok(".", 'a'); 
*/

セマフォの設定 (初期化値または破棄):

/* カーネルで定義された構造体*/ 
Union semun{ 
    int val; // SETVAL で使用される値
    struct semid_ds *buf; // IPC_STAT および IPC_SET で使用されるバッファ領域
    unsigned short *array; // GETALL で使用されるバッファ領域および SETALL、ALL、特定のセマフォ セットのすべてのセマフォ
    struct seminfo *__buf; // IPC_INFO (Linux 固有) はキャッシュ領域を使用します
}; 
/
* カーネルは各セマフォ セットの semid_ds 構造を維持します */ 
struct semid_ds{ 
    struct ipc_perm sem_perm; 
    unsigned short sem_nsems; 
    time_t sem_otime; 
    time_t sem_ctime; 
    ... 
} 
int
semctl(int semid, int semnum, int cmd, Union semun arg); 
/* 
    semid: セマフォセットの番号 cmd を入力します。 operated 
    : セマフォセット semid 内の semnum 番目のセマフォを操作します (範囲: 0 ~ nsems-1)
    : さまざまな異なる操作がヘッダー ファイル sem.h で定義されています。次の例のようになります。
        IPC_STAT: 特定のセマフォ コレクションの semid_ds 構造体を取得し、それを semun 共用体の buf パラメーターが指すアドレスに格納します。
        IPC_SET: 特定のセマフォ セットの semid_ds 構造体の ipc_perm メンバの値を設定します。値は、semun Union の buf パラメータから取得されます。
        IPC_RMID: カーネルはセマフォ コレクションを削除します。
        GETVAL: コレクション内のセマフォの値を返します。
        SETVAL: コレクション内の単一のセマフォの値を、共用体の val メンバーの値に設定します。
        
        cmd でよく使用される 2 つの値は次のとおりです: 
            SETVAL: semnum 番目のセマフォの値を arg.val に初期化します; 
            IPC_RMID: セマフォを削除します。
            一般に、初期値の設定とセマフォの削除を意味します。上記の cmd にはさまざまな操作が定義されています。使用するときに確認できます (オンラインで検索するか、man を使用してカーネルのマニュアルを参照してください)。戻り値: 成功: IPC_STAT、IPC_SETVALまたは IPC_RMID 操作: 0 
    、IPC_GETVAL 操作: 現在のセマフォの値を返します; 失敗: -1 を返します。
*/

セマフォ操作 (P/V 操作、セマフォの値の変更):

/* カーネル内で保持される構造体。特定のセマフォに対してどのような操作が実行されるかを記述するために使用されます */ 
struct sembuf 
{ 
    unsigned short sem_num; /* セマフォ番号 */ 
    short sem_op; /* セマフォの操作 */ 
    short sem_flg; / * 動作フラグ */ 
} 
/* 
    sem_num: 0 から始まるセマフォセット内のどのセマフォか
    sem_op: セマフォの動作 (P 動作または V 動作) を示します。その値が正の場合、その値は既存の信号コンテンツに追加されます。通常、制御されたリソースを使用する権利を解放するために使用されます。sem_op の値が負の数で、その絶対値がシグナルの現在の値より大きい場合、シグナルの値がその値以上になるまで操作はブロックされます。 sem_op の絶対値。通常、リソースを使用する権利を取得するために使用されます。
        値 -1 は P 動作、値 1 は V 動作です。
    sem_flg: 2 つの値を持つ信号操作フラグ: IPC_NOWAIT および SEM_UNDO: IPC_NOWAIT 
        : セマフォの操作が満足できない場合、 semop() はブロックされませんが、すぐに戻り、エラー メッセージを設定します; 
        SEM_UNDO: プログラムが終了したとき(正常か異常かに関係なく)シグナル値が設定されることが保証されると同時に、プロセス終了時にリソースが解放されなかった場合には、システムが自動的にリソースを解放します。セマフォは 20 で、プロセス a はセマフォを SEM_UNDO モードで操作します。1 を
            。セマフォのカウント値、
            たとえば、セマフォの初期値は 20 です。プロセス a はセマフォを SEM_UNDO モードで操作し、1 ずつインクリメントします。プロセスが終了しない場合、セマフォは 21 になり、プロセスが終了すると、セマフォの値は 21 に戻ります。 20 */ int semop(
*/ 
int
semop(int semid, struct sembuf *sops, unsigned nsops); 
int semtimedop(int semid, struct sembuf *sops, unsigned nsops,struct timespec *timeout); 
/* 
    semid: セマフォのコレクション番号
    sops: 実行される操作するには、まず struct sembuf 構造体に値を入力し、そのアドレスを配列 (仮引数 struct sembuf sops[]) で渡すことができます。 nsops: 操作するセマフォの数を示します
    。sops パラメータには配列を渡すことができ、nsops は SOPS の数を表します。1 つの SOP がセマフォの操作に対応するため、コレクション内の
        複数のセマフォを同時に操作できます。
    戻り値: 成功した場合は 0 が返され、- が返されます。失敗時は1。
*/ 
/
* 例、セマフォ V、つまりセマフォ +1 を操作します */ 
struct sembuf sops_v = {0, +1, SEM_UNDO}; // インデックス値 0 のセマフォに 1 つのセマフォを追加します
(semid, &sops) , 1); // 上記の関数は 1 回実行されます

マニュアル:

プロセス 1 (sem):

①semgetを呼び出してセマフォを作成します。

②semctlのSETVALを呼び出してセマフォの初期値を設定します。

③semopを呼び出してPとVの操作を行う

プロセス 2 (sem2):

① semget を呼び出して、既存のセマフォの識別子 semid を取得します。

②semopを呼び出してP演算とV演算を行います。

(注: 別のプロセスもセマフォを使用し、P 操作を実行してセマフォの値を -1 にする場合、このプロセスが P 操作を実行するとブロックされ、別のプロセスが V 操作を実行するために都市に入るまで待機します。 +1 リリースリソース)

例: IPC セマフォの詳細な説明である Qiuooooooのブログ (CSDN ブログ ipc semaphore)にある例を参照してください。

メッセージキュー

メッセージ キューは、カーネルに格納され、メッセージ キュー識別子によって識別されるメッセージのリンクされたリストです。メッセージ キューは、信号送信情報が少なく、パイプはフォーマットされていないバイト ストリームのみを伝送でき、バッファ サイズが制限されているという欠点を克服します。メッセージ リストはカーネルに格納され、各メッセージ キューはメッセージ キュー識別子によって識別されます。パイプとは異なり、メッセージ キューはカーネルに格納され、メッセージ キューはカーネルの再起動時にのみ削除できます。メッセージキューには制限があります。

メッセージ キューには特定の形式と特定の優先順位があります。書き込み権限を持つプロセスはメッセージ キューに新しいメッセージを追加でき、読み取り権限を持つプロセスはメッセージ キューからメッセージを読み取ることができます。

メッセージ キューは、同じマシン上で動作するプロセス間通信に使用されます。パイプに非常に似ています。実際、これは徐々に廃止されつつある通信方法です。ストリーム パイプまたはソケットに置き換えることができます

使い方についてはここでは触れませんので、使うときに確認してください。

共有メモリ

おそらく、プロセス間通信の最も便利な方法であり、IPC の最速の形式です。共有メモリは最速の IPC 方式であり、他のプロセス間通信方式の非効率性を考慮して特別に設計されています。

通常、1 つのプロセスが共有メモリ領域を作成し、他のプロセスがこのメモリ領域に対して読み書きを行います。共有メモリは、他のプロセスがアクセスできるメモリのセクションをマップすることです。この共有メモリは 1 つのプロセスによって作成されますが、複数のプロセスからアクセスできます (2 つのプロセスのページ テーブルが変更され、仮想プロセスが同じ物理ページにマップされたアドレスは共有できます)。Proc A プロセスはメモリにデータを書き込み、Proc B プロセスはメモリからデータを読み取ります。この間に、Proc A から共有メモリへ、共有メモリから Proc B の合計 2 つのコピーが発生します。メモリが共有され、メモリの速度も向上します。一般的な方法は、shmXXX 関数ファミリーを使用してストレージとして共有メモリを使用することです。

2 つのプロセスがページ テーブルを介して仮想アドレスを物理アドレスにマップする場合、物理アドレスには共通のメモリ領域、つまり共有メモリが存在し、このメモリは両方のプロセスから同時に参照できます。このようにして、あるプロセスが書き込み操作を実行し、別のプロセスが読み取り操作を実行するときに、プロセス間通信を実現できます。

ただし、共有メモリには対応する相互排他メカニズムが用意されていないため、共有メモリは通常、プログラマによる制御を必要とするセマフォと組み合わせて使用​​されます。共有メモリは、プロセス間の同期と通信を実現するために、セマフォなどの他の通信メカニズムと組み合わせて使用​​されることがよくあります。書き込み中にプロセスを読み取れないようにするため、セマフォを使用して同期と相互排他を実現します。

共有メモリを作成します。

#include <sys/types.h> 
#include <sys/ipc.h> 
#include <sys/shm.h> 
int shmget(key_t key, size_t size, int shmflg); 
/* 
    key: と上で紹介したセマフォsemget 関数のパラメータ key は同じであり、主にプロセスを区別するために使用されます。関数 key_t ftok(c​​onst char *pathname, int proj_id); を使用して
    、適用される共有メモリのサイズを示す size: を取得できます。、これは通常 4k (つまり 4096xn バイト) の整数倍です。
    flags: 
        IPC_CREAT と IPC_EXCL は、新しい共有メモリを作成するために一緒に使用されます。それ以外の場合は、-1 が返されます。新しいものを作成するため。
        IPC_CREAT は、単独で使用すると共有メモリを返します。存在する場合は直接返され、存在しない場合は作成されます。既存のキーを参照/バインドするために使用されます。
        semget() の semflag と同じ。
        例: IPC_CREAT|IPC_EXCL|0666
    戻り値: 共有メモリの ID、つまり shmid が正常に返され、失敗した場合は -1 が返されます。
*/

共有メモリのプロパティを設定します。

/* shmid_ds 構造体、設定コマンドのパラメータを共有メモリに送信 */ 
strcut shmid_ds{ 
    struct ipc_perm shm_perm; 
    size_t shm_segsz; 
    time_t shm_atime; 
    time_t shm_dtime; 
    ... 
} 
int
shmctl(int shmid,int cmd,const void* addr); 
/* 
    cmd の一般的に使用される値は次のとおりです: 
        (1) IPC_STAT は現在の共有メモリの shmid_ds 構造体を取得し、それを buf に保存します
        (2) IPC_SET は buf の値を使用して現在の共有メモリの shmid_ds 構造体を設定しますメモリ
        (3) IPC_RMID 現在の共有メモリを削除します。cmd
    が IPC_RMID の場合、共有メモリの一部を削除するために使用できます (例: shmctl(shm_id, IPC_RMID, NULL); 
*/)

共有ストレージを使用してプロセス間通信を実装するときに注意すべき重要な点は、データ アクセスの同期です。プロセスがデータを読み取るときに、必要なデータがすでに書き込まれていることを確認する必要があります。通常、セマフォは共有ストレージ データへのアクセスを同期するために使用されますが、これは、shmctl 関数を使用して、SHM_LOCK、SHM_UNLOCK などの共有ストレージ メモリの特定のフラグ ビットを設定することによって実現できます。

フック/アンフック機能:

void *shmat(int shm_id,const void *shmaddr,int shmflg); 
/* 
    shmat 関数は、shm_id を介して共有メモリをプロセスのアドレス空間に接続し
    、適用された共有メモリをプロセスのページ テーブルにマウントします。仮想メモリは物理メモリに対応します。
    shmid: 共有メモリの ID を入力します。
    ユーザーは shmaddr パラメータを指定して、共有メモリのアドレスをプロセス空間にマップできます。shm_addr が 0 の場合、カーネルはマップされていない領域を見つけようとします。shmaddr は通常 NULL で、システムは共有メモリのアドレスを選択します
        。接続するメモリ
    shmflg は SHM_RDONLY に対して返すことができます
    値: プロセスが読み書きできるこのメモリの仮想アドレスを返します
    
    使用例: 
        char* mem = (char*)shmat(shm_id, NULL, 0); 
* / 
int
shmdt(const void *shmaddr); 
/* 
    shmdt の機能は、ページ テーブルから共有メモリを切り離し、2 つの間のマッピング関係を削除することです。
    shmaddr: この物理メモリの仮想アドレスを表します。
    戻り値: 失敗した場合は -1。
    
    使用例: shmdt(mem); 
*/

マニュアル:

  1. ftok() を使用して key_t キーを取得します。

  2. shmget() を使用して、共有メモリの一部を作成/参照します。次に、shmat() を使用して、shmid 番号に対応する共有メモリのアドレスを返します。

  3. 共有メモリのアドレスを直接読み書きするだけです。

  4. 使用されなくなった場合は、shmdt() を使用してリンクをキャンセルし、shmctl() を使用して共有メモリを破棄します。

共有メモリへの相互排他的アクセスには、セマフォまたはロック メカニズムを使用できます。プロセス間通信のための共有メモリへの相互排他的アクセスをロックする方法: linux | プロセス間通信をロックする方法 - Zhihu (zhihu.com )

IPCエクストラモード

メモリマップ

つまり、 mmap() 関数を通じてファイルをメモリにマップし、読み取りおよび書き込み操作を実行します。両方のプロセスが同じファイルをマップした後は、I/O API を呼び出さずにメモリを直接操作することなく、別々に読み取りと書き込みを行うことができます。

このメカニズムを使用する各プロセスは、同じ共有ファイルを自身のプロセスのアドレス空間にマッピングすることにより、複数のプロセス間の通信を実現します (これは、1 つのプロセスがマッピングされたファイルのメモリ上で動作する限り、共有メモリに似ています。他のプロセスも見ることができます)すぐに)。

#include <sys/mman.h> 
#include <unistd.h> 
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); 
int munmap(void *start, size_t length);
void *mmap(void*start,size_t length,int prot,int flags,int fd,off_t offset); 
//Themmap 関数は、ファイルまたは他のオブジェクトをメモリにマップします。最初のパラメータはマッピング領域の開始アドレスです。これを 0 に設定すると、システムがマッピング領域の開始アドレスを決定します。2 番目のパラメータはマッピングの長さです。3 番目のパラメータは予期されるメモリ保護フラグです。 4 番目のパラメータはマッピング オブジェクトのタイプを指定し、5 番目のパラメータはファイル記述子 (マッピングされるファイルを示します)、6 番目のパラメータはマッピングされたオブジェクトのコンテンツの開始点です。マップされた領域へのポインタが正常に返され、失敗した場合は MAP_FAILED [その値は (void *)-1] が返されます。
int
munmap(void* start,size_t length); 
//munmap 関数は、パラメータ start が指すマップされたメモリの開始アドレスをキャンセルするために使用され、パラメータ length はキャンセルされるメモリ サイズです。アンマッピングが成功した場合は 0 が返され、それ以外の場合は -1 が返され、エラーの原因は errno のエラー コード EINVAL に格納されます。
int
msync(void *addr,size_t len,int flags); 
//msync関数は、ディスクファイルの内容と共有メモリのアクセス内容の整合性、つまり同期を実現します。最初のパラメータはプロセス空間にマップされたファイルのアドレス、2 番目のパラメータはマップされた空間のサイズ、3 番目のパラメータはリフレッシュ パラメータ設定です。

API の詳細な説明は、次のドキュメントにあります【Linux 应用开发】\0-用到的API-收集积累\文件IO、字符流收发和字符串处理相关的API收集积累.c詳細はオンラインでご覧いただけます。

mmap() Linux メモリ管理の詳細説明- mmap 原理の詳細説明 - Zhihu (zhihu.com)

例: ここでの例は、mmap() を通じてファイルをメモリにマップし、このメモリを読み取ることです。mmap() を介して同じファイルをマップする別のプロセスがあり、変更できます。

#include <stdio.h> 
#include <unistd.h> 
#include <stdlib.h> 
#include <sys/types.h> 
#include <sys/stat.h> 
#include <string.h> 
#include <sys /mman.h> 
#include <fcntl.h> 
int
main(int argc, const char* argv[]) 
{ 
 int fd = open("english.txt", O_RDWR); 
     if(fd == -1){ perror("オープンエラー"); 終了(1); } 
 // ファイルの長さを取得
 int len = lseek(fd, 0, SEEK_END); 
 void * ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd,
 // メモリマッピング領域を解放
 int ret = munmap(ptr, len); 
     if(ret == -1){ perror("munmap error"); exit(1); } 
 return 0; 
}

共有メモリとメモリ マップド ファイルの違い:

メモリ マップト ファイルは、仮想メモリを使用してファイルをプロセスのアドレス空間にマップし、その後、プロセスは C 言語の memcpy などのメモリ操作関数を使用して、プロセス空間のアドレスを操作するのと同じようにファイルを操作します。この方法は、ファイルや大きなファイルを頻繁に処理する必要がある場合に適しており、この IO 処理方法は通常の IO よりも効率的です。

共有メモリはメモリ マップト ファイルの特殊なケースで、ディスク上のファイルではなくメモリのブロックをマップします。共有メモリの対象はプロセスです。オペレーティング システムは、デフォルトで各プロセスにメモリ スペースを割り当てます。各プロセスは、オペレーティング システムによって割り当てられたメモリにのみアクセスでき、他のプロセスにはアクセスできません。場合によっては、異なるプロセス間で同じメモリにアクセスする必要がある場合、どうすればよいでしょうか? オペレーティング システムは、共有メモリを作成およびアクセスするための API を提供します。メモリを共有する必要があるプロセスは、この定義された API セットを使用して、複数のプロセス間で共有されるメモリにアクセスできます。各プロセスによるこのメモリへのアクセスは、ハードディスク上のメモリにアクセスするのと似ています。ファイルも同じです。

メモリ マップト ファイルと仮想メモリの違いと関係:

メモリ マップト ファイルと仮想メモリはどちらもオペレーティング システムのメモリ管理の重要な部分であり、類似点と相違点があります。

接続: 仮想メモリとメモリ マッピングはどちらも、コンテンツの一部をメモリにロードし、別の部分をディスクに配置するためのメカニズムです。ユーザーにとってはすべて透過的です。

違い: 仮想メモリはハードディスクの一部であり、メモリとハードディスク間のデータ交換領域であり、多くのプログラムの実行中に、一時的に使用されないプログラム データがこの仮想メモリに置かれ、メモリ リソースが節約されます。メモリ マッピングは、プログラムがメモリ ポインタを介してファイルにアクセスできるようにする、ファイルのメモリ ブロックへのマッピングです。

仮想メモリのハードウェア基盤はページング メカニズムです。もう 1 つの基礎は、局所性 (時間的局所性と空間的局所性) の原則であり、プログラムの一部をメモリにロードし、残りを外部メモリに残すことができ、アクセスした情報が存在しない場合、必要なデータを取得することができます。記憶に転送される。メモリ マップされたファイルはローカルではありませんが、仮想アドレス空間の特定の領域にあるシルバー スネーク ディスクの内容のすべてまたは一部が、ファイル I/O を必要とせずにこの領域を介してマップされたディスク ファイルにアクセスできるようになります。ファイルの内容はバッファリングされます。

ソケット

ソケットを作成するときは、システム内で使用する範囲を選択します (また、異なるマシン間の TCP/UDP/IP 通信にはイーサネットを選択します)。

ソケットには、さまざまなドメインに対応するドメイン、タイプ、プロトコルの 3 つの属性があり、ソケットの名前としてアドレスもあります。

ドメインは、ソケット通信に使用されるプロトコル ファミリを指定します。最も一般的に使用されるドメインは、ネットワーク ソケットを表す AF_INET です。基礎となるプロトコルは IP プロトコルです。ネットワークソケットの場合、サーバーは複数のサービスを提供する可能性があるため、クライアントは IP ポート番号を使用して特定のサービスを指定する必要があります。AF_UNIX はローカル ソケットを表し、Unix/Linux ファイル システムを使用して実装されます。

IP プロトコルには、ストリームとデータグラム (それぞれ TCP プロトコルと UDP プロトコルに相当) の 2 つの通信方式があり、対応するソケット タイプはそれぞれストリーム ソケットとデータグラム ソケットです。ストリーミング ソケット (SOCK_STREAM) は、コネクション型で信頼性の高いデータ送信サービスを提供するために使用されます。このサービスにより、データがエラーや重複なく送信され、順番に受信されることが保証されます。ストリーミング ソケットは TCP プロトコルを使用します。データグラム ソケット (SOCK_DGRAM) は、コネクションレス型サービスを提供します。本サービスは、データ通信の信頼性を保証するものではなく、通信中にデータの消失や重複が発生する可能性や、データが確実に受信されることを保証するものではありません。データグラム ソケットは UDP プロトコルを使用します。

#include <sys/types.h> 
#include <sys/socket.h> 
int
ソケット(it ドメイン,int 型,int プロトコル); 
int binding(int ソケット,const struct sockaddr *アドレス,サイズ_t アドレス_len); 
int listen(int ソケット,int バックログ); 
int accept(int ソケット,struct sockaddr *アドレス,サイズ_t *アドレス_len); 
int connect(intソケット,const struct sockaddr *addrsss,size_t address_len); 
ssize_t
send(int sockfd, const void *buf, size_t len, int flags); 
ssize_t recv(int sockfd, void *buf, size_t len, int flags); 
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

【Linux 应用开发】\3-Socket编程\中の内容を参照してください。詳細はオンラインでご覧いただけます。

おすすめ

転載: blog.csdn.net/Staokgo/article/details/132630719
おすすめ