linux 管道实现解析

Posted ty_laurel

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux 管道实现解析相关的知识,希望对你有一定的参考价值。

介绍

       管道是进程间通信的一种方式,分为无名管道和有名管道两种。使用无名管道可以进行相关进程间通信(也就是父子进程),使用有名管道可以进行不相关进程(没有父子关系的进程)之间的通信。下边主要介绍下无名管道的实现机制。 
      用户态创建无名管道函数有pipe()和pipe2(),通常在命令行的一个命令输出中查找一些特定数据时,也常用到管道技术,如“ps -elf | grep program”,其也是调用pipe函数来创建一个管道进行通信。系统API函数如下:

 
  
          #include <unistd.h>
  
  
          int pipe(int pipefd[2]);
  
  
    
  
  
          #define _GNU_SOURCE             /* See feature_test_macros(7) */
  
  
          #include <fcntl.h>              /* Obtain O_* constant definitions */
  
  
          #include <unistd.h>
  
  
          int pipe2(int pipefd[2], int flags);
  
 

管道大致框图如下: 

linux内核管道就是内存中的一个管道缓冲区pipe_buf,可以理解为存在于内存中的一个文件

源码分析

接下来具体分析下管道的内核实现代码,两个函数陷入内核调用:

 
  
   /*
  
  
    * sys_pipe() is the normal C calling standard for creating
  
  
    * a pipe. It's not the way Unix traditionally does this, though.
  
  
    */
  
  
   SYSCALL_DEFINE2(pipe2, int __user *, fildes, int, flags)
  
  
   
  
  
       struct file *files[2];
  
  
       int fd[2];
  
  
       int error;
  
  
    
  
  
           //__do_pipe_flags为内核管道创建两个file结构体对象,并分配两个空的文件描述符fd,并进行填充
  
  
       error = __do_pipe_flags(fd, files, flags);
  
  
       if (!error) 
  
  
           /* 将上一步和管道关联的2个fd拷贝到用户空间,copy_to_user函数执行成功,则接着执行else后边语句,否则执行if语句块中的部分。
  
  
                       unlikely()表示括号中的表达式极小的可能执行失败,若执行失败,则执行if语句块,否则执行else语句块。
  
  
                       此处是将fd数组先拷贝至至用户空间,然后将fd与file对象关联,
  
  
                   */
  
  
           if (unlikely(copy_to_user(fildes, fd, sizeof(fd)))) 
  
  
               fput(files[0]);
  
  
               fput(files[1]);
  
  
               put_unused_fd(fd[0]);
  
  
               put_unused_fd(fd[1]);
  
  
               error = -EFAULT;
  
  
            else 
  
  
                   //把fd和file的映射关系更新到该进程的文件描述符表中fdtable
  
  
               fd_install(fd[0], files[0]);
  
  
               fd_install(fd[1], files[1]);
  
  
           
  
  
       
  
  
       return error;
  
  
   
  
  
    
  
  
   SYSCALL_DEFINE1(pipe, int __user *, fildes)
  
  
   
  
  
       return sys_pipe2(fildes, 0);
  
  
   
  
 

SYSCALL_DEFINE2中主要是调用__do_pipe_flags函数创建内核管道文件及指向管道文件的两个file结构体对象和两个新的文件描述符号,以上步骤没有报错则将文件描述符数组fd[]拷贝至用户态fildes数组,成功则将__do_pipe_flags中创建的的files对象添加至当前进程的文件描述符表fdtable中,即就是将文件描述符和文件对象关联,使得通过当前进程的files_struct对象(文件系统介绍可以参照博客)可以找到内核管管道文件。

 
  
   static int __do_pipe_flags(int *fd, struct file **files, int flags)
  
  
   
  
  
       int error;
  
  
       int fdw, fdr;
  
  
    
  
  
       if (flags & ~(O_CLOEXEC | O_NONBLOCK | O_DIRECT))
  
  
           return -EINVAL;
  
  
    
  
  
       //为管道创建两个file结构体
  
  
       error = create_pipe_files(files, flags);
  
  
       if (error)
  
  
           return error;
  
  
    
  
  
       //get_unused_fd_flags分配一个文件描述符,并标记为busy
  
  
       error = get_unused_fd_flags(flags);
  
  
       if (error < 0)
  
  
           goto err_read_pipe;
  
  
       fdr = error;
  
  
    
  
  
       error = get_unused_fd_flags(flags);
  
  
       if (error < 0)
  
  
           goto err_fdr;
  
  
       fdw = error;
  
  
    
  
  
       audit_fd_pair(fdr, fdw); //设置审计数据信息
  
  
       fd[0] = fdr;
  
  
       fd[1] = fdw;
  
  
       return 0;
  
  
    
  
  
    err_fdr:
  
  
       put_unused_fd(fdr);
  
  
    err_read_pipe:
  
  
       fput(files[0]);
  
  
       fput(files[1]);
  
  
       return error;
  
  
   
  
 

create_pipe_files函数实现主要是创建管道的file对象,接着调用的get_unused_fd_flags函数是对__alloc_fd函数的封装,调用__alloc_fd函数主要是分配一个文件描述符,并设置为busy,其大致过程为:首先对用户打开文件表files_struct结构体对象加锁,获取当前进程的文件描述符表,依次遍历该文件描述符表,找到一个未使用的文件描述符号,最后释放files_struct的对象锁。

接下来主要看下create_pipe_files函数:

 
  
   int create_pipe_files(struct file **res, int flags)
  
  
   
  
  
       int err;
  
  
       struct inode *inode = get_pipe_inode(); //为管道创建一个inode并进程初始化
  
  
       struct file *f;
  
  
       struct path path;
  
  
       static struct qstr name =  .name = "" ;
  
  
    
  
  
       if (!inode)
  
  
           return -ENFILE;
  
  
    
  
  
       err = -ENOMEM;
  
  
       //分配一个目录项
  
  
       path.dentry = d_alloc_pseudo(pipe_mnt->mnt_sb, &name);
  
  
       if (!path.dentry)
  
  
           goto err_inode;
  
  
       path.mnt = mntget(pipe_mnt); //引用计数加1
  
  
    
  
  
       d_instantiate(path.dentry, inode);
  
  
    
  
  
       err = -ENFILE;
  
  
       //alloc_file函数分配并初始化一个写管道的file结构体对象,并传入pipe文件操作函数结构体对象
  
  
       f = alloc_file(&path, FMODE_WRITE, &pipefifo_fops);
  
  
       if (IS_ERR(f))
  
  
           goto err_dentry;
  
  
    
  
  
       f->f_flags = O_WRONLY | (flags & (O_NONBLOCK | O_DIRECT));
  
  
       f->private_data = inode->i_pipe;
  
  
    
  
  
           //创建并初始化读管道的file对象
  
  
       res[0] = alloc_file(&path, FMODE_READLinux 运维框图

命名管道吞下 Linux 命令输出的第一个字段

Linux声卡驱动框图

用 Linux 管道实现 online judge 的交互题功能

Linux进程间通信

linux中有名管道与匿名管道的实现