当安装Ext2文件系统时(执行诸如mount -t ext2 /dev/sda2 /mnt/test的命令),存放在Ext2分区的磁盘数据结构中的大部分信息将被拷贝到RAM中,从而使内核避免了后来的很多读操作。那么一些数据结构如何经常更新呢?因为所有的Ext2磁盘数据结构都存放在Ext2磁盘分区的块中,因此,内核利用页高速缓存来保持它们最新。
前面谈到,安装ext2文件系统时,最终会调用ext2_fill_super()函数来为数据结构分配空间,并写入从磁盘读取的数据。该函数首先用kmalloc分配一个ext2_sb_info描述符,将其地址赋给当做参数传递进来的超级块super_block的s_fs_info字段。
随后通过__bread()在缓冲区页中分配一个缓冲区和缓冲区首部,这个函数属于也高速缓存的范围,详细介绍请到博客“在页高速缓存中搜索块”
http://blog.csdn.net/yunsongice/archive/2010/08/30/5850807.aspx。
然后从磁盘读入超级块存放在缓冲区中。在“在页高速缓存中搜索块”一博我们讨论过,如果一个块已在页高速缓存的缓冲区页而且是最新的,那么无需再分配。将缓冲区首部地址存放在Ext2超级块对象sbi的s_sbh字段,之后再用kmalloc分配一个数组用于存放缓冲区首部指针,每个组描述符一个,把该数组地址存放在ext2_sb_info的s_group_desc字段。这样,就可以通过重复调用__bread()分配缓冲区,从磁盘读人包含Ext2组描述符的块。把缓冲区首部地址存放在上一步得到的s_group_desc数组中。
最后安装sb->s_op,sb->s_export_op,超级块增强属性。因为准备为根目录分配一个索引节点和目录项对象,必须把s_op字段为超级块建立好,从而能够从磁盘读入根索引节点对象:
sb->s_op = &ext2_sops;
sb->s_export_op = &ext2_export_ops;
sb->s_xattr = ext2_xattr_handlers;
root = iget(sb, EXT2_ROOT_INO);
sb->s_root = d_alloc_root(root);
注意,EXT2_ROOT_INO是2,也就是它的根节点,位于第一个块组的第三个位置上。sb->s_op 被赋值成了ext2_sops来自fs/ext2/super.c:
static struct super_operations ext2_sops = {
.alloc_inode = ext2_alloc_inode,
.destroy_inode = ext2_destroy_inode,
.read_inode = ext2_read_inode,
.write_inode = ext2_write_inode,
.put_inode = ext2_put_inode,
.delete_inode = ext2_delete_inode,
.put_super = ext2_put_super,
.write_super = ext2_write_super,
.statfs = ext2_statfs,
.remount_fs = ext2_remount,
.clear_inode = ext2_clear_inode,
.show_options = ext2_show_options,
#ifdef CONFIG_QUOTA
.quota_read = ext2_quota_read,
.quota_write = ext2_quota_write,
#endif
};
很显然,ext2_fill super()函数返回后,有很多关键的ext2磁盘数据结构的内容都保存在内存里了,例如ext2_super_block、块设备的索引节点等,只有当Ext2文件系统卸载时才会被释放。当内核必须修改Ext2超级块的字段时,它只要把新值写入相应缓冲区内的相应位置然后将该缓冲区标记为脏即可,有了页高速缓存事情就是这么简单!
以上内容仅简单介绍一下该函数的一些关键步骤。该函数的详细分析请查看博客“Ext2的超级块对象”http://blog.csdn.net/yunsongice/archive/2010/08/17/5819323.aspx, 并结合我们这里给出的数据结构图进行分析:
要弄清第二扩展文件系统Ext2层的处理,除了ext2超级快对象,还要弄懂一个ext2索引节点对象。我们在上一节看到ext2_fill_super函数中,当初始化超级快后,还有一步是调用iget将索引节点号为EXT2_ROOT_INO(一般为2)的索引节点分配给该ext2磁盘分区的加载目录,也就是根目录项。
static inline struct inode *iget(struct super_block *sb, unsigned long ino)
{
struct inode *inode = iget_locked(sb, ino);
if (inode && (inode->i_state & I_NEW)) {
sb->s_op->read_inode(inode);
unlock_new_inode(inode);
}
return inode;
}
看到ext2_sops全局变量我们得知,ext2_read_inode就是ext2超级块的s_op->read_inode具体实现函数,该函数会调用ext2_get_inode函数,从一个页高速缓存中读入一个磁盘索引节点结构ext2_inode,然后初始化VFS的inode。当然,这个根目录不是普通文件,而是一个目录文件,所以我们后面章节再来深入研究,这里只介绍其中最重要的初始化代码段,如下:
struct inode *ext2_read_inode(struct inode *ino)
{
……
if (S_ISREG(inode->i_mode)) { /* 普通文件操作 */
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;
}
} else if (S_ISDIR(inode->i_mode)) {/* 目录文件操作 */
inode->i_op = &ext2_dir_inode_operations;
inode->i_fop = &ext2_dir_operations;
if (test_opt(inode->i_sb, NOBH))
inode->i_mapping->a_ops = &ext2_nobh_aops;
else
inode->i_mapping->a_ops = &ext2_aops;
} else if (S_ISLNK(inode->i_mode)) {/* 符号链接文件操作 */
if (ext2_inode_is_fast_symlink(inode))
inode->i_op = &ext2_fast_symlink_inode_operations;
else {
inode->i_op = &ext2_symlink_inode_operations;
if (test_opt(inode->i_sb, NOBH))
inode->i_mapping->a_ops = &ext2_nobh_aops;
else
inode->i_mapping->a_ops = &ext2_aops;
}
} else {/* 其他特殊文件文件操作 */
inode->i_op = &ext2_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])));
}
……
}
我们看到根目录的inode结构的i_op 、i_mapping和i_fop字段被赋值了。先来看一下i_fop被初始化成了 ext2_file_operations 全局变量:
struct file_operations ext2_file_operations = {
.llseek = generic_file_llseek,
.read = generic_file_read,
.write = generic_file_write,
.aio_read = generic_file_aio_read,
.aio_write = generic_file_aio_write,
.ioctl = ext2_ioctl,
.mmap = generic_file_mmap,
.open = generic_file_open,
.release = ext2_release_file,
.fsync = ext2_sync_file,
.readv = generic_file_readv,
.writev = generic_file_writev,
.sendfile = generic_file_sendfile,
};
i_op也被初始化成了ext2_file_inode_operations全局变量:
struct inode_operations ext2_file_inode_operations = {
.truncate = ext2_truncate,
#ifdef CONFIG_EXT2_FS_XATTR
.setxattr = generic_setxattr,
.getxattr = generic_getxattr,
.listxattr = ext2_listxattr,
.removexattr = generic_removexattr,
#endif
.setattr = ext2_setattr,
.permission = ext2_permission,
.fiemap = ext2_fiemap,
};
i_mapping->a_ops也被初始化成了ext2_aops全局变量:
const struct address_space_operations ext2_aops = {
.readpage = ext2_readpage,
.readpages = ext2_readpages,
.writepage = ext2_writepage,
.sync_page = block_sync_page,
.prepare_write = ext2_prepare_write,
.commit_write = generic_commit_write,
.bmap = ext2_bmap,
.direct_IO = ext2_direct_IO,
.writepages = ext2_writepages,
.migratepage = buffer_migrate_page,
};