Linux进程管理之进程间通信的相关知识(映射、管道(Pipe)通信、命名管道(FIFO)、消息队列、信号量、信号)

管道(Pipe)通信

管道通信例示例代码

下面是一个简单的示例,展示了如何在Linux上使用管道(pipe)进行进程间通信(IPC)。

代码的功能:

  • 父进程创建一个管道,并 fork 出一个子进程。
  • 子进程向管道写入一条消息。
  • 父进程从管道读取这条消息并打印出来。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int main() {
    int fd[2];  // 文件描述符数组,fd[0]用于读,fd[1]用于写
    pid_t pid;
    char buffer[100];

    // 创建管道
    if (pipe(fd) == -1) {
        perror("pipe failed");
        exit(EXIT_FAILURE);
    }

    // 创建子进程
    pid = fork();
    if (pid < 0) {
        perror("fork failed");
        exit(EXIT_FAILURE);
    }

    if (pid == 0) { // 子进程
        close(fd[0]); // 关闭读端
        char msg[] = "Hello from child process!";
        write(fd[1], msg, strlen(msg) + 1); // 写入管道
        close(fd[1]); // 关闭写端
        exit(EXIT_SUCCESS);
    } else { // 父进程
        close(fd[1]); // 关闭写端
        read(fd[0], buffer, sizeof(buffer)); // 读取管道内容
        printf("Parent received: %s\n", buffer);
        close(fd[0]); // 关闭读端
    }

    return 0;
}

代码解析

  • pipe(fd) 创建一个无名管道,fd[0] 是读端,fd[1] 是写端。
  • fork() 创建子进程,子进程继承管道文件描述符。
  • 子进程关闭fd[0](读端),写入数据到fd[1](写端)。
  • 父进程关闭fd[1](写端),从fd[0](读端)读取数据。
  • 进程通信完成后,双方关闭各自使用过的文件描述符。

编译运行

  1. 在Linux终端编译:
    gcc -o pipe_example pipe_example.c
    
  2. 运行:
    ./pipe_example
    
  3. 可能的输出:
    Parent received: Hello from child process!
    

命名管道(FIFO)

命名管道介绍

命名管道(FIFO)是一种特殊类型的文件,允许不相关的进程进行进程间通信(IPC)。下面是一个简单的示例,展示如何使用 FIFO 进行进程通信。


示例代码

代码功能:

  • 写进程(Writer):创建 FIFO,并向其中写入一条消息。
  • 读进程(Reader):从 FIFO 读取消息并打印。

1. 写进程代码 (fifo_writer.c)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>

#define FIFO_PATH "/tmp/my_fifo"

int main() {
    // 创建命名管道(如果已存在,则不创建)
    if (mkfifo(FIFO_PATH, 0666) == -1) {
        perror("mkfifo failed");
    }

    // 打开 FIFO 并写入数据
    int fd = open(FIFO_PATH, O_WRONLY);
    if (fd == -1) {
        perror("open failed");
        exit(EXIT_FAILURE);
    }

    char msg[] = "Hello from FIFO writer!";
    write(fd, msg, strlen(msg) + 1); // 写入数据
    close(fd); // 关闭 FIFO

    return 0;
}

2. 读进程代码 (fifo_reader.c)

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>

#define FIFO_PATH "/tmp/my_fifo"

int main() {
    char buffer[100];

    // 打开 FIFO 读取数据
    int fd = open(FIFO_PATH, O_RDONLY);
    if (fd == -1) {
        perror("open failed");
        exit(EXIT_FAILURE);
    }

    read(fd, buffer, sizeof(buffer)); // 读取数据
    printf("Reader received: %s\n", buffer);

    close(fd); // 关闭 FIFO

    return 0;
}

代码解析

  • mkfifo(FIFO_PATH, 0666) 创建一个命名管道(文件路径 /tmp/my_fifo)。
  • open(FIFO_PATH, O_WRONLY)写模式打开 FIFO,write() 写入数据。
  • open(FIFO_PATH, O_RDONLY)读模式打开 FIFO,read() 读取数据。
  • close(fd) 关闭文件描述符。

编译运行

1. 编译

gcc -o fifo_writer fifo_writer.c
gcc -o fifo_reader fifo_reader.c

2. 运行
先运行 fifo_reader(等待数据):

./fifo_reader

再运行 fifo_writer(写入数据):

./fifo_writer

3. 输出

Reader received: Hello from FIFO writer!

注意事项

  • FIFO 在文件系统中以文件形式存在(/tmp/my_fifo)。
  • 读进程必须先运行,否则 write() 可能会阻塞。

消息队列

消息队列介绍

Linux 的 消息队列(Message Queue) 是一种 进程间通信(IPC) 方式,它允许进程以消息的形式发送和接收数据。消息队列比管道(Pipe)和 FIFO 更灵活,支持不同类型的消息,并且可以非阻塞地读取数据。

代码解析

  1. 创建消息队列msgget(MSG_KEY, 0666 | IPC_CREAT)

    • MSG_KEY 是消息队列的唯一标识符。
    • 0666 赋予读写权限。
  2. 发送消息msgsnd(msg_id, &message, sizeof(message.msg_text), 0)

    • msg_type 必须 > 0,用于区分不同类型的消息。
  3. 接收消息msgrcv(msg_id, &message, sizeof(message.msg_text), 1, 0)

    • 1 表示接收类型为 1 的消息。
  4. 删除消息队列(可选):msgctl(msg_id, IPC_RMID, NULL);

    • 只在 msg_receiver 运行后删除队列,防止消息残留。

示例代码

下面的示例包括发送进程(msg_sender.c)接收进程(msg_receiver.c),它们通过消息队列进行通信。

1. 发送进程 (msg_sender.c)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/msg.h>
#include <sys/ipc.h>

#define MSG_KEY 1234  // 消息队列的键值

// 定义消息结构体
struct msg_buffer {
    long msg_type;      // 消息类型(必须 > 0)
    char msg_text[100]; // 消息内容
};

int main() {
    int msg_id;
    struct msg_buffer message;

    // 创建消息队列
    msg_id = msgget(MSG_KEY, 0666 | IPC_CREAT);
    if (msg_id == -1) {
        perror("msgget failed");
        exit(EXIT_FAILURE);
    }

    // 发送消息
    message.msg_type = 1; // 消息类型
    strcpy(message.msg_text, "Hello from sender!");

    if (msgsnd(msg_id, &message, sizeof(message.msg_text), 0) == -1) {
        perror("msgsnd failed");
        exit(EXIT_FAILURE);
    }

    printf("Message sent: %s\n", message.msg_text);
    return 0;
}

2. 接收进程 (msg_receiver.c)

#include <stdio.h>
#include <stdlib.h>
#include <sys/msg.h>
#include <sys/ipc.h>

#define MSG_KEY 1234  // 消息队列的键值

// 定义消息结构体
struct msg_buffer {
    long msg_type;      // 消息类型(必须 > 0)
    char msg_text[100]; // 消息内容
};

int main() {
    int msg_id;
    struct msg_buffer message;

    // 获取消息队列
    msg_id = msgget(MSG_KEY, 0666 | IPC_CREAT);
    if (msg_id == -1) {
        perror("msgget failed");
        exit(EXIT_FAILURE);
    }

    // 接收消息
    if (msgrcv(msg_id, &message, sizeof(message.msg_text), 1, 0) == -1) {
        perror("msgrcv failed");
        exit(EXIT_FAILURE);
    }

    printf("Message received: %s\n", message.msg_text);

    // 删除消息队列(可选)
    msgctl(msg_id, IPC_RMID, NULL);
    return 0;
}

编译运行

1. 编译

gcc -o msg_sender msg_sender.c
gcc -o msg_receiver msg_receiver.c

2. 运行
先运行 msg_receiver(等待接收消息):

./msg_receiver

再运行 msg_sender(发送消息):

./msg_sender

3. 可能的输出

Message sent: Hello from sender!
Message received: Hello from sender!

信号量

信号量介绍

信号量(Semaphore)在 Linux 进程间通信中的作用
信号量(Semaphore)是一种 进程间同步(IPC) 机制,主要用于控制多个进程对共享资源的访问。它可以用于 互斥(Mutex)同步(Synchronization),防止数据竞争或资源冲突。

示例代码

本示例使用 System V 信号量,包含:

  1. sem_init.c:初始化信号量(只需运行一次)。
  2. sem_writer.c:模拟一个进程写数据,需要获取信号量。
  3. sem_reader.c:模拟另一个进程读取数据,需要获取信号量。

1. 初始化信号量 (sem_init.c)

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

#define SEM_KEY 1234 // 信号量键值

int main() {
    int sem_id;

    // 创建信号量集,包含 1 个信号量
    sem_id = semget(SEM_KEY, 1, 0666 | IPC_CREAT);
    if (sem_id == -1) {
        perror("semget failed");
        exit(EXIT_FAILURE);
    }

    // 初始化信号量值为 1(允许一个进程进入)
    if (semctl(sem_id, 0, SETVAL, 1) == -1) {
        perror("semctl failed");
        exit(EXIT_FAILURE);
    }

    printf("Semaphore initialized.\n");
    return 0;
}

2. 写进程 (sem_writer.c)

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

#define SEM_KEY 1234 // 信号量键值

// P 操作(等待信号量)
void sem_wait(int sem_id) {
    struct sembuf sem_op = {0, -1, SEM_UNDO};
    semop(sem_id, &sem_op, 1);
}

// V 操作(释放信号量)
void sem_signal(int sem_id) {
    struct sembuf sem_op = {0, 1, SEM_UNDO};
    semop(sem_id, &sem_op, 1);
}

int main() {
    int sem_id;

    // 获取信号量
    sem_id = semget(SEM_KEY, 1, 0666);
    if (sem_id == -1) {
        perror("semget failed");
        exit(EXIT_FAILURE);
    }

    // 申请信号量(P 操作)
    printf("Writer: Waiting for semaphore...\n");
    sem_wait(sem_id);
    printf("Writer: Writing data...\n");

    sleep(2); // 模拟写操作

    // 释放信号量(V 操作)
    printf("Writer: Done. Releasing semaphore.\n");
    sem_signal(sem_id);

    return 0;
}

3. 读进程 (sem_reader.c)

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

#define SEM_KEY 1234 // 信号量键值

// P 操作(等待信号量)
void sem_wait(int sem_id) {
    struct sembuf sem_op = {0, -1, SEM_UNDO};
    semop(sem_id, &sem_op, 1);
}

// V 操作(释放信号量)
void sem_signal(int sem_id) {
    struct sembuf sem_op = {0, 1, SEM_UNDO};
    semop(sem_id, &sem_op, 1);
}

int main() {
    int sem_id;

    // 获取信号量
    sem_id = semget(SEM_KEY, 1, 0666);
    if (sem_id == -1) {
        perror("semget failed");
        exit(EXIT_FAILURE);
    }

    // 申请信号量(P 操作)
    printf("Reader: Waiting for semaphore...\n");
    sem_wait(sem_id);
    printf("Reader: Reading data...\n");

    sleep(2); // 模拟读操作

    // 释放信号量(V 操作)
    printf("Reader: Done. Releasing semaphore.\n");
    sem_signal(sem_id);

    return 0;
}

代码解析

  1. semget(SEM_KEY, 1, 0666 | IPC_CREAT):创建或获取信号量集,包含 1 个信号量。
  2. semctl(sem_id, 0, SETVAL, 1):将信号量初始化为 1,表示可用。
  3. semop() P 操作(等待信号量)
    • sem_op = {0, -1, SEM_UNDO};
    • semop(sem_id, &sem_op, 1);
    • 如果 sem_op 变为 -1,表示当前资源被占用,进程进入 等待 状态。
  4. semop() V 操作(释放信号量)
    • sem_op = {0, 1, SEM_UNDO};
    • semop(sem_id, &sem_op, 1);
    • 释放信号量,其他等待的进程可以获取。

编译运行

1. 编译

gcc -o sem_init sem_init.c
gcc -o sem_writer sem_writer.c
gcc -o sem_reader sem_reader.c

2. 运行
(1)首先初始化信号量(只需执行一次):

./sem_init

(2)打开两个终端,分别运行 sem_writersem_reader

  • 终端 1:
    ./sem_writer
    
  • 终端 2:
    ./sem_reader
    

(3)运行结果

Writer: Waiting for semaphore...
Writer: Writing data...
(2 秒后)
Writer: Done. Releasing semaphore.
Reader: Waiting for semaphore...
(等待 writer 释放信号量)
Reader: Reading data...
(2 秒后)
Reader: Done. Releasing semaphore.

信号

信号介绍

信号(Signal)是 Linux 进程间通信(IPC)的一种机制,用于 通知进程发生特定事件,比如 终止、暂停、继续 等。

之前在编写驱动时已经使用过了,只是那个时候的信号量的发送来自于内核空间,详情见 https://blog.csdn.net/wenhao_ir/article/details/145258992

信号量与信号的区别

特性信号量(Semaphore)信号(Signal)
作用进程间 同步,用于控制对共享资源的访问(互斥、同步)进程间 通信,用于通知进程发生了某个事件
适用场景解决 临界区 资源竞争(如多个进程访问同一块内存、文件等)进程需要 通知 其他进程某个事件发生(如终止、暂停、继续等)
数据传递无数据传递,仅用于同步或互斥可携带信号编号,但不能传递复杂数据
进程间关系通常由多个进程 共同访问 信号量进程之间可以 一对一一对多
实现方式semget() 创建信号量,semop() 进行 P/V 操作kill() 发送信号,signal()sigaction() 处理信号
等待方式进程可以 阻塞 等待信号量变为可用进程 异步 接收信号,不一定会阻塞
典型用法线程同步、资源管理(如生产者-消费者模型)进程终止、暂停、继续、用户定义的事件处理

简单理解

  • 信号量(Semaphore) 用于 同步多个进程或线程,确保多个进程不会同时访问共享资源,类似于 红绿灯 控制多个汽车的通行。
  • 信号(Signal) 用于 通知进程某个事件已发生,类似于 手机短信,让进程接收到特定事件通知。

示例

📌 信号量(Semaphore)示例
多个进程 访问共享资源,需要使用 信号量 来控制顺序:

sem_wait(sem_id);   // 获取资源(P 操作)
printf("进程正在访问共享资源...\n");
sleep(2);
sem_signal(sem_id); // 释放资源(V 操作)

🔹 适用场景:多进程写入同一个文件、共享内存同步、生产者-消费者模型。


📌 信号(Signal)示例
一个进程 向另一个进程发送通知,让它执行特定任务:

kill(pid, SIGUSR1);   // 发送 SIGUSR1 信号

🔹 适用场景:进程通知(如终止、暂停、继续)、异常处理(如 Ctrl+C 终止进程)。


示例代码

我们编写两个程序:

  1. signal_receiver.c:注册信号处理函数,等待 SIGUSR1 信号。
  2. signal_sender.c:向接收进程发送 SIGUSR1 信号。

1. 信号接收进程 (signal_receiver.c)

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

// 信号处理函数
void signal_handler(int signum) {
    printf("Receiver: Received signal %d!\n", signum);
}

int main() {
    // 注册 SIGUSR1 信号处理函数
    signal(SIGUSR1, signal_handler);

    printf("Receiver: Waiting for signal (PID: %d)...\n", getpid());

    // 持续运行,等待信号
    while (1) {
        pause(); // 挂起进程,直到接收到信号
    }

    return 0;
}

2. 信号发送进程 (signal_sender.c)

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
    if (argc != 2) {
        printf("Usage: %s <PID>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    // 获取接收进程的 PID
    pid_t receiver_pid = atoi(argv[1]);

    // 发送 SIGUSR1 信号
    if (kill(receiver_pid, SIGUSR1) == 0) {
        printf("Sender: Sent SIGUSR1 to process %d\n", receiver_pid);
    } else {
        perror("kill failed");
    }

    return 0;
}

代码解析

  1. signal(SIGUSR1, signal_handler);

    • 注册信号 SIGUSR1,当进程收到该信号时,执行 signal_handler()
  2. pause();

    • 挂起进程,等待信号。
    • 收到 SIGUSR1 后,会执行 signal_handler(),然后继续等待下一个信号。
  3. kill(receiver_pid, SIGUSR1);

    • kill() 用于向指定 PID 发送信号。

编译运行

1. 编译

gcc -o signal_receiver signal_receiver.c
gcc -o signal_sender signal_sender.c

2. 运行
(1)启动 signal_receiver 进程

./signal_receiver

会输出:

Receiver: Waiting for signal (PID: 12345)...

其中 12345接收进程的 PID(请记住)。

(2)在另一个终端运行 signal_sender,发送信号

./signal_sender 12345

(把 12345 替换成 signal_receiver 进程的实际 PID)

(3)信号接收进程会输出

Receiver: Received signal 10!

SIGUSR1 的信号编号通常是 10,但可能因系统不同而变化)


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值