第47集:进程间通信

章节标题

进程间通信

核心知识点讲解

进程间通信的概念

进程间通信(IPC,Inter-Process Communication)是指不同进程之间交换数据和协调操作的机制。在Linux系统中,进程是相互独立的执行单元,它们有各自的地址空间,不能直接访问彼此的内存。因此,需要通过专门的IPC机制来实现进程间的通信。

常用的IPC机制

Linux系统提供了多种IPC机制,每种机制都有其特点和适用场景:

  1. 管道(Pipes):用于具有亲缘关系的进程之间的通信
  2. 命名管道(Named Pipes):用于无亲缘关系的进程之间的通信
  3. 信号(Signals):用于进程间的事件通知
  4. 共享内存(Shared Memory):用于高效的数据共享
  5. 消息队列(Message Queues):用于进程间的消息传递
  6. 信号量(Semaphores):用于进程间的同步
  7. 套接字(Sockets):用于网络通信,也可用于本地进程间通信

管道(Pipes)

匿名管道

匿名管道是最基本的IPC机制之一,它是一个单向的字节流,用于具有亲缘关系的进程之间的通信(如父子进程、兄弟进程)。

基本用法

在Shell中,可以使用 | 符号创建匿名管道:

# 使用管道将一个命令的输出作为另一个命令的输入
命令1 | 命令2

# 示例:使用管道统计文件行数
ls -l | wc -l

# 示例:使用管道过滤文件内容
grep "error" log.txt | head -10

在C语言中,可以使用 pipe() 函数创建匿名管道:

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

int main() {
    int pipefd[2];
    pid_t pid;
    char buffer[256];
    
    // 创建管道
    if (pipe(pipefd) == -1) {
        perror("pipe");
        exit(EXIT_FAILURE);
    }
    
    // 创建子进程
    pid = fork();
    if (pid == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    }
    
    if (pid == 0) {
        // 子进程:读取管道
        close(pipefd[1]);  // 关闭写端
        
        read(pipefd[0], buffer, sizeof(buffer));
        printf("子进程收到:%s\n", buffer);
        
        close(pipefd[0]);  // 关闭读端
    } else {
        // 父进程:写入管道
        close(pipefd[0]);  // 关闭读端
        
        char *message = "Hello from parent!";
        write(pipefd[1], message, sizeof(message));
        printf("父进程发送:%s\n", message);
        
        close(pipefd[1]);  // 关闭写端
        
        // 等待子进程结束
        wait(NULL);
    }
    
    return 0;
}
特点
  • 单向通信:数据只能从管道的一端流向另一端
  • 只能用于具有亲缘关系的进程之间
  • 管道中的数据是字节流,没有消息边界
  • 管道是基于文件描述符的,使用完后需要关闭

命名管道

命名管道(FIFO,First-In-First-Out)是一种特殊的文件,用于无亲缘关系的进程之间的通信。

基本用法

在Shell中,可以使用 mkfifo 命令创建命名管道:

# 创建命名管道
mkfifo 管道名称

# 示例:创建命名管道
mkfifo mypipe

# 向命名管道写入数据
echo "Hello" > mypipe

# 从命名管道读取数据
cat mypipe

在C语言中,可以使用 mkfifo() 函数创建命名管道:

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

int main() {
    const char *fifo_path = "/tmp/myfifo";
    int fd;
    char *message = "Hello from writer!";
    
    // 创建命名管道
    mkfifo(fifo_path, 0666);
    
    // 打开命名管道进行写入
    fd = open(fifo_path, O_WRONLY);
    if (fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }
    
    // 写入数据
    write(fd, message, sizeof(message));
    printf("写入数据:%s\n", message);
    
    // 关闭文件描述符
    close(fd);
    
    return 0;
}

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

int main() {
    const char *fifo_path = "/tmp/myfifo";
    int fd;
    char buffer[256];
    
    // 打开命名管道进行读取
    fd = open(fifo_path, O_RDONLY);
    if (fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }
    
    // 读取数据
    read(fd, buffer, sizeof(buffer));
    printf("读取数据:%s\n", buffer);
    
    // 关闭文件描述符
    close(fd);
    
    return 0;
}
特点
  • 可以用于无亲缘关系的进程之间
  • 数据是字节流,没有消息边界
  • 命名管道是一种特殊的文件,存在于文件系统中
  • 使用完后需要删除命名管道文件

信号(Signals)

信号是一种异步的IPC机制,用于进程间的事件通知。当一个进程向另一个进程发送信号时,接收信号的进程会中断当前的执行,转而去处理该信号。

常用信号

信号编号 信号名称 描述 默认行为
1 SIGHUP 终端挂起或控制进程终止 终止进程
2 SIGINT 中断信号(Ctrl+C) 终止进程
3 SIGQUIT 退出信号(Ctrl+\) 终止进程并生成核心转储
9 SIGKILL 强制终止信号 强制终止进程(不可捕获)
15 SIGTERM 终止信号 终止进程(默认信号)
18 SIGCONT 继续信号 继续已暂停的进程
19 SIGSTOP 停止信号 暂停进程(不可捕获)
20 SIGTSTP 终端停止信号(Ctrl+Z) 暂停进程

基本用法

在Shell中,可以使用 kill 命令向进程发送信号:

# 向进程发送默认信号(SIGTERM)
kill 进程ID

# 向进程发送指定信号
kill -信号编号 进程ID
kill -信号名称 进程ID

# 示例:向进程发送SIGINT信号
kill -2 1234

# 示例:向进程发送SIGKILL信号
kill -9 1234

在C语言中,可以使用 kill() 函数向进程发送信号:

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

// 信号处理函数
void signal_handler(int signum) {
    printf("收到信号:%d\n", signum);
    switch (signum) {
        case SIGINT:
            printf("处理SIGINT信号(Ctrl+C)\n");
            break;
        case SIGTERM:
            printf("处理SIGTERM信号\n");
            break;
        default:
            printf("处理其他信号\n");
            break;
    }
}

int main() {
    pid_t pid;
    
    // 创建子进程
    pid = fork();
    if (pid == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    }
    
    if (pid == 0) {
        // 子进程:设置信号处理函数
        signal(SIGINT, signal_handler);
        signal(SIGTERM, signal_handler);
        
        printf("子进程启动,PID:%d\n", getpid());
        printf("等待信号...\n");
        
        // 无限循环,等待信号
        while (1) {
            sleep(1);
        }
    } else {
        // 父进程:发送信号
        printf("父进程启动,子进程PID:%d\n", pid);
        
        // 等待2秒
        sleep(2);
        
        // 发送SIGINT信号
        printf("发送SIGINT信号...\n");
        kill(pid, SIGINT);
        
        // 等待2秒
        sleep(2);
        
        // 发送SIGTERM信号
        printf("发送SIGTERM信号...\n");
        kill(pid, SIGTERM);
        
        // 等待子进程结束
        wait(NULL);
        
        printf("父进程结束\n");
    }
    
    return 0;
}

特点

  • 用于进程间的事件通知
  • 信号是异步的,接收进程不知道信号何时到达
  • 有些信号是不可捕获的(如SIGKILL、SIGSTOP)
  • 信号处理函数应该简短,避免执行耗时操作

共享内存(Shared Memory)

共享内存是最快的IPC机制,它允许多个进程直接访问同一块物理内存区域,不需要数据拷贝。

基本用法

在C语言中,可以使用 shmget()shmat()shmdt()shmctl() 函数来使用共享内存:

// 写入端
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
    key_t key;
    int shmid;
    char *shmaddr;
    const char *message = "Hello from shared memory!";
    
    // 生成键值
    key = ftok("shmfile", 65);
    
    // 创建共享内存段
    shmid = shmget(key, 1024, 0666 | IPC_CREAT);
    if (shmid == -1) {
        perror("shmget");
        exit(EXIT_FAILURE);
    }
    
    // 附加共享内存
    shmaddr = (char *)shmat(shmid, NULL, 0);
    if (shmaddr == (char *)-1) {
        perror("shmat");
        exit(EXIT_FAILURE);
    }
    
    // 写入数据
    strcpy(shmaddr, message);
    printf("写入共享内存:%s\n", message);
    
    // 等待读取
    printf("等待读取...\n");
    while (*shmaddr != '*') {
        sleep(1);
    }
    
    // 分离共享内存
    shmdt(shmaddr);
    
    // 删除共享内存
    shmctl(shmid, IPC_RMID, NULL);
    
    printf("写入端结束\n");
    
    return 0;
}

// 读取端
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
    key_t key;
    int shmid;
    char *shmaddr;
    
    // 生成键值(与写入端相同)
    key = ftok("shmfile", 65);
    
    // 获取共享内存段
    shmid = shmget(key, 1024, 0666);
    if (shmid == -1) {
        perror("shmget");
        exit(EXIT_FAILURE);
    }
    
    // 附加共享内存
    shmaddr = (char *)shmat(shmid, NULL, 0);
    if (shmaddr == (char *)-1) {
        perror("shmat");
        exit(EXIT_FAILURE);
    }
    
    // 读取数据
    printf("读取共享内存:%s\n", shmaddr);
    
    // 标记读取完成
    *shmaddr = '*';
    
    // 分离共享内存
    shmdt(shmaddr);
    
    printf("读取端结束\n");
    
    return 0;
}

特点

  • 最快的IPC机制,不需要数据拷贝
  • 多个进程可以同时访问同一块内存
  • 需要使用信号量等机制来同步对共享内存的访问
  • 共享内存段需要显式创建和删除

消息队列(Message Queues)

消息队列是一种用于进程间传递消息的IPC机制,它允许进程发送和接收有结构的消息。

基本用法

在C语言中,可以使用 msgget()msgsnd()msgrcv()msgctl() 函数来使用消息队列:

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

// 消息结构
typedef struct {
    long mtype;     // 消息类型
    char mtext[100]; // 消息内容
} message;

int main() {
    key_t key;
    int msgid;
    message msg;
    
    // 生成键值
    key = ftok("msgfile", 65);
    
    // 创建消息队列
    msgid = msgget(key, 0666 | IPC_CREAT);
    if (msgid == -1) {
        perror("msgget");
        exit(EXIT_FAILURE);
    }
    
    // 设置消息类型和内容
    msg.mtype = 1;
    strcpy(msg.mtext, "Hello from message queue!");
    
    // 发送消息
    if (msgsnd(msgid, &msg, sizeof(msg.mtext), 0) == -1) {
        perror("msgsnd");
        exit(EXIT_FAILURE);
    }
    
    printf("发送消息:%s\n", msg.mtext);
    
    // 删除消息队列
    msgctl(msgid, IPC_RMID, NULL);
    
    printf("发送端结束\n");
    
    return 0;
}

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

// 消息结构(与发送端相同)
typedef struct {
    long mtype;     // 消息类型
    char mtext[100]; // 消息内容
} message;

int main() {
    key_t key;
    int msgid;
    message msg;
    
    // 生成键值(与发送端相同)
    key = ftok("msgfile", 65);
    
    // 获取消息队列
    msgid = msgget(key, 0666);
    if (msgid == -1) {
        perror("msgget");
        exit(EXIT_FAILURE);
    }
    
    // 接收消息
    if (msgrcv(msgid, &msg, sizeof(msg.mtext), 1, 0) == -1) {
        perror("msgrcv");
        exit(EXIT_FAILURE);
    }
    
    printf("接收消息:%s\n", msg.mtext);
    
    printf("接收端结束\n");
    
    return 0;
}

特点

  • 用于进程间传递有结构的消息
  • 消息队列是消息的链表,具有消息类型
  • 消息队列中的消息可以按照类型接收
  • 消息队列需要显式创建和删除

信号量(Semaphores)

信号量是一种用于进程间同步的IPC机制,它可以用来协调多个进程对共享资源的访问。

基本用法

在C语言中,可以使用 semget()semop()semctl() 函数来使用信号量:

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

// 信号量操作函数
void sem_wait(int semid) {
    struct sembuf sb;
    sb.sem_num = 0;
    sb.sem_op = -1;  // P操作
    sb.sem_flg = 0;
    semop(semid, &sb, 1);
}

void sem_post(int semid) {
    struct sembuf sb;
    sb.sem_num = 0;
    sb.sem_op = 1;   // V操作
    sb.sem_flg = 0;
    semop(semid, &sb, 1);
}

int main() {
    key_t key;
    int semid;
    pid_t pid;
    
    // 生成键值
    key = ftok("semfile", 65);
    
    // 创建信号量
    semid = semget(key, 1, 0666 | IPC_CREAT);
    if (semid == -1) {
        perror("semget");
        exit(EXIT_FAILURE);
    }
    
    // 初始化信号量值为1(互斥锁)
    semctl(semid, 0, SETVAL, 1);
    
    // 创建子进程
    pid = fork();
    if (pid == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    }
    
    if (pid == 0) {
        // 子进程
        printf("子进程:等待进入临界区...\n");
        sem_wait(semid);  // P操作
        
        // 临界区
        printf("子进程:进入临界区\n");
        sleep(3);  // 模拟耗时操作
        printf("子进程:离开临界区\n");
        
        sem_post(semid);  // V操作
    } else {
        // 父进程
        printf("父进程:等待进入临界区...\n");
        sem_wait(semid);  // P操作
        
        // 临界区
        printf("父进程:进入临界区\n");
        sleep(3);  // 模拟耗时操作
        printf("父进程:离开临界区\n");
        
        sem_post(semid);  // V操作
        
        // 等待子进程结束
        wait(NULL);
        
        // 删除信号量
        semctl(semid, 0, IPC_RMID);
    }
    
    return 0;
}

特点

  • 用于进程间的同步和互斥
  • 信号量是一个计数器,表示可用资源的数量
  • P操作(sem_wait):获取资源,信号量减1
  • V操作(sem_post):释放资源,信号量加1
  • 信号量需要显式创建和删除

套接字(Sockets)

套接字是一种用于网络通信的IPC机制,也可以用于本地进程间通信。

基本用法

在C语言中,可以使用 socket()bind()listen()accept()connect()read()write()close() 函数来使用套接字:

// 服务器端(本地套接字)
#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int main() {
    int server_fd, client_fd;
    struct sockaddr_un addr;
    char buffer[1024];
    
    // 创建套接字
    server_fd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (server_fd == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }
    
    // 设置地址
    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    strcpy(addr.sun_path, "/tmp/local_socket");
    
    // 删除旧的套接字文件
    unlink(addr.sun_path);
    
    // 绑定地址
    if (bind(server_fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
        perror("bind");
        exit(EXIT_FAILURE);
    }
    
    // 监听连接
    if (listen(server_fd, 5) == -1) {
        perror("listen");
        exit(EXIT_FAILURE);
    }
    
    printf("服务器启动,等待连接...\n");
    
    // 接受连接
    client_fd = accept(server_fd, NULL, NULL);
    if (client_fd == -1) {
        perror("accept");
        exit(EXIT_FAILURE);
    }
    
    printf("客户端连接成功\n");
    
    // 读取数据
    read(client_fd, buffer, sizeof(buffer));
    printf("收到消息:%s\n", buffer);
    
    // 发送数据
    char *response = "Hello from server!";
    write(client_fd, response, strlen(response) + 1);
    printf("发送消息:%s\n", response);
    
    // 关闭连接
    close(client_fd);
    close(server_fd);
    
    // 删除套接字文件
    unlink(addr.sun_path);
    
    printf("服务器结束\n");
    
    return 0;
}

// 客户端(本地套接字)
#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int main() {
    int sock_fd;
    struct sockaddr_un addr;
    char buffer[1024];
    
    // 创建套接字
    sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (sock_fd == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }
    
    // 设置地址
    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    strcpy(addr.sun_path, "/tmp/local_socket");
    
    // 连接服务器
    if (connect(sock_fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
        perror("connect");
        exit(EXIT_FAILURE);
    }
    
    printf("连接服务器成功\n");
    
    // 发送数据
    char *message = "Hello from client!";
    write(sock_fd, message, strlen(message) + 1);
    printf("发送消息:%s\n", message);
    
    // 读取数据
    read(sock_fd, buffer, sizeof(buffer));
    printf("收到消息:%s\n", buffer);
    
    // 关闭连接
    close(sock_fd);
    
    printf("客户端结束\n");
    
    return 0;
}

特点

  • 可用于网络通信和本地进程间通信
  • 支持多种通信协议(TCP、UDP等)
  • 提供可靠的面向连接的通信(TCP)
  • 本地套接字使用文件系统路径作为地址

实用案例分析

案例1:使用管道进行进程间通信

# 示例1:使用管道统计日志文件中的错误数量
grep "ERROR" /var/log/syslog | wc -l

# 示例2:使用管道过滤和排序文件内容
ls -la | grep "\.txt" | sort -k 5 -n

# 示例3:使用管道将命令输出保存到文件并同时显示
echo "Hello World" | tee output.txt

# 示例4:使用管道进行多命令组合
ps aux | grep "nginx" | grep -v grep | awk '{print $2}' | xargs kill

案例2:使用命名管道进行无亲缘关系进程间通信

# 1. 创建命名管道
mkfifo /tmp/data_pipe

# 2. 在一个终端中启动读取进程
cat /tmp/data_pipe

# 3. 在另一个终端中启动写入进程
echo "Hello from another terminal" > /tmp/data_pipe

# 4. 清理命名管道
rm /tmp/data_pipe

案例3:使用信号进行进程控制

# 1. 启动一个长时间运行的进程
sleep 300 &

# 2. 查看进程ID
pid=$!

# 3. 发送SIGINT信号(模拟Ctrl+C)
kill -2 $pid

# 4. 启动另一个进程
sleep 300 &
pid=$!

# 5. 发送SIGTERM信号(默认终止信号)
kill $pid

# 6. 启动另一个进程
sleep 300 &
pid=$!

# 7. 发送SIGKILL信号(强制终止)
kill -9 $pid

案例4:使用共享内存进行高效数据传输

// 共享内存示例:数据传输
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#define SHM_SIZE 1024 * 1024  // 1MB共享内存

int main() {
    key_t key;
    int shmid;
    char *shmaddr;
    pid_t pid;
    clock_t start, end;
    double time_taken;
    
    // 生成键值
    key = ftok("shm_test", 65);
    
    // 创建共享内存
    shmid = shmget(key, SHM_SIZE, 0666 | IPC_CREAT);
    if (shmid == -1) {
        perror("shmget");
        exit(EXIT_FAILURE);
    }
    
    // 创建子进程
    pid = fork();
    if (pid == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    }
    
    if (pid == 0) {
        // 子进程:读取共享内存
        shmaddr = (char *)shmat(shmid, NULL, 0);
        if (shmaddr == (char *)-1) {
            perror("shmat");
            exit(EXIT_FAILURE);
        }
        
        // 等待数据
        while (*shmaddr == 0) {
            sleep(1);
        }
        
        // 读取数据
        start = clock();
        printf("子进程:读取数据大小:%ld bytes\n", strlen(shmaddr));
        end = clock();
        
        time_taken = ((double)(end - start)) / CLOCKS_PER_SEC;
        printf("子进程:读取时间:%f seconds\n", time_taken);
        
        // 分离共享内存
        shmdt(shmaddr);
    } else {
        // 父进程:写入共享内存
        shmaddr = (char *)shmat(shmid, NULL, 0);
        if (shmaddr == (char *)-1) {
            perror("shmat");
            exit(EXIT_FAILURE);
        }
        
        // 写入大量数据
        start = clock();
        memset(shmaddr, 'A', SHM_SIZE - 1);
        shmaddr[SHM_SIZE - 1] = '\0';
        end = clock();
        
        time_taken = ((double)(end - start)) / CLOCKS_PER_SEC;
        printf("父进程:写入数据大小:%ld bytes\n", strlen(shmaddr));
        printf("父进程:写入时间:%f seconds\n", time_taken);
        
        // 通知子进程数据已写入
        *shmaddr = 'A';
        
        // 等待子进程结束
        wait(NULL);
        
        // 分离共享内存
        shmdt(shmaddr);
        
        // 删除共享内存
        shmctl(shmid, IPC_RMID, NULL);
    }
    
    return 0;
}

案例5:使用信号量进行进程同步

// 信号量示例:生产者-消费者问题
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define BUFFER_SIZE 5
#define ITEM_COUNT 10

// 缓冲区
int buffer[BUFFER_SIZE];
int in = 0;  // 写入位置
int out = 0; // 读取位置

// 信号量
int empty_sem;  // 空槽位数量
int full_sem;   // 已使用槽位数量
int mutex_sem;  // 互斥锁

// 信号量操作函数
void sem_wait(int semid) {
    struct sembuf sb;
    sb.sem_num = 0;
    sb.sem_op = -1;
    sb.sem_flg = 0;
    semop(semid, &sb, 1);
}

void sem_post(int semid) {
    struct sembuf sb;
    sb.sem_num = 0;
    sb.sem_op = 1;
    sb.sem_flg = 0;
    semop(semid, &sb, 1);
}

// 生产者函数
void producer() {
    int item;
    
    for (int i = 0; i < ITEM_COUNT; i++) {
        // 生产一个item
        item = i;
        printf("生产者:生产 item %d\n", item);
        
        // 等待空槽位
        sem_wait(empty_sem);
        
        // 进入临界区
        sem_wait(mutex_sem);
        
        // 将item放入缓冲区
        buffer[in] = item;
        in = (in + 1) % BUFFER_SIZE;
        printf("生产者:将 item %d 放入缓冲区\n", item);
        
        // 离开临界区
        sem_post(mutex_sem);
        
        // 通知有新item
        sem_post(full_sem);
        
        // 随机休眠
        sleep(rand() % 2);
    }
}

// 消费者函数
void consumer() {
    int item;
    
    for (int i = 0; i < ITEM_COUNT; i++) {
        // 等待有item
        sem_wait(full_sem);
        
        // 进入临界区
        sem_wait(mutex_sem);
        
        // 从缓冲区取出item
        item = buffer[out];
        out = (out + 1) % BUFFER_SIZE;
        printf("消费者:从缓冲区取出 item %d\n", item);
        
        // 离开临界区
        sem_post(mutex_sem);
        
        // 通知有空槽位
        sem_post(empty_sem);
        
        // 消费item
        printf("消费者:消费 item %d\n", item);
        
        // 随机休眠
        sleep(rand() % 3);
    }
}

int main() {
    key_t key;
    pid_t pid;
    
    // 生成键值
    key = ftok("sem_prod_cons", 65);
    
    // 创建信号量
    empty_sem = semget(key, 1, 0666 | IPC_CREAT);
    full_sem = semget(key + 1, 1, 0666 | IPC_CREAT);
    mutex_sem = semget(key + 2, 1, 0666 | IPC_CREAT);
    
    // 初始化信号量
    semctl(empty_sem, 0, SETVAL, BUFFER_SIZE);  // 初始空槽位为缓冲区大小
    semctl(full_sem, 0, SETVAL, 0);             // 初始已使用槽位为0
    semctl(mutex_sem, 0, SETVAL, 1);             // 互斥锁初始值为1
    
    // 创建子进程
    pid = fork();
    if (pid == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    }
    
    if (pid == 0) {
        // 子进程:消费者
        consumer();
    } else {
        // 父进程:生产者
        producer();
        
        // 等待子进程结束
        wait(NULL);
        
        // 删除信号量
        semctl(empty_sem, 0, IPC_RMID);
        semctl(full_sem, 0, IPC_RMID);
        semctl(mutex_sem, 0, IPC_RMID);
        
        printf("生产者-消费者演示结束\n");
    }
    
    return 0;
}

代码示例

示例1:管道通信示例

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

int main() {
    int pipefd[2];
    pid_t pid;
    char buffer[256];
    int bytes_read;
    
    // 创建管道
    if (pipe(pipefd) == -1) {
        perror("pipe");
        exit(EXIT_FAILURE);
    }
    
    // 创建子进程
    pid = fork();
    if (pid == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    }
    
    if (pid == 0) {
        // 子进程:读取管道
        close(pipefd[1]);  // 关闭写端
        
        printf("子进程:等待读取数据...\n");
        
        // 读取数据
        bytes_read = read(pipefd[0], buffer, sizeof(buffer) - 1);
        if (bytes_read > 0) {
            buffer[bytes_read] = '\0';
            printf("子进程:读取到 %d 字节数据:%s\n", bytes_read, buffer);
        }
        
        // 向父进程发送响应
        printf("子进程:发送响应...\n");
        write(pipefd[0], "Hello from child!", 17);
        
        close(pipefd[0]);  // 关闭读端
        exit(EXIT_SUCCESS);
    } else {
        // 父进程:写入管道
        close(pipefd[0]);  // 关闭读端
        
        // 向子进程发送数据
        char *message = "Hello from parent! This is a test message.";
        printf("父进程:发送数据:%s\n", message);
        write(pipefd[1], message, strlen(message));
        
        // 等待子进程响应
        sleep(1);
        
        // 重新打开读端
        close(pipefd[1]);
        int pipefd_read[2];
        pipe(pipefd_read);
        
        // 读取子进程响应
        bytes_read = read(pipefd_read[0], buffer, sizeof(buffer) - 1);
        if (bytes_read > 0) {
            buffer[bytes_read] = '\0';
            printf("父进程:读取到子进程响应:%s\n", buffer);
        }
        
        close(pipefd_read[0]);
        close(pipefd_read[1]);
        
        // 等待子进程结束
        wait(NULL);
        printf("父进程:子进程结束\n");
    }
    
    return 0;
}

示例2:消息队列示例

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

// 消息结构
typedef struct {
    long mtype;     // 消息类型
    int id;         // 消息ID
    char text[200]; // 消息内容
} message;

int main() {
    key_t key;
    int msgid;
    pid_t pid;
    message msg;
    
    // 生成键值
    key = ftok("msg_example", 65);
    
    // 创建消息队列
    msgid = msgget(key, 0666 | IPC_CREAT);
    if (msgid == -1) {
        perror("msgget");
        exit(EXIT_FAILURE);
    }
    
    // 创建子进程
    pid = fork();
    if (pid == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    }
    
    if (pid == 0) {
        // 子进程:接收消息
        printf("子进程:等待接收消息...\n");
        
        // 接收类型为1的消息
        if (msgrcv(msgid, &msg, sizeof(msg) - sizeof(long), 1, 0) == -1) {
            perror("msgrcv");
            exit(EXIT_FAILURE);
        }
        
        printf("子进程:接收到消息\n");
        printf("  类型:%ld\n", msg.mtype);
        printf("  ID:%d\n", msg.id);
        printf("  内容:%s\n", msg.text);
        
        // 发送响应消息(类型为2)
        msg.mtype = 2;
        msg.id = 2;
        strcpy(msg.text, "Message received successfully!");
        
        if (msgsnd(msgid, &msg, sizeof(msg) - sizeof(long), 0) == -1) {
            perror("msgsnd");
            exit(EXIT_FAILURE);
        }
        
        printf("子进程:发送响应消息\n");
    } else {
        // 父进程:发送消息
        printf("父进程:准备发送消息...\n");
        
        // 准备消息
        msg.mtype = 1;
        msg.id = 1;
        strcpy(msg.text, "Hello from parent process! This is a structured message.");
        
        // 发送消息
        if (msgsnd(msgid, &msg, sizeof(msg) - sizeof(long), 0) == -1) {
            perror("msgsnd");
            exit(EXIT_FAILURE);
        }
        
        printf("父进程:消息发送成功\n");
        
        // 等待子进程响应
        printf("父进程:等待子进程响应...\n");
        
        // 接收类型为2的消息
        if (msgrcv(msgid, &msg, sizeof(msg) - sizeof(long), 2, 0) == -1) {
            perror("msgrcv");
            exit(EXIT_FAILURE);
        }
        
        printf("父进程:接收到响应消息\n");
        printf("  类型:%ld\n", msg.mtype);
        printf("  ID:%d\n", msg.id);
        printf("  内容:%s\n", msg.text);
        
        // 等待子进程结束
        wait(NULL);
        
        // 删除消息队列
        msgctl(msgid, IPC_RMID, NULL);
        printf("父进程:消息队列已删除\n");
    }
    
    return 0;
}

示例3:本地套接字示例

// 服务器端
#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#define SOCKET_PATH "/tmp/local_server.sock"

int main() {
    int server_fd, client_fd;
    struct sockaddr_un addr;
    char buffer[1024];
    ssize_t bytes_read;
    
    // 创建套接字
    server_fd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (server_fd == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }
    
    // 设置地址
    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1);
    
    // 删除旧的套接字文件
    unlink(SOCKET_PATH);
    
    // 绑定地址
    if (bind(server_fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
        perror("bind");
        exit(EXIT_FAILURE);
    }
    
    // 监听连接
    if (listen(server_fd, 5) == -1) {
        perror("listen");
        exit(EXIT_FAILURE);
    }
    
    printf("服务器启动,等待连接...\n");
    printf("套接字路径:%s\n", SOCKET_PATH);
    
    // 接受连接
    client_fd = accept(server_fd, NULL, NULL);
    if (client_fd == -1) {
        perror("accept");
        exit(EXIT_FAILURE);
    }
    
    printf("客户端连接成功\n");
    
    // 循环处理消息
    while (1) {
        // 读取消息
        bytes_read = read(client_fd, buffer, sizeof(buffer) - 1);
        if (bytes_read == -1) {
            perror("read");
            break;
        }
        
        if (bytes_read == 0) {
            printf("客户端断开连接\n");
            break;
        }
        
        buffer[bytes_read] = '\0';
        printf("收到消息:%s\n", buffer);
        
        // 检查是否为退出消息
        if (strcmp(buffer, "exit") == 0) {
            printf("收到退出命令\n");
            break;
        }
        
        // 发送响应
        char response[1024];
        snprintf(response, sizeof(response), "Server received: %s", buffer);
        
        if (write(client_fd, response, strlen(response)) == -1) {
            perror("write");
            break;
        }
        
        printf("发送响应:%s\n", response);
    }
    
    // 关闭连接
    close(client_fd);
    close(server_fd);
    
    // 删除套接字文件
    unlink(SOCKET_PATH);
    
    printf("服务器关闭\n");
    
    return 0;
}

// 客户端
#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#define SOCKET_PATH "/tmp/local_server.sock"

int main() {
    int sock_fd;
    struct sockaddr_un addr;
    char buffer[1024];
    ssize_t bytes_read;
    
    // 创建套接字
    sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (sock_fd == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }
    
    // 设置地址
    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1);
    
    // 连接服务器
    if (connect(sock_fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
        perror("connect");
        exit(EXIT_FAILURE);
    }
    
    printf("连接服务器成功\n");
    printf("输入消息发送给服务器(输入'exit'退出):\n");
    
    // 循环发送消息
    while (1) {
        // 读取用户输入
        printf("> ");
        if (fgets(buffer, sizeof(buffer), stdin) == NULL) {
            break;
        }
        
        // 移除换行符
        buffer[strcspn(buffer, "\n")] = '\0';
        
        // 发送消息
        if (write(sock_fd, buffer, strlen(buffer)) == -1) {
            perror("write");
            break;
        }
        
        // 检查是否为退出消息
        if (strcmp(buffer, "exit") == 0) {
            break;
        }
        
        // 读取响应
        bytes_read = read(sock_fd, buffer, sizeof(buffer) - 1);
        if (bytes_read == -1) {
            perror("read");
            break;
        }
        
        if (bytes_read == 0) {
            printf("服务器断开连接\n");
            break;
        }
        
        buffer[bytes_read] = '\0';
        printf("服务器响应:%s\n", buffer);
    }
    
    // 关闭连接
    close(sock_fd);
    
    printf("客户端关闭\n");
    
    return 0;
}

总结

本集介绍了 Linux 中的进程间通信(IPC)机制,包括:

  1. 管道(Pipes):用于具有亲缘关系的进程之间的通信,包括匿名管道和命名管道
  2. 信号(Signals):用于进程间的事件通知,如中断、终止等
  3. 共享内存(Shared Memory):最快的IPC机制,允许多个进程直接访问同一块内存
  4. 消息队列(Message Queues):用于进程间传递有结构的消息
  5. 信号量(Semaphores):用于进程间的同步和互斥,协调对共享资源的访问
  6. 套接字(Sockets):用于网络通信,也可用于本地进程间通信

同时,本集还介绍了这些 IPC 机制的基本用法、特点和适用场景,以及多个实用案例和代码示例,包括:

  • 使用管道进行进程间通信
  • 使用命名管道进行无亲缘关系进程间通信
  • 使用信号进行进程控制
  • 使用共享内存进行高效数据传输
  • 使用信号量进行进程同步(生产者-消费者问题)
  • 管道通信示例
  • 消息队列示例
  • 本地套接字示例

通过掌握这些 IPC 机制,用户可以根据具体的应用场景选择合适的通信方式,实现进程间的高效通信和协调。在实际应用中,常常需要结合多种 IPC 机制来满足不同的通信需求,例如使用共享内存进行数据传输,同时使用信号量进行同步。

« 上一篇 后台进程管理 下一篇 » 进程资源限制