Linuxにおけるプロセス間通信の手法まとめ

パイプ

パイプは、親子プロセス間の通信に最もシンプルなIPC(Inter-Process Communication)手法です。一つのプロセスがパイプにデータを書き込み、別のプロセスがそのデータを読み取ることができます。

#include 
#include 
#include 
#include 
#include 
#include 

int main() {
    int pipe_fds[2]; // パイプの読み書き用ファイルディスクリプタ
    pid_t child_pid; // 子プロセスのID
    char data_buffer[1024]; // パイプから読み取ったデータを格納するバッファ
    const char *parent_msg = "親プロセスからのメッセージです!"; // 親プロセスが送信するメッセージ

    // パイプの作成
    if (pipe(pipe_fds) == -1) {
        perror("pipe");
        exit(EXIT_FAILURE);
    }

    // 子プロセスの生成
    child_pid = fork();
    if (child_pid == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    }

    // 親プロセス
    if (child_pid > 0) {
        close(pipe_fds[0]); // 読み取り端を閉じる(親は書き込み専用)

        // パイプにメッセージを書き込む
        write(pipe_fds[1], parent_msg, strlen(parent_msg) + 1);
        close(pipe_fds[1]); // 書き込みが完了したら書き込み端を閉じる

        // 子プロセスの終了を待つ
        waitpid(child_pid, NULL, 0);
        printf("親プロセスの処理が終了しました。
");
    }
    // 子プロセス
    else {
        close(pipe_fds[1]); // 書き込み端を閉じる(子は読み取り専用)

        // パイプからメッセージを読み取る
        ssize_t bytes_read = read(pipe_fds[0], data_buffer, sizeof(data_buffer) - 1);
        if (bytes_read > 0) {
            data_buffer[bytes_read] = '\0'; // 読み取った文字列の末尾にNULL文字を追加
            printf("子プロセスが受信しました: %s
", data_buffer);
        }
        close(pipe_fds[0]); // 読み取りが完了したら読み取り端を閉じる

        exit(EXIT_SUCCESS);
    }

    return 0;
}

名前付きパイプ(FIFO)

名前付きパイプ(FIFO)は、パイプと同様ですが、関連のないプロセス間での通信を可能にします。ファイルシステム上に対応するファイル名があり、ファイルディスクリプタを通じてアクセスします。

// writer.c - データを書き込むプロセス
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define FIFO_PATH "/tmp/my_fifo_example"
#define BUFFER_SIZE 256

int main() {
    int fifo_fd;
    char fifo_buffer[BUFFER_SIZE];
    const char *fifo_msg = "名前付きパイプからのメッセージ!";

    // 名前付きパイプの作成
    if (mkfifo(FIFO_PATH, 0666) == -1) {
        perror("mkfifo");
        exit(EXIT_FAILURE);
    }

    // 名前付きパイプを書き込み用に開く
    fifo_fd = open(FIFO_PATH, O_WRONLY);
    if (fifo_fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }

    // データを名前付きパイプに書き込む
    if (write(fifo_fd, fifo_msg, strlen(fifo_msg) + 1) == -1) {
        perror("write");
        exit(EXIT_FAILURE);
    }

    close(fifo_fd);
    printf("書き込みプロセスが終了しました。
");

    // 名前付きパイプの削除
    unlink(FIFO_PATH);

    return 0;
}
// reader.c - データを読み取るプロセス
#include 
#include 
#include 
#include 
#include 

#define FIFO_PATH "/tmp/my_fifo_example"
#define BUFFER_SIZE 256

int main() {
    int fifo_fd;
    char fifo_buffer[BUFFER_SIZE];

    // 名前付きパイプを読み取り用に開く
    fifo_fd = open(FIFO_PATH, O_RDONLY);
    if (fifo_fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }

    // 名前付きパイプからデータを読み取る
    ssize_t bytes_read = read(fifo_fd, fifo_buffer, BUFFER_SIZE - 1);
    if (bytes_read == -1) {
        perror("read");
        exit(EXIT_FAILURE);
    }

    fifo_buffer[bytes_read] = '\0';
    printf("読み取りプロセスが受信しました: %s
", fifo_buffer);

    close(fifo_fd);

    return 0;
}

シグナル

シグナルは、ソフトウェアレベルでの割り込み機構のシミュレーションであり、プロセスに特定のイベントが発生したことを通知するために使用されます。プロセスは受信したシグナルに応じて異なる操作を実行できます。

#include 
#include 
#include 
#include 

// シグナルハンドラ
void sig_handler(int signo) {
    if (signo == SIGUSR2) {
        printf("SIGUSR2シグナルを受信しました。
");
    }
}

int main() {
    // シグナルハンドラの登録
    if (signal(SIGUSR2, sig_handler) == SIG_ERR) {
        perror("signal");
        exit(EXIT_FAILURE);
    }

    printf("プロセスID: %d
", getpid());
    printf("自身にSIGUSR2シグナルを送信しています...
");

    // 自身にSIGUSR2シグナルを送信
    if (kill(getpid(), SIGUSR2) == -1) {
        perror("kill");
        exit(EXIT_FAILURE);
    }

    // シグナルハンドラが実行されるのを待つ
    sleep(1);

    printf("プログラムを終了します。
");
    return 0;
}

メッセージキュー

メッセージキューは、メッセージのリンクリストであり、プロセス間でメッセージの送受信を通じて通信を可能にします。メッセージキューはカーネルによって管理され、メッセージの独立性と安全性を保証します。

// sender.c - メッセージを送信するプロセス
#include 
#include 
#include 
#include 
#include 
#include 

#define MSG_SIZE 256
#define QUEUE_NAME "/posix_mq_example"

int main() {
    mqd_t mq_id;
    char queue_message[MSG_SIZE];
    struct mq_attr mq_attr;

    // メッセージキューの属性を設定
    mq_attr.mq_flags = 0;
    mq_attr.mq_maxmsg = 10;
    mq_attr.mq_msgsize = MSG_SIZE;
    mq_attr.mq_curmsgs = 0;

    // メッセージキューを開く(存在しない場合は作成)
    mq_id = mq_open(QUEUE_NAME, O_CREAT | O_WRONLY, 0644, &mq_attr);
    if (mq_id == (mqd_t) -1) {
        perror("mq_open");
        exit(EXIT_FAILURE);
    }

    // メッセージを送信
    strcpy(queue_message, "送信者からのメッセージ!");
    if (mq_send(mq_id, queue_message, strlen(queue_message) + 1, 0) == -1) {
        perror("mq_send");
        exit(EXIT_FAILURE);
    }
    printf("メッセージを送信しました: %s
", queue_message);

    // メッセージキューを閉じる
    mq_close(mq_id);
    return 0;
}
// receiver.c - メッセージを受信するプロセス
#include 
#include 
#include 
#include 
#include 
#include 

#define MSG_SIZE 256
#define QUEUE_NAME "/posix_mq_example"

int main() {
    mqd_t mq_id;
    char queue_message[MSG_SIZE];
    ssize_t bytes_read;
    unsigned int prio;

    // メッセージキューを開く
    mq_id = mq_open(QUEUE_NAME, O_RDONLY);
    if (mq_id == (mqd_t) -1) {
        perror("mq_open");
        exit(EXIT_FAILURE);
    }

    // メッセージを受信
    bytes_read = mq_receive(mq_id, queue_message, MSG_SIZE, &prio);
    if (bytes_read == -1) {
        perror("mq_receive");
        exit(EXIT_FAILURE);
    }
    printf("受信したメッセージ: %s
", queue_message);

    // メッセージキューを閉じる
    mq_close(mq_id);
    return 0;
}

共有メモリ

共有メモリは、複数のプロセスが同一のメモリ領域にアクセスできるようにする仕組みです。プロセスはこのメモリ領域を直接読み書きすることで通信します。この方法は通信速度が速いですが、競合を避けるための同期機構が必要です。

// server.c - 共有メモリを作成しデータを書き込むプロセス
#include 
#include 
#include 
#include 
#include 
#include 

#define SHM_SIZE 1024

int main() {
    key_t key;
    int shmid;
    char *shared_mem_ptr, *data_ptr;

    // 一意のキーを生成
    if ((key = ftok(".", 'M')) == -1) {
        perror("ftok");
        exit(1);
    }

    // 共有メモリの作成
    if ((shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0666)) == -1) {
        perror("shmget");
        exit(1);
    }

    // 共有メモリをプロセスのアドレス空間にアタッチ
    if ((shared_mem_ptr = shmat(shmid, NULL, 0)) == (char *) -1) {
        perror("shmat");
        exit(1);
    }

    // 共有メモリにデータを書き込む
    data_ptr = shared_mem_ptr;
    strcpy(data_ptr, "共有メモリからのメッセージ!");

    printf("サーバ: 共有メモリを作成し、データを書き込みました。
");
    printf("サーバ: クライアントがデータを読み取るのを待っています...
");
    getchar(); // ユーザー入力を待つ

    // 共有メモリをデタッチ
    if (shmdt(shared_mem_ptr) == -1) {
        perror("shmdt");
        exit(1);
    }

    // 共有メモリを削除
    if (shmctl(shmid, IPC_RMID, NULL) == -1) {
        perror("shmctl");
        exit(1);
    }

    return 0;
}
// client.c - 共有メモリを読み取るプロセス
#include 
#include 
#include 
#include 
#include 
#include 

#define SHM_SIZE 1024

int main() {
    key_t key;
    int shmid;
    char *shared_mem_ptr, *data_ptr;

    // サーバーと同じキーを生成
    if ((key = ftok(".", 'M')) == -1) {
        perror("ftok");
        exit(1);
    }

    // 共有メモリを取得
    if ((shmid = shmget(key, SHM_SIZE, 0666)) == -1) {
        perror("shmget");
        exit(1);
    }

    // 共有メモリをプロセスのアドレス空間にアタッチ
    if ((shared_mem_ptr = shmat(shmid, NULL, 0)) == (char *) -1) {
        perror("shmat");
        exit(1);
    }

    // 共有メモリからデータを読み取る
    data_ptr = shared_mem_ptr;
    printf("クライアント: 共有メモリから読み取りました: %s
", data_ptr);

    // 共有メモリをデタッチ
    if (shmdt(shared_mem_ptr) == -1) {
        perror("shmdt");
        exit(1);
    }

    return 0;
}

セマフォ

セマフォは、プロセス間の同期と排他制御を行うための機構です。共有リソースへのアクセスを制御し、競合やデッドロックなどの問題を防ぎます。

// semaphore_example.h
#ifndef SEMAPHORE_EXAMPLE_H
#define SEMAPHORE_EXAMPLE_H

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define SHM_SIZE 1024
#define SEM_KEY 5678

#endif // SEMAPHORE_EXAMPLE_H
// writer.c
#include "semaphore_example.h"

int main() {
    int shmid, semid;
    key_t shm_key, sem_key;
    char *shm_ptr;
    struct sembuf sem_op;

    // 共有メモリとセマフォのキーを生成
    if ((shm_key = ftok(".", 'W')) == -1) {
        perror("ftok");
        exit(1);
    }
    sem_key = SEM_KEY;

    // 共有メモリの作成
    if ((shmid = shmget(shm_key, SHM_SIZE, IPC_CREAT | 0666)) == -1) {
        perror("shmget");
        exit(1);
    }

    // 共有メモリをアタッチ
    if ((shm_ptr = shmat(shmid, NULL, 0)) == (char *) -1) {
        perror("shmat");
        exit(1);
    }

    // セマフォの作成
    if ((semid = semget(sem_key, 1, IPC_CREAT | 0666)) == -1) {
        perror("semget");
        exit(1);
    }

    // セマフォの値を0に初期化(リソースなし)
    if (semctl(semid, 0, SETVAL, 0) == -1) {
        perror("semctl");
        exit(1);
    }

    // セマフォが利用可能になるのを待つ(P操作)
    sem_op.sem_num = 0;
    sem_op.sem_op = -1; // リソースを要求
    sem_op.sem_flg = 0;
    if (semop(semid, &sem_op, 1) == -1) {
        perror("semop");
        exit(1);
    }

    // 共有メモリに書き込む
    strcpy(shm_ptr, "セマフォ経由の書き込み!");
    printf("ライター: 共有メモリにデータを書き込みました。
");

    // セマフォを解放(V操作)
    sem_op.sem_op = 1; // リソースを解放
    if (semop(semid, &sem_op, 1) == -1) {
        perror("semop");
        exit(1);
    }

    // 共有メモリをデタッチ
    if (shmdt(shm_ptr) == -1) {
        perror("shmdt");
        exit(1);
    }

    // 共有メモリとセマフォを削除
    if (shmctl(shmid, IPC_RMID, NULL) == -1) {
        perror("shmctl");
        exit(1);
    }
    if (semctl(semid, 0, IPC_RMID) == -1) {
        perror("semctl");
        exit(1);
    }

    return 0;
}
// reader.c
#include "semaphore_example.h"

int main() {
    int shmid, semid;
    key_t shm_key, sem_key;
    char *shm_ptr;
    struct sembuf sem_op;

    // 共有メモリとセマフォのキーを生成
    if ((shm_key = ftok(".", 'R')) == -1) {
        perror("ftok");
        exit(1);
    }
    sem_key = SEM_KEY;

    // 共有メモリを取得
    if ((shmid = shmget(shm_key, SHM_SIZE, 0666)) == -1) {
        perror("shmget");
        exit(1);
    }

    // 共有メモリをアタッチ
    if ((shm_ptr = shmat(shmid, NULL, 0)) == (char *) -1) {
        perror("shmat");
        exit(1);
    }

    // セマフォを取得
    if ((semid = semget(sem_key, 1, 0666)) == -1) {
        perror("semget");
        exit(1);
    }

    // セマフォが利用可能になるのを待つ(P操作)
    sem_op.sem_num = 0;
    sem_op.sem_op = -1;
    sem_op.sem_flg = 0;
    if (semop(semid, &sem_op, 1) == -1) {
        perror("semop");
        exit(1);
    }

    // 共有メモリから読み取る
    printf("リーダー: 共有メモリのデータ: %s
", shm_ptr);

    // セマフォを解放(V操作)
    sem_op.sem_op = 1;
    if (semop(semid, &sem_op, 1) == -1) {
        perror("semop");
        exit(1);
    }

    // 共有メモリをデタッチ
    if (shmdt(shm_ptr) == -1) {
        perror("shmdt");
        exit(1);
    }

    return 0;
}

ソケット

ソケットは、ローカルプロセス間通信だけでなくネットワーク通信にも使用できます。異なるホスト上のプロセス間通信を可能にする、ネットワークを跨いだIPC機構を提供します。

// server.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define SOCK_PATH "/tmp/unix_socket_demo"
#define BUFFER_SIZE 1024

int main() {
    int sock_fd, client_fd;
    struct sockaddr_un server_addr, client_addr;
    socklen_t client_len;
    char socket_buffer[BUFFER_SIZE];
    ssize_t bytes_read;

    // UNIXソケットの作成
    if ((sock_fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    // サーバーアドレス構造体をゼロクリア
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sun_family = AF_UNIX;
    strncpy(server_addr.sun_path, SOCK_PATH, sizeof(server_addr.sun_path) - 1);

    // ソケットをアドレスにバインド
    if (bind(sock_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
        perror("bind");
        exit(EXIT_FAILURE);
    }

    // 接続の待受
    if (listen(sock_fd, 5) == -1) {
        perror("listen");
        exit(EXIT_FAILURE);
    }

    // クライアント接続の受け入れ
    client_len = sizeof(client_addr);
    if ((client_fd = accept(sock_fd, (struct sockaddr*)&client_addr, &client_len)) == -1) {
        perror("accept");
        exit(EXIT_FAILURE);
    }

    // クライアントから送信されたメッセージを読み取る
    bytes_read = read(client_fd, socket_buffer, BUFFER_SIZE - 1);
    if (bytes_read == -1) {
        perror("read");
        exit(EXIT_FAILURE);
    }
    socket_buffer[bytes_read] = '\0';
    printf("受信しました: %s
", socket_buffer);

    // クライアントに応答メッセージを送信
    const char* response = "サーバーからの応答!";
    if (write(client_fd, response, strlen(response)) == -1) {
        perror("write");
        exit(EXIT_FAILURE);
    }

    // ソケットを閉じる
    close(client_fd);
    close(sock_fd);
    unlink(SOCK_PATH); // ソケットファイルを削除

    return 0;
}
// client.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define SOCK_PATH "/tmp/unix_socket_demo"
#define BUFFER_SIZE 1024

int main() {
    int sock_fd;
    struct sockaddr_un server_addr;
    char socket_buffer[BUFFER_SIZE];
    ssize_t bytes_written, bytes_read;

    // UNIXソケットの作成
    if ((sock_fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    // サーバーアドレス構造体をゼロクリア
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sun_family = AF_UNIX;
    strncpy(server_addr.sun_path, SOCK_PATH, sizeof(server_addr.sun_path) - 1);

    // サーバーに接続
    if (connect(sock_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
        perror("connect");
        exit(EXIT_FAILURE);
    }

    // サーバーにメッセージを送信
    const char* message = "クライアントからのメッセージ!";
    bytes_written = write(sock_fd, message, strlen(message));
    if (bytes_written == -1) {
        perror("write");
        exit(EXIT_FAILURE);
    }

    // サーバーからの応答を受信
    bytes_read = read(sock_fd, socket_buffer, BUFFER_SIZE - 1);
    if (bytes_read == -1) {
        perror("read");
        exit(EXIT_FAILURE);
    }
    socket_buffer[bytes_read] = '\0';
    printf("受信しました: %s
", socket_buffer);

    // ソケットを閉じる
    close(sock_fd);

    return 0;
}

タグ: linux IPC C言語 posix シグナル

7月30日 17:08 投稿