Linux通信 - プロセス通信IPC構築のための共有メモリソリューション | 共有メモリを利用したサーバ&クライアント通信の実現

共有メモリは IPC の最も高速な形式です。このようなメモリが、それを共有するプロセスのアドレス空間にマッピングされると、これらのプロセス間のデータ転送にはカーネルが関与しなくなります。つまり、プロセスは、カーネルへのシステムコールを実行することによって相互にデータを受け渡しなくなります。

目次

1. 共有メモリの原理

2. 共有メモリを使用する

3. 共有メモリ機能

1.shmget (共有メモリの作成に使用)

2.shmat (共有メモリをプロセスのアドレス空間に関連付ける)

3.shmctl (共有メモリの制御に使用)

4.shmdt (現在のプロセスから共有メモリセグメントを切り離す)

4. 共有メモリサーバー&クライアント通信テスト

①共有メモリの作成

 ②共有メモリとプロセスを関連づける

//2-3 通信

③プロセスと共有メモリの関連付けを解除する

④共有メモリを閉じる


1. 共有メモリの原理

  • 1. 物理メモリにスペースを作成します。
  • 2. さまざまなプロセスが、ページ テーブルを通じてこの空間を独自のプロセスの仮想アドレス空間にマップできるようにします。
  • 3. 異なるプロセスは、自身のプロセスの仮想空間内の仮想アドレスを操作することにより、共有メモリを操作します。

プロセス A とプロセス B は両方とも、それぞれのページ テーブルを通じて仮想アドレスを物理アドレスにマッピングし、プロセス A は仮想アドレスを操作してデータを書き込み、物理メモリに保存し、プロセス B は物理メモリを読み取ります。

2. 共有メモリを使用する

共有メモリは物理アドレス空間内にあり、共有領域内にあります。

  1. 共有メモリを作成する
  2. 関連プロセス - ページ テーブルを通じて、プロセスの仮想アドレスと共有メモリのアドレス間のマッピング関係を確立します。
  3. 通信
  4. プロセスの関連付けを解除します -- プロセス内のページ テーブルのキーと値を削除します。
  5. 共有メモリの解放

3. 共有メモリ機能

上図は、プロセス A とプロセス B が共有メモリを介して通信でき、同様に C と D も別の共有メモリを介して通信できることを示しています。したがって、システム内には同時に複数の共有メモリが必要であり、OS はこれらのメモリ ブロックを管理する必要があるため、共有メモリのさまざまな属性を格納する構造体が使用されます

したがって、共有メモリ == 共有メモリ カーネル データ構造 + 実際に開かれた物理空間

1.shmget (共有メモリの作成に使用)

原型:int shmget(key_t key,size_t size,int shmflg)

パラメータ: key、この共有メモリセグメントの名前、size、共有メモリサイズ、shmflg は 9 つの許可フラグで構成され、使用モードは同じです。

戻り値: 成功した場合は共有メモリセグメントの識別コードである非負の整数を返し、失敗した場合は -1 を返します。

ここで、key_t キーは ftok アルゴリズムによって生成された一意のランダムな値であり、ftok 関数は次のとおりです。

key_t ftok(c​​onst char * パス名,int proj_id)

仮パラメータ: pathname. アドレス proj_id を渡します. 数値を入力します. これら 2 つの値は任意に設定できますが、通信の両側で同じである必要があります. 具体的には、プロセス A は ftok を呼び出してキーを生成します, キーを struct shm に置き、B もそれを生成します。同じキーを使用して、B はこのキーを使用して struct shm と照合します。照合が成功すると、共有メモリが見つかります。

shmflg マクロ 意味
IPC_CREAT 単独で使用する場合は共有メモリを作成し、存在しない場合は直接作成し、存在する場合は既存の共有メモリの先頭アドレスを取得して返します。
IPC_EXCL IPC_CREAT を使用するには、IPC_CREAT に依存する必要があります。存在しない場合は共有メモリを作成し、存在する場合はすぐに終了してエラーを返します。

2.shmat (共有メモリをプロセスのアドレス空間に関連付ける)

原型:void * shmat(int shmid,const void * shmaddr,int shmflg);

パラメータ: shmid 共有メモリ識別子 shmaddr は接続のアドレスを指定します。 shmflg 可能な 2 つの値は SHM_RND と SHM_RDONLY です。

戻り値: 成功した場合は共有メモリの開始アドレスを指すポインタを返し、失敗した場合は -1 を返します (malloc と同様)

説明: shmaddr が null で、OS がアドレスを自動的に選択します。

           shmaddr が null ではなく、shmflg に SHM_RND タグがない場合は、shmaddr が接続アドレスとして使用されます。

         shmaddr が null ではなく、shmflg が SHM_RND フラグを設定すると、接続されたアドレスは shmlba の整数倍で自動的に下方調整されます。

            shmflg = SHM_RDONLY は、接続操作が読み取り専用共有メモリに使用されることを示します

3.shmctl (共有メモリの制御に使用)

原型: int shmctl(int shmid,int cmd,struct shmid_ds * buf);

パラメータ: shmget によって返される shmid 共有メモリ識別コード

            cmd: 実行されるアクション (以下の表に示すように、可能な値は 3 つあります)

           buf: 共有メモリのモードステータスとアクセス許可を保持するデータ構造体を指します。

戻り値: 成功した場合は 0、失敗した場合は -1

注文 説明する
IPC_STAT shmid_ds 構造体のデータを、共有メモリの現在関連付けられている値に設定します。
IPC_SET プロセスに十分な権限があることを前提として、共有メモリの現在関連付けられている値を shmid_ds データ構造で指定された値に設定します。
IPC_RMID 共有メモリセグメントの削除


4.shmdt (現在のプロセスから共有メモリセグメントを切り離す)

原型:int shmdt(const void * shmaddr);

パラメータ: shmaddr: shmat によって返されるポインタ

成功した場合は 0、失敗した場合は -1 を返します

注: 現在のプロセスから共有メモリを切り離すことは、共有メモリ セグメントを削除することを意味するものではありません。 

4. 共有メモリサーバー&クライアント通信テスト

共有メモリ通信を使用する手順: 共有メモリの作成、プロセスと共有メモリの関連付け、通信、関連付けの解除、共有メモリの削除

 4 つのファイルを作成します:server.cc、client.cc、common.hpp、makefile。サーバーは書き込みを実装し、クライアントは読み取りを実装します。

Makefile はサーバーとクライアントの 2 つのターゲット ファイルを生成する必要があり、次のように記述できます。

.PHONY:all
all:server client
client:client.c comm.c
 gcc -o $@ $^
server:server.c comm.c
 gcc -o $@ $^
.PHONY:clean
clean:
 rm -f client server

Common.hpp は、次のように、クライアントとサーバーに共通のいくつかの関数を宣言および定義します。

このファイルは主に共有メモリの作成、プロセスの関連付けと解除、共有メモリの解放を実装します。

①共有メモリの作成

  1. まず、shmgetを使用して共有メモリを作成します
  2. ftok を使用して生成され、両端で呼び出すことができるキーがあります\
  3. 通信時には、あるプロセスが共有メモリを作成し、別のプロセスがそれを取得します。
  4.   共有メモリが作成されると、バイト単位で測定されます。
//创建共享内存,要知道k和这个共享内存的大小
int createShm(key_t k, int size)
{
    //创建的时候,最好创建全新的
    int shmid = shmget(k,gsize,IPC_CREAT|IPC_EXCL);
    if(shmid == -1)
    {
       //创建失败
        exit(2);
    }
    
    return shmid;
}

//创建成功,一个进程获取
int getShm(key_t,int size)
{
    int shmid = shmget(k,gsize,IPC_CREAT);
    return shmid;
}

上記 2 つの関数は、一方は作成、もう一方は取得ですが、shmflg が異なるだけなので、1 つの関数にカプセル化できます。

//static 只在本文件内有效
static int createShmHelper(int k, int size,int flag)
{
    int shmid = shmget(k,gsize,flag);
    if(shmid == -1)
    {
        exit(2);
    }

    return shmid;
}



int createShm(key_t k,int size)
{
      //创建的时候注意加权限
      umask(0);
      return createShmHelper(key,gsize,IPC_CREAT|IPC_EXCL|0666);
}

int getShm(key_t k,int size)
{
    return createShmHelper(key,gsize,IPC_CREAT);
}
    

 ②共有メモリとプロセスを関連づける

        共有メモリが物理メモリに作成された後、共有メモリが動作するには、作成時にアクセス許可が必要です。共有メモリをプロセスに関連付けます。ページ テーブル マッピングを通じて、共有メモリの開始アドレスをプロセス PCB に配置します。PCB に接続する場所を自分で設定できます。システムが独立して選択できるようにするには、null に設定します。つまり、関連付けが完了します。

char * attachShm(int shmid)
{
    char * start = (char *)shmat(shmid,nullptr,0);
    return start;
}

//2-3 通信

特定の通信を自分で設定可能

③プロセスと共有メモリの関連付けを解除する

detach(char * start)
{
    int n = shmdt(start);
    (void)n;
}

④共有メモリの削除

両方のエンドを実行すると、プロセスが終了した後、再度実行するときに共有メモリを作成できないことがわかります。これは、共有メモリがプロセスで直接閉じられていないことを示しています。

共有メモリをオフにする方法は 2 つあります。

  1. ipcrm -m コマンド
  2. 関数 shmctl
void delShm(int shmid)
{
   int n =  shmctl(shmid,IPC_RMID,nullptr);
   assert(n != -1);
   (void)n;
}

Server.cc は主に、共有メモリの作成、共有メモリの関連付け、通信(共有メモリからのデータの読み取り)、関連付けの解除、共有メモリの削除を実装します。

Client.cc は主に、共有メモリの取得、共有メモリの関連付け、通信(共有メモリへのデータの書き込み)、関連付けの解除を実装します。

次に、上記のコードを適切に変更してカプセル化します。

共通.hpp:

//前面的方法不变


#define SERVER 1
#define CLIENT 0

class Init
{

public:
    //构造
    Init(int t):type(t)
    {
        key_t k = getKey();
        if(type == SERVER) 
            shmid = createShm(k, gsize);
        else
             shmid = getShm(k, gsize);

        start = attachShm(shmid);
    }

    char *getStart()
    {     
        return start;
    }

    //析构
    ~Init()
    {
        detachShm(start);
        if(type == SERVER) delShm(shmid);
    }
private:
    char *start;
    int type;     //server or client
    int shmid;
};


サーバー.cc

int main()
{
    Init init(SERVER);
    char * start = init.getStart();
    //开始通信
    ....
    //读取
    int n = 0;
    while(n <= 26)
    {
        cout<<" "<<start<<endl;  //设置start里都是字符串
        sleep(1);
    }

    //因为init是一个临时对象,所以函数跑完会自动调用析构
    return 0;
}

client.cc

int main()
{
    Init init(CLIENT);
    char * start = init.getStart();
    
    //开始通信

    ...
    //往start里写
    char c = 'A';
    while(c <= 'Z')
    {
        start[c-'A'] = c;
        c++;
        start[c] = '\0';
        sleep(1);
    }

    return 0;
}

おすすめ

転載: blog.csdn.net/jolly0514/article/details/132562297