Linux系统进程间通信方法(IPC机制) 详解
1. IPC概述IPCInter-Process Communication进程间通信是指不同进程之间进行信息交换的机制。在Linux系统中提供了多种IPC机制每种机制都有其特定的应用场景和优缺点。1.1 IPC的必要性数据共享不同进程需要共享数据和资源消息传递进程间需要传递控制信息和数据同步与互斥协调多个进程对共享资源的访问信号通知进程间需要发送事件通知2. Linux IPC机制分类Linux系统提供的IPC机制主要分为以下几类管道Pipes消息队列Message Queues共享内存Shared Memory信号量Semaphores套接字Sockets3. 管道Pipes3.1 匿名管道原理匿名管道是最基本的IPC机制用于具有亲缘关系的进程间通信它是一个单向的字节流只能在父子进程或兄弟进程之间使用管道是半双工的数据只能在一个方向上流动特点无名称只能在创建它的进程及其子进程之间使用大小有限制通常为4KB数据一旦被读取就会从管道中被移除匿名管道只存在只读或只写两种不存在读写方式使用示例#include unistd.h #include stdio.h int main() { int pipefd[2]; pid_t pid; char buf[20]; if (pipe(pipefd) -1) { perror(pipe); return 1; } pid fork(); if (pid -1) { perror(fork); return 1; } if (pid 0) { // 子进程 close(pipefd[1]); // 关闭写端 read(pipefd[0], buf, sizeof(buf)); printf(子进程收到: %s\n, buf); close(pipefd[0]); } else { // 父进程 close(pipefd[0]); // 关闭读端 write(pipefd[1], Hello from parent, 17); close(pipefd[1]); wait(NULL); } return 0; }3.2 命名管道FIFO原理命名管道是一种有名称的管道可以在不相关的进程之间使用它在文件系统中创建一个特殊的文件作为管道的接口特点有名称可以被不相关的进程访问大小有限制数据一旦被读取就会从管道中被移除使用示例// 创建命名管道 // mkfifo(myfifo, 0666); // 写入进程 #include stdio.h #include fcntl.h #include unistd.h int main() { int fd open(myfifo, O_WRONLY); write(fd, Hello from writer, 17); close(fd); return 0; } // 读取进程 #include stdio.h #include fcntl.h #include unistd.h int main() { char buf[20]; int fd open(myfifo, O_RDONLY); read(fd, buf, sizeof(buf)); printf(收到: %s\n, buf); close(fd); return 0; }3.3 注意管道创建在内存中不论管道中有没有数据大小始终为04. 消息队列Message Queues4.1 消息队列概述原理消息队列是一种存储消息的链表由内核维护每个消息都有类型和数据部分进程可以向队列发送消息也可以从队列接收消息特点消息队列有名称可用于不相关进程间通信消息是有类型的可以按类型接收消息队列是持久的直到被显式删除大小有限制4.2 消息队列APImsgget()创建或获取消息队列msgsnd()发送消息msgrcv()接收消息msgctl()控制消息队列如删除4.3 消息队列使用示例// 消息结构 struct msgbuf { long mtype; // 消息类型 char mtext[100]; // 消息数据 }; // 发送进程 #include sys/msg.h #include stdio.h int main() { int msgid; struct msgbuf msg; // 创建消息队列 msgid msgget((key_t)1234, 0666 | IPC_CREAT); if (msgid -1) { perror(msgget); return 1; } // 准备消息 msg.mtype 1; sprintf(msg.mtext, Hello from sender); // 发送消息 if (msgsnd(msgid, msg, sizeof(msg.mtext), 0) -1) { perror(msgsnd); return 1; } return 0; } // 接收进程 #include sys/msg.h #include stdio.h int main() { int msgid; struct msgbuf msg; // 获取消息队列 msgid msgget((key_t)1234, 0666 | IPC_CREAT); if (msgid -1) { perror(msgget); return 1; } // 接收消息 if (msgrcv(msgid, msg, sizeof(msg.mtext), 1, 0) -1) { perror(msgrcv); return 1; } printf(收到消息: %s\n, msg.mtext); // 删除消息队列 if (msgctl(msgid, IPC_RMID, 0) -1) { perror(msgctl); return 1; } return 0; }4.4注意消息类型必须大于00号消息为顺位接收5. 共享内存Shared Memory5.1 共享内存概述原理共享内存是最快的IPC机制因为它直接访问内存不需要数据拷贝多个进程可以访问同一块物理内存需要与其他同步机制如信号量配合使用以避免竞态条件特点速度快无数据拷贝需要手动同步大小有限制持久的直到被显式删除5.2 共享内存APIshmget()创建或获取共享内存段shmat()将共享内存附加到进程地址空间shmdt()将共享内存从进程地址空间分离shmctl()控制共享内存如删除5.3 共享内存使用示例// 共享内存使用示例 #include sys/shm.h #include stdio.h #include stdlib.h #include string.h int main() { int shmid; char *shmaddr; // 创建共享内存段 shmid shmget((key_t)1234, 1024, 0666 | IPC_CREAT); if (shmid -1) { perror(shmget); return 1; } // 附加共享内存 shmaddr shmat(shmid, NULL, 0); if (shmaddr (char *)-1) { perror(shmat); return 1; } // 写入数据 strcpy(shmaddr, Hello from shared memory); printf(写入共享内存: %s\n, shmaddr); // 等待读取 printf(按任意键继续...\n); getchar(); // 分离共享内存 if (shmdt(shmaddr) -1) { perror(shmdt); return 1; } // 删除共享内存 if (shmctl(shmid, IPC_RMID, 0) -1) { perror(shmctl); return 1; } return 0; }6. 信号量Semaphores6.1 信号量概述原理信号量是一种计数器用于控制对共享资源的访问主要用于进程间的同步和互斥信号量的值如果只取0或1称为二值信号量若大于1则称为计数信号量特点用于同步和互斥可以是二进制的或计数的持久的直到被显式删除6.2 信号量APIsemget()创建或获取信号量集semop()执行信号量操作P和V操作semctl()控制信号量如设置值、删除6.3 信号量使用示例// 信号量使用示例 #include sys/sem.h #include stdio.h #include unistd.h // 信号量操作结构体 struct sembuf p_op, v_op; int main() { int semid; pid_t pid; // 创建信号量集 semid semget((key_t)1234, 1, 0666 | IPC_CREAT); if (semid -1) { perror(semget); return 1; } // 初始化信号量为1二进制信号量 if (semctl(semid, 0, SETVAL, 1) -1) { perror(semctl); return 1; } // 设置P操作等待 p_op.sem_num 0; p_op.sem_op -1; p_op.sem_flg SEM_UNDO; // 设置V操作释放 v_op.sem_num 0; v_op.sem_op 1; v_op.sem_flg SEM_UNDO; pid fork(); if (pid -1) { perror(fork); return 1; } if (pid 0) { // 子进程 printf(子进程等待进入临界区...\n); semop(semid, p_op, 1); printf(子进程进入临界区\n); sleep(2); printf(子进程离开临界区\n); semop(semid, v_op, 1); } else { // 父进程 sleep(1); // 确保子进程先执行 printf(父进程等待进入临界区...\n); semop(semid, p_op, 1); printf(父进程进入临界区\n); sleep(2); printf(父进程离开临界区\n); semop(semid, v_op, 1); wait(NULL); } // 删除信号量 if (semctl(semid, 0, IPC_RMID) -1) { perror(semctl); return 1; } return 0; }7. 套接字Sockets7.1 套接字概述原理套接字是一种通用的IPC机制可用于同一台机器上的进程间通信也可用于网络通信支持面向连接TCP和无连接UDP的通信特点灵活可用于本地和网络通信支持多种协议可以在不相关的进程间使用7.2 本地套接字使用示例// 服务器端 #include sys/socket.h #include sys/un.h #include stdio.h #include unistd.h #include string.h int main() { int server_fd, client_fd; struct sockaddr_un addr; char buf[100]; // 创建套接字 server_fd socket(AF_UNIX, SOCK_STREAM, 0); if (server_fd -1) { perror(socket); return 1; } // 准备地址 unlink(/tmp/socket); addr.sun_family AF_UNIX; strcpy(addr.sun_path, /tmp/socket); // 绑定地址 if (bind(server_fd, (struct sockaddr *)addr, sizeof(addr)) -1) { perror(bind); return 1; } // 监听连接 if (listen(server_fd, 5) -1) { perror(listen); return 1; } // 接受连接 client_fd accept(server_fd, NULL, NULL); if (client_fd -1) { perror(accept); return 1; } // 读取数据 read(client_fd, buf, sizeof(buf)); printf(收到: %s\n, buf); // 关闭连接 close(client_fd); close(server_fd); unlink(/tmp/socket); return 0; } // 客户端 #include sys/socket.h #include sys/un.h #include stdio.h #include unistd.h #include string.h int main() { int sock_fd; struct sockaddr_un addr; // 创建套接字 sock_fd socket(AF_UNIX, SOCK_STREAM, 0); if (sock_fd -1) { perror(socket); return 1; } // 准备地址 addr.sun_family AF_UNIX; strcpy(addr.sun_path, /tmp/socket); // 连接服务器 if (connect(sock_fd, (struct sockaddr *)addr, sizeof(addr)) -1) { perror(connect); return 1; } // 发送数据 write(sock_fd, Hello from client, 17); // 关闭连接 close(sock_fd); return 0; }8. 各种IPC机制的比较IPC机制速度数据量通信方式适用场景匿名管道中小半双工父子进程间通信命名管道中小半双工任意进程间通信消息队列中中双向进程间消息传递共享内存极快大双向大量数据共享信号量快无同步进程同步和互斥套接字中大双向本地和网络通信9. 最佳实践9.1 选择合适的IPC机制少量数据父子进程使用管道少量数据不相关进程使用命名管道或消息队列事件通知使用信号大量数据需要快速交换使用共享内存配合信号量需要网络通信使用套接字9.2 注意事项同步问题使用共享内存时必须使用同步机制资源释放使用完IPC资源后必须释放如删除消息队列、共享内存等错误处理必须处理IPC操作的错误权限设置合理设置IPC资源的权限大小限制注意各种IPC机制的大小限制10. 总结Linux提供了多种IPC机制每种机制都有其特定的应用场景。选择合适的IPC机制取决于通信需求、数据量、速度要求等因素。在实际应用中常常需要结合使用多种IPC机制例如使用共享内存进行数据交换同时使用信号量进行同步。理解和掌握这些IPC机制对于开发高效、可靠的Linux应用程序至关重要。通过合理选择和使用IPC机制可以显著提高应用程序的性能和可靠性。