您的位置:首页 > 运维架构 > Linux

Linux kernel 分析之十八:设计模式-文件系统与抽象工厂

2015-07-23 22:01 549 查看
抽象工厂的典型应用是在gui设计中。为了在运行时方便地替换不同风格的gui设计,我们需要在上层把每种不同风格的gui的实现细节隐藏起来。我们不再显式地调用gui中的某个组件的构造函数,因为这样暴露了组件的实现。相反,我们为每一个风格提供一个“工厂”来生产该风格的“产品”。

抽象工厂可以看作是面向对象设计中常用的多态思想的扩展。普通的多态只是针对一个“产品”,如shape抽象类。而抽象工厂是针对一组“产品”。所以显得整齐划一。

在设计文件系统时,我们希望抽象的文件系统VFS和具体的文件系统分离。即在VFS中不涉及任何具体的文件系统。如果要添加一个新的文件系统,则只要添加几个源文件,然后编译成模块,就万事大吉了。为了达到这一目标,VFS的代码本身不应该涉及具体的文件系统 。VFS里需要构造dentry,inode,super block对象时,也应该想方设法地避免显式地调用构造函数。但是由于文件系统可以抽象出一些共同的要素和接口,如dentry,inode,super_block,这时我们想到了抽象工厂的设计模式。

每一类文件系统可以看作是“工厂”,而dentry,inode, super block可以看作是“产品”。那么,工厂在哪里呢?我们是在哪里创建”工厂”本身的呢?

我们知道文件访问的大概流程:

1.首先,我们得知道是哪个文件系统。然后,通过mount把文件系统安装到某个目录下。这个结构体就是:file_system_type。我们可以看一下ext2的 file_system_type1167 static struct file_system_type ext2_fs_type = {1168 .owner = THIS_MODULE,1169 .name = "ext2",1170 .get_sb = ext2_get_sb,1171 .kill_sb = kill_block_super,1172 .fs_flags = FS_REQUIRES_DEV,

1173 };

我们可以在注册文件系统时把这个结构体放到内核的全局链表:file_systems中。然后通过文件系统的名称找到对应的file_system_type结构体。

2.其次,我们得获得super block的信息,因为一个文件系统通常只有一个super block,由于super block的重要性,他会有很多个备份,但是从信息量的角度来讲仍然只有一份。文件系统的根目录也在super block中。我们可以构造出根目录的inode对象和dentry对象

好在我们已经有了file_system_type。不出所料,我们可以发现里面有获得super block的函数指针:get_sb。这里有一个有趣的现象:为什么ext2_fs_type,包括

ext2_get_sb要设置为static呢?因为这样做其它模块(包括VFS)就没有可能直接引用ext2_fs_type了。“逼”着VFS引藏细节。这其实是C风格的一种封装。由此我们也可以总结出:设计为static,并不意味着其它模块不会调用到该变量或者函数,只是意味着无法直接调用到而已。那么VFS如何调用 ext2_get_sb呢?在挂载文件系统时,在do_kern_mount()中有如下代码:

sb = type->get_sb(type, flags, name, data);

由于我们在mount文件系统时会指定文件系统的名称。内核就会找到对应的file_system_type。然后调用get_sb。

从面向对象的角度来说,super_block对象不仅包括super block的数据成员,还包括对super_block的一组操作的实现。所以应该有个接口叫super_operations。果然,ext2文件系统有个super_operations变量叫ext2_sops,在ext2_get_sb赋给对应的super block.

那么根结点如何找?不要急,根据之前的分析,super_operations应该提供读inode的操作,事实上,它是一个叫read_inode的函数指针。另一方面,super_block中有个dentry成员变量叫s_root,就是我们要找的根节点的dentry!此外,系统还悄悄地把一些函数指针也赋了值。如inode_operations 。inode ,dentry ,file对象的构造通常伴随着inode_operations,dentry_operatons,file_operations的赋值。这在C++中是由编译器来负责实现的。在C中只能人肉实现了。

通过这种方法,引藏了具体文件系统的细节。

3.接着,有了根目录的inode,我们可以很方便地访问它的子节点。于是采用lazy algorithm,只有当子节点被访问到,我们才去硬盘上访问它,并在内存中构造它。这一步是在sys_open中实现的。

sys_open()->filp_open()->open_namei()->path_lookup()->link_path_walk()->__link_path_walk()->do_lookup()->real_lookup()

在这里,当系统发现要访问的子节点不在内存中,,它不得不去硬盘上找。不过对应的lookup接口已经在父节点的“构造函数”中“人肉”赋值了。这里它只需要调用如下代码:

result = dir->i_op->lookup(dir, dentry, nd);就实现了在磁盘上查找文件结点的操作。由此我们可以总结出:

1.文件系统的“工厂”由很多组接口组成,包括file_system_type,

super_operatios,inode_operations,dentry_operations, file_operations.只要文件系统类型确定,这些接口即“工厂”也确定了。在VFS中就可以根据ext2工厂生产出对应的产品即ext2的super block ,inode ,dentry ,file对象。只要我们在mount时换一下文件系统如reiserfs,ext2“工厂”就立即摇身一变,成了reiserfs的“工厂”。而VFS的代码不需要作任何改变。

2.为了在语法上保证松散耦合,除了需要通过EXPORT_SYMBOL导出的符号,我们可以把模块中的函数和变量尽量地声明为static,以实现一定程度的封装。

3.对象的构造总是伴随着成员变量和方法的赋值。尤其是后者,使VFS能够不用调用下层文件系统的具体代码。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: