Linux Cシステムプログラミング(09)プロセス管理プロセス間通信

プロセス間通信の重要性は、複数のプロセスが相互にデータにアクセスできるようにする方法であり、Linuxでこれを実現するには多くの方法があります。


1プロセス間通信の概要

プロセス間通信は、ランタイムデータや互いのコードセグメントなど、複数のプロセスが相互にアクセスできるようにするもので、実際のアプリケーションでは非常に一般的です。IPCメカニズムは、データ通信用のデータ伝送チャネルを提供するだけです。    

プロセスの実行中、そのアドレス空間は他のプロセス(これは従来のプロセスの概念にすぎません)からは見えません。システム内で独立しており、相互にアクセスできません。

一般的なプロセス間通信方法は、パイプ、FIFOパイプ、シグナル、セマフォ、メッセージキュー、共有メモリ、ソケットです。

パイプには、半二重パイプと全二重パイプがあります。

  1. 半二重パイプライン:匿名の半二重パイプラインFIFO
  2. 全二重パイプライン:名前付き全二重パイプラインという匿名の全二重パイプライン

2パイプ

2.1パイプの概念

一般的な通信方法の1つは、2つのプロセス間でデータ循環パイプラインを実現することです。パイプラインは単方向または双方向であり、パイプラインは使いやすいですが、多くの制限があります。匿名の二重パイプは、システムでは実際の名前を持たず、ファイルシステムではまったく表示されません。これはプロセスのリソースであり、プロセスが終了するとシステムによって消去されます。パイプライン通信は、grepコマンドを使用して検索する必要があります。以下の通り

$ls |grep ipc

パイプラインは、データフローの方向で全二重パイプと半二重パイプに分けられます。特定の実装プロセスでは、全二重パイプラインはファイルを開く方法が少しだけ異なります(操作ルールも多少異なり、全二重パイプラインは半二重よりも複雑です)。

2.2匿名の半二重パイプライン

匿名パイプには名前がなく、パイプで使用されるファイル記述子のパス名はありません。つまり、重要なファイルはなく、メモリ内のインデックスポイントに関連付けられた2つのファイル記述子だけがあり、その特徴は次のとおりです。

  1. データは一方向にのみ移動できます。
  2. 共通の祖先を持つプロセス間、つまり親プロセスと子プロセス/兄弟プロセスの間でのみ通信できます。
  3. それにもかかわらず、半二重パイプは最も一般的な通信方法です。

Linux環境では、パイプ関数を使用して匿名の半二重パイプラインを作成します。関数のプロトタイプは次のとおりです。

#include <unistd.h>
int pipe(int pipefd[2]);

詳細については、linux関数リファレンスマニュアルを参照してください配列fdの場合、匿名パイプ名の起点である別名ファイルには関連付けられていません。

2.3匿名の半二重パイプラインの読み取りおよび書き込み操作

  • パイプラインの読み取りと書き込みを行うときは、読み取り関数と書き込み関数を使用してパイプラインを操作します。読み取り端が閉じていると、信号SIGPIPEが生成され、パイプの読み取り端が閉じていることを示します。書き込み操作は-1を返します。 errnoの値はEPIPEです。SIGPIPE信号をキャプチャできます書き込みプロセスがSIGPIPE信号をキャプチャ/無視できない場合、書き込みプロセスは中断されます。
  • パイプラインは継承できます。通常の状況では、パイプ関数とfork関数が一緒に使用されますが、注意が必要です。パイプラインの順序を維持するために、親プロセスがパイプラインを作成するときです。子プロセスがパイプラインを継承した場合のみ、親プロセスはパイプラインを閉じる操作を実行できます。分岐の前にパイプラインが閉じられている場合、子プロセスは使用可能なパイプラインを継承しません。
  • パイプを読み取ると、read関数は0を返します。2つの意味があります。1つは、パイプにデータがなく、書き込み端が閉じていること、もう1つは、パイプにデータがなく、書き込み端がまだ生きていることです。これら2つ個別に対処する必要があります。

2.4パイプラインを作成するための標準ライブラリ関数

通常の状況では、パイプライン操作に標準の一連の手順が使用されるため、上記の操作はANSI / ISO Cの2つの標準ライブラリ関数popenおよびpcloseで定義されています。Linuxでの関数プロトタイプは次のとおりです。

#include <stdio.h>
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);

詳細については、linux関数リファレンスマニュアルを参照してください


3 FIFOパイプライン

3.1 FIFOパイプラインの概念

FIFOは有名なパイプとも呼ばれ、システムで確認できるファイルタイプです。FIFOの通信方法は、ファイルを使用してプロセス内でデータを転送する方法と似ていますが、FIFOタイプのファイルにはパイプの特性があり、データの読み取りと同時にデータがクリアされる点が異なります。シェルのmkfifoコマンドは、有名なパイプを作成できます。

3.2 FIFOパイプラインの作成

FIFOファイルの作成はファイルの作成に似ており、パス名でFIFOにアクセスすることもできます。LinuxでFIFOを実現するには、mkfifo関数を使用します。関数のプロトタイプは次のとおりです。

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);

詳細については、linux関数リファレンスマニュアルを参照してください

3.3 FIFO読み取りおよび書き込み操作

FIFOファイルには、一般的なI / O関数を使用できます。ただし、open関数を使用してFIFOファイルを開く場合、open関数パラメーターのフラグフラグのO_NONBLOCKフラグは、関数の戻りステータスに関連しています。設定の論理関係は次のとおりです。

  1. 設定されている場合:読み取り専用のオープンはすぐに戻ります。オープンのみを書き込む場合、どのプロセスも読み取り用にFIFOを開かない場合、-1を返します。つまり、非ブロッキングの場合、読み取りと書き込みは同時に行う必要があります。
  2. 設定しない場合:状況に応じてブロックを開きます。読み取り専用オープンは、プロセスが書き込み用にFIFOを開くまでブロックされ、書き込みオープンのみがプロセスが読み取り用にFIFOを開くまでブロックされます。

FIFOのすべてのプロセスが閉じられると、FIFOの読み取りプロセスに対してファイル終了文字が生成されます。FIFOの出現により、アプリケーションプロセスでシステムによって生成される多数の中間ファイルの問題が解決されました。シェルからFIFOを呼び出して、あるプロセスから別のプロセスにデータを転送できます。システムは、中間チャネルの不要なゴミをクリーンアップしたり、チャネルのリソースを解放したりする必要がなく、後続のプロセスで使用して回避できます。さらに、匿名パイプのスコープ制限は、無関係のプロセス間に適用できます。

3.4 FIFOの短所

サーバー/クライアントアーキテクチャの場合は処理できますが、次のように、サーバーが提供するFIFOインターフェイスを事前に知っておく必要があります。

  1. クライアントはサーバーにリクエストを送信します。サーバーのパブリックFIFOを知る必要があります。
  2. サーバーはクライアントに情報を送り返します:各クライアントが応答するには専用のFIFOを作成する必要があります。数値が特定のレベルに達すると、FIFOはサーバーに過負荷をかけ、サーバーをクラッシュさせます。

4 System V IPC / POSIX IPC通信メカニズムの紹介

System V IPCには、メッセージキュー、セマフォ、共有メモリの3つの通信メカニズムが含まれています。これは古い方法であり、最近のバージョンではPOSIX IPCに置き換えられています。POSIX IPCとSystem V IPCは同じものを参照しますが、使用される機能が異なり、実装も異なります。IPCメカニズムは、パイプやFIFOとは異なります。パイプとFIFOはファイルシステムに基づいていますが、IPCはカーネルに基づいていますが、ipcsを使用してシステムのIPCオブジェクトの現在のステータスを表示できます。

4.1 IPCオブジェクトの概念

IPCオブジェクトは、カーネルレベルでアクティブなプロセス間通信のためのツールです。既存のIPCオブジェクトは、その識別子を通じて参照およびアクセスされます。この識別子は、IPCオブジェクトを一意に識別する負でない整数です。このIPCオブジェクトは、メッセージキュー、セマフォ、共有メモリのいずれかです。タイプ。

Linuxでは、識別子は整数として宣言されるため、可能な識別子の最大値は65535(2 ^ 16)です。注:ここでの識別子はファイル記述子とは異なります。open関数でファイルを開いた場合、返されるファイル記述子の値は、現在のプロセスで使用可能な最小のファイル記述子配列の添え字です。IPCオブジェクトが削除または作成されると、対応する識別子の値は増加し続け、最大値に達した後、ゼロへの戻りが割り当てられ、循環的に使用されます。

IPC識別子は、IPCオブジェクトへの内部アクセスの問題のみを解決します。複数のプロセスが特定のIPCオブジェクトにアクセスできるようにする方法も、外部キーを必要とします。各IPCオブジェクトはキーに関連付けられています。これにより、IPCオブジェクトでの複数のプロセスの収束の問題が解決されます。

このようなキーが存在することを複数のプロセスに通知するには、いくつかの方法があります。

  1. ファイルを中間チャネルとして使用し、IPCオブジェクトプロセスを作成し、キーIPC_PRIVATEを使用してIPCオブジェクトを正常に確立し、返された識別子をファイルに格納します。他のプロセスは、この識別子を読み取ることによってIPCオブジェクト通信を参照します。
  2. 複数のプロセスによって認識されるキーを定義します。各プロセスは、このキーを使用してIPCオブジェクトを参照します。IPCオブジェクトを作成するプロセスの場合、キーの値がIPCオブジェクトと結合されている場合は、IPCオブジェクトを削除して新しいオブジェクトを作成する必要があります。 IPCオブジェクト。
  3. マルチプロセス通信では、指定されたキーがIPCオブジェクトを参照するために、拡張できない場合があります。また、キー値がIPCオブジェクトによって結合されている場合も同様です。したがって、この既存のオブジェクトを削除してから、新しいオブジェクトを作成する必要があります。ただし、このオブジェクトを使用している他のプロセスに影響を与える可能性があります。関数ftokは、この問題をある程度解決できます。

関数ftokは、2つのパラメーターを使用してキー値を生成できます。関数のプロトタイプは次のとおりです。

#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);

詳細については、linux関数リファレンスマニュアルを参照してくださいこの関数は、パラメーターパス名ファイル内のstat構造体のst_devメンバーとst_inoメンバーの部分的な値を組み合わせて、パラメーターproj_idの8番目のビットと組み合わせ、キー値を生成します。st_devメンバーとst_inoメンバーの部分的な値のみが使用されるため、情報は失われ、2つの異なるファイルが同じIDを使用して同じキー値を取得することは除外されません。

システムは、IPCオブジェクトの権限と所有者を記述する各IPCオブジェクトのipc_perm構造を保存します。カーネルコンテンツの各バージョンには、IPCオブジェクトの権限と所有者を記述する異なるipc_perm構造があります。バージョンごとに異なります。各IPCオブジェクトについて、システムはstruct ipc_permデータ構造を共有して、IPC操作がIPCオブジェクトにアクセスできるかどうかを決定するための許可情報を格納します。ipc_perm構造は次のように実装されます。

struct ipc_perm
{
    __kernel_key_t key;
    __kernel_uid_t uid;
    __kernel_gid_t gid;
    __kernel_uid_t cuid;
    __kernel_gid_t cgid;
    __kernel_mode_t mode;
    unsigned short seq;
}; 

IPCオブジェクトを作成したルート/プロセスのみが、ipc_perm構造の値を変更する権利を持ちます。IPCオブジェクトの欠点は次のとおりです。

  1. 他の通信方法に比べて、プログラミングインターフェイスが複雑すぎるため、IPCで必要なコードの量が大幅に増加します。
  2. IPCは共通のファイルシステムを使用しません。したがって、標準のI / O操作は使用できません。新しい関数が追加されます。ファイル記述子が使用されないため、複数のI / O関数の選択/ポーリング関数を使用してIPCオブジェクトを操作することはできません。
  3. リソース回復メカニズムの欠如、通常はプロセスのみがメッセージを読み取り、IPC所有者/スーパーユーザーがこのオブジェクトを削除します。これは、パイプ/ FIFOに比べてIPCにはないリソース回復メカニズムでもあります。

4.2 IPCシステムコマンド

シェルでipcsコマンドを使用して、IPCステータスを表示します。ipcsが出力する情報:

  • keyは、IPCオブジェクトの外部キーを識別します
  • shmidはIPCオブジェクトの識別子を識別します
  • 所有者はIPCが属するユーザーを識別します
  • permsロゴの許可

IPCオブジェクトコマンドを削除します。

$ipcrm -m shmid号  

 現在のシステムIPCステータスコマンドを表示します。

$ipcs -m

5共有メモリ

共有メモリは、すべてのプロセス空間通信の最速の方法であり、カーネルレベルで存在するリソースです。ファイルシステム/ procディレクトリに記述されている対応するファイルがあります。

5.1共有メモリの概念

共有メモリメカニズムは原則に依存しています。システムカーネルがプロセスにアドレスを割り当てると、ページングメカニズムを通じてプロセスの物理アドレスが不連続になる可能性があります。同時に、メモリのセクションを異なるプロセスに同時に割り当てることができます。カーネルは、共有ストレージセグメントごとに、そのためのshmid_dsタイプの構造を維持します。shmid_ds構造の定義は次のとおりです。

struct shmid_ds
{
    struct ipc_perm shm_perm;                /* operation perms */
    int shm_segsz;                               /* size of segment (bytes) */
    __kernel_time_t shm_atime;          /* last attach time */
    __kernel_time_t shm_dtime;           /* last detach time */
    __kernel_time_t shm_ctime;           /* last change time */
    __kernel_ipc_pid_t shm_cpid;           /* pid of creator */
    __kernel_ipc_pid_t shm_lpid;           /* pid of last operator */
    unsigned short shm_nattch;                /* no. of current attaches */
    unsigned short shm_unused;                /* compatibility */
    void *shm_unused2;                          /* ditto - used by DIPC */
    void *shm_unused3;                          /* unused */
}; 

構造shmid_dsは、システムカーネルのバージョンによって若干異なり、共有ストレージセグメントのサイズはシステムによって制限されます。適用する場合は、関連するマニュアルを参照してください。

5.2共有メモリの作成

Linuxで共有メモリ領域を作成/開くには、shmget関数を使用します。shmget関数関数のプロトタイプは次のとおりです。

#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);

詳細については、linux関数リファレンスマニュアルを参照してください

5.3共有メモリ操作

共有メモリの特殊なリソースタイプのため、操作は通常のファイルとは異なり、独自の操作機能が必要です。Linuxは複数の操作に共有メモリを使用します。共有メモリ管理機能のプロトタイプは次のとおりです。

#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

詳細については、linux関数リファレンスマニュアルを参照してください注:

  1. 分岐後、子プロセスは接続された共有メモリアドレスを継承します。実行後、子プロセスは接続された共有メモリアドレスから自動的に切り離されます。プロセスが終了すると、接続された共有メモリアドレスが自動的に切り離されます
  2. 関数の実行後、共有メモリ識別子shmidの共有メモリが接続され、接続に成功すると、共有メモリ領域オブジェクトが呼び出しプロセスのアドレス空間にマッピングされ、ローカル空間と同様にアクセスできるようになります。

@ 3共有メモリセグメントの操作が終了したら、shmdt関数を呼び出して共有メモリを切断する必要があります。関数のプロトタイプは次のとおりです。

#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);

詳細については、linux関数リファレンスマニュアルを参照してください

5.4共有メモリ使用上の注意

  • 他の方法と比較して、共有メモリは、読み取りおよび書き込み時のデータをより透過的にします。共有メモリの一部が正常にインポートされると、現在のユーザーが自由にアクセスできるメモリの一部を指す文字列ポインタと同じになります。ただし、これの欠点は、データの書き込み/読み取りプロセス中に追加の構造制御が必要になると同時に、マルチプロセスの同期/相互排除で共有メモリメカニズムを支援するために追加のコードも必要になることです。
  • 共有メモリセグメントでは、文字列のデフォルトの終わりはメッセージの終わりです各プロセスはこのルールに従い、データの整合性を破壊しません。

6セマフォ

6.1セマフォの概念

セマフォ自体にはデータ送信の機能はありません。これは外部リソースの識別子にすぎません。これは、外部リソースが使用可能かどうかを判断するために使用できます。セマフォは、このプロセス中のデータの相互排除と同期を担当します。セマフォによって表されるリソースを要求する場合、プロセスは最初にセマフォの値を読み取って、対応するリソースが利用可能かどうかを判断する必要があります。

  1. セマフォの値が0より大きい場合、要求するリソースがあることを意味します。
  2. これが0の場合、使用可能なリソースがないことを意味しているため、使用可能なリソースがなくなるまでプロセスはスリープ状態になります。

プロセスがセマフォ制御の共有リソースを使用しなくなった場合、このセマフォの値は+1であり、セマフォの増減はアトミック操作です。これは、セマフォの主な機能がリソースの相互排除/マルチプロセスを維持することであるためです同期アクセス、およびセマフォの作成/初期化では、アトミック操作は保証されません。カーネルは各信号セットに対してshmid_ds構造体をセットアップし、名前のない構造体を使用してセマフォを識別します。定義はLinux環境によって異なります。shmid_ds構造体は次のように定義されています。

struct shmid_ds {
    struct ipc_perm    shm_perm;                /* operation perms */
    int    shm_segsz;                              /* size of segment (bytes) */
    __kernel_time_t    shm_atime;         /* last attach time */
    __kernel_time_t    shm_dtime;         /* last detach time */
    __kernel_time_t    shm_ctime;         /* last change time */
    __kernel_ipc_pid_t shm_cpid;         /* pid of creator */
    __kernel_ipc_pid_t shm_lpid;          /* pid of last operator */
    unsigned short     shm_nattch;              /* no. of current attaches */
    unsigned short     shm_unused;              /* compatibility */
    void               *shm_unused2;                /* ditto - used by DIPC */
    void               *shm_unused3;                /* unused */
};

shmid_dsデータ構造は、新しく作成された各共有メモリを表します。shmget()が共有メモリの新しい部分を作成すると、共有メモリのshmid_dsデータ構造を参照するために使用できる識別子を返します。

6.2セマフォの作成

共有メモリと同様に、システムもセマフォ用に一連の独自の操作関数(semget、semctlなど)をカスタマイズする必要があります。Linuxでは、関数semgetを使用してセマフォセットIDを作成/取得します。プロトタイプは次のとおりです。

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);

詳細については、linux関数リファレンスマニュアルを参照してください

6.3セマフォ操作

3つのIPCオブジェクトタイプのうち、セマフォセットの操作関数は他の2つのタイプの操作関数よりもはるかに複雑であり、同じセマフォの使用も他の2つよりも広範囲です。セマフォにも独自の排他操作があります。Linuxで動作するには、semctl関数を使用します。関数のプロトタイプは次のとおりです。

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ...);

詳細については、linux関数リファレンスマニュアルを参照してください


7メッセージキュー

7.1メッセージキューの概念

メッセージキューは、リンクリスト構造で編成されたデータのグループであり、カーネルに格納されます。メッセージキュー識別子を通じて各プロセスによって参照されるデータ転送方法です。また、カーネルによって維持され、3つのIPCオブジェクトの中で最もデータ操作が可能なデータ転送方法であり、メッセージキューでは、特定のデータタイプに従ってメッセージを自由に取得できます。もちろん、リンクされたリストを維持するには、より多くのメモリリソースが必要であり、データの読み取りと書き込みは共有メモリよりも複雑で、時間のオーバーヘッドも大きくなります。

7.2メッセージキューを作成する

Linuxでは、msgget関数を使用してキューを作成/オープンします。関数プロトタイプは次のとおりです。

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);

詳細については、linux関数リファレンスマニュアルを参照してください。  

7.3メッセージキュー操作、読み取りと書き込み

Linuxでは、msgsnd関数とmsgrcv関数を使用して、メッセージキューの読み取りと書き込みを行います。関数のプロトタイプは次のとおりです。

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg); //消息队列写操作
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg); //消息队列读操作

詳細については、linux関数リファレンスマニュアルを参照してください

元の記事289件を公開 賞賛された47件 30,000回以上の閲覧

おすすめ

転載: blog.csdn.net/vviccc/article/details/105159556