IPCプロセス間通信とサンプルコード

1. プロセスコミュニケーションとは何ですか?

プロセス間通信 (IPC) は、プロセス間の情報交換        を指します実際、プロセスの同期と相互排他は本質的にはプロセス通信の一種です(これが、後でプロセス通信メカニズムでセマフォと PV 操作について説明する理由です) が、セマフォを送信するだけです。セマフォを変更することで、プロセスは接続を確立できます。調整して連携しますが、データを転送する機能がありません

        特定のステータス情報のみを交換するなど、プロセス間で交換される情報の量が非常に少ない状況もありますが、プロセスの同期および相互排他メカニズムがその仕事を完全に実行できます。しかし、ほとんどの場合、情報のバッチやファイル全体の送信など、プロセス間で大量のデータを交換する必要があり、いわゆるプロセス通信と呼ばれる新しい通信メカニズムが必要になります。

        オペレーティング システム レベルからいくつかのプロセス通信を直観的に見てみましょう: セキュリティを確保するために、各プロセスのユーザー アドレス空間は独立していることはわかっています。一般的に、あるプロセスは別のプロセスのアドレス空間に直接アクセスできませんが、カーネル空間は各プロセスによって共有されるため、プロセス間の情報交換はカーネルを経由する必要があります

Linux カーネルが提供する一般的なプロセス通信メカニズムをリストしてみましょう。

  • パイプ (共有ファイルとも呼ばれます)
  • メッセージキュー (メッセージパッシングとも呼ばれます)
  • 共有メモリ (共有ストレージとも呼ばれます)
  • セマフォと PV 操作
  • 信号
  • ソケット

2. パイプライン

1.匿名パイプ

        パイプは半二重通信方式であり、データは一方向にのみ流れることができ、関連するプロセス間でのみ使用できます。プロセス アフィニティは通常、親子プロセス関係を指します。相互通信(全二重通信)を実現したい場合は、パイプを2本作成する必要があります。さらに、パイプ記号 | によって作成されたパイプは匿名パイプであり、使用されると自動的に破棄されます。また、匿名パイプは関係(親子プロセス)のあるプロセス間でのみ使用できます。つまり、匿名パイプは、親プロセスと子プロセス間の通信にのみ使用できます

Linux の実際のコーディングでは、pipe 関数を使用して匿名パイプを作成します。作成に成功すると 0 が返され、作成に失敗した場合は -1 が返されます。

#include<sys/types.h>
#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<stdlib.h>
#include<string.h>
int main(void)
{
	pid_t pid1;
	int fields[2];
	char buffer[80];
	char s[100];
	char ss[100];
	if(pipe(fields)!=0){
		fprintf(stderr,"Createpipe error:%s\n\a",strerror(errno));
		exit(1);
	}
	if((pid1=fork())<0)printf("fork child error!\n");
/* 子进程写入数据 */
	if(pid1==0){
	printf("fork child,child is sending a message !\n");
	char s[]="hello!\n";
	write(fields[1],s,sizeof(s));
    exit(0)
		}
/* 父进程读取数据 */
	else 
	{
	printf("parent read start !\n");
	read(fields[0],buffer,80);
printf("parent receive the message:%s",buffer);
	}
	exit (0);
}

コンパイルして実行する 

cc -o unamepipe unamepipe.c -g
./unamepipe

演算結果 

2. 名前付きパイプ

        名前付きパイプ fifo は、パイプが関連するプロセスとしか通信できないという問題を解決します。名前付きパイプの実装は、実際には FIFO ファイルを実装することです。名前付きパイプが確立されると、その後の読み取りと終了の操作は通常のパイプとまったく同じです。FIFO ファイルの i ノードはディスク上にありますが、それは 1 つのノードにすぎず、ファイルのデータは通常のパイプと同様にカーネル バッファ ページに格納されます。

Linux コマンド mkfifo を使用して、名前付きパイプを作成します。

$ mkfifo myPipe

myPipe はこのパイプの名前です。次に、有名なパイプ myPipe にデータを書き込みます。 

$ echo "hello" > myPipe

        このコマンド行を実行すると、ここで停止することがわかります。これは、パイプ内のコンテンツが読み取られていないためです。コマンドは、パイプ内のデータが読み取られた後でのみ正常に終了できます。そこで、別のコマンドを実行して、この有名なパイプ内のデータを読み取ります。 

$ cat < myPipe
hello

プログラムのソースコード例は以下のとおりです。

//读进程
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#define FIFO_PATH "myfifofile"
int main()
{
	int fd;
	char cont_r[255];
#创建命名管道
	if(mkfifo(FIFO_PATH,0666)<0 && errno!=EEXIST)
	{
	
		perror("create fifo failed");
		return -1;

	}else
	{
	  	printf("create fifo success\n");
    #打开文件进行读操作
		fd =open(FIFO_PATH,O_CREAT|O_RDONLY,0666);
		if(fd>0)
		{
			while(1){
				read(fd,cont_r,255);
				printf("read:%s\n",cont_r);
			}
			close(fd);
		}else
			perror("open failed");

	}
	return 0;
}
//写进程
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#define FIFO_PATH "myfifofile"
int main()
{
        int fd;
        char cont_w[] = "hello sundy";

        if(mkfifo(FIFO_PATH,0666)<0 && errno!=EEXIST)
        {

                perror("create fifo failed");
                return -1;

        }else
        {
                printf("create fifo success\n");

                fd =open(FIFO_PATH,O_CREAT|O_WRONLY,0666);
                if(fd>0)
                {
                        while(1){
                                write(fd,cont_w,strlen(cont_w));
                                printf("write success\n");
				                   sleep(2);
                        }
                        close(fd);
                }else
                        perror("open failed");

        }
        return 0;
}

コンパイルして実行し
、2 つのターミナルを開いて実行します

3. メッセージキュー

パイプのプロセス通信方法は使いやすいですが、比較的効率が悪く、プロセス間で頻繁にデータを交換するのには適しておらず、パイプはフォーマットされていないバイト ストリームしか送信できないことがわかり        ますこの目的のために、メッセージ受け渡しメカニズム (Linux ではメッセージ キューと呼ばれます) が誕生しました。たとえば、プロセス A がプロセス B にメッセージを送信したい場合、プロセス A は対応するメッセージ キューにデータを入れた後、正常に復帰し、プロセス B は必要なときに自らメッセージ キューからデータを読み込むだけで済みます。プロセス B がプロセス A にメッセージを送信したい場合も同様です。

メッセージ キューの本質はメモリに格納されたメッセージのリンク リストであり、メッセージは基本的にユーザー定義のデータ構造ですプロセスがメッセージ キューからメッセージを読み取ると、そのメッセージはメッセージ キューから削除されます。パイプラインのメカニズムを比較します。

  • メッセージ キューを使用すると、1 つ以上のプロセスがメッセージを読み書きできるようになります。
  • メッセージキューは、メッセージのランダムなクエリを実現することができ、メッセージを先入れ先出し順に読み取る必要はなく、メッセージの種類に応じて読み取ることもできます。これには、有名なパイプラインの先入れ先出し原則よりも多くの利点があります。
  • メッセージ キューの場合、プロセスがメッセージをキューに書き込む前に、別のプロセスがメッセージ キューにメッセージが到着するのを待つ必要はありません。パイプの場合、読み取りプロセスがすでに存在しない限り、書き込みプロセスが最初に書き込み操作を実行することは意味がありません。
  • メッセージ キューのライフ サイクルはカーネルによって異なり、メッセージ キューが解放されない場合、またはオペレーティング システムがシャットダウンされない場合、メッセージ キューは常に存在します。匿名パイプはプロセスの作成時に確立され、プロセスの終了時に破棄されます。

メッセージ キューは、回避すべき競合がないため、少量のデータを交換する場合に便利であることに注意することが重要です。ただし、ユーザープロセスがメモリ上のメッセージキューにデータを書き込む際には、ユーザー状態からカーネル状態へデータをコピーする処理が発生し、同様に、別のユーザープロセスがメモリ上のメッセージデータを読み込む際にも、カーネル状態からデータをコピーし、ユーザー モードにデータをコピーするプロセスが発生します。そのため、データ量が多い場合、メッセージキューを使用するとシステムコールが頻繁に発生し、カーネルの介入に時間がかかります

関連機能

メッセージのデータ形式は以下のとおりです。

ソースプログラムの例は以下のとおりです。

// 写进程 
#include <stdio.h> 
#include <sys/ipc.h> 
#include <sys/msg.h> 
  
// 消息队列数据结构
struct mesg_buffer { 
    long mesg_type; 
    char mesg_text[100]; 
} message; 
  
int main() 
{ 
    key_t key; 
    int msgid; 
  
    // ftok to generate unique key 
    key = ftok("progfile", 65); 
  
    // msgget creates a message queue 
    // and returns identifier 
    msgid = msgget(key, 0666 | IPC_CREAT); 
    message.mesg_type = 1; 
  
    printf("Write Data : "); 
    gets(message.mesg_text); 
  
    // msgsnd to send message 
    msgsnd(msgid, &message, sizeof(message), 0); 
  
    // display the message 
    printf("Data send is : %s \n", message.mesg_text); 
  
    return 0; 
}

 

// 读进程
#include <stdio.h> 
#include <sys/ipc.h> 
#include <sys/msg.h> 
  
// structure for message queue 
struct mesg_buffer { 
    long mesg_type; 
    char mesg_text[100]; 
} message; 
  
int main() 
{ 
    key_t key; 
    int msgid; 
  
    // ftok to generate unique key 
    key = ftok("progfile", 65); 
  
    // msgget creates a message queue 
    // and returns identifier 
    msgid = msgget(key, 0666 | IPC_CREAT); 
  
    // msgrcv to receive message 
    msgrcv(msgid, &message, sizeof(message), 1, 0); 
  
    // display the message 
    printf("Data Received is : %s \n",  
                    message.mesg_text); 
  
    // to destroy the message queue 
    msgctl(msgid, IPC_RMID, NULL); 
  
    return 0; 
}

演算結果

 

4. 共有メモリ

        メッセージ キューと同じくらい頻繁にメッセージをコピーしたりシステム コールを実行したりすることを避けるために、共有メモリ メカニズムが登場しました。名前が示すように、共有メモリにより、無関係なプロセスが同じ物理メモリをそれぞれのアドレス空間に接続できるようになり、これらのプロセスが同じ物理メモリにアクセスできるようになり、この物理メモリが共有メモリになります。プロセスが共有メモリにデータを書き込むと、その変更は共有メモリの同じセグメントにアクセスできる他のプロセスに直ちに影響します。

        メモリ管理の内容をまとめて、共有メモリの原理を深く理解しましょう。まず、各プロセスには独自のプロセス制御ブロック (PCB) と論理アドレス空間 (Addr Space) があり、プロセスの論理アドレス (仮想アドレス) と物理アドレスを比較する役割を担う対応するページ テーブルがあります。マッピング。メモリ管理ユニット (MMU) を通じて管理されます。2 つの異なるプロセスの論理アドレスは、ページ テーブルを通じて物理空間の同じ領域にマッピングされ、それらが共同で指す領域が共有メモリになります

         メッセージキューの頻繁なシステムコールとは異なり、共有メモリ機構では、共有メモリ領域を確立するときのみシステムコールが必要となり、一度共有メモリを確立すると、カーネルを介さずに通常のメモリとしてすべてのアクセスが可能となります。この方法では、プロセス間でデータをコピーする必要がなくなるため、これがプロセス間で最も高速に通信する方法になります。

関連機能

shmget: 共有メモリを適用
shmat: ユーザープロセス空間から共有メモリへのマッピングを確立
shmdt: マッピング関係をキャンセル
shmctl: 共有メモリ空間を再利用

プログラムのソースコード例

//写进程
#include <sys/ipc.h> 
#include <sys/shm.h> 
#include <stdio.h> 
  
int main() 
{ 
    // ftok to generate unique key 
    key_t key = ftok("shmfile",65); 
  
    // shmget returns an identifier in shmid 
    int shmid = shmget(key,1024,0666|IPC_CREAT); 
  
    // shmat to attach to shared memory 
    char *str = (char*) shmat(shmid,(void*)0,0); 
  
    gets(str); 
  
    printf("Data written in memory: %s\n",str); 
      
    //detach from shared memory  
    shmdt(str); 
  
    return 0; 
}
//读进程
#include <sys/ipc.h> 
#include <sys/shm.h> 
#include <stdio.h> 

  
int main() 
{ 
    // ftok to generate unique key 
    key_t key = ftok("shmfile",65); 
  
    // shmget returns an identifier in shmid 
    int shmid = shmget(key,1024,0666|IPC_CREAT); 
  
    // shmat to attach to shared memory 
    char *str = (char*) shmat(shmid,(void*)0,0); 
  
    printf("Data read from memory: %s\n",str); 
      
    //detach from shared memory  
    shmdt(str); 
    
    // destroy the shared memory 
    shmctl(shmid,IPC_RMID,NULL); 
     
    return 0; 
}

コンパイルして実行する

5. セマフォと PV の操作

        実際、複数の CPU を備えたシステムに関する最近の研究では、メッセージ キューでは競合を回避する必要がないのに対し、共有メモリ メカニズムでは競合を回避できるため、メッセージ パッシングがそのようなシステムでは実際に共有メモリよりも優れたパフォーマンスを発揮することが示されています。つまり、複数のプロセスが同じ共有メモリを同時に変更すると、最初のプロセスによって書き込まれた内容は、後のプロセスによって上書きされます。さらに、マルチチャネルのバッチ処理システムでは、複数のプロセスを同時に実行できますが、システムのリソースが限られているため、プロセスの実行は一貫性がなく、停止したり停止したり、予測できない速度で進行します(非同期性)。 。しかし、場合によっては、複数のプロセスが密接に連携し、特定の順序で実行して共通のタスクを達成できることを期待します。

        たとえば、データの読み取りと書き込みをそれぞれ担当する 2 つのプロセス A と B が存在する場合、これら 2 つのスレッドは相互に連携し、相互に依存します。次に、データの読み取り前にデータの書き込みを行う必要があります。実際には、非同期の存在により、最初に読み取り、その後に書き込みが発生する可能性がありますが、このとき、バッファにデータが書き込まれていないため、読み取りプロセス A には読み込むデータがないため、読み取りプロセス A はブロックされます。

        したがって、上記 2 つの問題を解決するには、常に 1 つのプロセスだけが共有メモリにアクセスできるようにし (相互排他)、プロセスが特定の順序で共有メモリにアクセスできるようにする (同期) ために、プロセス同期を使用できます。相互排他メカニズム、セマフォや PV 操作などの一般的なメカニズム。

        セマフォはロックに相当するアトミック カウンタであり、各プロセスが重要なリソースにアクセスしたい場合、重要なリソースの「部屋」に入ってドアをロックできるように、セマフォからロックを取得する必要があります。他のプロセスの侵入を許可しないでください。このとき、セマフォは P() 操作を実行します。ロックの数が 1 つ減るため、カウンタも 1 減ります。アクセスが完了すると、出てきて、セマフォにロックし、V() 操作を実行します。カウンタは 1 増加します。

プログラムのソースコード例:

2 つのプロセスがそれぞれ AA と BB をモニターに出力します (Linux ではすべてがファイルであり、重要なリソースです)。保護のためのセマフォがない場合、データの混乱が発生します (例: "AABBABAAAB...")。この問題を解決するために、セマフォを保護する機能を作成しました。「AA」または「BB」を印刷します

#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
#include<sys/sem.h>

union semun{
	int val;
	struct semid_ds *buf;
	unsigned short *array;
	//struct seminfo *buff;	
};

static int set_semvalue(void);
static void del_semvalue(void);
static int semaphore_p(void);
static int semaphore_v(void);
static int sem_id;

int main(int argc, char *argv[])
{
	int i;
	int pause_time;
	char op_char = 'O';
	srand((unsigned int)getpid());

	sem_id =semget((key_t)1234, 1, 0666 | IPC_CREAT);

/* 如果程序第一个被调用,也就是调用时含有一个参数,使得argc>1,此时就调用set_semvalue初始化信号量,并将op_char设置为x*/
	if (argc > 1){
		if(!set_semvalue()){
			fprintf(stderr, "Failed to initialize semaphore\n");
			exit(EXIT_FAILURE);
		}
		op_char = 'X';
		sleep(2);
	}

/*进入和离开临界区10次,每次循环开始的时候首先调用semaphore_p函数,它在程序将进入临界区域时设置信号量以等待进入*/
	for(i=0; i<10; i++){
		if(!semaphore_p()) exit(EXIT_FAILURE);
		printf("%c", op_char);fflush(stdout);
		pause_time = rand() % 3;
		sleep(pause_time);
		printf("%c", op_char);fflush(stdout);

/*进入临界区域后,调用semaphore_v将信号量设置为可用,然后等待一段随机的时间,再进入下一次循环*/
		if(!semaphore_v()) exit(EXIT_FAILURE);
		pause_time = rand() % 2;
		sleep(pause_time);
	}
	printf("\n%d - finished\n", getpid());
	if (argc > 1)
	{
		sleep(10);
		del_semvalue();
	}
	exit(EXIT_SUCCESS);
}

/*该函数用来将semctl调用的command参数设置为SETVAL来初始化信号量*/
static int set_semvalue(void)
{
	union semun sem_union;
	
	sem_union.val = 1;
	if (semctl(sem_id, 0, SETVAL, sem_union)==-1) return 0;
	return (1);
}
/*通过调用semctl调用的command设置为IPC_RMID来删除信号量ID*/
static void del_semvalue(void)
{
	union semun sem_union;

	if (semctl(sem_id, 0, IPC_RMID, sem_union)==-1)
	fprintf(stderr, "Failed to delete semaphore");
}

/*对信号量执行减1操作*/
static int semaphore_p(void)
{
	struct sembuf sem_b;
	sem_b.sem_num = 0;
	sem_b.sem_op = -1;
	sem_b.sem_flg = SEM_UNDO;
	if (semop(sem_id, &sem_b, 1) == -1){
		fprintf(stderr,"semaphore_p failed\n");
		return (0);
	}
	return(1);
}

/*对信号量执行加1操作*/
static int semaphore_v(void)
{
	struct sembuf sem_b;
	sem_b.sem_num = 0;
	sem_b.sem_op = 1;
	sem_b.sem_flg = SEM_UNDO;
	if (semop(sem_id, &sem_b, 1) == -1){
		fprintf(stderr,"semaphore_v failed\n");
		return (0);
	}
	return(1);
}

コンパイルして実行する

ソースコード例 2 は、親プロセスと子プロセス間のセマフォ機構になることを除いて、上記のコードとほぼ同じです。

#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
#include<sys/sem.h>
#include<sys/ipc.h>
#include<sys/types.h>

static int set_semvalue(void);
static void del_semvalue(void);
static int semaphore_p(void);
static int semaphore_v(void);
static int sem_id;

union semun{
    int val;
    struct semid_ds *buf;
    unsigned short *array;
    //struct seminfo *buff; 
};
int main(int argc, char *argv[])
{
    int i;
    int pause_time;
    char op_char = 'O';
    srand((unsigned int)getpid());

    sem_id =semget((key_t)1234, 1, 0666 | IPC_CREAT);
    int id = fork();
    if(id<0)
    {
        perror("fork failed\n");
        return -1;
    }

    else if (id>0){
        if(!set_semvalue()){
            fprintf(stderr, "Failed to initialize semaphore\n");
            exit(EXIT_FAILURE);
        }
        op_char = 'X';
        sleep(2);
    }
    for(i=0; i<10; i++){
        if(!semaphore_p()) exit(EXIT_FAILURE);
        printf("%c", op_char);fflush(stdout);
        pause_time = rand() % 3;
        sleep(pause_time);
        printf("%c", op_char);fflush(stdout);
        if(!semaphore_v()) exit(EXIT_FAILURE);
        pause_time = rand() % 2;
        sleep(pause_time);
    }
    printf("\n%d - finished\n", getpid());
    if (id> 0)
    {
        sleep(10);
        del_semvalue();
    }
    exit(EXIT_SUCCESS);
}

static int set_semvalue(void)
{
    union semun sem_union;
    
    sem_union.val = 1;
    if (semctl(sem_id, 0, SETVAL, sem_union)==-1) return 0;
    return (1);
}

static void del_semvalue(void)
{
    union semun sem_union;

    if (semctl(sem_id, 0, IPC_RMID, sem_union)==-1)
    fprintf(stderr, "Failed to delete semaphore");
}
static int semaphore_p(void)
{
    struct sembuf sem_b;
    sem_b.sem_num = 0;
    sem_b.sem_op = -1;
    sem_b.sem_flg = SEM_UNDO;
    if (semop(sem_id, &sem_b, 1) == -1){
        fprintf(stderr,"semaphore_p failed\n");
        return (0);
    }
    return(1);
}
static int semaphore_v(void)
{
    struct sembuf sem_b;
    sem_b.sem_num = 0;
    sem_b.sem_op = 1;
    sem_b.sem_flg = SEM_UNDO;
    if (semop(sem_id, &sem_b, 1) == -1){
        fprintf(stderr,"semaphore_v failed\n");
        return (0);
    }
    return(1);
}

コンパイルして実行する

ここで、子プロセスの pid が親プロセスの pid + 1 であることがわかります。子プロセスを作成するための fork の戻り値は 0 であり、ここでの pid とは異なることに注意してください。

6.信号

知らせ!信号とセマフォは 2 つのまったく異なる概念です

        Signal はプロセス通信機構の中で唯一の非同期通信機構であり、いつでもプロセスにシグナルを送信できます。指定されたシグナルを送信してプロセスに非同期イベントの送信を通知し、プロセスにシグナル ハンドラーの実行を強制します。シグナルが処理されると、中断されたプロセスが実行を再開しますユーザー、カーネル、プロセスはシグナルを生成して送信できます。

        信号イベントのソースには、主にハードウェア ソースとソフトウェア ソースが含まれます。いわゆるハードウェア ソースとは、キーボードから特定のキーの組み合わせを入力して、プロセスに信号を送信できることを意味します。たとえば、一般的なキーの組み合わせ Ctrl+C は、プロセスの終了を意味する SIGINT 信号を生成します。これは、kill 一連のコマンドを通じてプロセスにシグナルを送信できることを意味します。たとえば、kill -9 1111 は、PID 1111 のプロセスに SIGKILL シグナルを送信してプロセスを直ちに終了することを意味します。

プログラムのソースコード例

シミュレートされた目覚まし時計の形式では、プロセス間通信は、あるプロセスから別のプロセスに SIGALRM 信号を送信することによって表されます。

#include <sys/types.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

static int alarm_fired = 0;

/*该函数用来模拟闹钟*/
void ding(int sig)
{
    alarm_fired =1;
}

/*main函数中告诉子进程在等待5秒后发送SIGALRM信号给它的父进程*/
int main()
{
    pid_t pid;
    printf("alarm start\n");
    pid = fork();  /*创建子进程*/
    switch (pid)
    {
    case -1:
        perror("fork failed");
        exit(1);
    
    case 0:
        sleep(5);  /*子进程休眠5秒*/
        kill(getppid(), SIGALRM);  /*子进程在5秒后将SIGALRM信号传递给父进程*/
        exit(0);
}

/*父进程通过一个signal调用捕获SIGALRM信号的工作,等待该信号的到来*/
    printf("waitting for alarm to go on\n");
    (void) signal(SIGALRM, ding);
    pause();
    if (alarm_fired)
        printf("ding!\n");
    printf("done\n");
    exit(0);
}

コンパイルして実行する

7. ソケット

        ここまで紹介した 5 つの方法は、いずれも同一ホスト上のプロセス間で通信するためのものでしたが、ネットワークを介して異なるホスト上のプロセスと通信したい場合はどうすればよいでしょうか。これが Socket の通信の役割です (もちろん、Socket はホスト上のプロセスとの通信を完了することもできます)。

        ソケットは通信メカニズムであり、このメカニズムを使用すると、クライアント/サーバー (つまり、通信するプロセス) システムの開発を、ローカルのスタンドアロン マシン上またはネットワーク経由で実行できます。つまり、同じコンピュータ上になくても、ネットワークを介して接続されているコンピュータ上のプロセスが通信できるようになります。このため、ソケットはクライアントとサーバーを明確に区別します。

 プログラムのソースコード例

 //服务器
    #include <unistd.h>  
    #include <sys/types.h>  
    #include <sys/socket.h>  
    #include <netinet/in.h>  
    #include <signal.h>  
    #include <stdio.h>  
    #include <stdlib.h>  
     int main()  
    {  
        int server_sockfd = -1;  
        int client_sockfd = -1;  
        int client_len = 0;  
        struct sockaddr_in server_addr;  
        struct sockaddr_in client_addr;  
        //创建流套接字  
        server_sockfd = socket(AF_INET, SOCK_STREAM, 0);  
        //设置服务器接收的连接地址和监听的端口  
        server_addr.sin_family = AF_INET;//指定网络套接字  
        server_addr.sin_addr.s_addr = htonl(INADDR_ANY);//接受所有IP地址的连接  
        server_addr.sin_port = htons(9736);//绑定到9736端口  
        //绑定(命名)套接字  
        bind(server_sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));  
        //创建套接字队列,监听套接字  
        listen(server_sockfd, 5);  
        //忽略子进程停止或退出信号  
        signal(SIGCHLD, SIG_IGN);  
          
        while(1)  
        {  
            char ch = '\0';  
            client_len = sizeof(client_addr);  
            printf("Server waiting\n");  
            //接受连接,创建新的套接字  
            client_sockfd = accept(server_sockfd, (struct sockaddr*)&client_addr, &client_len);  
      
            if(fork() == 0)  
            {  
                //子进程中,读取客户端发过来的信息,处理信息,再发送给客户端  
                read(client_sockfd, &ch, 1);  
                sleep(5);  
                ch++;  
                write(client_sockfd, &ch, 1);  
                close(client_sockfd);  
                exit(0);  
            }  
            else  
            {  
                //父进程中,关闭套接字  
                close(client_sockfd);  
            }  
        }  
    }  

 

 //客户端
    #include <unistd.h>  
    #include <sys/types.h>  
    #include <sys/socket.h>  
    #include <netinet/in.h>  
    #include <arpa/inet.h>  
    #include <stdio.h>  
    #include <stdlib.h>  
      
    int main()  
    {  
        int sockfd = -1;  
        int len = 0;  
        struct sockaddr_in address;  
        int result;  
        char ch = 'A';  
        //创建流套接字  
        sockfd = socket(AF_INET, SOCK_STREAM, 0);  
        //设置要连接的服务器的信息  
        address.sin_family = AF_INET;//使用网络套接字  
        address.sin_addr.s_addr = inet_addr("127.0.0.1");//服务器地址  
        address.sin_port = htons(9736);//服务器所监听的端口  
        len = sizeof(address);  
        //连接到服务器  
        result = connect(sockfd, (struct sockaddr*)&address, len);  
      
        if(result == -1)  
        {  
            perror("ops:client\n");  
            exit(1);  
        }  
        //发送请求给服务器  
        write(sockfd, &ch, 1);  
        //从服务器获取数据  
        read(sockfd, &ch, 1);  
        printf("char form server = %c\n", ch);  
        close(sockfd);  
        exit(0);  
    }  

コンパイルして実行する

8. まとめ

Linux カーネルによって提供される上記の 6 つのプロセス通信メカニズムを簡単にまとめてみましょう。

1) まず第一に、最も簡単な方法はパイプラインです. パイプラインの本質はメモリに保存された特別なファイルです。つまり、カーネルはパイプ ファイルに関連付けられたメモリ内のバッファを開き、パイプ ファイルに対する操作はカーネルによってこのバッファに対する操作に変換されます。パイプは匿名パイプと名前付きパイプに分けられ、匿名パイプは親プロセスと子プロセスの間でのみ通信できますが、名前付きパイプには制限がありません。

2) パイプは使い方が簡単ですが、比較的効率が悪く、プロセス間の頻繁なデータ交換には適していません。また、パイプはフォーマットされていないバイト ストリームしか送信できません。このメッセージ キューアプリケーションのために生まれました。メッセージ キューの本質はメモリに格納されたメッセージのリンク リストであり、メッセージは基本的にユーザー定義のデータ構造です。プロセスがメッセージ キューからメッセージを読み取ると、そのメッセージはメッセージ キューから削除されます。

3) データの書き込みと読み取りのたびにユーザーモードとカーネルモードの間でデータのコピー処理が必要なため、メッセージキューが比較的遅くなりますが、共有メモリを使用することでこの問題を解決できます。いわゆる共有メモリとは、2 つの異なるプロセスの論理アドレスがページ テーブルを通じて物理空間の同じ領域にマッピングされ、それらが共同で指す領域が共有メモリです。プロセスが共有メモリにデータを書き込むと、その変更は共有メモリの同じセグメントにアクセスできる他のプロセスに直ちに影響します。

共有メモリ機構は、共有メモリ領域を確立するときのみシステムコールが必要で、一度共有メモリが確立されれば、カーネルを介さずに通常のメモリアクセスとしてすべてのアクセスが可能です。この方法では、プロセス間でデータをコピーする必要がなくなるため、これがプロセス間で最も高速に通信する方法になります。

4) 共有メモリは非常に高速ですが、競合の問題があるため、セマフォと PV 操作を使用して共有メモリへの相互排他アクセスを実現し、プロセスの同期も実現できます。

5)信号とセマフォは 2 つの完全に異なる概念です。Signal はプロセス通信機構の中で唯一の非同期通信機構であり、いつでもプロセスにシグナルを送信できます。指定されたシグナルを送信してプロセスに非同期イベントの送信を通知し、プロセスにシグナル ハンドラーの実行を強制します。シグナルが処理されると、中断されたプロセスが実行を再開します。ユーザー、カーネル、プロセスはシグナルを生成して送信できます。

6) 上記で紹介した5つの方法は、いずれも同一ホスト上のプロセス間の通信に使用しますが、ネットワークを介して異なるホスト上のプロセスと通信したい場合は、ソケット通信を使用する必要があります  。さらに、Socket はホスト上のプロセスとの通信を完了することもできます。

 

おすすめ

転載: blog.csdn.net/u013253075/article/details/132639710