Linux pipe 源码分析

管道pipe作为Unix中历史最悠久的IPC机制,存在各个版本的Unix中,主要用于父子进程之间的通信(使用fork,从而子进程会获得父进程的打开文件表),pipe()系统调用底层的实现就相当于一个特殊的文件系统,每次调用的时候创建一个inode关联着两个file,一个用于读,一个用于写,从而实现数据的单向流动。

用户层API:

 #include <unistd.h>

       int pipe(int pipefd[2]);

       #define _GNU_SOURCE             /* See feature_test_macros(7) */
       #include <unistd.h>

       int pipe2(int pipefd[2], int flags);

内核源码路径如下:

// sys_pipe(.......)
SYSCALL_DEFINE1(pipe, int __user *, fildes)
{
     return sys_pipe2(fildes, 0);
}

SYSCALL_DEFINE2(pipe2, int __user *, fildes, int, flags)
{
     struct file *files[2];
     int fd[2];
     int error;
     // 核心是do_pipe
     error = __do_pipe_flags(fd, files, flags);
     if (!error) {
          // 一切准备就绪后 把刚才和管道关联的2个fd拷贝到用户空间
          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;
}

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;
     // 为该管道创建俩struct file
     error = create_pipe_files(files, flags);
     if (error)
          return error;
     // 获得两个能用的文件描述符
     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;
}

/*
* 为管道创建两个file实例
*/
int create_pipe_files(struct file **res, int flags)
{
     int err;
     // 为pipe创建一个inode并做一定的初始化
     struct inode *inode = get_pipe_inode();
     struct file *f;
     struct path path;
     static struct qstr name = { .name = "" }; // quick string ??

     if (!inode)
          return -ENFILE;

     err = -ENOMEM;
     // 分配一个directory entry
     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;
     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;
     // 所以你会明白 fd[0]是读 fd[1]是写
     res[0] = alloc_file(&path, FMODE_READ, &pipefifo_fops);
     if (IS_ERR(res[0]))
          goto err_file;

     path_get(&path);
     res[0]->private_data = inode->i_pipe;
     res[0]->f_flags = O_RDONLY | (flags & O_NONBLOCK);
     res[1] = f;
     return 0;

err_file:
     put_filp(f);
err_dentry:
     free_pipe_info(inode->i_pipe);
     path_put(&path);
     return err;

err_inode:
     free_pipe_info(inode->i_pipe);
     iput(inode);
     return err;
}

static struct inode * get_pipe_inode(void)
{
     struct inode *inode = new_inode_pseudo(pipe_mnt->mnt_sb);
     struct pipe_inode_info *pipe;

     if (!inode)
          goto fail_inode;
     // 分配一个inode号
     inode->i_ino = get_next_ino();
     // 分配一个pipe的内核级对象
     pipe = alloc_pipe_info();
     if (!pipe)
          goto fail_iput;

     inode->i_pipe = pipe;
     pipe->files = 2;
     pipe->readers = pipe->writers = 1;
     inode->i_fop = &pipefifo_fops;

     /*
     * Mark the inode dirty from the very beginning,
     * that way it will never be moved to the dirty
     * list because "mark_inode_dirty()" will think
     * that it already _is_ on the dirty list.
     */
     inode->i_state = I_DIRTY;
     inode->i_mode = S_IFIFO | S_IRUSR | S_IWUSR;
     inode->i_uid = current_fsuid();
     inode->i_gid = current_fsgid();
     inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;

     return inode;

fail_iput:
     iput(inode);

fail_inode:
     return NULL;
}

// 针对pipe的文件操作实例
const struct file_operations pipefifo_fops = {
     .open          = fifo_open,
     .llseek          = no_llseek,
     .read          = new_sync_read,
     .read_iter     = pipe_read,
     .write          = new_sync_write,
     .write_iter     = pipe_write,
     .poll          = pipe_poll,
     .unlocked_ioctl     = pipe_ioctl,
     .release     = pipe_release,
     .fasync          = pipe_fasync,
};

整体的逻辑图可以这样:

TODO:具体读写的实现细节new_sync_read/write()有待分析。

参考:

(1)Linux kernel 3.18 source code

(2)Linux man page

(3)Linux内核源码情景分析

时间: 2024-10-11 12:10:10

Linux pipe 源码分析的相关文章

Linux内核源码分析--内核启动之(5)Image内核启动(rest_init函数)(Linux-3.0 ARMv7)【转】

原文地址:Linux内核源码分析--内核启动之(5)Image内核启动(rest_init函数)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.chinaunix.net/uid-25909619-id-4938395.html 前面粗略分析start_kernel函数,此函数中基本上是对内存管理和各子系统的数据结构初始化.在内核初始化函数start_kernel执行到最后,就是调用rest_init函数,这个函数的主要使命就是创建并启动内核线

Linux内核源码分析--内核启动之(6)Image内核启动(do_basic_setup函数)(Linux-3.0 ARMv7)【转】

原文地址:Linux内核源码分析--内核启动之(6)Image内核启动(do_basic_setup函数)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.chinaunix.net/uid-25909619-id-4938396.html 在基本分析完内核启动流程的之后,还有一个比较重要的初始化函数没有分析,那就是do_basic_setup.在内核init线程中调用了do_basic_setup,这个函数也做了很多内核和驱动的初始化工作,详解

Linux内核源码分析--内核启动之(3)Image内核启动(C语言部分)(Linux-3.0 ARMv7) 【转】

原文地址:Linux内核源码分析--内核启动之(3)Image内核启动(C语言部分)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.chinaunix.net/uid-25909619-id-4938390.html 在构架相关的汇编代码运行完之后,程序跳入了构架无关的内核C语言代码:init/main.c中的start_kernel函数,在这个函数中Linux内核开始真正进入初始化阶段, 下面我就顺这代码逐个函数的解释,但是这里并不会过于深入

Linux内核源码分析--内核启动之(4)Image内核启动(setup_arch函数)(Linux-3.0 ARMv7)【转】

原文地址:Linux内核源码分析--内核启动之(4)Image内核启动(setup_arch函数)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.chinaunix.net/uid-25909619-id-4938393.html 在分析start_kernel函数的时候,其中有构架相关的初始化函数setup_arch. 此函数根据构架而异,对于ARM构架的详细分析如下: void __init setup_arch(char **cmdlin

linux内存源码分析 - 内存压缩(同步关系)

本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 概述 最近在看内存回收,内存回收在进行同步的一些情况非常复杂,然后就想,不会内存压缩的页面迁移过程中的同步关系也那么复杂吧,带着好奇心就把页面迁移的源码都大致看了一遍,还好,不复杂,也容易理解,这里我们就说说在页面迁移过程中是如何进行同步的.不过首先可能没看过的朋友需要先看看linux内存源码分析 - 内存压缩(一),因为会涉及里面的一些知识. 其实一句话可以概括页面迁移时是如何进行同步的,就是:我要开始对这

ARMv8 Linux内核源码分析:__flush_dcache_all()

1.1 /* *  __flush_dcache_all() *  Flush the wholeD-cache. * Corrupted registers: x0-x7, x9-x11 */ ENTRY(__flush_dcache_all) //保证之前的访存指令的顺序 dsb sy //读cache level id register mrs x0, clidr_el1           // read clidr //取bits[26:24](Level of Coherency f

linux内存源码分析 - 内存回收(整体流程)

本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 概述 当linux系统内存压力就大时,就会对系统的每个压力大的zone进程内存回收,内存回收主要是针对匿名页和文件页进行的.对于匿名页,内存回收过程中会筛选出一些不经常使用的匿名页,将它们写入到swap分区中,然后作为空闲页框释放到伙伴系统.而对于文件页,内存回收过程中也会筛选出一些不经常使用的文件页,如果此文件页中保存的内容与磁盘中文件对应内容一致,说明此文件页是一个干净的文件页,就不需要进行回写,直接将此

linux内存源码分析 - SLAB分配器概述

本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 之前说了管理区页框分配器,这里我们简称为页框分配器,在页框分配器中主要是管理物理内存,将物理内存的页框分配给申请者,而且我们知道也可页框大小为4K(也可设置为4M),这时候就会有个问题,如果我只需要1KB大小的内存,页框分配器也不得不分配一个4KB的页框给申请者,这样就会有3KB被白白浪费掉了.为了应对这种情况,在页框分配器上一层又做了一层SLAB层,SLAB分配器的作用就是从页框分配器中拿出一些页框,专门把

linux 内核源码分析 - 获取数组的大小

#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) 测试程序: #include<stdio.h> #include<stdlib.h> struct dev { int a; char b; float c; }; struct dev devs[]= { { 1,'a',7.0, }, { 1,'a',7.0, }, { 1,'a',7.0, }, }; int main() { printf("int is %d \