Linux环境下6种进程通信方式和代码详解

本文内容包含AIGC

信号量(Semaphore)

信号量是一个计数器,用于管理对共享资源的访问。它可以用来解决多个进程之间的同步问题。信号量有两种操作:wait(P操作)和post(V操作)。wait操作会减少信号量的值,post操作会增加信号量的值。当信号量的值为0时,wait操作会阻塞进程,直到信号量的值大于0。信号量可以用于实现互斥(例如,保护对共享资源的访问)和同步(例如,等待其他进程完成某个任务)。

// 引入头文件
#include <fcntl.h>
#include <semaphore.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>

int main() {
    // 创建一个名为"/my_semaphore"的信号量,并设置初始值为1
    sem_t *sem = sem_open("/my_semaphore", O_CREAT, 0644, 1);
    // 创建一个子进程
    int pid = fork();

    if (pid == 0) {
        // 子进程
        // 等待信号量,如果信号量值大于0,则将其减1,否则阻塞等待
        sem_wait(sem);
        // 输出子进程信息
        printf("Child process\n");
        // 释放信号量,将信号量值加1
        sem_post(sem);
        // 子进程退出
        exit(0);
    } else {
        // 父进程
        // 等待信号量,如果信号量值大于0,则将其减1,否则阻塞等待
        sem_wait(sem);
        // 输出父进程信息
        printf("Parent process\n");
        // 释放信号量,将信号量值加1
        sem_post(sem);
        // 等待子进程结束
        wait(NULL);
    }

    // 关闭信号量
    sem_close(sem);
    // 删除信号量
    sem_unlink("/my_semaphore");
    // 程序正常退出
    return 0;
}

这段代码创建了一个父子进程,它们都试图获取一个信号量。当一个进程获取到信号量时,它会输出自己的进程类型(父进程或子进程),然后释放信号量。另一个进程在等待信号量被释放后,会执行相同的操作。

管道(Pipe)

管道是一种基于字节流的通信方式,允许一个进程向另一个进程发送数据。管道有两种类型:匿名管道和命名管道。匿名管道主要用于具有父子关系的进程之间的通信,而命名管道可以在任意两个进程之间进行通信。管道通常用于进程间的数据传输和同步。

// 引入头文件
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string>

int main() {
    // 创建一个管道,pipefd[0]用于读,pipefd[1]用于写
    int pipefd[2];
    pipe(pipefd);
    // 创建一个子进程
    int pid = fork();

    if (pid == 0) {
        // 子进程
        // 关闭写端
        close(pipefd[1]);
        // 创建一个缓冲区用于存储从管道中读取的数据
        char buf[32];
        // 从管道读端读取数据
        read(pipefd[0], buf, sizeof(buf));
        // 输出子进程接收到的数据
        printf("Child process received: %s\n", buf);
        // 关闭读端
        close(pipefd[0]);
        // 子进程退出
        exit(0);
    } else {
        // 父进程
        // 关闭读端
        close(pipefd[0]);
        // 创建一个字符串,用于发送给子进程
        std::string msg = "Hello, child!";
        // 将字符串写入管道的写端
        write(pipefd[1], msg.c_str(), msg.length());
        // 关闭写端
        close(pipefd[1]);
        // 等待子进程结束
        wait(NULL);
    }

    // 程序正常退出
    return 0;
}

这段代码创建了一个父子进程,它们通过管道进行通信。父进程向管道中写入一个字符串,子进程从管道中读取该字符串并输出。在这个过程中,父进程关闭管道的读端,子进程关闭管道的写端。

消息队列(Message Queue)

消息队列是一种基于消息的通信方式,允许进程将消息发送到一个队列中,然后由另一个进程接收。消息队列可以实现进程间的解耦和同步。每个消息都有一个类型,接收进程可以根据类型选择接收特定的消息。消息队列的优点是可以实现异步通信,缺点是需要额外的内核资源来管理消息队列。

// 引入头文件
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

// 定义消息结构体
struct my_msgbuf {
    long mtype;          // 消息类型
    char mtext[32];      // 消息内容
};

int main() {
    // 生成一个键值,用于创建消息队列
    key_t key = ftok("/tmp", 'a');
    // 创建一个消息队列,设置权限为0644
    int msgid = msgget(key, 0644 | IPC_CREAT);
    // 创建一个子进程
    int pid = fork();

    if (pid == 0) {
        // 子进程
        // 创建一个消息结构体变量
        struct my_msgbuf msg;
        // 从消息队列中接收类型为1的消息
        msgrcv(msgid, &msg, sizeof(msg.mtext), 1, 0);
        // 输出子进程接收到的消息
        printf("Child process received: %s\n", msg.mtext);
        // 子进程退出
        exit(0);
    } else {
        // 父进程
        // 创建一个消息结构体变量
        struct my_msgbuf msg;
        msg.mtype = 1;  // 设置消息类型为1
        // 将字符串"Hello, child!"复制到消息结构体的mtext字段中
        snprintf(msg.mtext, sizeof(msg.mtext), "Hello, child!");
        // 将消息发送到消息队列
        msgsnd(msgid, &msg, sizeof(msg.mtext), 0);
        // 等待子进程结束
        wait(NULL);
    }

    // 删除消息队列
    msgctl(msgid, IPC_RMID, NULL);
    // 程序正常退出
    return 0;
}

这段代码创建了一个父子进程,它们通过消息队列进行通信。父进程向消息队列中发送一个消息,子进程从消息队列中接收该消息并输出。在这个过程中,父子进程共享同一个消息队列。

共享内存(Shared Memory)

共享内存是一种允许多个进程访问同一块内存区域的通信方式。通过共享内存,进程可以直接读写其他进程的数据,从而实现高效的数据交换。共享内存通常与其他同步机制(如信号量)结合使用,以确保对共享数据的访问是互斥的。共享内存的优点是速度快,缺点是需要手动管理内存和同步。

// 引入头文件
#include <sys/wait.h>
#include <string>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>

int main() {
    // 生成一个键值,用于创建共享内存
    key_t key = ftok("/tmp", 'b');
    // 创建一个共享内存段,设置大小为1024字节,权限为0644
    int shmid = shmget(key, 1024, 0644 | IPC_CREAT);
    // 创建一个子进程
    int pid = fork();

    if (pid == 0) {
        // 子进程
        // 将共享内存段附加到子进程的地址空间
        char *data = static_cast<char*>(shmat(shmid, NULL, 0));
        // 输出子进程从共享内存中接收到的数据
        printf("Child process received: %s\n", data);
        // 将共享内存段从子进程的地址空间中分离
        shmdt(data);
        // 子进程退出
        exit(0);
    } else {
        // 父进程
        // 将共享内存段附加到父进程的地址空间
        char *data = static_cast<char*>(shmat(shmid, NULL, 0));
        // 将字符串"Hello, child!"复制到共享内存中
        strncpy(data, "Hello, child!", 1024);
        // 将共享内存段从父进程的地址空间中分离
        shmdt(data);
        // 等待子进程结束
        wait(NULL);
    }

    // 删除共享内存段
    shmctl(shmid, IPC_RMID, NULL);
    // 程序正常退出
    return 0;
}

这段代码创建了一个父子进程,它们通过共享内存进行通信。父进程向共享内存中写入一个字符串,子进程从共享内存中读取该字符串并输出。在这个过程中,父子进程共享同一个共享内存段。

信号(Signal)

信号是一种用于通知进程发生了某个事件的机制。信号可以由内核、其他进程或进程本身发送。进程可以定义信号处理函数来响应特定的信号。信号通常用于实现进程间的异步通知和同步,例如,通知子进程终止或等待子进程完成某个任务。

// 引入头文件
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

// 信号处理函数
void signal_handler(int signum) {
    // 输出接收到的信号编号
    printf("Signal received: %d\n", signum);
}

int main() {
    // 创建一个子进程
    int pid = fork();

    if (pid == 0) {
        // 子进程
        // 为SIGUSR1信号设置信号处理函数
        signal(SIGUSR1, signal_handler);
        // 暂停子进程,等待接收信号
        pause();
        // 子进程退出
        exit(0);
    } else {
        // 父进程
        // 等待1秒,确保子进程已经准备好接收信号
        sleep(1);
        // 向子进程发送SIGUSR1信号
        kill(pid, SIGUSR1);
        // 等待子进程结束
        wait(NULL);
    }

    // 程序正常退出
    return 0;
}

这段代码创建了一个父子进程,父进程向子进程发送一个SIGUSR1信号。子进程设置了一个信号处理函数,用于处理接收到的SIGUSR1信号。当子进程接收到信号时,信号处理函数会输出接收到的信号编号。

文件锁(File Lock)

文件锁是一种用于控制对文件的并发访问的同步机制。文件锁可以是共享锁(多个进程可以同时读取文件)或独占锁(只有一个进程可以写入文件)。文件锁可以实现对文件的互斥访问,从而避免数据不一致和竞争条件。文件锁的优点是可以跨文件系统和网络文件系统使用,缺点是需要额外的系统调用来管理锁。

// 引入头文件
#include <sys/wait.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

int main() {
    // 打开或创建一个名为"/tmp/lockfile.txt"的文件,并设置权限为0644
    int fd = open("/tmp/lockfile.txt", O_RDWR | O_CREAT, 0644);
    // 创建一个子进程
    int pid = fork();

    if (pid == 0) {
        // 子进程
        // 初始化一个文件锁结构体
        struct flock lock;
        lock.l_type = F_WRLCK;     // 设置锁类型为写锁
        lock.l_whence = SEEK_SET;  // 设置锁的起始位置为文件开头
        lock.l_start = 0;          // 设置锁的偏移量为0
        lock.l_len = 0;            // 设置锁的长度为0,表示锁定整个文件

        // 使用fcntl函数设置文件锁,F_SETLKW表示如果锁已被其他进程持有,则等待锁被释放
        fcntl(fd, F_SETLKW, &lock);

        // 输出子进程信息
        printf("Child process\n");

        // 解锁文件
        lock.l_type = F_UNLCK;
        fcntl(fd, F_SETLK, &lock);
        // 子进程退出
        exit(0);
    } else {
        // 父进程
        // 初始化一个文件锁结构体
        struct flock lock;
        lock.l_type = F_WRLCK;     // 设置锁类型为写锁
        lock.l_whence = SEEK_SET;  // 设置锁的起始位置为文件开头
        lock.l_start = 0;          // 设置锁的偏移量为0
        lock.l_len = 0;            // 设置锁的长度为0,表示锁定整个文件

        // 使用fcntl函数设置文件锁,F_SETLKW表示如果锁已被其他进程持有,则等待锁被释放
        fcntl(fd, F_SETLKW, &lock);

        // 输出父进程信息
        printf("Parent process\n");

        // 解锁文件
        lock.l_type = F_UNLCK;
        fcntl(fd, F_SETLK, &lock);
        // 等待子进程结束
        wait(NULL);
    }

    // 关闭文件描述符
    close(fd);
    // 程序正常退出
    return 0;
}

这段代码创建了一个父子进程,它们都试图获取一个文件的写锁。当一个进程获取到锁时,它会输出自己的进程类型(父进程或子进程),然后释放锁。另一个进程在等待锁被释放后,会执行相同的操作。