Linux:进程间通信-管道

前言:为什么需要进程间通信?

你有没有想过,当你在电脑上同时打开浏览器、音乐播放器和文档时,这些程序是如何协同工作的?比如,浏览器下载的文件,为什么能被文档编辑器直接打开?音乐播放器的音量调节,为什么能影响系统全局的声音输出?这背后,其实都是进程间通信(IPC)在发挥作用。

进程作为操作系统中独立运行的基本单位,彼此之间默认是隔离的——就像住在不同房间的人,没有门也没有窗,无法直接交流。但实际应用中,进程又必须协同工作:比如打印进程需要接收文档进程的数据,视频渲染进程需要获取解码进程的结果。这就要求我们打破这种隔离,建立进程间的"沟通渠道"。

今天这篇文章,我们就从最基础的管道开始,一步步揭开Linux进程间通信的神秘面纱。你会发现,看似复杂的IPC机制,其实和现实生活中的通信场景有着惊人的相似之处。

一、进程间通信的基本概念

1.1 什么是进程间通信?

进程间通信(IPC,Inter-Process Communication) 指的是两个或多个进程之间进行数据交换的过程。它的本质是让彼此独立的进程能够共享数据,实现协同工作。

举个生活中的例子:你在厨房做饭(进程A),需要客厅的家人帮忙递一下盐(进程B)。这里的"递盐"就是一次简单的IPC——你和家人(两个进程)通过语言(通信方式)交换了"需要盐"这个数据。

在计算机中,进程间的"语言"有很多种,比如管道、消息队列、共享内存等,我们今天重点讨论最基础也最常用的"管道"。

1.2 为什么需要进程间通信?

你可能会说:"进程各自干好自己的事就行了,为什么非要通信?"但实际场景中,进程间的协同是必不可少的,主要体现在这几个方面:

  • 数据传输:一个进程需要将数据发送给另一个进程。比如,输入法进程需要把你输入的文字发送给编辑器进程。
  • 资源共享:多个进程需要共享同一份资源(比如文件、内存)。比如,多个浏览器标签页需要共享同一个缓存文件。
  • 进程控制:一个进程需要控制另一个进程的行为。比如,任务管理器进程可以强制关闭无响应的程序进程。
  • 事件通知:一个进程需要向其他进程通知某个事件的发生。比如,下载进程完成后,通知用户进程弹出提示。

想想看,如果没有IPC,你的电脑会变成什么样?浏览器下载的文件无法保存到硬盘(需要与文件系统进程通信),播放音乐时无法调节音量(需要与音频进程通信),甚至连复制粘贴功能都无法实现(需要剪贴板进程在多个程序间传递数据)。

1.3 进程间通信的成本为什么高?

既然IPC这么重要,为什么实现起来不简单呢?这就要从进程的"独立性"说起了。

进程的独立性是操作系统设计的基本原则——每个进程都有自己独立的内存空间、寄存器状态和文件描述符表。这种隔离性保证了一个进程的崩溃不会影响其他进程,但也给通信带来了麻烦:进程A的内存数据,进程B默认是看不到的

就像两个加密的保险箱,各自有独立的密码,不借助外部工具(比如钥匙),里面的东西无法互通。要实现通信,就必须打破这种独立性,建立共享资源——而创建和管理共享资源,必然会带来系统开销(比如内存分配、权限检查)和复杂性(比如同步问题)。

举个例子:如果进程A想给进程B发送数据,需要先把数据从A的用户空间拷贝到内核空间的共享缓冲区,再由B从内核空间拷贝到自己的用户空间(两次拷贝)。这个过程比进程内部的数据访问要慢得多,这就是通信的成本。

二、进程间通信的实现基础

2.1 操作系统在IPC中扮演什么角色?

进程间通信不能靠进程自己"私下联系",必须由操作系统作为"第三方协调者"。操作系统的作用主要有三个:

  1. 提供共享资源:比如创建管道、消息队列等内核级资源,让进程可以通过这些资源交换数据。
  2. 管理资源生命周期:负责创建、使用和释放通信资源,避免资源泄露。
  3. 保证安全性和可控性:通过系统调用接口限制进程对资源的访问,防止越权操作。

打个比方,操作系统就像一个中介:进程A和进程B想通信,先向中介申请一个"会议室"(共享资源),中介创建并管理这个会议室,A和B只能通过中介规定的方式进入会议室交流。

2.2 通信资源是如何管理的?

操作系统管理通信资源的核心原则是"先描述,再组织"。

  • 描述:每个通信资源(比如管道)都会被内核用一个数据结构(如struct pipe_inode_info)描述,记录资源的属性(大小、权限)、状态(是否被使用)和操作方法(读、写函数)。
  • 组织:内核会把所有同类资源用链表或哈希表组织起来,方便查询和管理。比如,所有管道会被放在一个全局链表中,操作系统可以通过遍历链表找到某个特定管道。

这种管理方式就像图书馆的图书管理:每本书(资源)都有一张卡片(描述结构),记录书名、作者等信息;所有卡片按分类(组织方式)存放在卡片柜里,方便查找。

2.3 常见的IPC标准有哪些?

早期的Unix系统中,不同厂商实现的IPC机制各不相同,导致程序兼容性很差。后来行业逐渐形成了两套主流标准:

  • System V IPC:由AT&T贝尔实验室提出,主要包括消息队列、信号量和共享内存三种方式,适用于单机内的进程通信。
  • POSIX IPC:由IEEE制定,兼容System V的部分功能,同时支持线程通信和网络通信,接口更统一,现在应用更广泛。

这两套标准就像通信领域的"普通话",让不同进程(甚至不同程序语言编写的进程)能按照统一的规则交流。

三、管道:最古老的IPC方式

3.1 什么是管道?

管道(Pipe)是Unix系统中最古老的IPC方式,它的设计非常朴素:用内存中的文件缓冲区模拟"管道",让一个进程往管道里写数据,另一个进程从管道里读数据

你可以把管道想象成一根水管:一端进水(写端),另一端出水(读端),水(数据)在管内单向流动。这种单向性是管道的核心特征——就像现实中的水管,你不能同时从一端既进水又出水。

在Linux命令行中,你其实早就用过管道了。比如ps aux | grep "chrome"这个命令,ps进程的输出通过|(管道符号)传递给grep进程,这里的|就是一个匿名管道。

3.2 管道的实现原理

管道本质上是一个内存级文件,它有这些特点:

  • 不在磁盘上存储,数据只存在于内存缓冲区中。
  • 遵循文件操作的接口(打开、读、写、关闭),但不需要刷新到磁盘。
  • 通过文件描述符表让进程访问:一个描述符对应读端,另一个对应写端。

具体实现步骤如下:

  1. 创建管道:通过pipe()系统调用创建管道,内核会分配一个内存缓冲区,并返回两个文件描述符:fd[0](读端)和fd[1](写端)。
  2. 创建子进程:通过fork()创建子进程,子进程会继承父进程的文件描述符表,因此也能访问同一个管道。
  3. 关闭无用端口:父进程关闭读端(fd[0]),子进程关闭写端(fd[1]),形成单向通信信道(父写子读);或者反过来(父读子写)。
  4. 通信:父进程通过write()fd[1]写数据,子进程通过read()fd[0]读数据。

举个例子:父进程想给子进程发送"hello",步骤如下:

  • 父进程调用pipe(fd),得到fd[0]=3(读)、fd[1]=4(写)。
  • 父进程fork()出子进程,子进程的fd数组也是[3,4]
  • 父进程close(fd[0])(关闭读端),子进程close(fd[1])(关闭写端)。
  • 父进程write(fd[1], "hello", 5),子进程read(fd[0], buf, 5),最终buf中就有"hello"。

3.3 管道的代码实现

下面我们用C语言实现一个简单的父子进程管道通信:父进程向子进程发送消息,子进程打印消息。

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#include <stdlib.h>int main() {int fd[2];// 1. 创建管道if (pipe(fd) == -1) {perror("pipe error");exit(1);}// 2. 创建子进程pid_t pid = fork();if (pid == -1) {perror("fork error");exit(1);}if (pid == 0) { // 子进程:读数据close(fd[1]); // 关闭写端char buf[1024];ssize_t len = read(fd[0], buf, sizeof(buf)-1);if (len > 0) {buf[len] = '\0';printf("子进程收到:%s\n", buf);}close(fd[0]); // 关闭读端exit(0);} else { // 父进程:写数据close(fd[0]); // 关闭读端const char* msg = "你好,子进程!";write(fd[1], msg, strlen(msg));close(fd[1]); // 关闭写端(触发子进程读结束)wait(NULL); // 等待子进程退出exit(0);}
}

编译运行后,会输出:子进程收到:你好,子进程!

这里有几个关键点:

  • 子进程必须关闭写端,父进程必须关闭读端,否则会导致阻塞(比如子进程读完数据后,会一直等父进程写更多数据)。
  • 当写端关闭后,读端read()会返回0,表示数据已读完(类似文件结束)。
  • 管道的缓冲区大小是固定的(通常为64KB),如果写端写满缓冲区,会阻塞直到读端读取数据释放空间。

3.4 管道的五大特性

通过上面的例子,我们可以总结出管道的五个核心特性:

  1. 只能单向通信:管道是半双工的,数据只能从一端到另一端。如果需要双向通信,必须创建两个管道。

    (思考:为什么管道设计成单向的?其实是为了简化实现——双向通信需要更复杂的同步机制,而单向通信能满足大部分场景。)

  2. 只能用于有血缘关系的进程:因为管道没有名字,只能通过fork()继承文件描述符的方式让进程共享。父子进程、兄弟进程(同一个父进程创建)之间可以用管道通信,但两个无关进程不行。

  3. 面向字节流:管道中的数据是连续的字节流,没有消息边界。比如,父进程分两次写"hello"和"world",子进程可能一次就读到"helloworld"。

    (注意:这意味着应用程序需要自己定义协议来区分消息,比如用换行符分隔,或固定消息长度。)

  4. 自带同步机制

    • 读端:如果管道为空,read()会阻塞,直到有数据写入。
    • 写端:如果管道满了,write()会阻塞,直到有数据被读走。
  5. 生命周期随进程:管道会在所有访问它的进程都关闭文件描述符后,被内核自动销毁。

3.5 管道的四种典型情况

管道通信中,读写端的状态会直接影响通信行为,常见的四种情况需要特别注意:

情况现象原因
读写端正常,管道为空读端阻塞读端等待写端写入数据
读写端正常,管道满了写端阻塞写端等待读端读取数据释放空间
读端关闭,写端继续写写端进程被杀死操作系统发送SIGPIPE信号终止写进程(避免无效写入)
写端关闭,读端继续读读端读到0(文件结束)写端关闭后,管道中剩余数据读完后,read()返回0

比如,如果你在代码中忘了关闭写端,子进程的read()会一直阻塞(以为还有数据要读),导致程序卡死。这也是为什么我们强调"一定要关闭无用的文件描述符"。

四、命名管道:让无关进程也能通信

4.1 匿名管道的局限性

匿名管道虽然简单,但有个致命缺点:只能用于有血缘关系的进程。如果两个完全无关的进程(比如浏览器和音乐播放器)想通信,匿名管道就无能为力了——因为它们无法共享文件描述符。

这就像两个陌生人住在不同的小区,没有共同的朋友(父进程)介绍,无法知道对方的地址(管道的文件描述符)。要解决这个问题,就需要一种"有名字"的管道——命名管道(FIFO)。

4.2 什么是命名管道?

命名管道(FIFO,First In First Out)和匿名管道的核心原理相同,但它有一个关键区别:命名管道有文件名和路径,可以通过文件系统被所有进程访问。

就像一个公共邮箱:任何知道邮箱地址(路径)的人,都可以往里面放信(写数据)或取信(读数据),不需要彼此认识。

在Linux中,你可以用mkfifo命令创建命名管道:

mkfifo myfifo  # 创建一个名为myfifo的命名管道

创建后,你会在目录中看到这个文件,类型为p(管道):

ls -l myfifo
# 输出:prw-r--r-- 1 user user 0 8月  21 10:00 myfifo

4.3 命名管道的使用方式

命名管道的使用步骤和文件操作类似,分为创建、打开、读写、关闭四个步骤:

  1. 创建:用mkfifo命令或mkfifo()函数创建。

    #include <sys/stat.h>
    int mkfifo(const char *pathname, mode_t mode);
    // 参数:pathname(管道路径)、mode(权限,如0666)
    // 返回值:0成功,-1失败
    
  2. 打开:用open()函数打开,指定读或写模式。

    int fd = open("myfifo", O_RDONLY);  // 只读打开(读端)
    // 或
    int fd = open("myfifo", O_WRONLY);  // 只写打开(写端)
    
  3. 读写:用read()write()函数操作,和匿名管道相同。

  4. 关闭:用close()关闭文件描述符。

  5. 删除:用unlink()函数删除管道文件(类似rm命令)。

4.4 命名管道的代码实现

下面我们实现两个无关进程的通信:一个写进程向命名管道发送消息,一个读进程接收消息。

写进程(writer.c)

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#include <stdlib.h>int main() {// 1. 创建命名管道(如果不存在)if (mkfifo("myfifo", 0666) == -1) {perror("mkfifo error");exit(1);}// 2. 打开管道(写端)int fd = open("myfifo", O_WRONLY);if (fd == -1) {perror("open error");exit(1);}// 3. 发送消息const char* msg = "来自writer的消息:你好,reader!";write(fd, msg, strlen(msg));printf("发送成功\n");// 4. 关闭管道close(fd);// 5. 删除管道(可选)unlink("myfifo");return 0;
}

读进程(reader.c)

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#include <stdlib.h>int main() {// 1. 打开管道(读端)int fd = open("myfifo", O_RDONLY);if (fd == -1) {perror("open error");exit(1);}// 2. 接收消息char buf[1024];ssize_t len = read(fd, buf, sizeof(buf)-1);if (len > 0) {buf[len] = '\0';printf("收到消息:%s\n", buf);}// 3. 关闭管道close(fd);return 0;
}

运行步骤:

  1. 编译两个程序:gcc writer.c -o writergcc reader.c -o reader
  2. 先启动读进程:./reader(会阻塞等待写端打开)。
  3. 再启动写进程:./writer(发送消息后退出)。
  4. 读进程会输出:收到消息:来自writer的消息:你好,reader!

4.5 命名管道与匿名管道的区别

特性匿名管道命名管道
存在形式内存中,无文件名有文件名(在文件系统中可见)
适用进程有血缘关系(父子、兄弟)任意进程(只要知道路径)
创建方式pipe()系统调用mkfifo()函数或mkfifo命令
打开方式继承文件描述符通过open()函数打开路径
生命周期随进程(所有进程关闭后销毁)随文件(需用unlink()删除)

本质上,命名管道只是比匿名管道多了一个"文件名",其他特性(单向通信、面向字节流、同步机制)完全相同。

五、基于管道的进程池设计

5.1 什么是进程池?

在实际开发中,我们经常需要创建多个子进程处理任务(比如服务器处理多个客户端请求)。如果每次有任务才创建子进程,会带来很大的开销(创建进程需要分配内存、初始化PCB等)。

进程池就是一种优化方案:提前创建一批子进程,当有任务时,直接让空闲的子进程处理,避免频繁创建和销毁进程

就像餐厅的服务员团队:开业前招聘好服务员(创建子进程),客人来了(任务)直接安排空闲服务员接待,不用等客人来了再临时招聘。

5.2 基于管道的进程池通信模型

进程池的核心是父进程如何给子进程分配任务。我们可以用管道实现这种通信:

  1. 创建进程池:父进程创建N个子进程,为每个子进程创建一个管道(父写子读)。
  2. 子进程等待任务:每个子进程阻塞在管道的读端,等待父进程发送任务。
  3. 父进程分配任务:父进程有任务时,选择一个空闲子进程,通过对应的管道发送任务数据。
  4. 子进程处理任务:子进程收到任务后,执行任务,完成后继续等待下一个任务。

这种模型的优点是:

  • 父进程可以精确控制每个子进程的任务(通过不同管道)。
  • 子进程专注于处理任务,不需要关心任务分配逻辑。

5.3 进程池代码实现

下面我们实现一个简单的进程池:父进程创建3个子进程,向它们发送不同的任务(打印不同的消息)。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <vector>
#include <string>
#include <cstring>// 任务结构体(这里简化为字符串)
struct Task {std::string msg;
};// 子进程处理任务的函数
void handle_task(int read_fd) {while (true) {// 读取任务char buf[1024];ssize_t len = read(read_fd, buf, sizeof(buf)-1);if (len <= 0) break; // 写端关闭,退出buf[len] = '\0';printf("子进程[%d]处理任务:%s\n", getpid(), buf);}close(read_fd);exit(0);
}int main() {const int NUM_PROCESSES = 3; // 进程池大小std::vector<int> write_fds;  // 保存每个子进程对应的写端// 创建进程池for (int i = 0; i < NUM_PROCESSES; ++i) {int fd[2];if (pipe(fd) == -1) {perror("pipe error");exit(1);}pid_t pid = fork();if (pid == -1) {perror("fork error");exit(1);}if (pid == 0) { // 子进程close(fd[1]); // 关闭写端handle_task(fd[0]);} else { // 父进程close(fd[0]); // 关闭读端write_fds.push_back(fd[1]); // 保存写端}}// 向子进程发送任务std::vector<Task> tasks = {{"任务1:打印日志"},{"任务2:处理数据"},{"任务3:发送网络请求"},{"任务4:更新缓存"},{"任务5:生成报表"}};for (size_t i = 0; i < tasks.size(); ++i) {int fd = write_fds[i % NUM_PROCESSES]; // 轮询分配任务write(fd, tasks[i].msg.c_str(), tasks[i].msg.size());sleep(1); // 间隔1秒发送}// 关闭所有写端(触发子进程退出)for (int fd : write_fds) {close(fd);}// 等待所有子进程退出for (int i = 0; i < NUM_PROCESSES; ++i) {wait(NULL);}return 0;
}

运行后,输出类似:

子进程[1234]处理任务:任务1:打印日志
子进程[1235]处理任务:任务2:处理数据
子进程[1236]处理任务:任务3:发送网络请求
子进程[1234]处理任务:任务4:更新缓存
子进程[1235]处理任务:任务5:生成报表

这个例子中,父进程通过轮询的方式给3个子进程分配5个任务,子进程处理完后继续等待新任务,直到父进程关闭写端才退出。

5.4 进程池的优化方向

上面的简单实现可以进一步优化:

  • 动态扩容:当任务过多时,自动创建新的子进程;任务过少时,销毁部分子进程。
  • 任务优先级:给任务设置优先级,父进程按优先级分配。
  • 结果返回:子进程处理完任务后,通过另一个管道将结果返回给父进程。
  • 异常处理:子进程崩溃时,父进程能检测到并重新创建子进程。

这些优化可以让进程池更适应实际应用场景,比如高并发的服务器程序。

六、其他IPC方式简介

除了管道,Linux还有其他常用的IPC方式,这里简单介绍:

6.1 消息队列

消息队列是内核中的一个消息链表,进程可以向队列中添加消息,也可以从队列中读取消息。每个消息都有类型,读取时可以按类型筛选。

优点:可以实现双向通信,消息有边界(不需要自己定义协议)。
缺点:消息大小和队列长度有限制,效率不如共享内存。

6.2 信号量

信号量不是用于传递数据,而是用于实现进程间的同步和互斥(比如控制多个进程对共享资源的访问)。

比如,信号量可以比作停车场的车位计数器:进程要进入临界区(停车场),需要先获取信号量(车位);离开时释放信号量(腾出车位)。

6.3 共享内存

共享内存是效率最高的IPC方式:操作系统在内存中开辟一块区域,让多个进程直接映射到自己的地址空间,进程可以直接读写这块内存,不需要内核中转。

优点:数据不需要拷贝,速度极快。
缺点:需要自己处理同步问题(比如用信号量防止同时写入)。

七、总结与展望

进程间通信是操作系统中非常重要的概念,而管道作为最基础的IPC方式,虽然简单但应用广泛。通过本文的学习,你应该掌握:

  • 进程间通信的必要性和成本来源。
  • 匿名管道的原理、实现和特性(单向通信、血缘关系限制)。
  • 命名管道如何解决匿名管道的局限性,让无关进程通信。
  • 基于管道的进程池设计,理解如何高效管理多个子进程。

管道虽然好用,但在高并发、大数据量的场景下,可能需要更高效的方式(如共享内存)。下一篇文章,我们将深入探讨共享内存的实现原理和使用技巧,敬请期待!

7.1、为什么管道的缓冲区大小是固定的?动态调整缓冲区大小有什么问题?

管道的缓冲区大小被设计为固定值(通常为64KB,不同内核版本可能略有差异),核心原因是简化操作系统对管道的管理,并保证通信的稳定性和效率。具体来说:

  1. 固定大小便于内核管理
    管道的缓冲区是内核维护的一块连续内存。固定大小可以让内核提前分配内存、设置边界,避免频繁的动态内存申请/释放(比如用kmallocvmalloc)。动态调整需要内核实时计算所需空间、处理内存碎片,会增加系统开销,降低通信效率。

  2. 避免进程通信的不确定性
    如果缓冲区大小动态变化,进程无法预判写入/读取的边界。比如,写进程可能以为缓冲区足够大而持续写入,导致内存耗尽;读进程也无法确定何时能读完数据,容易引发阻塞或数据截断。固定大小能让进程明确通信的“上限”,便于设计可靠的读写逻辑。

动态调整缓冲区大小的主要问题:

  • 同步复杂:缓冲区扩容/缩容时,正在进行的读写操作可能被打断,需要内核额外加锁保护,增加死锁风险。
  • 效率下降:动态内存分配(尤其是大内存)耗时较长,且可能因内存碎片导致分配失败,影响管道的实时性。
  • 接口不统一:用户进程无法提前知晓缓冲区大小,难以设计兼容不同内核版本的代码(不同系统动态调整策略可能不同)。

7.2、如何用两个管道实现父子进程的双向通信?

管道是单向通信的(“半双工”),但通过创建两个管道,可以让父子进程实现双向通信。核心思路是:

  • 管道1:父进程写,子进程读(父→子方向)。
  • 管道2:子进程写,父进程读(子→父方向)。

具体步骤(代码示例):

  1. 创建两个管道
    pipe()创建两个管道pipe1pipe2,分别对应两个方向的通信信道。

    int pipe1[2], pipe2[2];
    pipe(pipe1);  // pipe1[0]:读端;pipe1[1]:写端(父→子)
    pipe(pipe2);  // pipe2[0]:读端;pipe2[1]:写端(子→父)
    
  2. 创建子进程并关闭无关端口
    父子进程通过fork()继承管道的文件描述符后,需关闭不需要的端口,避免干扰:

    • 父进程:关闭pipe1的读端(pipe1[0])和pipe2的写端(pipe2[1]),保留pipe1[1](写)和pipe2[0](读)。
    • 子进程:关闭pipe1的写端(pipe1[1])和pipe2的读端(pipe2[0]),保留pipe1[0](读)和pipe2[1](写)。
  3. 双向通信

    • 父进程通过write(pipe1[1], ...)向子进程发送数据,子进程通过read(pipe1[0], ...)接收。
    • 子进程通过write(pipe2[1], ...)向父进程回复数据,父进程通过read(pipe2[0], ...)接收。

代码片段示例:

#include <stdio.h>
#include <unistd.h>
#include <string.h>int main() {int pipe1[2], pipe2[2];pipe(pipe1);  // 父→子pipe(pipe2);  // 子→父pid_t pid = fork();if (pid == 0) {  // 子进程close(pipe1[1]);  // 关闭pipe1写端close(pipe2[0]);  // 关闭pipe2读端// 接收父进程数据char buf[100];read(pipe1[0], buf, sizeof(buf));printf("子进程收到:%s\n", buf);// 向父进程回复const char* reply = "子进程已收到!";write(pipe2[1], reply, strlen(reply));close(pipe1[0]);close(pipe2[1]);} else {  // 父进程close(pipe1[0]);  // 关闭pipe1读端close(pipe2[1]);  // 关闭pipe2写端// 向子进程发送数据const char* msg = "父进程:你好!";write(pipe1[1], msg, strlen(msg));// 接收子进程回复char buf[100];read(pipe2[0], buf, sizeof(buf));printf("父进程收到:%s\n", buf);close(pipe1[1]);close(pipe2[0]);}return 0;
}

运行后输出:

子进程收到:父进程:你好!
父进程收到:子进程已收到!

7.3、命名管道在文件系统中可见,但数据不写入磁盘,这是如何实现的?

命名管道(FIFO)在文件系统中可见(有路径和文件名),但数据不写入磁盘,核心原因是它本质是“内存级文件”,文件系统中的条目仅作为“标识”,不存储实际数据。具体实现如下:

  1. 文件系统中的“标识”作用
    命名管道通过mkfifo创建时,内核会在文件系统中创建一个特殊的inode(索引节点),记录管道的路径、权限、创建者等元信息,但不分配磁盘数据块。这个inode的作用是让所有进程通过路径找到同一个管道(类似“地址牌”),而非存储数据。

  2. 数据存储在内存缓冲区
    命名管道的实际数据存储在内核维护的内存缓冲区中(和匿名管道一样)。当进程通过open打开命名管道时,内核会将管道的内存缓冲区映射到进程的文件描述符表中,进程的read/write操作实际是读写这块内存,而非磁盘。

  3. 不写入磁盘的原因
    命名管道设计的核心是“进程间临时通信”,数据无需持久化。如果写入磁盘,会带来额外的I/O开销(磁盘速度远慢于内存),且通信结束后数据无用,反而浪费磁盘空间。内核通过将数据限制在内存中,既保证了通信效率,又避免了不必要的磁盘操作。

简单说:命名管道在文件系统中的“可见性”只是为了让进程找到它,而实际数据始终在内存中流转,用完即弃,不会落地到磁盘。
欢迎在评论区留下你的答案和疑问,我们一起讨论!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/94137.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/94137.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/94137.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Jmeter + FFmpeg 直播压测遇到的问题及解决方案

1、压测机安装FFmpeg&#xff0c;下载安装步骤可见&#xff1a;https://zhuanlan.zhihu.com/p/692019886 2、Jmeter与FFmpeg位数要一致&#xff0c;不允许在32位的进程中运行一个64位的程序&#xff0c;反之亦然 3、OS进程取样器&#xff08;Thread Group -> Add -> Sa…

安卓app、微信小程序等访问多个api时等待提示调用与关闭问题

安卓app、微信小程序访问webapi&#xff0c;将需要一时间&#xff0c;我们称之为耗时操作&#xff0c;其它诸如密集型计算、访问文件与设备等亦是如此。在这个期间我们应该跳出提示&#xff0c;告知用户正在等待&#xff0c;并且很多时候&#xff0c;在等待时不允许用户再对UI进…

一个状态机如何启动/停止另一个状态机

一个状态机如何启动/停止另一个状态机 这个过程主要依赖于动作列表&#xff08;Action List&#xff09; 中的特定动作项和状态管理服务&#xff08;ARA::SM&#xff09;提供的API。 1. 通过动作列表&#xff08;Action List&#xff09;进行预配置控制 这是最常见的方式&#…

基于IPO智能粒子优化的IIR滤波器参数识别算法matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.部分程序 4.算法理论概述 5.完整程序 1.程序功能描述 IIR&#xff08;Infinite Impulse Response&#xff09;滤波器即无限冲激响应滤波器&#xff0c;其输出不仅与当前和过去的输入有关&#xff0c;还与过去的输出…

欧州服务器String 转 double 有BUG?

string 转 double 的常见问题通常与文化差异、格式解析或特殊值处理相关&#xff0c;而非框架本身的 “BUG”。以下是可能导致转换异常的常见场景及解决方案&#xff1a; 文化差异导致的解析问题 现象&#xff1a;同样的字符串&#xff08;如 “1.23” 或 “1,23”&#xff09;…

鸿蒙中网络诊断:Network分析

上面的图很熟悉吧 Network 面板的表格列出了所有请求&#xff0c;每一列都提供了关键信息&#xff1a; Name: 请求的资源名称和路径。 Status: HTTP 状态码&#xff08;诊断核心&#xff09;。200成功&#xff0c;304未修改&#xff08;缓存&#xff09;&#xff0c;404找不到…

HarmonyOS 实战:6 种实现实时数据更新的方案全解析(含完整 Demo)

摘要 在当下的应用开发中&#xff0c;用户体验越来越依赖“实时性”。消息要第一时间送达、订单状态要立刻刷新、数据变化不能延迟……这些需求推动了“实时数据更新”成为应用的必备功能。在鸿蒙系统&#xff08;HarmonyOS&#xff09;中&#xff0c;我们既可以用系统内置的数…

第十六届蓝桥杯青少组C++省赛[2025.8.10]第二部分编程题(4、矩阵圈层交错旋转)

参考程序&#xff1a;#include <bits/stdc.h> using namespace std;const int MAXN 105; int a[MAXN][MAXN];int main() {int n;if (!(cin >> n)) return 0;for (int i 0; i < n; i)for (int j 0; j < n; j)cin >> a[i][j];int layers n / 2; // 每…

AI供应链情报预警 | 恶意Py包伪装AI框架库开展数据窃密及应用劫持攻击

AI供应链情报概述近日&#xff08;18th Aug. , 2025&#xff09;&#xff0c;悬镜安全情报中心在Python官方仓库中捕获1起伪装成知名AI框架库pytensor&#xff08;https://pypi.org/project/pytensor&#xff09;的组件投毒事件。在北京时间8月18日凌晨&#xff0c;投毒者连续发…

AI需要防火墙,云计算需要重新构想

Akamai创始人Tom Leighton欲终结云膨胀&#xff0c;从内到外守护AI安全 Akamai创始人Tom Leighton 当前超大规模云服务商主导着企业IT市场&#xff0c;鲜有人敢挑战云计算经济模式、AI基础设施和网络安全架构的现状。但Akamai联合创始人兼CEO Tom Leighton正是这样的挑战者。他…

线段树详解【数据结构】

简介 线段树是一种应用极其广泛&#xff0c;使用范围较广并且非常知名的树形数据结构&#xff0c;主要用于进行区间操作&#xff0c;如区间修改&#xff0c;区间查询等。这种数据结构唯一的不足就是巨大的代码量&#xff0c;因此处理一些较简单的问题时建议用树状数组。 原理…

Maven 入门与进阶:聚合、继承与生命周期详解

Maven 是 Java 项目管理的核心工具&#xff0c;其强大的依赖管理、项目构建和模块化设计能力&#xff0c;极大地提升了开发效率。本文将深入探讨 Maven 的 聚合&#xff08;Multi-module&#xff09;、继承&#xff08;Inheritance&#xff09; 和 生命周期&#xff08;Lifecyc…

手搓MCP客户端动态调用多MCP服务,调用哪个你说了算!

01 引言 前两天&#xff0c;有个粉丝朋友咨询MCP服务如何动态调用&#xff0c;动态加载MCP服务的链接&#xff1f;我们都知道MCP客户端可以配置多个MCP服务的地址&#xff1a; spring.ai.mcp.client.sse.connections.server1.urlhttp://localhost:xxxx spring.ai.mcp.client.ss…

Go语言中的优雅并发控制:通道信号量模式详解

在Go语言的并发编程中&#xff0c;“通过通信共享内存”的设计哲学贯穿始终。当面对高并发场景时&#xff0c;无限制创建goroutine可能导致资源耗尽、CPU过载等问题&#xff0c;通道信号量模式&#xff08;Channel Semaphore Pattern&#xff09; 正是一种基于Go通道特性的优雅…

鸿蒙 NEXT开发中轻松实现人脸识别功能

大家好&#xff0c;我是 V 哥。 今天给大家介绍在 HarmonyOS 原生鸿蒙开发中&#xff0c;实现人脸识别功能&#xff0c;这个功能在常用的 APP 开发中上镜率还是很高的&#xff0c;在传统的 Android 或 iOS 开发中&#xff0c;通常我们要借助第三方库来实现&#xff0c;而在鸿蒙…

华为开发者空间训练营-优秀作品公布

排名标题总分奖品1手把手教你开发一个地区智能查询MCP&#xff0c;赋能地理位置类MCP服务的“零输入”无感交互95华为 freebuds 6i 蓝牙耳机2基于华为开发者空间云主机DeepSeek助力电商企业AI海报文案驱动的最佳实践落地 94华为 freebuds 6i 蓝牙耳机32小时基于华为开发者空间和…

基于Python与Tkinter开发的微博多功能自动化助手

文章目录 摘要 1. 背景与意义 2. 需求分析 3. 核心架构设计 3.1. 技术选型 3.2. 核心思想:UI与逻辑分离的异步架构 4. 深度模块化剖析 4.1. 微博核心API交互模块 4.2. 健壮性设计:代理与重试机制 4.3. GUI界面模块 (WeiboApp 类) 4.4. 异步任务处理模块 5. 难点分析与解决方案…

效果驱动复购!健永科技RFID牛场智能称重项目落地

近日&#xff0c;北京某养殖企业持续下单电子耳标识读器&#xff0c;在牛场智能称重中落地应用&#xff0c;通过自动、准确地识别牛只并记录体重数据&#xff0c;显著提升效率和数据精准度&#xff0c;实现了“效果驱动复购”的良性循环。健永科技RFID技术在北京某养殖企业智能…

计算机网络:2、TCP和UDP

2、TCP和UDP 简介 TCP(transmission Control Protocol)&#xff1a;是一种通信标准&#xff0c;它使应用程序和计算设备能够在网络上交换消息。它的设计目的是在互联网上发送数据包&#xff0c;并确保数据和信息在网络上的成功传递。UDP(the User Datagram Protocol)&#xf…

WEB安全篇:浏览器攻击原理及防护

1、XSS&#xff1a;跨站脚本攻击就是攻击者想尽一切办法将可以执行的代码注入到网页中。攻击者在web页面恶意插入HTML或script标签&#xff0c;当用户浏览该页面时&#xff0c;恶意代码就会被执行&#xff0c;从而达到攻击的目的。XSS利用的是用户对指定网站的信任。比如&#…