Cプログラミング
 Computer >> コンピューター >  >> プログラミング >> Cプログラミング

【C言語】fork()とpipe()を使って2つのプロセス間で文字列を連結する方法


本記事では、Linux環境で動作するC言語プログラムを作成し、fork()pipe()の動作を実際に確認します。題材は「2つのプロセスによる文字列連結」です。一方のプロセスがユーザー入力を受け取ってもう一方へ送信し、受け取った側がその文字列を事前に定義しておいた文字列と連結して、結果を元のプロセスへ返すという流れになります。

fork()とpipe()のおさらい

fork() … 子プロセスを新しく生成するシステムコールです。生成された子プロセスは、新しいPID(プロセスID)とPPID(親プロセスID)を持ちます。

pipe() … Unix・Linuxにおけるシステムコールの一つで、プロセス間通信(IPC)を実現するために使われます。パイプはデータを一方向にしか流せないため、双方向のやり取りには2本のパイプが必要です。

処理の流れを理解するための例

入力

Learn programming
Predefined string: at tutorialspoint

出力

Learn programming at tutorialspoint

解説

P1が文字列「Learn programming」の入力を受け取る

P1はこの文字列をパイプ経由でP2へ送信します。P2は受け取った文字列と定義済みの文字列を連結し、再びパイプ経由でP1へ返します。最後にP1がその結果を出力します。

プログラムの仕組み

このプログラムでは、fork()関数を使ってP1とP2という2つのプロセスを生成します。fork()の戻り値は次の3通りがあり、それぞれプログラムの状態を表しています。

  • 戻り値 < 0 … プロセスの生成に失敗したことを示します。
  • 戻り値 = 0 … 現在実行中のコードが子プロセス側であることを示します。
  • 戻り値 > 0 … 子プロセスのプロセスIDが親プロセスに返されます。つまり、この分岐では親プロセスが実行されます。

また、パイプは一方向の通信しかできないため、P1からP2への通信用と、P2からP1への通信用の、合計2本のパイプを作成します。

fork()とpipe()をデモンストレーションするCプログラム

サンプルコード

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<string.h>
#include<sys/wait.h>
int main(){
    int p12[2];
    int p21[2];
    char fixed_str[] = " at tutorialspoint";
    char input_str[100];
    pid_t P;
    if (pipe(p12)==-1 || pipe(p21)==-1 ){
        fprintf(stderr, "Failed to create pipe");
        return 1;
    }
    scanf("%s", input_str);
    P = fork();
    if (P < 0){
        fprintf(stderr, "fork Failed");
        return 1;
    }
    else if (P > 0){
        char concat_str[100];
        close(p12[0]);
        write(p12[1], input_str, strlen(input_str)+1);
        close(p12[1]);
        wait(NULL);
        close(p21[1]);
        read(p21[0], concat_str, 100);
        printf("Concatenated string %s\n", concat_str);
        close(p21[0]);
    }
    else{
        close(p12[1]);
        char concat_str[100];
        read(p12[0], concat_str, 100);
        int k = strlen(concat_str);
        int i;
        for (i=0; i<strlen(fixed_str); i++)
            concat_str[k++] = fixed_str[i];
        concat_str[k] = '\0';
        close(p12[0]);
        close(p21[0]);
        write(p21[1], concat_str, strlen(concat_str)+1);
        close(p21[1]);
        exit(0);
    }
}

出力

Concatenated string Learn at tutorialspoint

補足:出力が「Learn」だけになる理由

サンプルコードでは入力にscanf("%s", ...)を使用しています。%sは空白を含む文字列を読み込めないため、「Learn programming」と入力しても実際に読み込まれるのは最初の単語「Learn」だけです。そのため、実行結果は「Learn at tutorialspoint」となります。スペースを含む行全体を読み込みたい場合は、scanf(" %[^\n]", input_str)のように書き換えるとよいでしょう。

まとめ

このように、fork()で複数のプロセスを生成し、pipe()でプロセス間のデータのやり取りを実現できます。パイプが一方向の通信チャネルである点に注意し、双方向の通信が必要な場合はパイプを2本用意するのがポイントです。プロセス間通信の基礎を理解するうえで、非常に良い学習教材となるサンプルと言えるでしょう。

  1. C言語で前置・後置インクリメント演算子の動作を示すプログラムを作成する方法

    インクリメント演算子(++)とはインクリメント演算子は、変数の値を1だけ増加させるために使用される演算子です。「前置インクリメント(++a)」と「後置インクリメント(a++)」の2種類があり、記述位置によって処理の順序が異なります。前置インクリメント: 演算子をオペランドの前に置きます。値が先に1増やされ、その増加した値に対して処理が行われます。例:z = ++a; → a = a + 1; その後 z = a;後置インクリメント: 演算子をオペランドの後ろに置きます。現在の値で処理が先に行われ、その後に値が1増やされます。例:z = a++; → z = a; その後 a = a + 1;例

  2. C言語で文字列へのポインタの配列とそのアドレスを出力する方法

    まずは、C言語における「ポインタの配列」とは何かを確認しましょう。 文字列へのポインタの配列とは 各要素が、文字列の先頭アドレスを指すポインタになっている配列のことです。 宣言と初期化は次のように行います。 char *a[ ] = {"one", "two", "three"}; ここで、a[0] は文字列 one の、a[1] は two の、a[2] は three のそれぞれ先頭アドレスを指すポインタです。 イメージとしては、配列の各要素が対応する文字列の先頭番地を保持しており、その番地をたどることで文字列本体へアクセスで