【003 オペレーティングシステム】プロセス間通信方式とは何ですか?長所と短所は何ですか?

1. プロセス間通信方式とそのメリット・デメリット

1.匿名のパイプパイプ

原理:オペレーティング システム カーネルによって提供されるバッファに基づいて、あるプロセスの出力を別のプロセスの入力に接続することによってプロセス間通信を実装します。プロセスがパイプラインにデータを書き込むと、データはパイプラインのバッファーに保存され、別のプロセスがパイプラインからデータを読み取るのを待ちます。バッファーがいっぱいでない限り、書き込みプロセスは読み取り前にブロックされません。別のプロセスがパイプからデータを読み取ると、データはバッファから読み取られてそのプロセスに渡され、データが来ない場合はブロックが待機します (したがって同期通信)。(概要:半二重通信、一方向のデータフロー、アフィニティ関係のあるプロセス間でのみ使用可能)

利点:シンプルで便利。

欠点:一方向の通信に限定され、そのプロセスとその関連プロセスの間でのみ作成でき、バッファ領域が制限されます。

2. 名前付きパイプ FIFO

原則:名前付きパイプの原則は匿名パイプに似ており、オペレーティング システムのカーネルによって提供されるバッファに基づいてプロセス間通信も実装されます。プロセスが名前付きパイプにデータを書き込むと、データは名前付きパイプのバッファーに保存され、別のプロセスが名前付きパイプからデータを読み取るのを待ちます。バッファーがいっぱいでない限り、書き込みプロセスは読み取り前にブロックされません。別のプロセスが名前付きパイプからデータを読み込むと、そのデータはバッファから読み取られてプロセスに渡され、データが来ない場合はブロックして待機します (つまり、同期通信)。

利点:あらゆる関係のプロセス間の通信を実現できます。

短所:システムに長期間存在し、不適切に使用するとエラーが発生しやすく、バッファが限られています。

3. メッセージキュー メッセージキュー

原則: 1. メッセージ キューは先入れ先出しキュー タイプのデータ構造であり、実際にはシステム カーネル内の内部リンク リストです。メッセージはキューに順番に挿入され、送信プロセスはメッセージをキューの最後に追加し、受信プロセスはキューの先頭からメッセージを読み取ります。
2. 複数のプロセスが同時にメッセージ キューにメッセージを送信したり、メッセージ キューから同時にメッセージを受信したりできます。送信プロセスはメッセージをキューの最後尾に送信し、受信プロセスはメッセージキューの先頭からメッセージを読み取り、読み取ったメッセージはキューから削除されます。

メリット:任意のプロセス間の通信が実現でき、メッセージ送受信の同期もシステムコール関数で実現できるため、同期問題を考慮する必要がなく便利である。

欠点:情報のコピーには追加の CPU 時間が必要となるため、大量の情報や頻繁な操作が必要な状況には適していません。

4. 共有メモリ 共有メモリ

原則:共有メモリは、他のプロセスがアクセスできるメモリのセクションをマップすることです。この共有メモリは 1 つのプロセスによって作成されますが、複数のプロセスがアクセスできます。共有メモリは、最も高速な IPC (プロセス間通信) 方式であり、他のプロセス間通信方式の動作効率の低さを考慮して特別に設計されています。

利点:コピーする必要がなく、高速で大量の情報が得られます。

欠点:共有メモリはデータ共有のメカニズムのみを提供し、同期と相互排除のメカニズムは提供しません。

5. セマフォ

原則:セマフォは通常、共有リソースの利用可能な量を記録するために使用される整数変数であり、複数のスレッドまたはプロセスによって同時にアクセスおよび変更できます。(セマフォは本質的にカウンターです)

利点:プロセスを同期できる。

短所:メッセージ パッシングはサポートされておらず、同期相互排他のみが可能であり、機能が制限されています。

6.信号信号

原則:シグナルは、イベントが発生したことを受信プロセスに通知するために使用される比較的複雑な通信方法です。主に、プロセス間および同じプロセスの異なるスレッド間の同期手段として使用されます。Linux の場合、実際の信号はソフト割り込みであり、多くの重要なプログラムは信号を処理する必要があります。たとえば、エンド ユーザーが ctrl+c を入力してプログラムを中断すると、プログラムはシグナル メカニズムを通じて停止されます。

  • プロセスはシグナルを送信します。シグナルは、kill、raise、alarm およびその他の関数を通じてターゲット プロセスに送信されます。

  • プロセスは信号を受け取ります。signal、sigaction、その他の関数を通じて信号処理関数を登録します。

  • シグナルが来ると処理関数が呼び出され、関数内のシグナルに応じて対応する処理が行われます。

利点:信号が異なれば、異なる情報を伝えることができます。各信号に対応する処理関数を定義することで、比較的柔軟な通信を実現できます。

デメリット:伝達される情報が少なくなります。

7. ソケット

原理:ソケットはプロセス間通信メカニズムでもあり、他の通信メカニズムとは異なり、異なるマシン間のプロセス通信に使用できます。

利点: 1) 送信データがバイトレベルでカスタマイズ可能、データ量が少なく効率が高い 2) データ送信時間が短くパフォーマンスが高い 3) リアルタイムに適しているクライアントとサーバー間の情報のやりとり; 4) 暗号化可能、強力なデータセキュリティ
欠点: 1) 送信されたデータを分析し、アプリケーションレベルのデータに変換する必要があります。

プロセス間通信方式とそれぞれのメリット・デメリット_4つのプロセス間通信方式の違いとメリット・デメリットの分析とまとめ_Echo_Annaのブログ-CSDNブログ

https://www.cnblogs.com/jiangknownet/p/14516630.html (要約は非常に優れています)


2. プロセス間通信方式の選択

PIPE と FIFO (名前付きパイプ) は、プロセス間で非常に短い高頻度のメッセージを相互に送信するために使用され、通常、これら 2 つの方法は 2 つのプロセス間の通信に適しています。

共有メモリは、プロセス間で共有される非常に大きなデータと、高頻度の読み取りおよび書き込み操作を実現するために使用されます。

ソケットの使用を検討する人もいます。主に分散開発で使用されます。


3. スレッド間の同期方法は何ですか?

  • アトミック操作
  • スピンロック
  • 読み取りおよび書き込みスピン ロック
  • シーケンシャル ロック (seqlock、2.6 カーネル以降のバージョンにのみ含まれる)
  • 信号量
  • セマフォの読み取りと書き込み
  • ミューテックス
  • Big Kernel Lock (BKL、Big Kernel Lock、2.4 カーネルにのみ含まれており、説明されていません)
  • 大型リーダー ロック (brlock、2.4 カーネルにのみ含まれ、発言しない)
  • RCU (読み取り/書き込みロックの最適化、2.6 カーネル以降のバージョンにのみ含まれる)

【005 基礎知識】Linuxの同期の仕組みとは?_Kashin のブログ - CSDN ブログ


4. プロセス間通信方式の詳細説明

1.パイプライン

  • 新しい子プロセスを作成するには、fork() 関数を呼び出します。fork() 関数は 2 回戻ります。1 回目は親プロセスで子プロセスのプロセス ID を返し、もう 1 回目は子プロセスで 0 を返します。
  • 親プロセスで、sleep(3) 関数を呼び出して子プロセスを最初に実行させ、プロンプト メッセージを出力し、パイプラインの読み取り側を閉じてから、write(fd[1], "hello from Father", strlen) を呼び出します。 ("hello from Father" )) 関数は、パイプの書き込み端にデータを書き込みます。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
 
/*使用匿名管道实现进程间通信*/
int main()
{
        int fd[2];//fd[0]为读端 fd[1]为写端
        pid_t pid;
        char buf[128];
        //int pipe(int pipefd[2]);
        if(pipe(fd) == -1)//创建管道
        {
                printf("管道创建失败\n");
                perror("why"); // 打印错误信息
        }
 
        pid = fork(); // 创建子进程
 
        if(pid < 0 )
        {
                printf("子进程开辟失败\n");
                perror("why"); // 打印错误信息
        }else if(pid > 0){
 
                sleep(3);//让子进程先执行
                printf("这是一个父进程\n");//父进程完成写操作
                close(fd[0]); // 关闭读端
                write(fd[1],"hello from father",strlen("hello from father")); // 往写端写入数据
        }else{
 
                printf("这是一个子进程\n");//子进程完成读操作
                close(fd[1]); // 关闭写端
                read(fd[0],buf,sizeof(buf)); // 从管道读取数据,并存储到buf数组中
                printf("buf = %s\n",buf); // 输出读取的数据
        }
 
        return 0;
}

2.名前付きパイプ FIFO

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

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
//       int mkfifo(const char *pathname, mode_t mode);
 
int main()
{
        if(mkfifo("myfifo",0600) == -1 && errno != EEXIST)
        {
                printf("mkfifo failed\n");
                perror("why");
        }
 
        return 0;
}

read.c のパイプを読み取ります。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
 
//       int mkfifo(const char *pathname, mode_t mode);
 
int main()
{
        int nread;
        char buf[30] = {'\0'};
 
        if(mkfifo("myfifo",0600) == -1 && errno != EEXIST)//创建命名管道
        {
                printf("mkfifo failed\n");
                perror("why");
        }
 
        int fd = open("./myfifo",O_RDONLY);//以只读的形式打开管道,程序阻塞在这,直到有另一个进程对其执行写操作
        if(fd < 0)
        {
                printf("read open failed\n");
        }else
        {
                printf("read open successn\n");
        }
 
        while(1)
        {
                nread = read(fd,buf,sizeof(buf));
                printf("read %d byte,context is:%s\n",nread,buf);
        }
 
        close(fd);
 
        return 0;
}

write.c のパイプに書き込みます。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
//       int mkfifo(const char *pathname, mode_t mode);
 
int main()
{
        int nread;
        char buf[30] = "message from myfifo";
 
        if(mkfifo("myfifo",0600) == -1 && errno != EEXIST)//创建命名管道
        {
                printf("mkfifo failed\n");
                perror("why");
        }
 
        int fd = open("./myfifo",O_WRONLY);//打开管道,程序阻塞在这,直到其他进程为读而打开它
        if(fd < 0)
        {
                printf("write open failed\n");
        }
        else
        {
                printf("write open success\n");
        }
 
        while(1)
        {
                sleep(1);
                write(fd,buf,strlen(buf));
        }
        close(fd);
 
        return 0;
}
  1. mkfifo() 関数は名前付きパイプの作成に使用され、パラメータはパイプのパスと権限です。パイプがすでに存在する場合は、-1 を返し、errno を EEXIST に設定します。

  2. 読み取りプロセスはパイプを読み取り専用 (O_RDONLY) として開き、書き込みプロセスはパイプを書き込み専用 (O_WRONLY) として開きます。

  3. パイプがオープンされると、読み取りプロセスは読み取り呼び出しでブロックされ、書き込みプロセスは書き込み呼び出しでブロックされます。

  4. 両方のプロセスがパイプを開くと、読み取りプロセスの読み取りは書き込まれたデータを返し、書き込みプロセスの書き込みも正常に戻ります。

3.メッセージキュー:

メッセージ キューは、カーネルに保存されるメッセージのリンクされたリストです。メッセージキューは識別子(キューID)によって識別されます。

ユーザー プロセスは、メッセージ キューにメッセージを追加したり、メッセージ キューからメッセージを読み取ることができます。

// メッセージ キューを作成または開きます: 成功した場合はキュー ID を返し、失敗した場合は -1 を返します
int msgget(key_t key, int flag);
// メッセージを追加します: 成功した場合は 0 を返し、失敗した場合は -1 を返します
int msgsnd(int msqid , const void *ptr , size_t size, int flag);
// メッセージの読み取り: メッセージ データの長さを正常に返すか、失敗した場合は -1 を返します
int msgrcv(int msqid, void *ptr, size_t size, long type, int flag); //
メッセージ キューを制御します : 成功した場合は 0、失敗した場合は -1 を返します
int msgctl(int msqid, int cmd, struct msqid_ds *buf);

msgSend.c ファイル:

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
//       int msgget(key_t key, int msgflg);
// 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);
struct msgbuf{
        long mtype;       /* message type, must be > 0 */
        char mtext[128];    /* message data */
};
 
 
int main()
{
        struct msgbuf sendbuf={888,"message from send"};
        struct msgbuf readbuf;
 
        key_t key;
 
        if((key = ftok(".",'z')) < 0){
                printf("ftok error\n");
        }
        int msgId = msgget(key,IPC_CREAT|0777);
 
        if(msgId == -1){
                printf("get quen failed\n");
        }
 
        msgsnd(msgId,&sendbuf,strlen(sendbuf.mtext),0);
        printf("send over\n");
 
        msgrcv(msgId,&readbuf,sizeof(readbuf.mtext),999,0);
        printf("read from get is:%s\n",readbuf.mtext);
 
        msgctl(msgId,IPC_RMID,NULL);
 
        return 0;
}

msgGet.c ファイル:

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
//       int msgget(key_t key, int msgflg);
// 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);
struct msgbuf{
        long mtype;       /* message type, must be > 0 */
        char mtext[128];    /* message data */
};
 
int main()
{
        struct msgbuf readbuf;
        memset(readbuf.mtext, '\0', sizeof(readbuf.mtext));
        struct msgbuf sendbuf={999,"thank for your reach"};
 
        key_t key;
 
        //获取key值
        if((key = ftok(".",'z')) < 0){
                printf("ftok error\n");
        }
 
        int msgId = msgget(key,IPC_CREAT|0777);
 
        if(msgId == -1){
                printf("get quen failed\n");
                perror("why");
        }
 
        msgrcv(msgId,&readbuf,sizeof(readbuf.mtext),888,0);
        printf("read from send is:%s\n",readbuf.mtext);
 
        msgsnd(msgId,&sendbuf,strlen(sendbuf.mtext),0);
 
        msgctl(msgId,IPC_RMID,NULL);
 
        return 0;
}

msgsnd() は、指定されたメッセージ キューにメッセージを送信するために使用され、次のパラメータが含まれます。

  • msgId: 送信するメッセージキューのID。

  • &sendbuf: 送信メッセージ バッファへのポインタ。メッセージ構造は mtext で表されます。

  • strlen(sendbuf.mtext): 送信するデータの長さ。

  • 0: 送信フラグ、通常は 0。

msgrcv() は、指定されたメッセージ キューからメッセージを受信するために使用され、次のパラメータが含まれます。

  • msgId: メッセージを受信するキューの ID。

  • &readbuf: 受信メッセージ バッファへのポインタ。メッセージ構造は mtext で表されます。

  • sizeof(readbuf.mtext): 今回受信するメッセージの最大長を指定します。

  • 999: 受信メッセージ タイプ。999 は、任意のタイプのメッセージを受信することを意味します。

  • 0: 受信フラグ。通常は 0。

4. 共有メモリ

共有メモリとは、特定の記憶領域を共有する 2 つ以上のプロセスを指します。

// 共有メモリを作成または取得します: 正常に共有メモリ ID を返し、失敗した場合は -1 を返します
int shmget(key_t key, size_t size, int flag);
// 共有メモリを現在のプロセスのアドレス空間に接続します: return共有メモリを正常にポイントしている ポインタ、失敗した場合は -1 を返します
void *shmat(int shm_id, const void *addr, int flag);
// 共有メモリから切断します: 成功した場合は 0、失敗した場合は -1 を返します
int shmdt(void * addr); 
// 共有メモリの制御に関する情報: 成功した場合は 0、失敗した場合は -1 が返されます
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);

詳細については、https: //www.cnblogs.com/52php/p/5861372.htmlを参照してください。

shmwrite.c ファイル:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
 
//       int shmget(key_t key, size_t size, int shmflg);
// void *shmat(int shmid, const void *shmaddr, int shmflg);
 
//       int shmdt(const void *shmaddr);
 
int main()
{
        int shmId;
        key_t key;
        char *shmaddr;
 
        if((key = ftok(".",1)) < 0){
                printf("ftok error\n");
        }
 
        shmId = shmget(key, 1024*4, IPC_CREAT|0666);//内存大小必须得是MB的整数倍
 
        if(shmId == -1){
                printf("shmget error\n");
                exit(-1);
        }
 
        /*第二个参数一般写0,让linux内核自动分配空间,第三个参数也一般写0,表示可读可写*/
        shmaddr = shmat(shmId, 0, 0);
        printf("shmat OK\n");
        strcpy(shmaddr,"I am so cool");
 
        sleep(5);//等待5秒,让别的进程去读
 
        shmdt(shmaddr);
        shmctl(shmId, IPC_RMID, 0);//写0表示不关心
        printf("quit\n");
 
        return 0;
}

shmread.c ファイル:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
 
//       int shmget(key_t key, size_t size, int shmflg);
// void *shmat(int shmid, const void *shmaddr, int shmflg);
 
//       int shmdt(const void *shmaddr);
 
int main()
{
        int shmId;
        key_t key;
        char *shmaddr;
 
        if((key = ftok(".",1)) < 0){
                printf("ftok error\n");
        }
 
        shmId = shmget(key, 1024*4, 0);//内存大小必须得是MB的整数倍
 
        if(shmId == -1){
                printf("shmget error\n");
                exit(-1);
        }
 
        /*第二个参数一般写0,让linux内核自动分配空间,第三个参数也一般写0,表示可读可写*/
        shmaddr = shmat(shmId, 0, 0);
        printf("shmat OK\n");
        printf("data : %s\n",shmaddr);
 
        shmdt(shmaddr);
 
        return 0;
}
if((key = ftok(".",1)) < 0){
    printf("ftok error\n");
}

ご覧のとおり、両方のプログラムで数値 1 がパラメータとして使用されており.、生成されるキー値が同じであることが保証されます。この方法でのみ、メモリの同じブロックが確実に操作されるようにすることができます。


5. 参考内容

プロセス間通信の 6 つの一般的な方法_プロセス間通信の最も一般的な方法_Quiet Corner ブログ-CSDN ブログ(主要なリファレンス)

プロセス間通信方式とそれぞれのメリット・デメリット_4つのプロセス間通信方式の違いとメリット・デメリットの分析とまとめ_Echo_Annaのブログ-CSDNブログ

おすすめ

転載: blog.csdn.net/qq_41709234/article/details/132007328
おすすめ