进程间通信(IPC)机制详解

进程间通信(IPC)机制详解

在操作系统中,进程间通信(Inter-Process Communication, IPC)是不同进程间交换数据与同步操作的机制。本文将详细讲解五种常用的IPC方式:管道、信号、共享内存、消息队列和信号量,并附上相关函数的用法示例。

1. 管道(Pipe)

管道是Unix/Linux中最古老的IPC形式,分为匿名管道和命名管道两种。

匿名管道

#include <unistd.h>

int pipe(int pipefd[2]);
  • pipefd[0]: 读端
  • pipefd[1]: 写端
  • 返回值:成功返回0,失败返回-1

示例代码:

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main() {
    int pipefd[2];
    char buf[20];
    
    if (pipe(pipefd) == -1) {
        perror("pipe");
        return 1;
    }
    
    pid_t pid = fork();
    if (pid == 0) { // 子进程
        close(pipefd[0]); // 关闭读端
        write(pipefd[1], "Hello, Parent!", 14);
        close(pipefd[1]);
    } else { // 父进程
        close(pipefd[1]); // 关闭写端
        read(pipefd[0], buf, sizeof(buf));
        printf("Parent received: %s\n", buf);
        close(pipefd[0]);
        wait(NULL);
    }
    return 0;
}

命名管道(FIFO)

#include <sys/stat.h>

int mkfifo(const char *pathname, mode_t mode);
  • 创建命名管道文件
  • mode参数指定文件权限

使用流程:

  1. 创建FIFO:mkfifo("/tmp/myfifo", 0666)
  2. 进程A:open("/tmp/myfifo", O_WRONLY) 并写入数据
  3. 进程B:open("/tmp/myfifo", O_RDONLY) 并读取数据

2. 信号(Signal)

信号是异步通信机制,用于通知进程发生了某个事件。

常用函数

#include <signal.h>

void (*signal(int signum, void (*handler)(int)))(int);
int kill(pid_t pid, int sig);
int raise(int sig);
unsigned int alarm(unsigned int seconds);

常见信号:

  • SIGINT (2): 终端中断(Ctrl+C)
  • SIGKILL (9): 强制终止
  • SIGSEGV (11): 段错误
  • SIGALRM (14): 定时器信号

示例代码:

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

void handler(int sig) {
    printf("Received signal: %d\n", sig);
}

int main() {
    signal(SIGINT, handler); // 注册SIGINT处理函数
    signal(SIGALRM, handler); // 注册SIGALRM处理函数
    
    alarm(3); // 3秒后发送SIGALRM
    
    while(1) {
        pause(); // 等待信号
    }
    return 0;
}

3. 共享内存(Shared Memory)

共享内存允许多个进程访问同一块内存区域,是最快的IPC方式。

关键函数

#include <sys/shm.h>

int shmget(key_t key, size_t size, int shmflg);
void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

使用步骤:

  1. 创建/获取共享内存段:shmget()
  2. 附加到进程地址空间:shmat()
  3. 读写共享内存
  4. 分离共享内存:shmdt()
  5. 控制共享内存(删除等):shmctl()

示例代码:

#include <stdio.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <string.h>

#define SHM_SIZE 1024

int main() {
    key_t key = ftok("/tmp", 'A');
    int shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0666);
    
    if (shmid == -1) {
        perror("shmget");
        return 1;
    }
    
    char *shm_ptr = (char*)shmat(shmid, NULL, 0);
    if (shm_ptr == (void*)-1) {
        perror("shmat");
        return 1;
    }
    
    // 写入数据
    strcpy(shm_ptr, "Hello, Shared Memory!");
    
    // 读取数据(在另一个进程中)
    // printf("Read from shared memory: %s\n", shm_ptr);
    
    shmdt(shm_ptr);
    shmctl(shmid, IPC_RMID, NULL); // 删除共享内存
    return 0;
}

4. 消息队列(Message Queue)

消息队列允许进程通过发送/接收消息来通信。

关键函数

#include <sys/msg.h>

int msgget(key_t key, int msgflg);
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
int msgctl(int msqid, int cmd, struct msqid_ds *buf);

消息结构:

struct msgbuf {
    long mtype;     // 消息类型
    char mtext[1];  // 消息数据
};

示例代码:

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

struct msgbuf {
    long mtype;
    char mtext[100];
};

int main() {
    key_t key = ftok("/tmp", 'B');
    int msgid = msgget(key, IPC_CREAT | 0666);
    
    struct msgbuf msg;
    msg.mtype = 1;
    strcpy(msg.mtext, "Hello, Message Queue!");
    
    // 发送消息
    if (msgsnd(msgid, &msg, sizeof(msg.mtext), 0) == -1) {
        perror("msgsnd");
        return 1;
    }
    
    // 接收消息(在另一个进程中)
    // if (msgrcv(msgid, &msg, sizeof(msg.mtext), 1, 0) == -1) {
    //     perror("msgrcv");
    //     return 1;
    // }
    // printf("Received: %s\n", msg.mtext);
    
    msgctl(msgid, IPC_RMID, NULL); // 删除消息队列
    return 0;
}

5. 信号量(Semaphore)

信号量用于进程间的同步,控制对共享资源的访问。

关键函数

#include <sys/sem.h>

int semget(key_t key, int nsems, int semflg);
int semop(int semid, struct sembuf *sops, unsigned nsops);
int semctl(int semid, int semnum, int cmd, ...);

操作结构体:

struct sembuf {
    unsigned short sem_num;  // 信号量编号
    short sem_op;   // 操作(正数加,负数减)
    short sem_flg;  // 标志(通常为0)
};

示例代码:

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

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

int main() {
    key_t key = ftok("/tmp", 'C');
    int semid = semget(key, 1, IPC_CREAT | 0666);
    
    union semun arg;
    arg.val = 1; // 初始值设为1(二进制信号量)
    semctl(semid, 0, SETVAL, arg);
    
    struct sembuf op_lock = {0, -1, 0}; // P操作
    struct sembuf op_unlock = {0, 1, 0}; // V操作
    
    // 加锁
    semop(semid, &op_lock, 1);
    
    printf("Critical section start\n");
    sleep(2); // 模拟临界区操作
    printf("Critical section end\n");
    
    // 解锁
    semop(semid, &op_unlock, 1);
    
    semctl(semid, 0, IPC_RMID); // 删除信号量
    return 0;
}

IPC方式对比

机制 速度 复杂度 适用场景
管道 中等 父子进程间简单通信
命名管道 中等 任意进程间流式通信
信号 事件通知、简单控制
共享内存 最快 大数据量、高性能通信
消息队列 中高 结构化的进程间通信
信号量 进程同步、资源访问控制

总结

不同的IPC机制适用于不同的场景:

  • 管道/命名管道:适合流式数据传输
  • 信号:适合事件通知和简单控制
  • 共享内存:适合高性能大数据传输
  • 消息队列:适合结构化消息传递
  • 信号量:适合进程同步和资源保护

在实际系统编程中,通常需要组合多种IPC机制来满足复杂需求。理解各种IPC机制的特点和使用场景,能够帮助开发者设计出高效可靠的进程间通信方案。

posted @ 2025-07-30 21:09  Rare_30  阅读(154)  评论(2)    收藏  举报