文件系统 随笔


在打开一个文件时,最后往往到磁盘里面寻找inode读入,这个时候会调用相应文件系统的inode的i_ops里面的lookup方法,在ext3中则是ext3_lookup方法,ext3_lookup会调用struct inode *ext3_iget(struct super_block *sb, unsigned long ino),用超级块来获得指定的inode节点返回,并初始化vfs的inode,并且一个文件系统对应的操作函数在inode中的表现是i_io f_op以及node->i_mapping->a_ops。后者涉及到真正地读写操作。

if (S_ISREG(inode->i_mode)) {
inode->i_op = &ext3_file_inode_operations;
inode->i_fop = &ext3_file_operations;
ext3_set_aops(inode);
} else if (S_ISDIR(inode->i_mode)) {
inode->i_op = &ext3_dir_inode_operations;
inode->i_fop = &ext3_dir_operations;
} else if (S_ISLNK(inode->i_mode)) {
if (ext3_inode_is_fast_symlink(inode)) {
inode->i_op = &ext3_fast_symlink_inode_operations;
nd_terminate_link(ei->i_data, inode->i_size,
sizeof(ei->i_data) - 1);
} else {
inode->i_op = &ext3_symlink_inode_operations;
ext3_set_aops(inode);
}
} else {
inode->i_op = &ext3_special_inode_operations;
if (raw_inode->i_block[0])
init_special_inode(inode, inode->i_mode,
  old_decode_dev(le32_to_cpu(raw_inode->i_block[0])));
else
init_special_inode(inode, inode->i_mode,
  new_decode_dev(le32_to_cpu(raw_inode->i_block[1])));
}

static int ext2_create (struct inode * dir, struct dentry * dentry, int mode, struct nameidata *nd)
{
struct inode *inode;


dquot_initialize(dir);


inode = ext2_new_inode(dir, mode);
if (IS_ERR(inode))
return PTR_ERR(inode);


inode->i_op = &ext2_file_inode_operations;
if (ext2_use_xip(inode->i_sb)) {
inode->i_mapping->a_ops = &ext2_aops_xip;
inode->i_fop = &ext2_xip_file_operations;
} else if (test_opt(inode->i_sb, NOBH)) {
inode->i_mapping->a_ops = &ext2_nobh_aops;
inode->i_fop = &ext2_file_operations;
} else {
inode->i_mapping->a_ops = &ext2_aops;
inode->i_fop = &ext2_file_operations;
}
mark_inode_dirty(inode);
return ext2_add_nondir(dentry, inode);
}

根据文件类型初始化i_op i_fop,其中如果是字符设备或块设备,则会在init_special_inode里面赋值i_fop,

void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev)
{
inode->i_mode = mode;
if (S_ISCHR(mode)) {
inode->i_fop = &def_chr_fops;
inode->i_rdev = rdev;
} else if (S_ISBLK(mode)) {
inode->i_fop = &def_blk_fops;
inode->i_rdev = rdev;
} else if (S_ISFIFO(mode))
inode->i_fop = &def_fifo_fops;
else if (S_ISSOCK(mode))
inode->i_fop = &bad_sock_fops;
else
printk(KERN_DEBUG "init_special_inode: bogus i_mode (%o) for"
 " inode %s:%lu\n", mode, inode->i_sb->s_id,
 inode->i_ino);
}

在后续的过程中会调用__dentry_open

if (!open && f->f_op)
open = f->f_op->open;
if (open) {
error = open(inode, f);
if (error)
goto cleanup_all;
}

如果是字符设备,f->f_op->open即是chrdev_open,该函数会将inode指向的cdev机构里面的file_ops赋值给f->f_op。从这可以看出字符设备和快设备设计的不同。

如果是块设备,f->f_op->open即是blkdev_open,它会根据设备号经过及其复杂的操作获得block_device对象给inode的i_bdev赋值,关联起来。

node->i_mapping->a_ops。涉及到真正地读写操作。

const struct address_space_operations ext2_aops = {
.readpage = ext2_readpage,
.readpages = ext2_readpages,
.writepage = ext2_writepage,
.sync_page = block_sync_page,
.write_begin = ext2_write_begin,
.write_end = ext2_write_end,
.bmap = ext2_bmap,
.direct_IO = ext2_direct_IO,
.writepages = ext2_writepages,
.migratepage = buffer_migrate_page,
.is_partially_uptodate= block_is_partially_uptodate,
.error_remove_page= generic_error_remove_page,
};

static int ext2_readpage(struct file *file, struct page *page)
{
return mpage_readpage(page, ext2_get_block);
}

int mpage_readpage(struct page *page, get_block_t get_block)
{
struct bio *bio = NULL;
sector_t last_block_in_bio = 0;
struct buffer_head map_bh;
unsigned long first_logical_block = 0;


map_bh.b_state = 0;
map_bh.b_size = 0;
bio = do_mpage_readpage(bio, page, 1, &last_block_in_bio,
&map_bh, &first_logical_block, get_block);
if (bio)
mpage_bio_submit(READ, bio);
return 0;
}



inode 还引用特定于 inode 的操作(createlookuplink 和 mkdir 等等)。最后,对于由地址空间对象表示的对象的数据,有一个管理结构。地址空间对象 是为 inode 管理页缓存中的各种页的对象。地址空间对象用于为文件管理页,也用于将文件部分映射到独立的进程地址空间。地址空间对象有自己的操作集(writepagereadpage 和 releasepage 等等)。


注意,dentry 对象仅存在文件系统内存中,而不能储存在磁盘上。仅永久储存文件系统 inode,dentry 对象的目的是改善性能。您可以在 ./linux/include/dcache.h 中看到 dentry 的完整描述。

记住:对目录或文件的操作将最终由目录或文件所对应的 inode 结构中的 i_op 和 i_fop 所指向的函数表中对应的函数来执行。所以,不管最终解决方案如何,都可以设想必然要通过将对 "/dev" 目录所对应的 inode 中 i_op 和 i_fop 的调用转换到 hda2 上根文件系统 ext2 中根目录所对应的 inode 中 i_op 和 i_fop 的操作。


比如在 Linux 的应用程序中 open 或 read 一个文件 /home/windfly.cs 时,这里的 /home/windfly.cs 就是文件路径名,path_lookup() 函数的责任就是对文件路径名中进行搜索,直到找到目标文件所属目录所对应的 dentry 或者目标直接就是一个目录,通过给int path_lookup(const char *name, unsigned int flags,
struct nameidata *nd)其中的nd赋值完成的。

一个文件系统的挂载往往是形成一个vfsmount,file_system_types,super_block,相应的dentry,inode,形成如下的结构。

mnt_init->init_rootfs();
          init_mount_tree();

以rootfs文件系统为例。要挂载一个文件系统,先必须注册。init_rootfs() 通过调用 register_filesystem(&rootfs_fs_type) 函数来完成 rootfs 文件系统注册的,其中rootfs_fs_type 定

static void __init init_mount_tree(void)
{
struct vfsmount *mnt;
struct mnt_namespace *ns;
struct path root;


mnt = do_kern_mount("rootfs", 0, "rootfs", NULL);
if (IS_ERR(mnt))
panic("Can't create rootfs");
ns = create_mnt_ns(mnt);
if (IS_ERR(ns))
panic("Can't allocate initial namespace");


init_task.nsproxy->mnt_ns = ns;
get_mnt_ns(ns);


root.mnt = ns->root;
root.dentry = ns->root->mnt_root;


set_fs_pwd(current->fs, &root);
set_fs_root(current->fs, &root);
}

do_kern_mount是核心函数。

do_kern_mount-->|get_fs_type 
            |vfs_kern_mount-->|mnt = alloc_vfsmnt (调用kmem_cache_zalloc分配vfsmount) 
                  |type->get_sb(type, flags, name, data, mnt)(见下面) 
static int rootfs_get_sb(struct file_system_type *fs_type, 
         int flags, const char *dev_name, void *data, struct vfsmount *mnt) 

         return get_sb_nodev(fs_type, flags|MS_NOUSER, data, ramfs_fill_super, mnt); 

int get_sb_nodev(struct file_system_type *fs_type, 
         int flags, void *data, 
         int (*fill_super)(struct super_block *, void *, int), 
         struct vfsmount *mnt) 

         int error; 
         struct super_block *s = sget(fs_type, NULL, set_anon_super, NULL); 
         if (IS_ERR(s)) 
                 return PTR_ERR(s); 
         s->s_flags = flags; 
         error = fill_super(s, data, flags & MS_SILENT ? 1 : 0); 
         if (error) { 
                 up_write(&s->s_umount); 
                 deactivate_super(s); 
                 return error; 
         } 
         s->s_flags |= MS_ACTIVE; 
         return simple_set_mnt(mnt, s); 

看一下fill_super 
static int ramfs_fill_super(struct super_block * sb, void * data, int silent) 

         struct inode * inode; 
         struct dentry * root; 
         sb->s_maxbytes = MAX_LFS_FILESIZE; 
         sb->s_blocksize = PAGE_CACHE_SIZE; 
         sb->s_blocksize_bits = PAGE_CACHE_SHIFT; 
         sb->s_magic = RAMFS_MAGIC; 
         sb->s_op = &ramfs_ops; 
         sb->s_time_gran = 1; 
         inode = ramfs_get_inode(sb, S_IFDIR | 0755, 0); 
         if (!inode) 
                 return -ENOMEM; 
         root = d_alloc_root(inode); 
         if (!root) { 
                 iput(inode); 
                 return -ENOMEM; 
         } 
         sb->s_root = root; 
         return 0; 

文件系统所对应的根 inode dentry是在fill_super里面形成的。其他文件系统的都是类似





3.1.1__path_lookup_intent_open()

不管是path_lookup_open()还是path_lookup_create()最终都是调用__path_lookup_intent_open()来实现查找文件的功能。 查找时,在遍历路径的过程中,会逐层地将各个路径组成部分解析成目录项对象,如果此目录项对象在目录项缓存中,则直接从缓存中获得;如果该目录项在缓存中不存在,则进行一次实际的读盘操作,从磁盘中读取该目录项所对应的索引节点。得到索引节点后,则建立索引节点与该目录项的联系。如此循环,直到最终找到目标文件对应的目录项,也就找到了索引节点,而由索引节点找到对应的超级块对象就可知道该文件所在的文件系统的类型。 从磁盘中读取该目录项所对应的索引节点;这将引发VFS和实际的文件系统的一次交互。从前面的VFS理论介绍可知,读索引节点方法是由超级块来提供的。而当安装一个实际的文件系统时,在内存中创建的超级块的信息是由一个实际文件系统的相关信息来填充的,这里的相关信息就包括了实际文件系统所定义的超级块的操作函数列表,当然也就包括了读索引节点的具体执行方式。 当继续追踪一个实际文件系统ext3的ext3_read_inode()时,可发现这个函数很重要的一个工作就是为不同的文件类型设置不同的索引节点操作函数表和文件操作函数表。



4.2“一切皆是文件”的实现根本

不论是普通的文件,还是特殊的目录、设备等,VFS都将它们同等看待成文件,通过同一套文件操作界面来对它们进行操作。操作文件时需先打开;打开文件时,VFS会知道该文件对应的文件系统格式;当VFS把控制权传给实际的文件系统时,实际的文件系统再做出具体区分,对不同的文件类型执行不同的操作。这也就是“一切皆是文件”的根本所在。


注意dentry是目录项,不是目录,目录是文件,有对应的inode表示.

必须要记住每一种文件系统,所对应的inode operations super_block里面的operations,等等都不一样,这些事具体文件系统提供给VFS的接口。通过这些接口操作文件,比如,查找,创建,修改,等等文件操作。

比如proc文件系统,每个文件都有一个proc_dir_entry结构,ext3则在磁盘上有ext3_dir_entry,是在具体存储介质上的信息,ext的比较简单,因为有具体存储介质。proc系统是虚拟介质的,所以额外信息很多,表示这个目录里面记录的文件信息,是proc里面的内部结构,所有的文件都有这个结构,非常简单,要操作一个文件时,找到文件的inode,对应的operations会调用文件对应的proc_dir_entry里面的read_proc。每个文件对应的read_proc不一样,比如查看

/proc/cpuinfo的内容,相应的read_proc会去查找对应的数据结构然后返回出来,输出。

struct ext3_dir_entry {
__le32 inode; /* Inode number */
__le16 rec_len; /* Directory entry length */
__le16 name_len; /* Name length */
char name[EXT3_NAME_LEN]; /* File name */
};

struct proc_dir_entry {
unsigned int low_ino;
umode_t mode;
nlink_t nlink;
kuid_t uid;
kgid_t gid;
loff_t size;
const struct inode_operations *proc_iops;
/*
* NULL ->proc_fops means "PDE is going away RSN" or
* "PDE is just created". In either case, e.g. ->read_proc won't be
* called because it's too late or too early, respectively.
*
* If you're allocating ->proc_fops dynamically, save a pointer
* somewhere.
*/
const struct file_operations *proc_fops;
struct proc_dir_entry *next, *parent, *subdir;
void *data;
read_proc_t *read_proc;
write_proc_t *write_proc;
atomic_t count; /* use count */
int pde_users; /* number of callers into module in progress */
struct completion *pde_unload_completion;
struct list_head pde_openers; /* who did ->open, but not ->release */
spinlock_t pde_unload_lock; /* proc_fops checks and pde_users bumps */
u8 namelen;
char name[];
};



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值