文章目录
- 引言
- 理解 Linux 进程间通信(IPC)基础
- 什么是进程间通信(IPC)?
- 管道(Pipe)的基本介绍
- 使用场景:
- 管道特点:
- 管道类型:
- 匿名管道
- 命名管道(FIFO)
- 结论
坚持不懈,努力奋斗,每一步都是通向成功的征程。在追逐梦想的路上,坚定的信念和不屈的意志将成就辉煌的明天。愿你勇敢面对挑战,坚定前行,绽放出属于自己的光芒。加油!✨🌟
——家驹
引言
《Linux系统编程篇》——基础篇首页传送门
我们学习了,fork
,wait
,waitpid
,exit
,以及exec系列函数等,但是还是相当局限,唯一能够通讯的还是等待子进程结束靠着wait
等待,中间好像没有办法在传递消息了,内存又是独立,我怎么能实现呢,还是上节说到的,丰富自己。提高自己的认知,加油吧,我们开始学习新的内容,进程之间的通讯在技术领域我们称之为IPC
。
理解 Linux 进程间通信(IPC)基础
进程间通信(Inter-Process Communication,IPC) 是操作系统中使不同进程交换数据、协同工作的机制。进程间通信广泛用于客户端-服务器应用、多进程应用及系统服务中。Linux 提供了多种 IPC 方式,本文将介绍几种主要的 IPC 方法:管道、消息队列、共享内存和信号。
什么是进程间通信(IPC)?
在 Linux 中,进程是相互独立的,每个进程都有独立的内存空间。为了在不同进程之间交换数据,操作系统提供了各种 IPC 机制。这些机制可帮助进程完成数据共享、同步、通知和事件处理等任务。
常见的 IPC 方式包括:
- 管道(Pipe)
- 命名管道(FIFO)
- 消息队列(Message Queue)
- 共享内存(Shared Memory)
- 信号(Signal)
- 套接字(Socket)
管道(Pipe)的基本介绍
管道是最简单的 IPC 方式之一,适合在具有亲缘关系的进程(如父子进程)之间传递数据。管道是单向的,数据只能从一端流向另一端。它允许一个进程的输出直接作为另一个进程的输入。管道是一种半双工的通信方式,数据只能在一个方向上流动。
我们在没有学习进程通讯的时候,我们其实就已经在使用管道了,当我们使用命令行|
(竖线shift+\)其实就已经使用管道了,我们回忆一下。
还记得之前我们使用ps -aux
命令的时候吗,我们使用了管道(pipe)
和grep
配合进行了筛选,筛选出来了我们想要的数据。
使用场景:
-
进程间通信:父子进程之间或者兄弟进程之间进行数据传输。
-
管道重定向:将一个进程的输出重定向到另一个进程的输入。
-
Shell命令管道:通过管道将多个命令连接起来,实现数据流的传递。
管道特点:
-
单向通信:管道是单向的,数据只能从一个端口流向另一个端口。
-
半双工通信:管道可以同时读写,但不能同时进行读写操作。
-
进程间通信:管道通常用于实现父子进程或者兄弟进程之间的通信。
管道类型:
管道我们分俩种类型,一种是有名字的管道(命名管道),一种是没有名字的管道(匿名管道),我们来看两者的区别。
-
匿名管道(Anonymous Pipe):最常见的管道类型,用于在父子进程或者兄弟进程之间进行通信。通过
pipe
系统调用创建,通常是一种临时的通信方式。 -
命名管道(Named Pipe,FIFO):是一种特殊类型的文件,可以在不相关的进程间进行通信。通过
mkfifo
命令或系统调用创建,通常用于持久性通信。
看上去只是调用的系统接口不一样,我们直接上代码直观的了解一下。
匿名管道
#include <unsistd.h>
int pipe(int pipefd[2]);
用法:
- 使用
pipe()
系统调用创建管道。 - 管道有两个文件描述符:一个用于读取,另一个用于写入。
- 父进程和子进程可以通过管道实现数据传输。
示例代码:
#include <stdio.h>
#include <unistd.h>
#include <string.h>int main() {int fd[2];char buffer[20];// 创建管道if (pipe(fd) == -1) {perror("pipe failed");return 1;}if (fork() == 0) { // 子进程close(fd[0]); // 关闭读端char message[] = "Hello from child!";write(fd[1], message, strlen(message) + 1); // 写入管道close(fd[1]);} else { // 父进程close(fd[1]); // 关闭写端read(fd[0], buffer, sizeof(buffer)); // 从管道读取printf("Parent received: %s\n", buffer);close(fd[0]);}return 0;
}
结果:
父进程创建了一个管道,然后通过fork
创建了一个子进程,父子进程之间通过管道进行通信。子进程向管道中写入一个消息,父进程从管道中读取该消息,并输出到标准输出。
在代码中,子进程关闭了管道的读端(fd[0]
),然后向管道的写端(fd[1]
)写入消息。父进程关闭了管道的写端,然后从管道的读端读取消息到buffer
中,并输出到标准输出。
注意:在子进程中写入消息后,父进程才能从管道中读取消息。这保证了父子进程之间的同步通信。
对于管道通信可以看作特殊的文件,对于他的读写可以使用write
,read
函数进程操作但他不是普通文件,并不存在于文件系统当中,而是存在于内存当中。且数据只能被读取一次;
命名管道(FIFO)
命名管道(FIFO)是管道的一种,但支持无亲缘关系的进程间通信。FIFO 在文件系统中以特殊文件形式存在,可以被多个进程打开进行读写操作。
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
函数执行成功返回0,失败则返回-1;俩个参数分别为:创建管道的路径及文件名字,和权限。
例如:mkfifo("./file",0666)
这段表示在当前文件夹(目录)下创建名字为file的管道文件,权限为0666(可读可写);
文件的权限:r 表示可读取,w 表示可写入,x 表示可执行,分别表示为数字为r=4,w=2,x=1;
权限又按用户的不同分为三类:User、Group、及Other三类用户的权限。
如,对于User用户,若拥有rw权限,则为4+2=6,所以0666中的666代表User、Group、及Other的权限分别是6,6,6,即均为rw权限。
而0666中的0代表不设置特殊的用户id,此处还可设为4,2,1,4代表具有root权限(即suid),2代表sgid,1代表sticky
用法:
- 使用
mkfifo()
或mknod()
创建 FIFO 文件。 - 进程可以通过文件路径打开 FIFO 文件进行读写。
示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>int main() {const char *fifo_path = "/tmp/my_fifo";// 创建 FIFO 文件mkfifo(fifo_path, 0666);if (fork() == 0) { // 子进程int fd = open(fifo_path, O_WRONLY);write(fd, "Hello from child!", 17);close(fd);} else { // 父进程char buffer[20];int fd = open(fifo_path, O_RDONLY);read(fd, buffer, sizeof(buffer));printf("Parent received: %s\n", buffer);close(fd);}return 0;
}
绿色部分是因为我的buffer
没有初始化所以会有乱码,可以加一个memset
进行初始化一下就看不到乱码了,可以看到我在当前目录创建了一个文件my_fifo
,子进程往我们的my_fifo
写入了字符串,在父进程,父进程去读取,然后打印了出来。
结论
在管道操作过程中,需要注意错误处理,如处理管道创建失败、读写数据异常等情况。
对于管道的探索远不止我博客写的这些,希望以后学员们可以灵活应用这一通讯技术。通过管道,不同进程之间可以方便地进行数据交换和通信,实现更复杂的系统功能。