本文共 5127 字,大约阅读时间需要 17 分钟。
1.图解
对于每个管道来说,内核都要创建一个索引节点对象和两个文件对象,一个文件对象用于读,一个文件对象用于写。当进程希望从管道中读取数据或向管道中写入数据时,必须使用适当的文件描述符。在linux 中,管道的实现并没有使用专门的数据结构,而是借助了文件系统的file结构和VFS的索引节点inode。通过将两个 file 结构指向同一个临时的 VFS 索引节点,而这个 VFS 索引节点又指向一个物理页面而实现。
当索引节点指的是管道时,其i_pipe字段指向一个pipe_inode_info结构
/** * struct pipe_inode_info - a linux kernel pipe * @wait: reader/writer wait point in case of empty/full pipe * @nrbufs: the number of non-empty pipe buffers in this pipe * @curbuf: the current pipe buffer entry * @tmp_page: cached released page * @readers: number of current readers of this pipe * @writers: number of current writers of this pipe * @waiting_writers: number of writers blocked waiting for room * @r_counter: reader counter * @w_counter: writer counter * @fasync_readers: reader side fasync * @fasync_writers: writer side fasync * @inode: inode this pipe is attached to * @bufs: the circular array of pipe buffers **/struct pipe_inode_info { wait_queue_head_t wait; //管道/FIFO等待队列 unsigned int nrbufs, curbuf; //待读数据的缓冲区数 待读数据的第一个缓冲区索引 struct page *tmp_page; unsigned int readers; unsigned int writers; unsigned int waiting_writers; //在等待队列中睡眠的写进程的个数 unsigned int r_counter; unsigned int w_counter; struct fasync_struct *fasync_readers; struct fasync_struct *fasync_writers; struct inode *inode; struct pipe_buffer bufs[PIPE_BUFFERS]; //管道缓冲区数据};利用inode里面的i_ pipe指向的pipe_inode_info结构来表示管道的特殊信息( 缓冲区,等待队列,当前待读数据信息等)
每个管道都有自己的管道缓冲区(pipe buffer),其中包含了已写人管道等待读出的数据。现在每个管道可以使用多个管道缓冲区(2.6.11以后有16个)。这个改变大大增强了向管道写大量数据的用户态应用的性能。
16个缓冲区可以被看做一个整体环形缓冲区:写进程不断向这个大缓冲区追加数据,而读进程则不断移出数据。为提高效率,仍然要读的数据可以分散在几个未填充满的管道缓冲区内:事实上,在上一个管道缓冲区没有足够空间存放新数据时,每个写操作都可能把数据拷贝到一个新的空管道缓冲区。
故,内核必须记录:
为了避免对管道数据结构的竞争条件,内核使用包含在索引节点对象的i_sem信号量
pipe()系统调用由sys_pipe()函数处理,后者又会调用do_pipe()函数。为了创建新的管道,do_pipe主要执行以下操作:
为内核管道创建两个file结构体对象并分配两个空的文件描述符fd指向file结构体对象,两个file结构体对象中的f_inode指向inode索引节点
(在linux2.6.34源码中,最终调用的是do_pipe_flags)
int do_pipe_flags(int *fd, int flags){ struct file *fw, *fr; int error; int fdw, fdr; if (flags & ~(O_CLOEXEC | O_NONBLOCK)) return -EINVAL; fw = create_write_pipe(flags); if (IS_ERR(fw)) return PTR_ERR(fw); fr = create_read_pipe(fw, flags); error = PTR_ERR(fr); if (IS_ERR(fr)) goto err_write_pipe; 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_install(fdr, fr); fd_install(fdw, fw); fd[0] = fdr; fd[1] = fdw; return 0; err_fdr: put_unused_fd(fdr); err_read_pipe: path_put(&fr->f_path); put_filp(fr); err_write_pipe: free_write_pipe(fw); return error;}
struct file *create_write_pipe(int flags){ int err; struct inode *inode; struct file *f; struct path path; struct qstr name = { .name = "" }; err = -ENFILE; inode = get_pipe_inode(); if (!inode) goto err; err = -ENOMEM; path.dentry = d_alloc(pipe_mnt->mnt_sb->s_root, &name); if (!path.dentry) goto err_inode; path.mnt = mntget(pipe_mnt); path.dentry->d_op = &pipefs_dentry_operations; d_instantiate(path.dentry, inode); err = -ENFILE; f = alloc_file(&path, FMODE_WRITE, &write_pipefifo_fops); if (!f) goto err_dentry; f->f_mapping = inode->i_mapping; f->f_flags = O_WRONLY | (flags & O_NONBLOCK); f->f_version = 0; return f; err_dentry: free_pipe_info(inode); path_put(&path); return ERR_PTR(err); err_inode: free_pipe_info(inode); iput(inode); err: return ERR_PTR(err);}
struct file *create_read_pipe(struct file *wrf, int flags){ /* Grab pipe from the writer */ struct file *f = alloc_file(&wrf->f_path, FMODE_READ, &read_pipefifo_fops); if (!f) return ERR_PTR(-ENFILE); path_get(&wrf->f_path); f->f_flags = O_RDONLY | (flags & O_NONBLOCK); return f;}
1.do_pipe_flags调用create_write_pipe和create_read_pipe
2.create_write_pipe中,调用get_pipe_inode()。该函数为pipefs文件系统中的管道分配一个索引节点对象 、pipe_inode_info结构等并对其进行初始化。具体来说,该函数执行以下操作:
(1)在pipefs文件系统中分配一个新的索引节点
(2)分配pipe_inode_info数据结构,并把它的地址存放在索引节点的i_pipe字段
(3)设置pipe_inode_info的curbuf和nrbufs字段为0,并把bufs数组中的管道缓冲区对象的所有字段都清为0
(4)把r_counter、w_counter 、readers、writers字段初始化为1
3.为管道的写通道分配一个文件对象(struct file结构体),将文件操作f_op字段赋值为write_pipefifo_fops,设置文件对象标识f_flags字段为O_WRONLY,O_NONBLOCK,返回文件对象指针
4.调用的create_read_pipe中,为管道的读通道分配一个读文件对象(struct file结构体),将文件对象中的f_op字段设置为read_pipefifo_fops,设置文件对象标识f_flags字段为O_RDONLY,O_NONBLOCK,返回文件对象指针
5.从进程的打开文件描述符表中取出两个未使用的文件描述符,将管道读写端文件对象(struct file)与文件描述符关联起来 6.返回管道读写端文件描述符(赋值fd[0]和fd[1])相关的VFS对象被组织为pipefs特殊文件系统以加速它们的处理。
pipe()创建管道,在pipefs中分配新的索引结点并初始化,初始化pipe_inode_info结构。分配一个只读文件对象和一个只写文件对象,
它们的f_op字段分别初始化成read_pipe_fops和write_pipe_fops表的地址。分配一个目录项对象把两个文件对象和索引结点对象连接在一起。最后返回两个文件描述符。 read()从管道中读数据,调用的是read_pipe_fops表中的pipe_read(),获取索引结点i_sem信号量,检测管道写端是否关闭(判断pipe_inode_info中的writers是否为0),如果关闭则返回0。【 否则检测pipe_inode_info结构nrbufs字段表示的管道大小是否为0。检测O_NONBLOCK标志,是直接返回,否的话阻塞直到有数据可读。映射缓冲区页框读取数据到用户缓冲区中, 唤醒阻塞中的写进程。最后是一些释放i_sem信号量的工作。返回读取的字节数或错误信息。 write()类似read(),没有读进程时(判断pipe_inode_info中的writers是否为0),发送SIGPIPE信号并返回-EPIPE。同样是检查阻塞标志,等待缓冲区可写入全部数据, 唤醒读进程,最后是一些释放i_sem信号量的工作。返回写入的字节数或错误信息。(为什么规定多个进程并发写入一个管道时任何少于4096个字节的写操作必须是原子的?)