プロセス間通信 (匿名パイプ、名前付きパイプ、共有メモリ、セマフォ)

目次

匿名パイプ

パイプラインを作成します ---pipe()

 名前付きパイプ

FIFOの作成 

FIFO動作

名前付きパイプを使用したサーバーとクライアントの通信を実装する

共有メモリ

共有メモリ関数 shmget() の作成

共有メモリアドレス取得関数 shmat()

共有メモリ関数shmdt()を削除します。

共有メモリ制御関数 shmctl()

信号量 

セマフォのデータ構造

新しいセマフォ関数 semget()

セマフォ操作関数 semop() 

制御セマフォパラメータ semctl() 関数


Linux における複数のプロセス間の通信メカニズムは IPC と呼ばれ、複数のプロセスが相互に通信する方法です。Linux には、半二重パイプ、FIFO (名前付きパイプ)、メッセージ キュー、セマフォ、共有メモリなど、さまざまなプロセス間通信方法があります。これらの通信メカニズムを使用すると、Linux でのネットワーク サーバーの開発に柔軟で堅牢なフレームワークを提供できます。


匿名パイプ

パイプは、2 つのプロセス間で標準入力と標準出力を接続するためのメカニズムです。パイプラインはプロセス間通信の長い間確立されている方法であり、UNIX オペレーティング システムの誕生以来存在しています。
1. 基本概念
パイプラインはプロセスの出力と別のプロセスの入力を接続する一方向の通信方式にすぎないため、「半二重」と呼ばれます。パイプラインはシェル内では「 | 」で表されます。

ls -l | grep *.c

ls -l の出力を「grep *.c」の入力として取り込み、パイプラインは前のプロセスで入力チャネルを確立し、後のプロセスで出力チャネルを確立し、パイプラインの左側からデータを次のプロセスに転送します。パイプラインの右側 出力は「grep *.c」にパイプされます。
このプロセスはパイプを作成し、パイプを操作するために一度に 2 つのファイル記述子を作成します。そのうちの 1 つはパイプに書き込み、もう 1 つの記述子はパイプから読み取ります。パイプはカーネルを介して 2 つのプロセスを接続し、2 つのファイル記述子は相互に接続されます。プロセスがパイプ fda[0] を介してデータを送信すると、fdb[0] から情報を取得できます。

プロセス A とプロセス B の両方がパイプラインの 2 つの記述子にアクセスできるため、パイプラインの作成後、各プロセスの方向を設定する必要があり、データはその方向に送信されることが期待されます。これには適切な計画が必要です。両方のプロセスを均一に設定する必要があります。プロセス A で読み取り、プロセス B で書き込むようにパイプ記述子を設定すると、失われます。パイプラインの読み取りおよび書き込みは、一般的な I0 システム関数と一致しています。データの書き込みには write() 関数を使用し、データの読み取りには read() 関数を使用します。オフセット関数など、一部の特定の IO 操作パイプラインはサポートされていませんlseek()。 

パイプラインを作成します ---pipe()

#include <unistd.h>

        int パイプ(int fd[2]);

fd はファイル記述子の配列で、パイプラインによって返された 2 つのファイル記述子を保存するために使用されます。配列の最初の要素 (添え字 0) は読み取り操作用に開かれ、2 番目の要素 (添え字 1) は書き込み操作用に作成されて開かれます。(0は読む場合の口、1は書く場合のペンとみなされます)。関数が正常に実行されると 0 が返され、失敗するとエラー コードが返されます。

パイプラインを確立するだけでは意味がないように思えますが、パイプラインを有効にするには、プロセスの作成と組み合わせて、親プロセスと子プロセス間の通信に 2 つのパイプラインを使用する必要があります。親プロセスと子プロセスの間にパイプラインが確立され、子プロセスはパイプラインにデータを書き込み、親プロセスはパイプラインからデータを読み取ります。このようなモデルを実装するには、親プロセスで書き込み端が閉じられ、子プロセスで読み取り端が閉じられる必要があります。

#include <iostream>
#include <unistd.h>
#include <string>
#include <string.h>
#include <cerrno>
#include <cassert>
#include <sys/types.h>
using namespace std;

int main()
{
    // 任何一种任何一种进程间通信中,一定要先保证不同的进程之间看到同一份资源
    int pipefd[2] = {0};
    //1. 创建管道
    int n = pipe(pipefd);
    if(n < 0)
    {
        std::cout << "pipe error, " << errno << ": " << strerror(errno) << std::endl;
        return 1;
    }
    //2.创建子进程
    pid_t id=fork();
    assert(id != -1);
    if(id==0)
    {
        //子进程
        //3.关闭不需要的fd,让父进程进行读取,子进程进行写入
        close(pipefd[0]);

        //4.开始通信
        string strmessage="hello,我是子进程";
        char buffer[1024];
        int count=1;
        while(true)
        {
            snprintf(buffer,sizeof buffer,"%s,计数器:%d,我的PId:%d",strmessage.c_str(),count++,getpid());
            write(pipefd[1],buffer,strlen(buffer));
            sleep(1);
        }
        close(pipefd[1]);
        exit(0);
    }

    //父进程
    //3.关闭不需要的fd,让父进程进行读取,子进程进行写入
    close(pipefd[1]);

    //4.开始通信
    char buffer[1024];
    while(true)
    {
        int n=read(pipefd[0],buffer,sizeof(buffer)-1);
        if(n>0)
        {
            buffer[n]='\0';
            cout<<"我是父进程,child give me message:"<<buffer<<endl;
            
        }
    }

    close(pipefd[0]);
    return 0;
}

特徴

  1. 一方通行のコミュニケーション
  2. fd のライフ サイクルはプロセスに従い、パイプラインのライフ サイクルはプロセスに従うため、パイプラインの本質はファイルです。
  3. これは、共通の祖先を持つプロセス (血縁関係のあるプロセス) 間の通信にのみ使用できます。通常、パイプはプロセスによって作成され、その後プロセスが fork を呼び出し、親プロセスと子プロセスの間でパイプを使用できます。(パイプはパイプを開きますが、パイプの名前は明確ではありません -- 匿名パイプ)。
  4. パイプライン通信では、書き込み数と読み取り数は、厳密には一致しない読み取りおよび書き込み数と強い関連性はありません。
  5. 一定の調整機能があり、リーダーとライターが特定の手順に従って通信できるように、同期メカニズムが付いています。
#include <iostream>
#include <string>
#include <cerrno>
#include <cassert>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

int main()
{
    int pipefd[2] = {0};
    //1. 创建管道
    int n = pipe(pipefd);
    if(n < 0)
    {
        std::cout << "pipe error, " << errno << ": " << strerror(errno) << std::endl;
        return 1;
    }
    std::cout << "pipefd[0]: " << pipefd[0] << std::endl; // 读端, 0->嘴巴->读书
    std::cout << "pipefd[1]: " << pipefd[1] << std::endl; // 写端, 1->笔->写东西的

    //2. 创建子进程
    pid_t id = fork();
    assert(id != -1); 

    if(id == 0)// 子进程
    {
        //3. 关闭不需要的fd,让父进程进行读取,让子进程进行写入
        close(pipefd[0]);

        //4. 开始通信 -- 结合某种场景
        // const std::string namestr = "hello, 我是子进程";
        // int cnt = 1;
        // char buffer[1024];
        int cnt = 0;
        while(true)
        {
            char x = 'X';
            write(pipefd[1], &x, 1);
            std::cout << "Cnt: " << cnt++<<std::endl;
            sleep(1);
            // break;

            // snprintf(buffer, sizeof buffer, "%s, 计数器: %d, 我的PID: %d", namestr.c_str(), cnt++, getpid());
            // write(pipefd[1], buffer, strlen(buffer));
        }

        close(pipefd[1]);
        exit(0);
    }

    //父进程
    //3. 关闭不需要的fd,让父进程进行读取,让子进程进行写入
    close(pipefd[1]);

    //4. 开始通信 -- 结合某种场景
    char buffer[1024];
    int cnt = 0;
    while(true)
    {
        // sleep(10);
        // sleep(1);
        int n = read(pipefd[0], buffer, sizeof(buffer) - 1);
        if(n > 0)
        {
            buffer[n] = '\0';
            std::cout << "我是父进程, child give me message: " << buffer << std::endl;
        }
        else if(n == 0)
        {
            std::cout << "我是父进程, 读到了文件结尾" << std::endl;
            break;
        }
        else 
        {
            std::cout << "我是父进程, 读异常了" << std::endl;
            break;
        }
        sleep(1);
        if(cnt++ > 5) break;
    }
    close(pipefd[0]);

    int status = 0;
    waitpid(id, &status, 0);
    std::cout << "sig: " << (status & 0x7F) << std::endl;

    sleep(100);

    return 0;
}

 

  • パイプラインのデータを全て読み終え​​ても、相手がデータを送ってこなければ、待つしかありません。読み取り呼び出しはブロックされます。つまり、プロセスは実行を一時停止し、データが到着するまで待機します。
  • ライター側がパイプラインをいっぱいにすると、書くことができなくなります。書き込み呼び出しは、プロセスがデータを読み取るまでブロックされます。
  • 書き込みエンドを閉じ、パイプライン データを読み取り、再度読み取ると、read はファイルの終わりが読み取られたことを示す 0 を返します。
  • 書き込み側が書き込みを続け、読み取り側が閉じられるのは意味がありません。OS は、無意味、非効率、またはリソースの無駄なものを保守しません。OS は書き込みを続けるプロセスを強制終了します。OS はシグナル (SIGPIPE-シグナル 13) を通じてプロセスを終了します。

 名前付きパイプ

名前付きパイプは通常のパイプとほぼ同じように機能しますが、いくつかの顕著な違いがあります。

  • 名前付きパイプは、デバイス特殊ファイルとしてファイル システムに存在します。
  • さまざまなプロセスが名前付きパイプを介してデータを共有できます。

FIFOの作成 

名前付きパイプを作成するにはさまざまな方法があります。その中でも、シェルで直接実行できます。たとえば、現在のディレクトリにnamedfifoという名前付きパイプを作成するには、次のようにします。

mkfifo という名前の fifo

 namedfifo の属性に p があり、これがパイプラインであることを示していることがわかります。

名前付きパイプはプログラムから作成することもできます。関連する関数は次のとおりです。

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

        int mkfifo(const char *ファイル名,mode_t モード);

名前付きパイプを作成します。 

int main(int argc, char *argv[]) {

        mkfifo("p2", 0644);

        0を返します。

FIFO動作

名前付きパイプ FIFO の場合、IO 操作は基本的に通常のパイプ IO 操作と同じですが、2 つの大きな違いが 1 つあります。FIFO では、open() 関数を使用して、パイプに接続されたチャネルを明示的に確立する必要があります。一般に、FIFO は常にブロックされます。つまり、名前付きパイプ FIFO を開くときに読み取り許可が設定されている場合、他のプロセスが FIFO を開いてパイプにデータを書き込むまで、読み取りプロセスは「ブロック」されます。このブロック アクションは逆にも当てはまります。プロセスがデータを書き込むためにパイプを開いた場合、パイプ内のデータの読み取りを急ぐプロセスがない場合、書き込まれたデータが読み取られるまでパイプへの書き込み操作もブロックされます。 。名前付きパイプ操作の実行時にブロックしたくない場合は、open() 呼び出しで O_NONBLOCK フラグを使用して、デフォルトのブロック アクションをオフにすることができます。

名前付きパイプを使用したサーバーとクライアントの通信を実装する

comm.hpp (client.cc およびserver.cc の名前付きパイプ)

#pragma once
#include <iostream>
#include <string>

#define NUM 1024

const std::string fifoname="./fifo";
uint32_t mode = 0666;

サーバー.cc

#include <iostream>
#include <cerrno>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "comm.hpp"

int main()
{
    // 1. 创建管道文件,只需要一次创建
    umask(0); //这个设置并不影响系统的默认配置,只会影响当前进程
    int n = mkfifo(fifoname.c_str(), mode);
    if(n != 0)
    {
        std::cout << errno << " : " << strerror(errno) << std::endl;
        return 1;
    }
    std::cout << "create fifo file success" << std::endl;
    // 2. 让服务端直接开启管道文件
    int rfd = open(fifoname.c_str(), O_RDONLY);//只读方式打开
    if(rfd < 0 )
    {
        std::cout << errno << " : " << strerror(errno) << std::endl;
        return 2;
    }
    std::cout << "open fifo success, begin ipc" << std::endl;

    // 3. 正常通信
    char buffer[NUM];
    while(true)
    {
        buffer[0] = 0;
        ssize_t n = read(rfd, buffer, sizeof(buffer) - 1);
        if(n > 0)
        {
            buffer[n] = 0;
            std::cout << "client# " << buffer << std::endl;
            //printf("%c", buffer[0]);
            //fflush(stdout);
        }
        else if(n == 0)
        {
            std::cout << "client quit, me too" << std::endl;
            break;
        }
        else 
        {
            std::cout << errno << " : " << strerror(errno) << std::endl;
            break;
        }
    }

    // 关闭不要的fd
    close(rfd);

    unlink(fifoname.c_str());

    return 0;
}

client.cc 

#include <iostream>
#include <cstdio>
#include <cerrno>
#include <cstring>
#include <cassert>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
// #include <ncurses.h>
#include "comm.hpp"

int main()
{
    //1. 不需创建管道文件,我只需要打开对应的文件即可!
    int wfd = open(fifoname.c_str(), O_WRONLY);//只写方式打开
    if(wfd < 0)
    {
        std::cerr << errno << ":" << strerror(errno) << std::endl;
        return 1;
    }

    // 可以进行常规通信了
    char buffer[NUM];
    while(true)
    {
        std::cout<<"请输入你的消息#:";
        char *msg = fgets(buffer,sizeof(buffer),stdin);
        assert(msg);
        (void)msg;

        buffer[strlen(buffer)-1]=0;
        if(strcasecmp(buffer,"quit") == 0) break;
        ssize_t n = write(wfd,buffer,strlen(buffer));
        assert(n > 0);
        (void)n;
    }
    
    close(wfd);

    return 0;
}

共有メモリ

共有メモリとは、複数のプロセス間でメモリ領域を共有するプロセス間通信方式であり、複数のプロセス間でメモリセグメントをマッピングすることでメモリの共有を実現します。共有メモリ方式の通信には中間プロセスが存在せず、パイプライン、メッセージ キューなどのメソッドは中間メカニズムを介してデータを変換する必要があるため、これは IPC の最も速い方法です。これに対し、共有メモリ方式は、特定のメモリセグメントマッピングを直接変換し、複数のプロセス間の共有メモリはアドレスが異なるだけで同じ物理空間であるため、コピーする必要がなく、この空間を直接使用できます。

共有メモリ関数 shmget() の作成

関数 shmget() は、新しい共有メモリ セグメントを作成するか、既存の共有メモリ セグメントにアクセスするために使用されます。関数 shmget() のプロトタイプは次のとおりです。

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

key: 共有メモリセグメントの名前

サイズ: 共有メモリのサイズ

shmflg: 9 つの許可フラグで構成され、その使用法はファイルの作成時に使用されるモード mode フラグと同じです。

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

shmget() の最初の引数はキーワードの値です。この値は、カーネル内に存在する他の共有メモリ セグメントのキー値と比較されます。比較後のオープン操作とアクセス操作は、shmflg パラメータの内容に依存します。

  • IPC_CREAT: カーネルにメモリ セグメントが存在しない場合は、メモリ セグメントを作成します。
  • IPC_EXCL: IPC CREAT とともに使用すると、メモリ セグメントがすでに存在する場合、呼び出しは失敗します。

IPC_CREAT のみが使用される場合、shmget() は新しく作成されたメモリ セグメントのセグメント識別子、または同じキー値を持つカーネル内にすでに存在するメモリ セグメントの識別子のいずれかを返します。IPC_CREAT と IPC_EXCL を同時に使用すると、2 つの結果が生じる可能性があります: メモリ セグメントが存在しない場合は、新しいメモリ セグメントが作成され、メモリ セグメントが既に存在する場合、呼び出しは失敗し、-1 が返されます。 。IPC_EXCL はそれ自体では役に立ちませんが、IPC_CREAT と組み合わせて使用​​すると、既存のメモリ セグメントがアクセスのために開かれるのを防ぐことができます。プロセスが特定のメモリ セグメントの有効な IPC 識別子を取得したら、次のステップは、そのメモリ セグメントをアタッチするか、そのメモリ セグメントを独自のアドレス空間にマップすることです。

IPC_CREAT を単独で使用する: 共有メモリを作成します。共有メモリが存在しない場合は作成し、存在する場合は既存の共有メモリを取得して IPC_EXCT を返します。単独では使用できません。通常は IPC_CREAT と連携する必要があります。 IPC_CREAT |
IPC_EXCT
 :共有メモリを作成、共有メモリが存在しない場合は作成、存在する場合はエラーでリターン(共有メモリの作成に成功した場合、共有メモリは最新である必要があります)

共有メモリアドレス取得関数 shmat()

関数 shmat() を使用して共有メモリのアドレスを取得し、共有メモリの取得に成功すると、通常のメモリと同様に読み書きが可能になります。関数のプロトタイプは次のとおりです。

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

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

shmid: 共有メモリID

shmaddr: 接続のアドレスを指定します

shmflg: 可能な 2 つの値は SHM_RND と SHM_RDONLY です

戻り値: 正常に共有メモリの最初のバイトへのポインタを返し、失敗した場合は -1 を返します。

shmaddr パラメータ値が 0 に等しい場合、カーネルはマップされていない領域を見つけようとします。ユーザーはアドレスを指定できますが、通常、このアドレスは所有するハードウェアにアクセスするため、または他のアプリケーションとの競合を解決するためにのみ使用されます。SHM_RND フラグとフラグ パラメータの論理和を演算し、その結果をフラグ パラメータとして設定することで、送信されるアドレス ページを整列させることができます。
さらに、SHM_RDONLY フラグが flag パラメータと OR 演算され、その結果が flag パラメータとして設定される場合、マップされた共有メモリ セグメントは読み取り専用としてのみマークできます。
アプリケーションが成功した場合、共有メモリの動作は一般メモリと同じで、直接書き込み、読み出しが可能で、オフセット動作も可能です。 

共有メモリ関数shmdt()を削除します。

関数 shmdt() は、共有メモリのセクションを削除するために使用されます。関数のプロトタイプは次のとおりです。

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

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

戻り値: 成功した場合は 0 が返され、失敗した場合は -1 が返されます。

注: 現在のプロセスから共有メモリ セグメントを切り離しても、共有メモリ セグメントが削除されるわけではありません。

プロセスが共有メモリ セグメントを必要としなくなった場合、この関数を呼び出してセグメントを切断する必要があります。これは、カーネルからメモリ セグメントを削除することと同じではありません。切断操作が成功すると、関連する shmid ds 構造体の shm natch メンバーの値が 1 減らされます。この値が 0 に減らされると、カーネルは実際にメモリ セグメントを削除します。

共有メモリ制御関数 shmctl()

共有メモリ制御関数 shmctl は、iocl() と同様のメソッドを使用して共有メモリを操作します。共有メモリ ハンドルにコマンドを送信して、特定の関数を完了します。関数 shmcl() のプロトタイプは次のとおりです。 shmid は共有メモリのハンドル、emd は共有メモリに送信されるコマンド、最後のパラメータ buf は共有メモリにコマンドを送信するパラメータです。

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

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

cmd: 実行されるアクション (可能な値は 3 つあります)

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

戻り値: 成功した場合は 0 が返され、失敗した場合は -1 が返されます。

  •  IPC_STAT: shmid_ds 構造体のデータを、共有メモリの現在関連付けられている値として設定します。
  •  IPC_SET: メモリセグメントの shmid_ds 構造体を取得し、buf パラメータで指定されたアドレスに格納します。IPC_SET はメモリ セグメントの shmid_ds 構造体の ipc_perm メンバの値を設定し、このコマンドは buf パラメータから値を取得します。
  • IPC_RMID: メモリセグメントを削除対象としてマークします。このコマンドは、実際にはメモリからメモリ セグメントを削除しません。代わりに、将来の削除のためにメモリセグメントにマークを付けるだけです。実際の削除は、現在メモリ セグメントに接続されている最後のプロセスがメモリ セグメントから適切に切断された場合にのみ行われます。もちろん、現在メモリ セグメントにプロセスが接続されていない場合、削除はただちに行われます。共有メモリセグメントから適切に切断するには、プロセスは shmdt() 関数を呼び出す必要があります。

ちょっとした実験: 

 comm.hpp (メソッドのカプセル化)

#ifndef __COMM_HPP__
#define __COMM_HPP__

#include <iostream>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <cassert>
#include <string>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/stat.h>

using namespace std;

// IPC_CREAT and IPC_EXCT
// 单独使用 IPC_CREAT :创建一个共享内存,如果共享内存不存在 ,就创建它;如果存在,就获取已经存在的共享内存并且返回
// IPC_EXCT不能单独使用,一般都要配合IPC_CREAT
// IPC_CREAT |IPC_EXCT:创建一个共享内存,如果共享内存不存在,就创建它,如果存在,出错并返回(如果共享内存创建成功,则这个共享内存一定是最新的)

#define PATHNAME "."
#define PROJID 0x6666

const int gsize = 4096;

key_t getKey()
{
    key_t k = ftok(PATHNAME, PROJID);
    if (k == -1)
    {
        cerr << "error:" << errno << strerror(errno) << endl;
        exit(1);
    }
    return k;
}

// 十六进制转换
string toHex(int x)
{
    char buffer[64];
    snprintf(buffer, sizeof buffer, "0x%x", x);
    return buffer;
}

static int createShmHelper(key_t k, int size, int flag)
{
    int shmid = shmget(k, size, flag);
    if (shmid == -1)
    {
        cerr << "error:" << errno << ":" << strerror(errno) << endl;
        exit(2);
    }
    return shmid;
}

int createShm(key_t k, int size)
{
    umask(0);
    return createShmHelper(k, size, IPC_CREAT | IPC_EXCL | 0666);
}

int getShm(key_t k, int size)
{
    umask(0);
    return createShmHelper(k, size, IPC_CREAT);
}
char *attachShm(int shmid)
{
    char *start = (char *)shmat(shmid, nullptr, 0);
    return start;
}

void detachShm(char *start)
{
    int n = shmdt(start);
    assert(n != -1);
    (void)n;
}
void delShm(int shmid)
{
    int n = shmctl(shmid, IPC_RMID, nullptr);
    assert(n != -1);
    (void)n;
}
#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;
};
#endif

サーバー.cc

#include "comm.hpp"
#include <unistd.h>
int main()
{
    Init init(SERVER);
    char* start = init.getStart();

    int n = 0;
    while(n <= 26)
    {
        cout<<"client -> server#"<<start<<endl;
        sleep(1);
        n++;
    }
    // //1.创建key
    // key_t k = getKey();
    // cout<<"server key:"<<toHex(k)<<endl;

    // //2.创建共享内存
    // int shmid = createShm(k,gsize);
    // cout<<"server shmid:"<<shmid<<endl;

    // //3.将自己和共享内存关联起来
    // char* start = attachShm(shmid); 
    // sleep(5);
    // //4.将自己和共享内存去关联
    // detachShm(start);
    // //删除共享内存
    // delShm(shmid);
    return 0;
}

client.cc

#include "comm.hpp"
#include <unistd.h>
#include <string>
#include <vector>
int main()
{
    Init init(CLIENT);
    char* start = init.getStart();

    char c = 'A';

    while(c <= 'Z')
    {
        start[c - 'A'] = c;
        c++;
        start[c - 'A'] = '\0';
        sleep(1);
    }
    // //1.创建key
    // key_t k = getKey();
    // cout<<"client key:"<<toHex(k)<<endl;

    // //2.创建共享内存
    // int shmid = createShm(k,gsize);
    // cout<<"client shmid:"<<shmid<<endl;

    // //3.将自己和共享内存关联起来
    // char* start = attachShm(shmid);

    // sleep(10);
    // //4.将自己和共享内存去关联
    // detachShm(start);
    return 0;
}

信号量 

セマフォは、複数のプロセスによって共有されるリソースへのアクセスを制御するために使用されるカウンターです。これらは、あるプロセスが特定のリソース上で動作している間に、別のプロセスが特定のリソースにアクセスするのを防ぐためのロック メカニズムとしてよく使用されます。プロデューサおよびコンシューマ モデルは、セマフォの典型的な使用法です。

セマフォのデータ構造

セマフォ データ構造は、セマフォ プログラミングでよく使用されるデータ構造です。

Union semun { /*セマフォ演算の共用体構造体*/
int val; /*整数変数*/
struct semids *buf; /*semid_ ds 構造体ポインタ*/
unsigned short *array; /*配列型*/
struct semiinfo *_buf; /*セマフォの内部構造*/
};

新しいセマフォ関数 semget()

semget() 関数は、セマフォの新しいセットを作成するか、既存のセットにアクセスするために使用されます。そのプロトタイプは次のとおりです。最初のパラメータ key は ftok によって生成されたキー値、2 番目のパラメータ nsems パラメータは新しいコレクションに作成するセマフォの数を指定でき、3 番目のパラメータ semflsg はコレクションを開く方法です。セマフォ。

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

semflsg はセマフォを開く方法です。
IPC_CREAT: そのようなセマフォ セットがカーネルに存在しない場合は、作成します。
IPC_EXCL: IPC_CREAT とともに使用すると、セマフォ セットがすでに存在する場合、操作は失敗します。IPC_CREAT が単独で使用された場合、 semget() は、新しく作成されたセマフォ セットのセマフォ セット識別子、または同じキー値を持つ既存のセットの識別子のいずれかを返します。IPC_EXCL と IPC_CREAT を同時に使用すると、2 つの結果が考えられます: コレクションが存在しない場合は、新しいコレクションが作成され、コレクションが既に存在する場合、呼び出しは失敗し、-1 が返されます。IPC_EXCL はそれ自体では役に立ちませんが、IPC_CREAT と組み合わせると、既存のセマフォのセットがアクセスのために開かれるのを防ぐために使用できます。

typedef int sem_t;
union semun {   /*信号量操作的联合结构*/
	int val;   /*整型变量*/
	struct semid ds * buf;   /*semid_ ds结构指针*/
	unsigned short* array;  /*数组类型*/
}arg;
sem_t CreateSem(key_t key, int value)//建立信号量
{
	union semun sem;/*信号量结构变量*/
	sem t semid;/*信号量ID*/
	sem.val = value;/*设置初始值*/
	semid = semget(key, 0, IPC_ CREAT | 0666);/*获得信号量的ID*/

	if (-1 == semid)/*获得信号量ID失败*/
	{
		printf("create semaphore error\n"); /*打印信息*/
		return -1;/*返回错误*/
	}
	semctl(semid, 0,SETVAL, sem);/*发送命令,建立value个初始值的信号量*/
	return semid;/*返回建立的信号量*/
	
}

 CealeSem() 関数は、ユーザーのキー値に従ってセマフォを生成し、セマフォの初期値をユーザーが入力した値に設定します。

セマフォ操作関数 semop() 

セマフォの P および V 操作は、(semget() 関数を使用して) 確立されたセマフォにコマンドを送信することによって完了します。セマフォにコマンドを送信する関数は semop() で、この関数のプロトタイプは次のとおりです。

#include<sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops,unsigned nsops) ;

semop() 関数の 2 番目のパラメータ (sops) は、セマフォ セットに対して演算を実行する配列へのポインタで、3 番目のパラメータ (nsops) は配列内の演算の数です。sops パラメータは、sembuf 構造体の配列を指します。sembuf 構造体は linux/sem.h で次のように定義されています。

struct sembuf {         ushort sem_num; /*セマフォの番号*/         short sem_op; /*セマフォの動作*/         short sem_ flg; /*セマフォの動作フラグ*/


}

  • sem_num: ユーザが処理するセマフォの番号。
  • sem_op: 実行される操作 (正、負、またはゼロ)。
  • sem_flg: セマフォ操作のフラグ。sem_op が負の場合、セマフォから値を減算します。sem_op が正の場合、セマフォからの値を追加します。sem_op が 0 の場合、プロセスはセマフォの値が 0 になるまでスリープするように設定されます。 

たとえば、「stuet sembuf sem={0, +1, NOWAIT}:」は、セマフォ 0 に 1 を加算することを意味します。基本的な P および V 操作は関数 semop() を使用して構築できます。コードは次のとおりです。Sem_P は、セマフォ値を増やすために {0, +1, NOWAIT} の sembuf 構造体を構築します。 Sem_V は、セマフォが渡されることに対応して、セマフォを減らす操作を実行するために {0, -1, NOWAIT} の sembuf 構造体を構築します。 (セミ) 関数による。

int Sem P(sem t semid)  /*增加信号量*/ 
{

        struct sembuf sops={0,+1, IPC NOWAIT} ; /*建立信号量结构值*/
        return (semop (semid, &sops,1)) ; /*发送命令*/

}
int Sem V(sem t semid)  /*减小信号量值*/
{
        struct sembuf sops={0,-1,IPC NOWAIT} ;  /*建立信号量结构值*/
        return (semop (semid, &sops,1));  /*发送信号量操作方法*/

}

制御セマフォパラメータ semctl() 関数

ファイル操作の ioctl() 関数と同様に、セマフォに対する他の操作は semctl() 関数を通じて実行されます。関数 semctl() のプロトタイプは次のとおりです。

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

関数 semctl() は、セマフォ セットに対する制御操作を実行するために使用されます。この呼び出しは msgctl() 関数に似ており、msgctl() 関数はメッセージ キューの操作に使用されます。semctl() 関数の最初のパラメータはキーワードの値です (この場合、それは semget 関数の呼び出しによって返された値です)。2 番目のパラメータ (semun) は、操作を実行するセマフォの番号です。これは、セマフォ セットのインデックス値です。セット内の最初のセマフォ (このセマフォのみが存在する場合もあります) の場合、そのインデックス値は、値は 0。cmd パラメータは、コレクションに対して実行されるコマンドを表します。

  • IPC_STAT: コレクションの semid_ds 構造体を取得し、semun Union の buf パラメータで指定されたアドレスに格納します。
  • IPC_SET: セットの semid_ds 構造体の ipc_perm メンバーの値を設定します。このコマンドの値は、精液結合の buf パラメータから取得されます。
  • IPC_ _RMID: セットをカーネルから削除します。
  • GETALL: セット内のすべてのセマフォの値を取得するために使用されます。整数値は、共用体の配列メンバーによって指定された符号なし短整数の配列に格納されます。
  • GETNCNT: 現在リソースを待っているプロセスの数を返します。
  • GETPID: 最後の semop 呼び出しを実行したプロセスの PID を返します。
  • GETVAL: コレクション内のセマフォの値を返します。
  • GETZCNT: リソース使用率が 100% に達するのを待機しているプロセスの数を返します。
  • SETALL: セット内のすべてのセマフォの値を、共用体の配列メンバーに含まれる対応する値に設定します。
  • SETVAL: セット内の単一のセマフォの値を、共用体の val メンバーの値に設定します。
     
void SetvalueSem(sem_t semid, int val)
{
	union semun sem;
	sem.val = value;
	semctl(semid, 0, SETVAL, sem);
}
void GetvalueSem(sem_t semid)
{
	union semun sem;
	return semctl(semid, 0, GETVAL, sem);
}

SetvalueSem() 関数はセマフォの値を設定します。セマフォの値は SETVAL コマンドを通じて実現され、設定値はジョイント変数 sem の val フィールドを通じて実現されます。GetvalueSem() 関数はセマフォの値を取得するために使用され、semctl() 関数のコマンド GETVAL を使用すると、指定されたセマフォの現在の値が返されます。もちろん、セマフォの破棄は semctl() 関数を使用して行うこともできます。

void DeatroySem(sem_t semid)
{
	union semun sem;
	sem.val = 0;

	semctl(semid, 0, IPC_RMID, sem);
}

おすすめ

転載: blog.csdn.net/m0_55752775/article/details/130699692