8.6.1 管道文件系统pipefs
pipefs是一种简单的、虚拟的文件系统类型,因为它没有对应的物理设备,因此其安装时不需要块设备,在第10章将看到,大部分文件系统是以模块的形成来实现的。该文件系统相关的代码在fs/pipe.c中:
static DECLARE_FSTYPE(pipe_fs_type, "pipefs", pipefs_read_super,
FS_NOMOUNT|FS_SINGLE);
static int __init init_pipe_fs(void)
{
int err = register_filesystem(&pipe_fs_type);
if (!err) {
pipe_mnt = kern_mount(&pipe_fs_type);
err =
PTR_ERR(pipe_mnt);
if (IS_ERR(pipe_mnt))
unregister_filesystem(&pipe_fs_type);
else
err = 0;
}
return err;
}
static void __exit exit_pipe_fs(void)
{
unregister_filesystem(&pipe_fs_type);
mntput(pipe_mnt);
}
module_init(init_pipe_fs)
module_exit(exit_pipe_fs)
pipefs文件系统是作为一个模块来安装的,其中module_init()是模块的初始化函数,module_exit()是模块的卸载函数,其更详细的解释将在第10章给出。
从DECLARE_FSTYPE()宏定义可以看出,pipefs文件系统的FS_NOMOUNT和FS_SINGLE标志位为1,这就意味着该文件系统不能从用户空间进行安装,并且在整个系统范围内只有一个超级块。FS_SINGLE标志也意味着在通过register_filesystem()成功地注册了该文件系统后,应该通过kern_mount()来安装。
register_filesystem()函数把pipe_fs_type链接到file_systems链表,因此,你可以通过读/proc/filesystems找到“pipefs”入口点,在那里,“nodev”标志表示没有设置FS_REQUIRES_DEV标志,即该文件系统没有对应的物理设备。
kern_mount()类似于do_mount(),用来安装pipefs文件系统。当安装出现错误时,则调用unregister_filesystem()把pipe_fs_type从file_systems链表中拆除。
现在,pipefs文件系统已被注册,并成为内核中的一个模块,从此我们就可以使用它了。Pipefs文件系统的入口点就是pipe()系统调用,其内核实现函数为sys_pipe(),而真正的工作是调用do_pipe()函数来完成的,其代码在/fs/pipe.c中,我们并同时给出了对代码的注释。
int do_pipe(int *fd)
{
struct qstr this;
char name[32];
struct dentry *dentry;
struct inode * inode;
struct file *f1, *f2; /*进程对每个已打开文件的操作是通过file结构进行的。
一个管道实际上就是一个存在于内存的文件,
对这个文件的操作要通过两个已打开的文件进行,
f1、f2分别代表该管道的两端。*/
int error;
int i,j;
error = -ENFILE;
f1 = get_empty_filp(); /*管道两端各分配一个file 结构*/
if (!f1)
goto no_files;
f2 = get_empty_filp();
if (!f2)
goto close_f1;
inode = get_pipe_inode(); /*每个文件都有一个inode结构。由于管道文件在管道创建之前并不存在,因此,在创建管道时临时创建一个inode结构。*/
if (!inode)
goto close_f12;
error = get_unused_fd(); /* 分配打开文件号*/
if (error < 0)
goto close_f12_inode;
i = error;
error = get_unused_fd();
if (error < 0)
goto close_f12_inode_i;
j = error;
error = -ENOMEM;
sprintf(name, "[%lu]", inode->i_ino);
this.name = name;
this.len = strlen(name);
this.hash = inode->i_ino; /* will go */
dentry = d_alloc(pipe_mnt->mnt_sb->s_root, &this); /* File结构中有个指针f_dentry指向所打开文件的目录项dentry结构,而dentry 中有个指针指向相应的inode结构。所以,调用d_alloc()分配一个目录项是为了把file结构与inode结构联系起来。*
if (!dentry)
goto close_f12_inode_i_j;
dentry->d_op = &pipefs_dentry_operations;
d_add(dentry, inode); /*使已分配的inode 结构与已分配的目录项结构挂勾*/
f1->f_vfsmnt = f2->f_vfsmnt = mntget(mntget(pipe_mnt)); /* pipe_mnt就是在init_pipe_fs()中所获得的指向vfsmount结构的指针,因为这个结构多了两个使用者,因此调用两次mntget ()使其引用计数加2 */
f1->f_dentry = f2->f_dentry = dget(dentry); /*让两个已打开文件中的f_dentry指针都指向这个目录项,并使目录项的引用计数加1*/
/* read file */
f1->f_pos = f2->f_pos = 0;
f1->f_flags = O_RDONLY;
f1->f_op = &read_pipe_fops;
f1->f_mode = 1;
f1->f_version = 0;
/* write file */
f2->f_flags = O_WRONLY;
f2->f_op = &write_pipe_fops;
f2->f_mode = 2;
f2->f_version = 0;
fd_install(i, f1); /*将已打开文件结构与分配得的打开文件号相关联(打开文件号只在一个进程的范围类有效)。*/
fd_install(j, f2);
fd[0] = i; /*使得fd[0]为管道读出端的打开文件号*/
fd[1] = j; /*使得fd[1]为管道写出端的打开文件号*/
return 0;
/*以下为释放各种资源*/
close_f12_inode_i_j:
put_unused_fd(j);
close_f12_inode_i:
put_unused_fd(i);
close_f12_inode:
free_page((unsigned long) PIPE_BASE(*inode));
kfree(inode->i_pipe);
inode->i_pipe = NULL;
iput(inode);
close_f12:
put_filp(f2);
close_f1:
put_filp(f1);
no_files:
return error;
}
下面对管道的单向性再做进一步的说明。从代码看出,把f1一端设置成“只读(O_RDONLY)”,另一端则设置成“只写(O_WRONLY)”。同时,两端的文件操作也分别设置成read_pipe_fops和write_pipe_fops,其定义于pipe.c中:
struct file_operations read_pipe_fops = {
llseek: pipe_lseek,
read: pipe_read,
write: bad_pipe_w,
poll: pipe_poll,
ioctl: pipe_ioctl,
open: pipe_read_open,
release: pipe_read_release,
};
struct file_operations write_pipe_fops = {
llseek: pipe_lseek,
read: bad_pipe_r,
write: pipe_write,
poll: pipe_poll,
ioctl: pipe_ioctl,
open: pipe_write_open,
release: pipe_write_release,
};
在read_pipe_fops()中的写操作函数为bad_pipe_w(),而在write_pipe_fops()中的读操作函数为bad_pipe_r(),这两个函数分别返回一个出错代码。尽管代表着管道两端的两个已打开文件一个只能读,一个只能写。但是,另一方面,这两个逻辑上已打开的文件指向同一个inode,即用作管道的缓冲区,显然,这个缓冲区既支持读也支持写。这进一步说明了file 、inode及dentry之间的不同和联系。