揭秘Linux管道(Pipe)源码实现
linux pipe 源码

作者:IIS7AI 时间:2025-01-21 15:19



Linux Pipe源码深度解析:进程间通信的基石 在Linux操作系统中,管道(pipe)是实现进程间通信(IPC)的一种基本且高效的机制

    它通过内存中的缓冲区,允许一个进程将数据发送到另一个进程,从而实现了进程间的数据传递

    本文将深入探讨Linux pipe的源码实现,揭示其内部工作原理,以及如何通过这一机制实现进程间的通信

     一、管道的基本概念与特性 管道,也称为无名管道,是UNIX系统IPC的最古老形式之一,所有的UNIX系统都支持这种通信机制

    管道具有以下几个关键特性: 1.半双工通信:数据在同一时刻只能在一个方向上流动,即数据只能从管道的一端写入,从另一端读出

     2.先入先出规则:写入管道中的数据遵循先入先出的规则,这意味着最早写入的数据将最先被读出

     3.无格式数据传输:管道所传送的数据是无格式的,因此管道的读出方与写入方必须事先约定好数据的格式

     4.内存中存在:管道不是普通的文件,不属于某个文件系统,它只存在于内存中,对应一个缓冲区

     5.一次性操作:从管道读数据是一次性操作,数据一旦被读走,它就从管道中被抛弃,释放空间以便写更多的数据

     6.使用限制:管道只能在具有公共祖先的进程(如父进程与子进程)之间使用

     二、Linux pipe的源码实现 在Linux内核中,pipe的实现涉及多个关键函数和数据结构

    以下是对这些核心组件的详细解析

     1. pipe系统调用 用户态的pipe系统调用通过``头文件中的`pipe`函数实现

    其原型如下: include int pipe(int pipefd【2】); `pipefd`是一个整型数组,长度为2

    `pipefd【0】`用于读取,`pipefd【1】`用于写入

    成功时,函数返回0;失败时,返回-1,并设置`errno`以指示错误原因

     内核态的pipe系统调用入口是`SYSCALL_DEFINE1(pipe,int __user fildes)和SYSCALL_DEFINE2(pipe2, int__userfildes, int flags)`

    这两个系统调用最终都会调用`do_pipe2`函数来完成管道的创建工作

    `do_pipe2`函数接受一个文件描述符数组和一个标志位参数,标志位参数用于指定管道的一些额外属性,如非阻塞(`O_NONBLOCK`)和`fork/exec`时是否关闭(`O_CLOEXEC`)

     2.do_pipe2函数 `do_pipe2`函数是管道创建的核心

    它首先调用`__do_pipe_flags`函数来分配两个`struct file`数据结构,一个用于读,一个用于写

    然后,它调用`copy_to_user`将这两个文件描述符拷贝至用户态

    如果成功,这些文件描述符将被安装到当前进程的打开文件表中

     static intdo_pipe2(int__userfildes, int flags) { // 分配两个file结构,一个用于读,一个用于写 structfile fw, fr; int fdw, fdr; // ...(省略部分代码) // 调用__do_pipe_flags创建管道 err= __do_pipe_flags(p, &fr, &fw,flags); if(err) gotoerr_cleanup; fd【0】 = fdr; fd【1】 = fdw; // 将文件描述符拷贝至用户态 if(copy_to_user(fildes, fd,sizeof(fd))){ err = -EFAULT; gotoerr_fd; } // 安装文件描述符到当前进程的打开文件表中 fd_install(fdr,fr); fd_install(fdw,fw); return 0; } 3.__do_pipe_flags函数 `__do_pipe_flags`函数负责实际的管道创建工作

    它首先检查标志位参数的有效性,然后调用`create_pipe_files`函数来创建两个`struct file`结构

    接着,它使用`get_unused_fd_flags`函数分别获取两个未使用的文件描述符,分别对应读和写

     static int__do_pipe_flags(intp, struct file fr, struct filefw, int flags) { // ...(省略部分代码) // 创建两个file结构 err = create_pipe_files(p, &f, &w, flags); if(err) goto out; fr = f; fw = w; out: return err; } 4.create_pipe_files函数 `create_pipe_files`函数为管道分配一个inode,并为读端和写端分别申请`structfile`结构

    它首先调用`get_pipe_inode`函数来分配一个inode,然后使用`alloc_file_pseudo`为写端申请一个`struct file`结构,并使用`alloc_file_clone`分配一个读端`struct file`结构

    这两个`struct file`结构共享同一个inode和文件操作符结构`pipefifo_fops`

     static intcreate_pipe_files(int p, struct file fr, structfile fw, int flags) { // ...(省略部分代码) // 为管道分配一个inode inode = get_pipe_inode(); if(!inode) return -ENFILE; // 为写端申请一个file结构 f = alloc_file_pseudo(inode, pipe_mnt, ,O_WRONLY |(flags& (O_NONBLOCK |O_DIRECT)), &pipefifo_fops); if(IS_ERR(f)) { iput(inode); returnPTR_ERR(f); } // 为读端分配一个file结构 w = alloc_file_clone(f, O_RDONLY| (flags &O_NONBLOCK), &pipefifo_fops); if(IS_ERR(w)) { fput(f); returnPTR_ERR(w); } // ...(省略部分代码) } 5. pipefifo_fops结构 `pipefifo_fops`结构定义了管道文件操作符,包括打开、读写、轮询、ioctl等操作

    这些操作的具体实现在Linux内核源码中有详细的定义

     const structfile_operations pipefifo_fops ={ .owner =THIS_MODULE, .llseek =no_llseek, .read_iter =pipe_read, .write_iter =pipe_write, .poll =pipe_poll, .unlocked_ioctl =pipe_ioctl, .open =fifo_open, .release =pipe_release, .fasync = pipe_fasync, }; 三、管道的使用示例 以下是一个简单的示例程序,展示了如何在父子进程之间使用管道进行通信: include include include include int main() { int pipefd【2】; pid_t pid; charbuffer【100】; // 创建管道 if(pipe(pipefd) == -{ perror(pipe); exit(EXIT_FAILURE); } // 创建子进程 pid = fork(); if(pid == -{ perror(fork); exit(EXIT_FAILURE); } if(pid == { // 子进程:关闭写端,从管道读取数据 close(pipefd【1】); read(pipefd【0】, buffer,sizeof(buffer)); printf(子进程接收到: %sn,buffer); close(pipefd【0】); }else { // 父进程:关闭读端,向管道写入数据 close(pipefd【0】); constchar message = Hello from parentprocess!; write(pipefd【1】, message,strlen(message) + 1); close(pipefd【1】); } return 0; } 在这个示例中,父进程首先创建一个管道,并生成一个子进程

    然后,父进程关闭管道的读端,并向管道的写端写入一条消息

    子进程关闭管道的写端,并从管道的读端读取消息

    这样,父子进程之间就通过管道实现了数据的传递

     四、总结 Linux管道是一种高效且简单的进程间通信机制

    它通过内存中的缓冲区,允许一个进程将数据发送到另一个进程

    本文深入探讨了Linux pipe的源码实现,包括pipe系统调用、do_pipe2函数、__do_pipe_flags函数、create_pipe_files函数以及pipefifo_fops结构等关键组件

    通过理解这些组件的工作原理,我们可以更好地掌握管道的使用