Linux - プロセス通信用の共有メモリ

目次

I. 上記を思い出す

2. 共有メモリ

1. 定義

2. 特徴:

3. 実装手順:

共有メモリの使用権を正常にリンクするための完全な手順は次のとおりです。

4. 機能紹介

        4.1 shmget関数

        4.1.2 パラメータの導入       

        4.2ftok関数:

        4.2.1 パラメータの導入

                ftok(); shmget(); 関数に関するコード実験:

        コードは次のように実行されます。 

                人生の例を挙げると、次のようになります。

                操作結果:

                したがって、それを解決するには 2 つの方法があります。 

               4.3shmctl();—共有メモリ空間を削除するために使用されます

               パラメータの紹介:

               戻り値:

              コードデモ: 

    次のステップは 3 番目のステップです。各プロセスと共有メモリ空間の間の「足かせ」を関連付けます。

            4.4shmat();——共有メモリ空間のアドレスを取得する

         パラメータの紹介:

        コードデモ: 

        操作結果:

         4.6 shmdt();——共有メモリ空間との関連付けを解除する

         コードデモ:

        最後は両者間のデータ通信です!

 2. 最終的なコードマップ:

        通信.hpp:

        サーバー.cc:

        クライアント.cc: 

        操作結果:

3. 概要:

共有メモリやパイプラインと比較して、2 つのプロセスがプロセス間で通信する場合、データを何回コピーする必要があるでしょうか?

共有メモリの欠点:


I. 上記を思い出す

        プロセス間データ通信とは、2 つのプロセスを例にとると、一方のプロセスは書き込みのみ、もう一方のプロセスは読み取り専用でデータ通信を行い、その実装は一方向です。

        1. パイプラインはプロセス通信の方法であり、半二重通信方式です。つまり、データは一方向です (あるプロセスが読み取り、別のプロセスが書き込みます)。

        2. パイプは匿名パイプと名前付きパイプに分けられます。

        3. 匿名パイプとは血縁関係のある親子プロセス間で特殊なデータ通信を行うためのもので、pipe関数でパイプを作成し、fork関数でデータ通信を開始する子プロセスを作成します。ただし、匿名パイプは対応するファイル記述子を閉じる必要があり、親プロセスは読み取り専用または書き込み専用であり、子プロセスも同じです。

        4. 名前付きパイプはメモリ内のバッファに対応します。パイプからのデータの読み取りは、読み取りプロセスにとって 1 回限りの操作です。データが読み取られると、書き込みプロセスが書き込みできるようにスペースを解放するために破棄されます。より多くのデータ。名前付きパイプは、mkfifo関数でパイプファイルを作成し、ファイル形式でIO送信することで2つのプロセスを介さずにデータ通信を行うことができます。

        

        パイプラインに加えて、データ通信の別の方法である System V を学習する必要があります。System V 通信で最も一般的なテストは共有メモリであるため、次は共有メモリに焦点を当てます。

         上の図では、これら 2 つのプロセスは独立しています。システムの下部には、これらすべてが独自のカーネル データ構造 (struct_task) (PCB と呼ばれる) を持ち、独自のプロセス アドレス空間 (mm_struct) も持っています。データはすべてメモリ内にありますが、相互に干渉しないため、独立しています。

        しかし、無関係な 2 つのプロセスが通信できるようにするには、2 つのプロセスに同じ共通リソースを参照させる必要があります。今日の共通リソースは共有メモリです。

2. 共有メモリ

1. 定義

        共有メモリは、物理メモリに格納されるスペースであり、オペレーティング システムによって割り当ておよび管理されます。ファイル システムと同様に、オペレーティング システムが共有メモリを管理する場合、メモリ データを保存するだけでなく、管理を容易にするために共有メモリの属性を記録するフェーズ構造も作成します。

        したがって、共有メモリは 1 つだけではなく、必要に応じて複数の共有メモリを適用することができます。

2. 特徴:

       パイプラインと比較して、共有メモリは非親子プロセス間の通信に使用できるだけでなく、パイプラインよりも高速にデータにアクセスできます。これは、通信がメモリに直接アクセスするのに対し、パイプラインはメモリ データを取得する前にオペレーティング システムを通じてファイルにアクセスする必要があるという事実によるものです。

3. 実装手順:

        共有メモリを使用すると、2 つのプロセスがこの共有スペース リソースをポイントできます。したがって、2 番目のステップは、各プロセスにこのパブリック リソースの使用許可を与え、相互に通信できるようにすることです。

        しかし、共有メモリを使用する権利を得るのは簡単ではなく、それを達成するには複数の手順が必要です。

共有メモリの使用権を正常にリンクするための完全な手順は次のとおりです。

4. 機能紹介

         4.1 shmget関数

        shmget 関数は共有メモリ空間を作成し、共有メモリ識別子を返します。正常に完了すると、shmget() は非負の整数、共有メモリ識別子を返します。それ以外の場合は、-1 を返し、エラーを示す Errno を設定します。

        4.1.2 パラメータの導入
       

        上記の Shmget 関数パラメータ: 3 つのパラメータ、2 番目は共有メモリ空間のサイズを設定するパラメータ、3 番目のパラメータはフラグ ビットで、open 関数の O_RDONLYO や O_CTREAT などのマクロ定義オプションと同様です。

        以下の図は、3 番目のパラメータのデフォルトのオプションを示しています。IPC_CREAT、IPC_EXCL の 2 つがあります。

 

IPC_CREAT: 共有メモリ空間が存在しない場合は共有メモリ空間を作成し、共有メモリ空間が存在する場合はそれを取得します。

IPC_EXCL: 単独で使用することはできません。IPC_CREAT と一緒に使用する必要があります。

複合的な効果として、スペースが存在しない場合は作成され、スペースが存在する場合はエラーが返されます

        最初のパラメータ key_t key は ftok 関数の戻り値であるため、このキーを使用する場合は、まず ftok 関数の戻り値を取得する必要があります。

        4.2ftok関数:

         4.2.1 パラメータの導入

        ftok 関数には 2 つのパラメータがあります。最初のパラメータは open 関数の最初のパラメータと同じで、どちらもパスのファイル名を指定する必要があります。2 番目のパラメータ ID は、値の範囲が 0 ~ 255 です。この範囲内であれば自由にお取りいただけます。

        ftok 関数の使用に成功すると key_t 型のキー値が返され、関数の使用に失敗してエラーが発生した場合は -1 が返されます。

ftok(); shmget(); 関数に関するコード実験:

        注: この実験では、2 つの .cc ファイルを使用して 2 つのプロセスをシミュレートし、1 つのヘッダー ファイルを使用して ftok 関数と shmget 関数をカプセル化します。

コードは次のように実行されます。 

        結果から、2 つのプロセスのキーアドレスが等しいことがわかります。これは、2 つのプロセスが getKey() を呼び出したときの ftok 関数のパラメータが同じであるため、同じキー値が割り当てられているためです。

        2 つのプロセスは同じ Key 値を持っているため、同じ共有メモリ空間を参照し、共有メモリ空間を作成するための強固な基盤が築かれています。ここでは、サーバー プロセスを使用して共有メモリ空間を作成します。これには制限がありません。どのプロセスでも作成できます。しかし、あるプロセスが作成されると、別のプロセスは共有空間を直接取得できるため、新たにプロセスを作成する必要がなく、先代が木を植え(サーバープロセス)、次の世代がその木陰を楽しむことができます(クライアントプロセス)。 

人生の例を挙げると、次のようになります。

        友人をレストランに夕食に誘い、そのレストランのホール3の空いている個室を予約し、準備が整った後、位置情報をWeChatで友人に送信し、2月にxxホテルに夕食に来てほしいとお願いしました。部屋3。友人はメッセージを受け取って、約束の時間にホテルに来ました、個室を探す必要があったので、ウェイターに個室のことを伝え、ウェイターが彼を個室に連れて行き、私を見つけました。食べたり、おしゃべりしたり。


        この例では、私はサーバー プロセスのようなものです。私と二人が所有する共有メモリ空間を開きました。私が彼 (クライアント プロセス) に位置情報を送信し、彼はサーバー プロセスの位置情報を取得 (getShm()) します。レストランの個室の場合、彼はウェイターに特定の個室情報 (キー値) を示し、ウェイターは彼を個室 (共有メモリ空間) のドアまで連れて行き、彼 (OS システム) はそのキーを比較します。クライアントと個室の鍵が同じであれば、クライアントは個室に入室できます。
キーはシステムが認識する共有メモリ空間の一意な識別子であり、プロセスAが共有メモリ空間を作成すると、別のプロセスBはプロセスAと同じキーを保持している限り、同じ共有メモリ空間に入ることができます。

        このキーは、shmget を実行して共有メモリ属性に設定することを示すために使用されます。

操作結果:

         上の図に示すように、2 つのプロセスの shmid は両方とも 1 であり、サーバー プロセスが共有メモリ空間を正常に作成し、クライアント プロセスもサーバーによって作成されたメモリ空間を正常に取得したことを示しており、ステップ 1 は完成しました!ステップ 2 に進みます。各プロセスにページ テーブル マッピングを実行させ、共有メモリ空間に接続させます。


        ここで注意すべき点が 1 つあります。サーバー プロセスを 2 回目に実行するときは、初めて共有メモリが正常に作成されたため、2 回目の実行では shmget 関数の戻り値に関するエラーが報告されます。エラーは「ファイル」を示します。これは、shmget 関数の 3 番目のパラメーター IPC_CREAT | IPC_EXCL によって引き起こされます。これは、初めて正常に作成された shmid のライフサイクルは、プロセスではなく OS システムに従うためです。 shmid の値は追従しません。これはプロセスの最後に破棄され、Linux システムをシャットダウンしたときにのみ消えます。

        したがって、それを解決するには 2 つの方法があります。 

1. Linux システムをシャットダウンし、再起動して ./server を実行します。

2. コマンド ipcrm -m shmid (値を入力) を使用します。

ipcs コマンドは、共有メモリ空間、メッセージ キュー、およびセマフォのプロパティ パネルを表示することを意味します。

         上図から、このコマンドは共有メモリ空間のキーアドレス、shmid値、所有者値(owner)を表示していることがわかります。残りの属性については後で説明します。

        ipcrm -m shmid コマンドを使用して、初めて正常に実行された shmid 値を削除し、サーバー プロセスを再度実行して正常に実行します。ただし、shmid 値を削除すると、2 回目に作成された shmid 値は 32769 であり、最初の 32768 とは異なるため、最初に作成された共有メモリ空間もそれに応じて削除されます。

                  実際、共有メモリ == 物理メモリ ブロック (共有メモリ空間は物理メモリに格納されます) + 共有メモリの関連属性です。

        たとえば、C 言語を学習しているとき、動的ストレージ用のヒープ領域を作成するために malloc を使用します。たとえば、ポインタを使用して 1024 バイトの領域を開き、解放するとシステムはポインタをリサイクルします。アドレスであり、ポインタはこのアドレスの最初のアドレスのみを指しています。このアドレス空間の大きさと、再利用する必要のある領域のバイト数はどのようにしてわかるのでしょうか? 具体的な原理はどのように実現されるのでしょうか?


        なぜなら、開く必要があるのは 1024 バイトのスペースであり、CPU はそのために 1024 バイトより大きいスペースを開き、1034 バイトも開く可能性があり、追加の 10 バイトのスペースには、このオブジェクトの関連属性情報が保存されます。たとえば、このスペースの開始アドレス、最後のバイトのサイズ、作成時間など... CPU が解放関数を実行するとき、このヒープ スペースの関連する属性を参照して、スペースを正確に解放します。 !!!

       したがって、私たちが使用している ipcrm -m shmid 命令の原理は、システムが共有メモリ空間の関連属性 shmid を通じて共有メモリ空間を解放することです。shmid の重要性は、プロセスにおける pid の重要性と同等です。


鉄は熱いうちに打ってください。共有メモリの解放について話しているので、まずコード内の共有メモリ領域を解放する関数を学びましょう。

4.3shmctl();—共有メモリ空間を削除するために使用されます

        パラメータの紹介:

1. shmid は shmget 関数によって返される共有ストレージ識別子です;
2. cmd パラメータはマクロ定義パラメータであり、合計で 3 つあります。

        IPC_RMID: 通常、共有メモリを削除するために使用されます。

        IPC_STAT:: 共有メモリの状態を取得し、共有メモリの shmid ds 構造体を bu にコピーします。

        IPC SET: 共有メモリの状態を変更し、bu が指す shmid ds 構造体の uid、gid、mode を共有メモリの shmid ds 構造体にコピーします。(カーネルは各共有ストレージ セグメントの構造を維持します。この構造は shmid ds と呼ばれ、共有メモリのサイズ、pid、保存時間などのいくつかのパラメーターが保存されます) 3. buf は shmid ds 構造
です。通常は nullptr を埋めます。


        戻り値:

                成功した場合は 0 を返します。失敗した場合はエラーを返します。

コードデモ: 

次のステップは 3 番目のステップです。各プロセスと共有メモリ空間の間の「足かせ」を関連付けます。


4.4shmat();——共有メモリ空間のアドレスを取得する

 パラメータの紹介:

この関数には 3 つのパラメータがあります。
        最初のパラメータは shmget 関数の戻り値 shmid です。

        2 番目のパラメーターの提案は、nullptr を使用することです。NULL の場合、共有メモリは適切な仮想アドレス空間に接続されます。NULL 以外: システムはパラメータとアドレス境界アライメントに従って適切なアドレスを割り当てます。

        3 番目のパラメーターはマクロ定義オプションです。指定しない場合、デフォルト値は 0 です

        この関数の戻り値は -1 で、共有メモリ空間のアドレスの取得に失敗し、アソシエーションが成功しないことを意味します。

コードデモ: 

        コード解析:startがポインタなので、Linux64ではポインタのサイズが8バイトなので、ポインタ(8バイト)を整数int(4バイト)に変換した値が-1に等しいかどうかを判定したい( shmat 関数が正常に返るかどうかを判断して)、強制的に変換すると 4 バイトに変換できませんが、8 バイト整数と同じサイズの Long Long 整数を見つける必要があるため、次のように覚えてください。 if((int) start==-1 ))それは違います!

操作結果:

    


4.6 shmdt();——共有メモリ空間との関連付けを解除する

 コードデモ:

プロセスの両側でのデータの書き込みと読み取りを除いて、すべての準備が完了しました。概要は次のとおりです。

1. key_t Create_Key();—— ftok の戻り値キーを取得するために使用されます。

2. int Create_Shm(key_t k);—— 共有メモリ空間の作成に使用されます。

3. int Get_Shm(key_t k);—— 別のプロセスが共有メモリ空間を取得するために使用されます;
4. void*attach_Shm(int shmid);—— 各プロセスが共有メモリ空間のアドレスを取得し、それらを関連付けるために使用されます;
5 , void Dattach_Shm(void* start); —— 各プロセスに共有メモリ空間との関連付けを解除させるために使用;
6, void void Del_Shm(int shmid) —— 共有メモリ空間を作成したプロセス A に使用させるために使用スペースを解放します。


最後は両者間のデータ通信です!

        データ通信は非常にシンプルで、一方が共有メモリ領域にコンテンツを書き込み、もう一方が共有領域からコンテンツを読み取って印刷します。コード内の開始ポインタは、開かれた共有メモリ空間を指します。開始をバッファとして扱うだけです。

        コンテンツを記述する方法は 2 つあり、1 つは上記のように事前に文字列を記述して渡すだけ、もう 1 つは次のように入力してすぐに使用する方法です。


 2. 最終的なコードマップ:

        通信.hpp:

#include<cstdio>
#include<iostream>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<cstring>
#include<unistd.h>
#include<cerrno>

#define NAME_ "./tmp/"
#define ID 0x62
#define SIZE 4096

key_t Creat_Key(){
    key_t k=ftok(NAME_,ID);
    if(k<0){
        std::cerr<<"errno:"<<strerror(errno)<<std::endl;
        exit(-1);
    }
    //创建成功
    return k;
}

int Shmet(key_t k,int flags){
    int shmid=shmget(k,SIZE,flags);
    if(shmid<0){
         std::cerr<<"errno:"<<strerror(errno)<<std::endl;
        exit(-2);
    }
    return shmid;
}


int Creat_Shm(key_t k){
    return Shmet(k,IPC_CREAT |IPC_EXCL|0600);
}

int Get_Shm(key_t k){
    return Shmet(k,IPC_CREAT);
}


//关联
void* attach_Shm(int shmid){
    void* start=shmat(shmid,nullptr,0);
    if((long long)start==-1L){
            std::cerr<<"errno:"<<strerror(errno)<<std::endl;
        exit(-4);
    }
    //关联成功
    printf("related successily!\n");
    return start;
}

//去关联
void Dattach_Shm(void* start){
    if(shmdt(start)==-1){
        std::cerr<<"errno:"<<strerror(errno)<<std::endl;
        exit(-5);
    }
    //去关联成功
    printf(" abondon successily !\n");
}


void Del_Shm(int shmid){
    if(shmctl(shmid,IPC_RMID,nullptr)==-1){
           std::cerr<<"errno:"<<strerror(errno)<<std::endl;
        exit(-3);
    }
    //删除成功
    printf("删除共享内存空间\n");
}

        サーバー.cc:

#include "Comm.hpp"

int main(){
    key_t k=Creat_Key();
    printf("key:%u\n",k);

    int shmid=Creat_Shm(k);
    printf("shmid:%d\n",shmid);

    //关联
    char* start=(char*)attach_Shm(shmid);
    printf("start:%p\n",start);

    //数据通信
    while(true){
        printf(" Client says:%s\n",start);
        sleep(1);
    }

    Dattach_Shm(start);

    Del_Shm(shmid);
    return 0;
}

        クライアント.cc: 

#include "Comm.hpp"

int main(){
    int k=Creat_Key();
    printf("key:%u\n",k);

    int shmid=Get_Shm(k);
    printf("shmid:%d\n",shmid);

//关联
     sleep(1);
    char* start=(char*)shmat(shmid,nullptr,0);
    printf("start:%p\n",start);

    int cnt=0;
    const char* s="我是另一个进程,我正在给Sever发消息!";
    while(true){
       snprintf(start,SIZE,"%s:pid:[%d] cnt:[%d]",s,getpid(),cnt++); 
        sleep(1);
    }  
    
    //去关联
    Dattach_Shm(start);

    return 0;
}

 操作結果:

3. 概要:

共有メモリのメリット: 転送速度が最速!
なぜ共有メモリを使用するとプロセス転送が最速になるのでしょうか?

        その理由は、これら 2 つのプロセスが独自のバッファーを作成する必要がないためです。一つ一つ情報をダイレクトに!

共有メモリやパイプラインと比較して、2 つのプロセスがプロセス間で通信する場合、データを何回コピーする必要があるでしょうか?

        まず、パイプライン内の 2 つのプロセスのデータ コピーを確認します。 (注: 次の図では、C/C++ の stdin ストリームと stdout ストリームが考慮されていません)。

        上の図から: 書き込みプロセスでは、C バッファーにデータをパイプラインに書き込み、パイプラインからバッファーに取り出して読み取りプロセスに至るまで、4 回のデータ コピーが発生しました。

(注: 以下の図は、C/C++ の stdin ストリームと stdout ストリームを考慮しています)

        上図より: 入力ストリームと出力ストリームを追加した後、データは合計 4+2=6 回コピーされます。


        次に、共有メモリ空間内の 2 つのプロセスのデータ コピーを見てみましょう。C の stdin および stdout ストリームは無視します。

  上図から、書き込みプロセスによって書き込まれたデータは直接共有メモリ空間に送られ、その後この空間から読み取りプロセスに取り出され、合計 2 つのデータ コピーが行われます。 

         上図より: 入力ストリームと出力ストリームを追加した後、データは合計 2+2=4 回コピーされます。

       

共有メモリの欠点:

        共有メモリには同期メカニズムが提供されていないため、プロセス間通信に共有メモリを使用する場合は、他の手段 (セマフォ、ミューテックスなど) を使用してプロセス間同期を実行することがよくあります。

        率直に言うと、共有メモリを使用すると、プロセス A がデータを書き込んでいる間に、プロセス B がデータを読み取る可能性があります。今回はプロセス A がデータを完全には書き込めず、プロセス B がデータの半分を取得した後に終了したため、この結果が得られます。通信方法にデータのセキュリティがありません。


 

 

操作結果:

 

おすすめ

転載: blog.csdn.net/weixin_69283129/article/details/131448849