Linux文件系统原理.docx
《Linux文件系统原理.docx》由会员分享,可在线阅读,更多相关《Linux文件系统原理.docx(36页珍藏版)》请在冰豆网上搜索。
Linux文件系统原理
Linux文件系统原理
第1章概述
要理解linux的文件系统,要从理解虚拟文件系统(VFS,VirtualFilesystem)开始。
其结构可以用图表1来描述。
图表1
第2章虚拟文件系统
2.1它到底是什么?
虚拟文件系统是Linux内核中的一个软件层,用于给用户空间的程序提供文件系统接口。
它也提供了内核中的一个抽象功能,允许不同的文件系统共存。
2.2工作方式概述
2.2.1注册和安装一个文件系统
如果你想在内核中支持一种新的文件系统的话,你所需要做的仅仅是调用函数register_filesystem()。
你向内核中传递一个描述文件系统实现的结构(structfilesystem),此结构将被加入到内核的支持文件系统表中去。
你可以运行下面的命令:
%cat/proc/filesystems
这样可以看到你的系统支持哪些文件系统。
内核模块必须提供两个用于加载和卸载时调用的接口,这两个接口由两个固定的宏引出,分别为module_init(),module_exit()。
文件系统在作为模块被加载到内核时,调用register_filesystem(),把自己注册为一种文件系统。
在模块被卸载时,调用unregister_filesystem()从内核中注消。
以JFFS为例:
//定义一种文件系统jffs_fs_type,其名称为“JFFS”
staticDECLARE_FSTYPE_DEV(jffs_fs_type,"jffs",jffs_read_super);
staticint__init
init_jffs_fs(void)
{
printk(KERN_INFO"JFFSversion"JFFS_VERSION_STRING
",(C)1999,2000AxisCommunicationsAB\n");
#ifdefCONFIG_JFFS_PROC_FS
jffs_proc_root=proc_mkdir("jffs",proc_root_fs);
#endif
fm_cache=kmem_cache_create("jffs_fm",sizeof(structjffs_fm),
0,SLAB_HWCACHE_ALIGN,NULL,NULL);
node_cache=kmem_cache_create("jffs_node",sizeof(structjffs_node),
0,SLAB_HWCACHE_ALIGN,NULL,NULL);
//注册文件系统
returnregister_filesystem(&jffs_fs_type);
}
staticvoid__exit
exit_jffs_fs(void)
{
//注消文件系统
unregister_filesystem(&jffs_fs_type);
kmem_cache_destroy(fm_cache);
kmem_cache_destroy(node_cache);
}
//引出模块的加载和卸载时调用的接口
module_init(init_jffs_fs)
module_exit(exit_jffs_fs)
当一个mount请求出现时,VFS将会为特定的文件系统调用相应的方法。
安装点的dentry结构将会被改为指向新文件系统的根i节点。
2.2.2打开一个文件
VFS实现了open系统调用。
路径参数被VFS用来在目录入口缓存(dentrycacheor"dcache")。
这提供了一个将路径名转化为特定的dentry的一个快的查找机制。
一个单独的dentry通常包含一个指向i节点(inode)的指针。
i节点存在于磁盘驱动器上,它可以是一个规则文件,目录,FIFO文件,等等。
Dentry存在于RAM中,并且永远不会被存到磁盘上:
它们仅仅为了提高系统性能而存在。
i节点存在于磁盘上,当需要时被拷入内存中,之后对它的任何改变将被写回磁盘。
存在于RAM中的i节点就是VFS的i节点,dentry所包含的指针指向的就是它。
dcache是你的整个文件空间的观察点。
大多数情况下不可能有足够的RAM空间来放我们的文件空间的所有文件的目录入口缓存(dentry),所以我们的dcache会有缺少的项。
为了将路径名转换为一个dentry,VFS不得不采取创建dentry的方式,并在创建dentry时将指针指向相应的i节点。
这是通过对i节点的查找完成的。
为了查找一个文件的i节点(通常从磁盘上读),VFS需要调用该文件的父目录的lookup()方法,此方法是特定的文件系统所设置的。
后面对此将会有更详尽的描述。
一旦VFS得到了所需要的dentry(同时也得到了相应的i节点),我们就能够对文件做想要的操作:
打开文件,或者用stat来看i节点中的数据。
stat的操作非常简单:
在VFS得到dentry之后,它取得inode中的一些数据并将其中的一部分送回用户空间。
打开一个文件需要其它的操作:
分配一个structfile(定义于linux/fs.h,这是内核中的文件描述)结构。
新分配的structfile结构被指向dentry的指针和对文件进行操作的函数集合所初始化,这些都是从i节点中得到的。
通过这种方式,特定的文件系统实现才能起作用。
文件结构(structfile)被放在进程的文件描述符表中。
读,写和关闭文件(或者其它的VFS操作)是通过使用用户空间的文件描述符找到相应的文件结构(structfile),然后调用所需要的方法函数来实现的。
当文件处于打开状态时,系统保持相应的dentry为"open"状态(正在使用),这表示相应的i节点在被使用。
2.3主要结构说明
2.3.1structfile_system_type
此结构描述了文件系统。
在内核2.4.18中,此结构的定义如下:
structfile_system_type{
constchar*name;
intfs_flags;
structsuper_block*(*read_super)(structsuper_block*,void*,int);
structmodule*owner;
structfile_system_type*next;
structlist_headfs_supers;
};
其中各个域的意义:
name:
文件系统的类型名称,如"vfat","ext2",等等。
fs_flags:
变量标志,如FS_REQUIRES_DEV,FS_NO_DCACHE,等等。
read_super:
当此种文件系统的一个新的实例要被安装时,此方法会被调用。
owner:
实例所在的模块,通常默认为THIS_MODULE。
next:
被内部的VFS实现所使用,你只需要将其初始化为NULL。
fs_supers:
超级块链表,初始化为NULL即可。
函数read_super具有以下的参数:
structsuper_block*sb:
超级块结构。
此结构的一部分被VFS初始化,余下的部分必须被函数read_super初始化。
void*data:
任意的安装选项,通常是ASCII的字符串。
在JFFS中没有使用。
intsilent:
表示当出现错误时是否保持安静。
(不报警?
)在JFFS中没有使用。
read_super方法必须确定指定的块设备是否包含了一个所支持的文件系统。
当成功时返回超级块结构的指针,错误时返回NULL。
read_super方法填充进超级块结构(structsuper_block)的最有用的域是"s_op"域。
这是一个指向structsuper_operations的指针,此结构描述了文件系统实现的下一层细节。
2.3.2structsuper_operations
此结构描述了VFS对文件系统的超级块所能进行的操作。
在内核2.4.18中,此结构的定义如下:
structsuper_operations{
void(*read_inode)(structinode*);
void(*read_inode2)(structinode*,void*);
void(*dirty_inode)(structinode*);
void(*write_inode)(structinode*,int);
void(*put_inode)(structinode*);
void(*delete_inode)(structinode*);
void(*put_super)(structsuper_block*);
void(*write_super)(structsuper_block*);
void(*write_super_lockfs)(structsuper_block*);
void(*unlockfs)(structsuper_block*);
int(*statfs)(structsuper_block*,structstatfs*);
int(*remount_fs)(structsuper_block*,int*,char*);
void(*clear_inode)(structinode*);
void(*umount_begin)(structsuper_block*);
structdentry*(*fh_to_dentry)(structsuper_block*sb,__u32*fh,intlen,intfhtype,intparent);
int(*dentry_to_fh)(structdentry*,__u32*fh,int*lenp,intneed_parent);
int(*show_options)(structseq_file*,structvfsmount*);
};
除非特别提出,所有的方法都在未加锁的情况下被调用,这意味着大多数方法都可以安全的被阻塞。
所有的方法都仅仅在进程空间被调用(例如,在中断处理程序和底半部中不能调用它们)
read_inode:
从一个文件系统中读取一个特定的i节点时调用此方法。
i节点中的域"i_ino"被VFS初始化为指向所读的i节点,其余的域被此方法所填充。
read_inode2:
专为reiserfs留的,其它文件系统现在用不到。
write_inode:
当VFS需要向磁盘上的一个i节点写时调用。
put_inode:
当VFS的i节点被从i节点缓冲池移走时被调用。
此方法是可选的。
delete_inode:
当VFS想删除一个i节点时调用次方法。
notify_change:
当VFS的i节点的属性被改变时调用。
若此域为NULL则VFS会调用rite_inode。
此方法调用时需要锁住内核。
put_super:
当VFS要释放超级块时调用(umount一个文件系统)。
此方法调用时需要锁住内核。
write_super:
当VFS超级块需要被写入磁盘时被调用。
此方法为可选的。
statfs:
当VFS需要得到文件系统的统计数据时调用。
此方法调用时需要锁住内核。
remount_fs:
当文件系统被重新安装时调用。
此方法调用时需要锁住内核。
clear_inode:
当VFS清除i节点时调用。
可选项。
以上方法中,read_inode需要填充"i_op"域,此域为一个指向structinode_operations结构的指针,它描述了能够对一个单独的i节点所能进行的操作。
其它的一些域在很多文件系统里都没有使用,可以不用关心。
2.3.3structinode_operations
此结构描述了VFS能够对文件系统的一个i节点所能进行的操作。
在内核2.4.18中,此结构的定义如下:
structinode_operations{
int(*create)(structinode*,structdentry*,int);
structdentry*(*lookup)(structinode*,structdentry*);
int(*link)(structdentry*,structinode*,structdentry*);
int(*unlink)(structinode*,structdentry*);
int(*symlink)(structinode*,structdentry*,constchar*);
int(*mkdir)(structinode*,structdentry*,int);
int(*rmdir)(structinode*,structdentry*);
int(*mknod)(structinode*,structdentry*,int,int);
int(*rename)(structinode*,structdentry*,
structinode*,structdentry*);
int(*readlink)(structdentry*,char*,int);
int(*follow_link)(structdentry*,structnameidata*);
void(*truncate)(structinode*);
int(*permission)(structinode*,int);
int(*revalidate)(structdentry*);
int(*setattr)(structdentry*,structiattr*);
int(*getattr)(structdentry*,structiattr*);
};
create:
被open和creat所调用,仅仅在你要支持普通文件时才需要。
参数中的dentry不应该包含有i节点的指针(即应该为一个negativedentry)。
这里你可能需要对传入的dentry和i节点调用函数d_instantiate。
lookup:
当VFS要在一个父目录中查找一个i节点时调用。
待查找的文件名在dentry中。
此方法必须调用d_add函数把找到的i节点插入到dentry中,i节点的"i_count"域要加一。
若指定的i节点不存在的话,一个NULL的i节点指针将被插入到dentry中去(这种情况的dentry被称为negativedentry)。
link:
被link所调用。
仅在你需要支持hardlink时才需要它。
跟create方法相同的原因,你可能在此方法中也需要调用d_instantiate()函数来验证。
unlink:
被unlink所调用。
仅在你要支持对i节点的删除时才需要它。
symlink:
被symlink调用。
仅在需要支持符号链接时才需要它。
通上面两处,你需要对传入的参数进行验证,要调用d_instantiate()函数。
mkdir:
被mkdir调用。
仅在你要支持建立子目录时才需要它。
同上,你需要调用d_instantiate()函数进行验证。
rmdir:
被rmdir所调用。
仅在你要支持对子目录的删除时才需要它。
mknod:
被mknod所调用,用于建立一个设备i节点,或者FIFO,或socket。
仅当你需要支持对这些类型的i节点的建立时才需要此方法。
同上面几个,你可能也需要调用_instantiate来验证参数。
rename:
被rename调用。
仅在你要支持对文件和目录改名时才需要它。
readlink:
被readlink调用。
仅当你要支持对符号链接的读取才需要它。
follow_link:
被VFS调用,用以从一个符号链接找到相应的i节点。
仅当你需要支持符号链接时才需要此方法。
2.3.4structfile_operations
结构file_operations包含了VFS对一个已打开文件的操作。
在内核2.4.18中,此结构的定义如下:
structfile_operations{
structmodule*owner;
loff_t(*llseek)(structfile*,loff_t,int);
ssize_t(*read)(structfile*,char*,size_t,loff_t*);
ssize_t(*write)(structfile*,constchar*,size_t,loff_t*);
int(*readdir)(structfile*,void*,filldir_t);
unsignedint(*poll)(structfile*,structpoll_table_struct*);
int(*ioctl)(structinode*,structfile*,unsignedint,unsignedlong);
int(*mmap)(structfile*,structvm_area_struct*);
int(*open)(structinode*,structfile*);
int(*flush)(structfile*);
int(*release)(structinode*,structfile*);
int(*fsync)(structfile*,structdentry*,intdatasync);
int(*fasync)(int,structfile*,int);
int(*lock)(structfile*,int,structfile_lock*);
ssize_t(*readv)(structfile*,conststructiovec*,unsignedlong,loff_t*);
ssize_t(*writev)(structfile*,conststructiovec*,unsignedlong,loff_t*);
ssize_t(*sendpage)(structfile*,structpage*,int,size_t,loff_t*,int);
unsignedlong(*get_unmapped_area)(structfile*,unsignedlong,unsignedlong,unsignedlong,unsignedlong);
};
llseek:
当VFS需要移动文件指针的位置时调用。
read:
被read所调用。
write:
被write所调用。
readdir:
当VFS需要读取目录中的内容时被调用。
poll:
当一个进程想知道文件是否可用是调用,进程可以一直休眠等待文件可用。
功能有点类似SOCKET中的select。
ioctl:
被ioctl所调用。
mmap:
被mmap所调用。
open:
当VFS要打开一个i节点时调用它。
当VFS打开一个文件时,它建立一个新的structfile结构,并用i节点中的"default_file_ops"来初始化其中的f_op域,然后对新分配的文件结构调用open方法。
你可以认为open方法实际上属于structinode_operations。
open方法是一个很好的初始化文件结构中的"private_data"域的的地方。
release:
当没有对被打开文件的引用时调用此方法。
fsync:
被fsync所调用。
fasync:
当用fcntl激活一个文件的异步模式时此方法被调用。
这些文件操作是由i节点所在的特定文件系统所实现的。
当打开一个设备节点时(字符或块设备特殊文件),大多数文件系统会调用VFS中的特定支持例程,由此来找到所需要的设备驱动信息;
这些支持例程用设备驱动程序的方法来代替文件系统的文件操作,然后继续对文件调用新的open方法。
这就是为什么当你打开文件系统上的一个设备特殊文件时,最后被调用的却是设备驱动程序的open方法。
另外,devfs(DeviceFilesystem)有一个从设备节点到设备驱动程序的更直接的方式(这是非官方的内核补丁)
2.3.5structdentry_operations
operations.Dentries和dcache是属于VFS和单个文件系统实现的,设备驱动与此无关。
在内核2.4.18中,此结构的定义如下:
structdentry_operations{
int(*d_revalidate)(structdentry*,int);
int(*d_hash)(structdentry*,structqstr*);
int(*d_compare)(structdentry*,structqstr*,structqstr*);
int(*d_delete)(structdentry*);
void(*d_release)(structdentry*);
void(*d_iput)(structdentry*,structinode*);
};
d_revalidate:
当VFS要使一个dentry重新生效时被调用。
d_hash:
当VFS向哈希表中加入一个dentry时被调用。
d_compare:
当指向一个dentry的最后的引用被去除时此方法被调用,因为这意味这没有人在使用此dentry;当然,此dentry仍然有效,并且仍然在dcache中。
d_release:
当一个dentry被清除时调用此方法。
d_iput:
当一个dentry释放它的i节点时(在dentry被清除之前)此方法被调用。
为空时自动调用VFS的input(),如果定义了这个函数,那要自己调用input()。
每个dentry都有一个指向其父目录dentry的指针,一个子dentry的哈希列表。
子dentry基本上就是目录中的文件。
dget:
为一个已经存在的dentry打开一个新的句柄(这仅仅增加引用计数)
dput:
关闭一个dentry的句柄(减少引用计数)。
如果引用计数减少为0,d_delete方法将会被调用;并且,如果此dentry仍然在其父目录的哈希列表中的话,此dentry将被放置于一个未被使用的列表中。
将dentry放置于未使用表中意味着当系统需要更多的RAM时,将会遍历未使用的dentry的列表,并回收其内存空间。
假如当detry的引用计数为0时,它已经没有在父目录的哈希表中的话,在d_delete方法被调用之后系统就会回收起内存空间。
d_drop:
此方法将一个dentry从其父目录的哈希列表中去掉