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 运维框图