这一篇我们来看看,虚拟文件系统是如何抹平各个文件系统的差异,又是如何和具体的文件系统串起来的。
我们先来回顾一下之前的讲的内容。
void mount_root(void){
...
for (fs_type = file_systems ; fs_type ; fs_type = fs_type->next) {
if(retval)
break;
// 没有关联到设备则不需要往下执行,有些文件系统是没有对应的底层设备的
if (!fs_type->requires_dev)
continue;
// 读根设备的超级块,设备的第一扇区是分区表,接着是超级块
sb = read_super(ROOT_DEV,fs_type->name,root_mountflags,NULL,1);
// 读取成功
if (sb) {
// 根节点
inode = sb->s_mounted;
inode->i_count += 3 ; /* NOTE! it is logically used 4 times, not 1 */
sb->s_covered = inode;
sb->s_flags = root_mountflags;
// 当前进程(init进程)的根目录和工作目录设置为根节点
current->fs->pwd = inode;
current->fs->root = inode;
printk ("VFS: Mounted root (%s filesystem)%s.\n",
fs_type->name,
(sb->s_flags & MS_RDONLY) ? " readonly" : "");
// 直接返回,即第一个读取成功的文件系统成为根文件系统
return;
}
}
...
}
我们看到由read_super加载超级块的内容,假设根文件系统是ext文件系统。从函数sys_setup中我们知道ext文件系统的read_super函数是ext_read_super。
#ifdef CONFIG_EXT_FS
register_filesystem(&(struct file_system_type)
{ext_read_super, "ext", 1, NULL});
#endif
接着往下看。
struct super_block *ext_read_super(struct super_block *s,void *data,
int silent)
{
struct buffer_head *bh;
struct ext_super_block *es;
int dev = s->s_dev,block;
lock_super(s);
set_blocksize(dev, BLOCK_SIZE);
// 读取设备的内容,即超级块的内容
if (!(bh = bread(dev, 1, BLOCK_SIZE))) {
s->s_dev=0;
unlock_super(s);
printk("EXT-fs: unable to read superblock\n");
return NULL;
}
// 文件系统的一些属性
es = (struct ext_super_block *) bh->b_data;
// 超级块的一些属性
...
s->s_dev = dev;
// 操作函数集
s->s_op = &ext_sops;
// 读取根节点,超级块的s_mounted字段指向根节点
if (!(s->s_mounted = iget(s,EXT_ROOT_INO))) {
s->s_dev=0;
printk("EXT-fs: get root inode failed\n");
return NULL;
}
return s;
}
我们看到这个函数主要做的几件事情是
1 从硬盘读取超级块的内容
2 设置超级块的操作函数集
3 最后读取文件系统的根节点。
读超级块内容这个没有太多需要解释的,我们先看看超级块操作函数集的定义
static struct super_operations ext_sops = {
ext_read_inode,
NULL,
ext_write_inode,
ext_put_inode,
ext_put_super,
ext_write_super,
ext_statfs,
NULL
};
然后我们看一下读取inode的函数iget。
extern inline struct inode * iget(struct super_block * sb,int nr)
{
return __iget(sb,nr,1);
}
__iget函数的主要逻辑是获取一个新的inode结构体,然后执行read_inode(inode)从硬盘把inode数据读取进来(inode.c后续单独分析)。核心代码如下。
if (!empty) {
h->updating++;
// 获取一个空闲inode
empty = get_empty_inode();
if (!--h->updating)
wake_up(&update_wait);
if (empty)
goto repeat;
return (NULL);
}
inode = empty;
inode->i_sb = sb;
inode->i_dev = sb->s_dev;
inode->i_ino = nr;
inode->i_flags = sb->s_flags;
put_last_free(inode);
insert_inode_hash(inode);
read_inode(inode);
下面直接看read_inode函数。
static void read_inode(struct inode * inode)
{
lock_inode(inode);
if (inode->i_sb && inode->i_sb->s_op && inode->i_sb->s_op->read_inode)
inode->i_sb->s_op->read_inode(inode);
unlock_inode(inode);
}
我们看文章的开头可以发现,read_inode函数在ext文件系统中的实现函数是ext_read_inode。
void ext_read_inode(struct inode * inode)
{
struct buffer_head * bh;
struct ext_inode * raw_inode;
int block;
// 每个硬盘块可以存储的inode结构体数,+2是代表启动扇区、超级块数据的硬盘块
block = 2 + (inode->i_ino-1)/EXT_INODES_PER_BLOCK;
// 读取某设备的某个块
if (!(bh=bread(inode->i_dev, block, BLOCK_SIZE)))
panic("unable to read i-node block");
// 取余算出偏移从而得到inode结构体的数据
raw_inode = ((struct ext_inode *) bh->b_data) +
(inode->i_ino-1)%EXT_INODES_PER_BLOCK;
// 复制某些字段到内存的inode结构体
inode->i_mode = raw_inode->i_mode;
inode->i_uid = raw_inode->i_uid;
inode->i_gid = raw_inode->i_gid;
inode->i_nlink = raw_inode->i_nlinks;
inode->i_size = raw_inode->i_size;
inode->i_mtime = inode->i_atime = inode->i_ctime = raw_inode->i_time;
inode->i_blocks = inode->i_blksize = 0;
// 字符和块设备的i_zone[0]是设备号
if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode))
inode->i_rdev = raw_inode->i_zone[0];
else for (block = 0; block < 12; block++)
// 否则i_zone里存的是文件数据的硬盘直接块号或者间接块号
inode->u.ext_i.i_data[block] = raw_inode->i_zone[block];
brelse(bh);
inode->i_op = NULL;
// 不同类型的文件赋值不同的操作函数集
if (S_ISREG(inode->i_mode))
inode->i_op = &ext_file_inode_operations;
else if (S_ISDIR(inode->i_mode))
inode->i_op = &ext_dir_inode_operations;
else if (S_ISLNK(inode->i_mode))
inode->i_op = &ext_symlink_inode_operations;
else if (S_ISCHR(inode->i_mode))
inode->i_op = &chrdev_inode_operations;
else if (S_ISBLK(inode->i_mode))
inode->i_op = &blkdev_inode_operations;
else if (S_ISFIFO(inode->i_mode))
init_fifo(inode);
}
至此,把根文件系统的超级块和根节点inode都读取进来了。再回头看mount_root函数的代码
inode = sb->s_mounted;
// 当前进程(init进程)的根目录和工作目录设置为根节点
current->fs->pwd = inode;
current->fs->root = inode;
下面我们看看此时的架构图。
这一篇就先说到这里,现在有根节点了,下一篇看一下如何进行文件的读写和多文件系统是如何一起工作的。