Chinaunix首页 | 论坛 | 博客
  • 博客访问: 506172
  • 博文数量: 176
  • 博客积分: 4045
  • 博客等级: 上校
  • 技术积分: 2491
  • 用 户 组: 普通用户
  • 注册时间: 2008-03-19 11:23
文章分类

全部博文(176)

文章存档

2011年(7)

2009年(12)

2008年(157)

我的朋友

分类:

2008-06-02 15:52:22

原文地址:http://blog.chinaunix.net/u1/43031/showart_376552.html

 

    Linux可以支持许多种磁盘类型和磁盘格式使用的方法就是虚拟文件系统的概念.也就是说他能支持任何实际文件系统所提供的任何操作.对于访问文件的每个操作函数,虚拟文件系统都能把他们替换成支持实际文件系统的相对应的函数.

 

虚拟文件的使用

    虚拟文件系统也可以称为(Virtual Filesystem Switch)(VFS).用来处理与Unix标准文件系统相关的所有系统调用.为各个文件系统提供一个通用的接口.这样应用程序就可以通过VFS来进行跨文件系统的操作(copy).

   

    VFS支持的文件系统可以分为三种类型:

l         基于磁盘的文件系统

l         网络文件系统

l         特殊文件系统(虚拟文件系统)

   

    Unix目录树的根目录”/”包含在根文件系统中,而其他所有的文件系统都可以被”mount”到根文件系统的子目录中.

 

通用文件模型

    虚拟文件系统的主要思想就是引入一个通用文件模型.这个模型能够表示所有支持的文件系统.通过文件模型是将目录当作普通的文件来看待的,Unix的文件系统如FAT中存放的是目录而不是文件,所以对于每个具体的文件系统,都要能转化成虚拟文件系统通用的文件模型.

   

    Linux内核对关于文件的操作都使用一个指针,这个指针指向具体文件系统的适当函数.

 

    通用文件模型由下列的对象类型组成:

l         超级块对象(superblock object):存在已经mount的文件系统有关信息.对于基于磁盘的文件系统,就对应于文件系统控制块(filesystem control block).

l         索引点对象(inode object):存放关于具体文件的一般信息.对于磁盘文件系统也就是文件控制块(file control block).每个索引点对象都有一个唯一表示文件系统中文件的索引节点号.

l         文件对象(file object):存放打开文件与进程之间交互的相关信息.

l         目录项对象(dentry object):存放目录项与对应文件进行链结的信息.

 

    多个进程打开一个文件都会产生一个文件对象,但是只有通过硬链结打开文件的进程才会产生目录项对象.但一个文件只会产生一个索引节点对象和超级块对象.见下图:

 

VFS所处理的系统调用

    (深入12-1)列出了VFS的系统调用.这些系统调用涉及文件系统,正规文件,目录文件和符号链结文件.此外VFS还处理设备文件,管道文件,套接字系统调用.同是我们可以知道并非所有的文件操作都要调用下一层的具体函数,比如关闭文件,lseek等VFS就可以自己完成.

 

VFS的数据结构

    每个VFS对象都存放在一个数据结构中,其中包括对象的属性和指向对象方法表的指针.

 

超级块对象

    super_block结构组成.具体见表(深入12-2)

    超级块对象每个安装的文件系统都有一个,而且以双向链表的形式存在.super_block:s_list中的nextprev描绘了整个链表的结构.如下图:

 

    结构体的最后是一个有关具体文件系统超级块信息的U联合体.U域的数据通常被复制到内存,这样所有基于硬盘的文件系统访问和修改自己的磁盘时都可以直接对内存中的U域进行操作而不用访问磁盘.这样所引起的VFS超级块与磁盘上对应超级块不同步的问题,就通过s_dirt标志,表示超级块是否脏,也就是磁盘上的数据是否需要更新.

 

    与超级块关联的方法就是超级块操作,这个操作由super_operation描述(s_op).每个具体文件系统都有自己的超级块操作函数.

 

    下面介绍一些具体的操作块操作函数

l         Read_inode(inode):用磁盘上的数据填充参数指定的节点索引对象.

l         Write_inode(inode):用参数指定的节点索引对象内容更新文件系统上的对应节点.

l         Put_indoe(inode):释放参数指定的索引节点对象.

l         Delete_inode(inode):删除包含文件,磁盘索引节点和VFS索引节点的数据块.

l         Notify_change(dentry,iattr):根据iattr修改索引节点的一些属性.

l         Put_super(super):释放超级块对象.

l         Write_super(super):根据参数内容更新文件系统的超级块.

l         Statfs(super,buf,bufsize):返回文件系统的信息.

 

缺页374

 

索引节点对象

    具体见表(深入12-3).

    super_block一样,最后一个U联合域存放具体文件系统的索引节点信息.每个索引节点对象也会复制磁盘索引节点的一些数据,同样也存在的问题.(I_DIRTY),也就上说对应的磁盘索引节点必须被更新.

 

    每个索引节点对象总是在下列双向链表中的一个,三种又通过i_list链结在一起:

l         未使用索引节点的链表.变量inode_unused:nextprev指向首尾元素.

l         正在使用索引节点的链表.变量inode_in_use指向首尾元素.

l         脏索引节点的链表.由超级块对象的s_dirty指向首尾元素.

 

    与索引节点对象关联的方法也叫做索引节点操作.(inode operation)

    具体有:create,lookup,link,unlink,symlink,mkdir,rmdir,mknod,rename,readlink

Follow_link,readpage,writepage,bmap,truncate,pemission,smap,updatepage,revalidate.

 

文件对象

    文件对象描述进程如何与一个打开的文件交互的过程,是在文件被打开的时候创建,有一个file结构体组成.文件对象中主要存放的是文件当前操作位置的指针.

 

    深入表12-4 文件对象的域.每个文件对象都包含在下列一个循环双向链表之中:”未使用文件对象链表,”正在使用文件对象链表.不管当前属于哪个链表,其域的f_next,f_pprev分别指向链表的下一个元素/上一个元素的f_next.

 

    每个文件系统也都有其自己的文件操作函数(file operation).例如:llseek,read,write,rcaddir.

 

对目录文件对象的特殊处理

    由于存在几个进程同时改变目录内容的情况,所以就不适合使用显示加锁.所以使用文件对象的f_verison和索引节点对象的i_verison结合,可以确保对目录文件访问的一致性.

 

    具体的操作是这样的:当某目录文件索引节点对象被修改,那么i_version加一,如果一个新文件创建或者文件被修改那么f_version加一.当一个进程访问修改一个目录的时候,那么i_version必然是和f_version相等的.但是如果在该进程两次操作之间并发了另一个进程,那么i_verison必然和本进程的f_verison不等.所以通过对两值的检查,家而言确保目录文件访问的一致性.

    在不等的情况下,系统将自动重新计算该目录的文件指针,并将f_verison=i_verison.

 

目录项对象

    VFS将每个目录看做有若干子目录和文件组成的普通文件.目录项读入内存后,VFS将值转化为基于dentry结构的目录项对象.对象域表见(深入表12-5).进程查找路径名的每个分量,内核都为其创建一个目录项对象,而且将每个分量与其对应的索引节点相联系.

 

    目录项对象在磁盘上没有对应的映像.

 

    每个目录项对象属于以下四种状态之一:

l         空闲状态:不包括有效信息.

l         未使用状态:还没有被内核使用.

l         正使用状态:正在被内核使用.

l         负状态:与目录项相关的索引节点不存在.

 

以下参考第二版

 

目录项高速缓存

    由于构造一个目录项对象需要时间,而且其又要被经常访问,所以从效率考虑,需要使用目录项高速缓存.由以下两种类型的数据结构组成:

l         正在使用,未使用或者负状态的目录项对象集合.

l         散列表,从中查找与给定文件名和目录对应的目录项对象.如干对象不在其中,则返回空值.

 

    未使用的目录项对象放置于最近最少使用的双向链接表中.其放置的顺序是以时间的顺序压栈.所以最近不使用的项在链表首,最少使用的项在链表尾.如干整个高速缓存空间过小,那么就从链表尾开始删除元素.

 

    每个在使用的目录项也被插入在一个双向链表中,而且由相对应的索引节点对象的i_dentry字段指向.目录项对象的d_alias存放链表相邻元素的地址.

 

    散列表是由dentry_hashtable数组实现的.数组中每个元素都是一个指向链接表的指针.

 

    Dcache_lock自旋锁保护目录项高速缓存避免并发问题.

 

    与目录项对象关联的方法称为目录项操作,这些方法由结构体dentry_operation描述.

 

与进程相关的文件

    每一个进程都有它自己当前的工作目录和它自己的根目录.体现为fs_struc的数据结构(深入表12-6).需要注意的是fd字段,表示指向文件对象的指针数组,fd数组的索引就是文件描述符,比如第一个(0)就是标准输入文件,第二个(1)是标准输入等.

 

文件系统类型

特殊文件系统

    特殊文件系统可以操作内核数据结构并实现操作系统的特征.下表列出了最常用的特殊文件系统.

 

文件系统类型注册

    VFS必须对目前已经在内核映像中的所有文件系统进行追踪,这就是通过文件系统注册来实现的.每个注册的文件系统都用类型为file_system_type的对象表示,详细见表(深入12-9).

 

    当系统初始化和文件系统模块被装入的时候,都要调用register_filesystem()函数来注册文件系统,将对应的file_system_type插入到文件系统类型的链接表中.(撤销对应unregister_filesystem()).

 

文件系统的安装

    每个文件系统都有其自己的根目录,也就上其挂载点.如果某文件系统的根目录就是系统的根目录,那么这个文件系统就是根文件系统.

 

    一个文件系统可以被多次安装,但其super block只有一个.记录文件系统安装的信息保存在已安装文件系统描述符的数据结构中.体现为vfsmount类型的数据结构体.具体见表(深入12-11).

 

安装根文件系统

    安装根文件系统分为两个阶段:

1.     内核安装特殊的rootfs文件系统,该文件系统为真实的根文件系统做为初始安装点.

2.     内核在上述安装点上安装一个真正的根目录.

 

    第一阶段是由init_mount_tree()函数完成的.代码上执行过程如下:

    Struct file_system_type root_fs_type

    Root_fs_type.name = “rootfs”

    Root_fs_type.read_super = rootfs_read_super

    Root_fs_type.fs_flags = FS_NOMOUNT

    Register_filesystem(&root_fs_type)

    Root_vfsmnt = do_kern_mount(“rootfs”,”0”,”rootfs”,NULL)

    简要说明一下:Root_fs_type类型变量存放rootfs特殊文件系统的描述符对象.传递给regsiter_filesystem()函数.do_kern_mount()函数安装特殊文件系统并返回安装文件系统的对象地址.root_vfsmnt就表示所安装文件系统树的根.

 

    Do_kern_mount()函数在由init_mount_tree()调用来安装rootfs特殊文件系统的时候,需要调用get_sb_nodev()分配超级块对象.

 

    根文件系统安装的第二个阶段是由mount_root()函数在系统初始化即将结束的时候进行的.以下列举磁盘情况下的流程:

1.     分配缓冲区并用文件系统类型名的链结表来填充.

2.     检查ROOT_EDV是否存在,是否正常工作.

3.     检查超级块对象.(由于此时文件系统正在安装,通常是找不到的).根文件系统一般安装两次,一次做只读,一次读写.

4.     扫描由步骤一建立的文件系统类型名链接表.

5.     分配新安装的文件系统对象,并用ROOT_DEV块设备名,超级块对象地址以及根目录的目录项地址对象初始化.

6.     将新安装的文件系统插入相应的链结表,散列表.

7.     init进程fs_struct表的rootpwd字段作为根目录的目录项对象.

 

安装一般文件系统

    Mount()系统调用运用sys_mount服务routing来安装一个文件系统.sys_mount()函数把相关参数(文件系统路径名,目录名,类型等)拷贝到临时内核缓冲区,然后调用do_mount()函数处理真正的安装操作.安装操作的核心是do_kern_mount()函数.

 

卸载一个文件系统

    Umount()系统调用使用服务routing sys_umount()来卸载一个文件系统.其核心函数是do_umount().

 

路径名查找

    本章主要介绍VFS如何从文件路径名寻找到相应的索引节点.执行这个操作的标准过程就是把路径名拆分成一个个文件名序列,除了最后一个是文件名外,其他都是目录.如果是绝对路径,current->fs->root开始寻找,如果是相对路径,那么从current->fs->pwd开始寻找.

 

    具体操作流程是这样的:首先从和第一个文件名匹配的目录项中获得对应的索引节点,然后从磁盘中读取索引节点的文件,并检查和第二个名字匹配的目录项和对应的索引节点,依此类推.由于存在目录项高速缓存,可以使得这个过程的效率大大提高.路径名的查找是以下面三个函数的顺序进行的

1.     path_init(),初始化nameidata数据结构

2.     path_walk(),负责处理查找操作.

3.     path_release().负责结束后的操作.

 

标准路径名的查找

    就是路径名不包含符号链结时候具体的查找过程.

 

父路径名查找

    当查找操作必须解析的目录包含的是路径名最后一个分量而不是最后一个分量本身.其实就是要寻找最后一个文件名的父路径目录对象.

 

符号链结的查找

    符号链结是一个普通文件,其中存放的是另一个文件名的路径名.为了放置符号链结到本身而导致寻找函数递归调用无限循环的问题,规定了链结嵌套的层数不超过5.

 

VFS系统调用的实现

    本节概述几个系统调用的实现,以及说明VFS的数据结构是如何相互作用的.

 

Open()系统调用

    Open()系统调用的服务例程是sys_open()函数.如果调用成功就返回指向文件对象指针数组分配给新文件的索引的文件描述符.

 

    Sys_open()函数的操作流程:

1.     使用getname()从进程的地址空间读取文件的路径名.

2.     调用get_unused_fd()在文件对象指针数组中寻找一个空的位置放置新的文件描述符.

3.     使用filp_ioen函数

A.     设置访问模式

B.     调用open_namei(),修改访问模式标志以及nameidata数据结构地址.

C.     调用dentry_open()函数.分配新的文件对象,并设置访问模式,同时将文件对象插入文件系统超级块中指向打开文件的链接表等.

D.     返回文件对象的地址.

4.     将文件对象指针数组中对应文件描述符的内容设置为文件对象的地址.

5.     返回文件描述符.

 

Read()write()系统调用

    两个调用都需要三个参数,一个文件描述符,一个内存区地址buf,一个计数的count. read将数据从文件传送到缓冲区,write执行相反的操作.

 

    两者的服务例程分别为sys_read(),sys_write()几乎都执行相同的步骤:

1.     fget()fd获得文件对象所对应文件的地址.并使得计数器file->f_count1.

2.     检查读写操作是否被允许.

3.     调用locks_verify_area()检查访问文件是否有强制锁.

4.     调用file->f_op->read或者file->f_op->write来传送数据.

5.     调用fput()减少计数器的值.

6.     返回实际传送的字节数.

 

Close()系统调用

    Close()调用sys_close()关闭文件描述符fd:

1.     获得current->files->fd[fd]中文件对象的地址

2.     将上述地址的内容置为NULLL.

3.     调用filp_close():调用文件操作的flush,释放任何强制锁,调用fput()释放文件对象.

4.     返回flush方法的出错码.

 

文件加锁

    对于一个文件同时被几个进程访问的并发问题,采用加锁的机制来解决.劝告锁并不知道其他进程被关在文件外面,其要在其他进程的打开函数的配合下才能有效.劝告锁可以对整个文件或者文件的一部分加锁.强制锁要求其他进程的任何文件相关调用都不能违背文件上的强制锁.两个锁都可以使用共享锁和独占写锁,也就是读锁和写锁.

 

Linux文件加锁

    Linux支持所有的文件加锁方式:劝告锁,强制锁,fcntl(),flock(),lockf().fcntl既可以产生强制锁,也可以产生劝告锁.文件系统默认是关闭强制锁的.flock2.4内核前是只有劝告锁的,但在此后,增加了一种只适用于2.4平台的共享模式强制锁.2.4同时还引入了基于flock的强制锁叫租借锁,其特点是进程对文件的拥有有时间的限制.通过fcntlflock产生的锁类型是不同的(FL_POSIX VS FL_LOCK,Lock_MAND(共享),FL_LEASE(租借)),加锁文件看起来也是不一样的.

 

文件锁的数据结构

    File_lock数据结构表示文检索,具体的item见表(深入12-19).所有的file_lock是包含在一个双向循环链接表中的.同一个文件的所有的file_lock结构都在一个链接表中,链表第一个元素有索引节点对象的i_flock指向.file_lock中的fl_next指向链接表的下一个元素.

 

    由于文件上某个锁的存在而进入休眠的进程队列将保存在file_lock中的fl_wait,而一个文件上所有因为锁而休眠的进程将保存在一个双向链表上.链表头和相邻元素由blocked_lostfile_lock->fl_block指向.

 

FL_FLOCK

    FL_FLOCK锁是和一个文件对象相关联.当一个锁被请求或者允许时候,内核就把这个进程在同一个文件的上的锁都替换掉.

   

    flock()系统调用的两个参数分别为:文件描述符fd,指定锁类型参数cmd. CMD可以为LOCK_SH.共享读锁.LOCK_EX,排他写锁.LOCK_UN释放.LOCK_NB不阻塞. 这种锁要用于整个文件.flock()函数将调用flock_lock_file()完成核心过程.

 

FL_POSIX

    PL_POSIX锁是与一个进程和一个索引节点相关联.当进程死亡或者fd关闭的时候,这种锁会被自动释放.而且不会被子进程通过fork继承.

   

    fcntl()使用三个参数,文件fd,锁参数cmd,以及指向flock结构的指针.PL_POSIX不仅不能锁一个文件也能锁单独的字节.要加锁的区域有flock机构指定:l_start:起始偏移量,l_whence:起始位置.cmd参数:F_GETTK,检查是否和已有的PL_POSIX冲突,冲突则用新的.F_SETLK:直接设置,且不阻塞.F_SETLKW:直接设置,阻塞.

 

    fcntl()中的sys_fcntl()系统调用 调用posix_lock_file()实现核心操作.
阅读(484) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~