《Linux原理与结构》课件第11章_第1页
《Linux原理与结构》课件第11章_第2页
《Linux原理与结构》课件第11章_第3页
《Linux原理与结构》课件第11章_第4页
《Linux原理与结构》课件第11章_第5页
已阅读5页,还剩167页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第十一章虚拟文件系统11.1虚拟文件系统管理结构11.2文件系统管理11.3文件管理11.4文件I/O操作11.5文件缓存管理即使提供了虚拟内存、互斥与同步、进程间通信等支持机制,进程仍然无法正常工作,原因是还未为其提供与外界交互的手段,即I/O机制。离开了I/O机制的支持,进程既无法接收外界信息,也无法输出处理结果,就会失去存在的意义。

事实上,计算机系统中除了处理器、内存、中断、时钟等核心硬件资源之外,通常还配置有多种外部设备,如磁盘、光盘等存储设备,网卡等通信设备,键盘、鼠标等输入设备,显示器、打印机等输出设备。如果说处理器、内存等是大脑的话,那么外部设备就是计算机系统的五官和四肢。显然,外部设备管理是操作系统的核心任务之一。在所有的外部设备中,外部存储设备是最重要的一类,操作系统对其进行了一系列的抽象。外存设备上的存储空间被抽象成了逻辑块的数组,用户可以以块为单位对其进行随机访问,因此外存设备又被称为块设备。块设备上存储的信息被抽象成了文件,一个块设备上的所有文件被组织在一个目录结构中,因此单个块设备上的信息管理系统又被称为物理文件系统。不同块设备上的物理文件系统被统一组织起来,形成了单一的虚拟文件系统(VirtualFileSystem,VFS)。块设备驱动程序负责物理块设备操作的实施,块设备管理层负责逻辑块数组的抽象,物理文件系统负责单个块设备中的存储空间与文件的管理,虚拟文件系统负责物理文件系统的管理。进一步地,Linux将系统中所有的外部设备全都抽象成了文件(称为设备特殊文件或设备文件),用普通的文件操作统一了千差万别的设备操作,从而统一了外部设备的管理。因此,虚拟文件系统是I/O系统的总接口,是现代操作系统的核心之一。

虚拟文件系统是由SUN公司首先提出的,最初的设计目标有四个,分别是可同时支持多种类型的物理文件系统;可屏蔽物理文件系统之间的差别,统一物理文件系统的使用;可为在网络上共享文件提供支持;允许用户开发并以模块方式动态加载自己的物理文件系统。11.1虚拟文件系统管理结构经过多年的努力,VFS达到并超过了自己的设计目标,演变成了Unix系列操作系统的标准输入/输出管理系统。事实上,除了管理物理文件系统之外,VFS还管理着系统中的各类外部设备。VFS与物理文件系统和块设备管理程序合作共同完成了块设备的管理,与字符设备管理程序合作完成了字符设备的管理,与网络协议和网络设备管理程序合作完成了网络设备的管理。11.1.1虚拟文件系统框架

如果仅从块设备管理的角度观察,VFS与物理文件系统合作主要完成三项管理工作,其中逻辑块的组织与管理工作主要由物理文件系统负责,文件的组织与管理工作由物理文件系统与VFS共同负责,物理文件系统的管理工作主要由虚拟文件系统负责。块设备管理程序、物理文件系统与VFS之间的关系如图11.1所示。

图11.1VFS与物理文件系统和块设备管理程序间的关系然而,VFS不是真实的文件系统,它仅存在于内存之中,在外存上并没有对应的实体(所以称为虚拟文件系统)。VFS中的实体和管理结构都是在使用过程中动态生成的,会在系统关闭时自动消亡。

事实上,VFS仅是一个管理框架,它定义上下两个层次的接口。物理文件系统通过下层接口被插入到VFS框架中。只要实现了下层接口,VFS就认为它是一个物理文件系统。用户通过VFS的上层接口使用I/O系统,如安装、卸载物理文件系统,组织与读写文件,操作外部设备等。VFS将用户请求的文件或设备操作转交给下层的物理文件系统或设备管理程序,因此VFS又被称为虚拟文件交换机(VirtualFileSwitch),如图11.2所示。图11.2虚拟文件系统框架为了实现对物理文件系统的管理,实现上下层接口之间的转接,VFS建立了一整套数据结构,包括超级块结构super_block、索引节点结构inode、目录项结构dentry等,每一个结构中都包含一到多个操作集。设计物理文件系统的核心工作是实现这些结构中的操作集。11.1.2超级块结构

VFS管理的最重要的实体或对象是物理文件系统。为描述物理文件系统,VFS专门定义了超级块结构super_block,又称为文件系统类。Linux为它的每个活动的物理文件系统都建立了一个超级块实例,就像为每个进程都建立一个task_struct结构一样。超级块结构中记录着物理文件系统的所有管理信息,大致包括如下几类:

(1)底层块设备。除了一些特殊的伪文件系统之外,大部分的物理文件系统都建立在块设备之上。块设备的设备号记录在域s_dev中,逻辑块设备的描述结构(即结构block_device,见12.1.3节)记录在域s_bdev中。

(2)块尺寸。文件系统以块为单位读写底层块设备,每次至少一块。不同物理文件系统可以选用不同的块尺寸,但一旦选定就不可再更改,除非重建该物理文件系统。物理文件系统所选用的块尺寸记录在域s_blocksize中。块尺寸是一个逻辑单位,必须是物理单位(扇区尺寸,512字节)的整倍数,通常与页的尺寸相同,如4096字节。

(3)文件的最大尺寸。理论上说,文件的尺寸可以无限大,只要块设备能够存储它。然而实际上,由于受到管理结构的限制,各个物理文件系统都限制了它的最大文件尺寸。物理文件系统允许存储的最大文件尺寸记录在域s_maxbytes中。

(4)类型。文件系统类型(由结构file_system_type描述)中记录着获取物理文件系统管理信息(即超级块)的方法,每类物理文件系统一个。物理文件系统所属的类型记录在域s_type中。

(5)状态。在域s_flags中记录着物理文件系统的当前状态,如是否已安装就绪、是否为只读安装、是否可被用户使用、是否允许访问其中的块设备特殊文件、是否允许执行其中的程序、是否限制更新文件中的最近存取时间等。另外,域s_dirt是一个脏标志,表示超级块中的信息是否曾被修改过。

(6)根目录。每个物理文件系统都将自己的文件组织成一棵目录树(实际是一个非循环图),树根称为根目录。域s_root指向物理文件系统的根目录。

(7)管理队列。属于同一物理文件系统的所有inode结构被组织在一个队列中,队头为域s_inodes。属于同一物理文件系统的所有file结构被组织在一个队列中,队头为域s_files。

(8)超级块操作集。域s_op指向超级块操作集super_operations的一个实例,其中记录着物理文件系统实现的超级块管理操作(如写出、释放等操作)、inode管理操作(如分配、写出、清理、删除、销毁等操作)、文件系统管理操作(如同步、重装、冻结、解冻等操作)等。

(9)配额管理。配额用于界定一个用户可用的外存空间和inode的上限。域dq_op指向配额操作集dquot_operations,内含配额的分配、获取、写出、释放、销毁等操作。

(10)私有信息。除了上述的公共信息之外,每个物理文件系统还可以定义一个私有的结构,用于记录自己特有的管理信息。域s_fs_info指向物理文件系统的私有结构。

系统中所有的超级块结构被组织在一个全局链表super_blocks中。属于同一类型的所有超级块结构也被组织在一个链表中,表头是文件系统类型结构中的fs_supers。11.1.3索引节点结构

超级块用于描述物理文件系统,物理文件系统所管理的主要实体是文件。文件是按一定形式组织起来的一组信息,包含两方面的内容,一是文件数据,二是元数据(用于描述文件的组织与管理信息)。作为管理者,文件系统并不关心文件数据的具体内容,所关心的是文件的元数据。文件元数据常被组织成文件控制块(FileControlBlock,FCB),Linux称为索引节点(IndexNode),由结构inode定义。VFS的结构inode是对各类文件控制块共有特征的抽象,用于统一描述不同物理文件系统中的文件,也可称之为文件类。VFS将它使用的每一个文件都看成inode类的一个实例。进一步地,VFS将它使用的设备、目录、管道、符号链接等输入/输出实体(简称VFS实体)全都看成虚拟的文件,并用inode类统一描述它们,从而统一了Linux的所有输入/输出实体。

结构inode中包含输入/输出实体的所有属性信息,但不包含文件的名称和正文。事实上有些实体,如设备、管道等,也没有正文。结构inode中主要包含如下几类属性:

(1)所属物理文件系统。由inode描述的每个实体都属于一个物理文件系统,其中的域i_sb指向物理文件系统的超级块结构。

(2)标识符。物理文件系统用无符号长整数i_ino标识自己的实体。i_ino在一个物理文件系统内是唯一的,但不是全局唯一的。i_ino与i_sb合起来才能够唯一地标识一个VFS

实体。

(3)属主。每个VFS实体都属于一个用户,该用户就是这一实体的属主。属主由UID、GID标识,分别记录在inode结构的i_uid和i_gid域中。

(4)模式。实体模式i_mode是一个无符号小整数(16位),其中记录着实体的类型和访问权限等信息,其格式如图11.3所示。

图11.3实体模式域的格式在i_mode域中,最高4位表示实体的类型。目前的实体类型包括命名管道、字符设备、目录、块设备、普通文件、符号链接、Socket等。

第11位是SUID标志,第10位是SGID标志,第9位是SVTX标志。

第6~8位是文件属主的访问权限(读、写、执行),第3~5位是同组用户的访问权限(读、写、执行)、第0~2位是其它用户的访问权限(读、写、执行)。

(5)大小。域i_size、i_blocks和i_bytes中记录的都是实体的大小(如普通文件所占用的外存空间、块设备的容量等),关系是i_size=i_blocks

×

512+i_bytes。

(6)时间。在inode结构中,i_atime是实体最近一次被访问的时间、i_mtime是实体正文最近一次被修改的时间、i_ctime是实体属性最近一次被修改的时间。

(7)状态。域i_state中记录着inode结构的状态,包括I_NEW(正在创建)、I_DIRTY_SYNC(已被改变但不需要同步)、I_DIRTY_DATASYNC(数据部分已被改变)、I_DIRTY_PAGES(有脏的数据页)、I_WILL_FREE(即将被释放)、I_FREEING(正在被释放)、I_CLEAR(inode是干净的)、I_SYNC(正在同步过程中)等。

(8)设备信息。如果inode描述的是字符或块设备,那么它的i_rdev域中记录的是设备号,i_bdev或i_cdev域中记录的是设备的逻辑描述结构。

(9)硬链接信息。一个inode结构可以被加入到多个目录中,从而使实体拥有多个路径名。硬链接计数(域i_nlink)表示该inode在不同目录中出现的次数。

(10)引用计数。域i_count中记录着该inode结构的当前用户数。

(11)操作集。对实体元数据的操作方法记录在操作集inode_operations中。域i_op指向实体自己的inode_operations实例,其中包含数十个操作,如:

文件创建操作create用于在目录中创建一个指定名称的新文件。

查找操作lookup用于获得目录中某指定名称的文件的inode结构。

链接操作link用于为文件建立一个新的硬链接(起一个新的名称)。

删除操作unlink用于删除文件的一个指定名称的硬链接。目录创建操作mkdir用于在目录中创建一个指定名称的子目录。

目录删除操作rmdir用于删除目录中的一个指定名称的子目录。

符号链接操作symlink用于为实体建立一个新的符号链接或软链接。

换名操作rename用于更换一个实体的名称。

设备文件创建操作mknod用于创建一个指定名称的新设备特殊文件。属性获取操作getattr用于获取实体的属性。

属性设置操作setattr用于设置实体的属性。

长度重置操作truncate用于设置文件的长度属性。

对实体正文(如文件、管道、设备中的数据)的操作方法记录在操作集file_operations中。域i_fop指向实体自己的file_operations实例,其中主要包含如下几个操作:

打开操作open用于完成实体打开时的特定初始化工作。

释放操作release用于完成实体关闭时的善后处理工作。

读写头定位操作llseek用于设置文件读写头的位置。文件同步读操作read用于读实体中的数据或从实体中接收数据。

文件同步写操作write用于向实体中写数据或向实体中输出数据。

文件异步读操作aio_read用于读实体中的数据或从实体中接收数据。

文件异步写操作aio_write用于向实体中写数据或向实体中输出数据。

映射操作mmap用于为映射到该文件的虚拟内存区域指定操作集。目录读操作readdir用于读目录文件中的内容。

控制操作ioctl用于向字符或块设备发布控制命令。

同步操作fsync用于将缓存中的文件内容同步到物理文件系统中。

文件锁操作lock用于使能文件的内容锁。

(12)地址空间。地址空间address_space是文件的页缓存。指针i_mapping指向文件当前使用的地址空间,域i_data是一个嵌入在inode中的address_space结构。

(13)私有信息。除了上述的公共信息之外,每个物理文件系统都为它的文件定义了特殊的管理结构,用于记录文件的私有信息,如各文件块的存储位置等。域i_private指向文件的私有管理结构。

VFSinode是输入/输出实体在内存中的表示,只有即将使用的实体才需建立inode结构。但由于inode结构的建立比较费时,所以应该将经常使用的inode结构缓存起来。为了缓存系统中的inode结构,Linux定义了一个名为inode_hashtable的Hash表、一个名为inode_unused的空闲inode队列、一个名为inode_in_use的在用inode队列和一个名为b_dirty的脏inode队列。域i_hash用于将inode结构插入Hash表,域i_list用于将inode结构插入其它三个队列之一。在图11.4中,inode0处于非活动状态(结构有效但已无用户),inode1和inode2都处于活动状态,但inode1是干净的而inode2是脏的(修改后的内容还未被写回块设备)。脏inode应该也是活动的inode。另外,inode1和inode2属于同一个物理文件系统。

图11.4inode结构队列11.1.4目录项结构

虽然inode结构中包含了文件实体的主要描述信息,但其中缺少了实体名称和实体间的组织关系,因此还不够完整。在物理文件系统中,用于描述实体间组织关系的常用手段是目录。一个目录通常就是一张表,其中的一个表项(称为目录项)记录着一个实体名称与一个FCB的对应关系。除最上层的目录(称为根目录)之外,每个目录又都包含在其它目录之中,因而目录之间形成了一种自然的树状或网状组织关系。从根目录开始,按名称逐层搜索各个目录表,可以找到物理文件系统中任意一个实体的FCB结构。将实体名称与FCB分开的好处是可以给一个实体起多个名称,因而可以从多条路径搜索到同一个实体。

为了在内存中描述实体间的组织关系,VFS对各物理文件系统的目录项进行了抽象,定义了虚拟目录项结构dentry,其中的主要内容如下:

(1)实体名称。实体名称就是文件或目录名,记录在域d_name中。

(2)实体描述结构。实体描述结构即实体的inode结构,记录在域d_inode中。域d_name和d_inode描述了实体名称与inode之间的一个对应关系。如果一个实体有多个名称,VFS会为其创建多个dentry结构,它们指向同一个inode结构,并被域d_alias串成一个链表,表头为inode中的i_dentry。

(3)父目录。域d_parent指向父目录项。根目录的父目录就是自己。

(4)目录树。属于一个目录的所有实体的dentry结构被它们的d_child域串成一个队列,队头为父目录dentry结构中的d_subdirs域。各目录项的d_subdirs队列组合起来构成一棵目录树,如图11.5所示。

(5)

Hash表。为了加快dentry的查找速度,除目录树之外,Linux还为dentry结构建立了一个Hash表dentry_hashtable。域d_hash用于将目录项插入到Hash表中。

(6)安装点标志。域d_mounted非0表示该目录上安装了物理文件系统。

(7)操作集。目录项操作集dentry_operations中记录着目录项特有的操作方法,如Hash值计算方法d_hash、名字比较方法d_compare、inode释放方法d_iput、目录项释放方法d_release、目录项删除方法d_delete、目录项生效方法d_revalidate等。

由此可见,inode是对实体本身的抽象,dentry是对实体间组织关系的抽象。VFS为它的每个实体都定义了一个inode结构,同时还会为其定义至少一个dentry结构。与inode结构相似,只有即将使用的实体才需建立dentry结构。由于目录项的建立比较耗时且使用频繁,因而应将已建立的dentry结构缓存起来。Linux用两种方式管理dentry结构的缓存,一是目录树,二是Hash表,如图11.5所示。

图11.5目录项结构之间的关系

常见的计算机系统中通常配有多种块设备,如磁盘、光盘、U盘等,每一种块设备上都可能安装着物理文件系统,如EXT3、NTFS、FAT、ISO9660等。出于某些特殊的目的,Linux还会建立不需要块设备的虚文件系统,11.2文件系统管理如sysfs、proc、pipefs、mqueue、shm等,因而运行着的Linux系统中常会包含多种物理文件系统,每一种物理文件系统都有自己独特的文件组织方法与操作方法。为了统一管理各种不同类型的物理文件系统,屏蔽它们之间的差别,VFS提供了一系列的文件系统管理手段,如物理文件系统的注册、安装、重装、卸载等。按照VFS的约定,只有注册过的物理文件系统才能够安装,只有安装后的物理文件系统才能够使用。11.2.1文件系统注册

文件系统管理的首要工作是为即将使用的物理文件系统建立超级块结构,大致可分为两步,一是申请一块能容纳super_block结构的物理内存,二是填写其中的管理信息,难点在第二步。由于不同类型的物理文件系统有不同的信息组织方式,因而VFS不可能理解所有的物理文件系统,无法直接读取其管理信息,更何况有些物理文件系统(如sysfs、proc等)并非建立在块设备之上,也无处读取其管理信息。能够为VFS提供管理信息的只有物理文件系统自己。因而,VFS要求即将使用的每种物理文件系统都必须注册一个结构file_system_type,在其中声明一个获取管理信息、填写超级块结构的方法。

结构file_system_type又称为物理文件系统类型,其中的主要内容如下:

(1)名称name是该文件系统的类型名,如“ext3”、“vfat”、“ntfs”等。

(2)标志fs_flags是文件系统的特殊要求,如是否需要块设备等。

(3)获取操作get_sb用于收集物理文件系统的管理信息并将它们填写到超级块结构中,从而为物理文件系统创建一个super_block结构的实例。

(4)清理操作kill_sb用于物理文件系统卸载时的善后处理。

(5)超级块队列fs_supers中排列着属于该类型的所有超级块结构。

系统中已注册的file_system_type结构被其中的next指针串成一个单向链表,表头为file_systems,如图11.6所示。

图11.6文件系统类型注册表系统将要使用的每类物理文件系统都要在file_systems中注册一个file_system_type结构,但不允许重复注册。没有注册的文件系统无法安装,因而也无法使用。如果物理文件系统的实现代码被编译在内核中,注册工作已在系统初始化时完成;如果物理文件系统的实现代码未编译在内核中,注册工作将在安装(模块插入)时进行。11.2.2文件系统安装

注册操作仅仅向VFS声明了一个file_system_type结构,并未建立起物理文件系统的管理结构,因而注册后的物理文件系统还不能使用。为物理文件系统建立管理结构的工作由安装程序负责。在被安装之后,物理文件系统的超级块结构super_block被建立,其目录树也被嫁接在VFS的某个目录(称为安装点)之上,形成了统一的全局视图,如图11.7所示。只有在安装工作完成之后,物理文件系统才能够使用。图11.7文件系统的目录树嫁接在嫁接之前,每个物理文件系统都有自己独立的目录树,多棵目录树放在一起形成的是目录树森林。将目录树森林嫁接在一起的方法是用各目录树的根去覆盖它的安装点目录。在图11.7中,块设备dsk0和dsk1上分别安装着不同的物理文件系统,两个物理文件系统的目录树相互独立,D3是dsk0的一个目录。当把dsk1中的物理文件系统安装在目录D3上之后,两棵目录树就拼接在了一起,dsk1的根目录覆盖了dsk0的D3目录,或者说目录/D1/D3变成了dsk1的文件系统的根,文件f6的路径名变成了/D1/D3/f6。如果文件系统B被安装在文件系统A的某个目录之上,那么B是A的子文件系统,A是B的父文件系统。位于最顶层的文件系统是所有其它文件系统的祖先,称为根文件系统。显然,根文件系统必须预先建立起来。在早期的Linux版本中,通常将来自根块设备的文件系统预装成根文件系统,并通过dentry中的指针在子文件系统的根目录与父文件系统的安装点目录之间建立连接关系。随着Linux的发展,人们发现这种安装方法存在许多问题,如难以更换根文件系统、一个文件系统仅能安装一次而且仅能安装在一个点上、一个安装点上仅能安装一个文件系统、每个进程都可以看到整棵目录树等等。为解决上述问题,新版本的Linux采取了如下改进措施:

(1)在系统初始化时,预先建立了一个伪文件系统用做整个系统的根文件系统,其类型为rootfs。伪根文件系统是一种内存文件系统(Ramfs),仅有一个空的根目录,用于安装实际的根文件系统。

(2)在子文件系统的根目录与父文件系统的安装点目录之间不再建立直接的连接关系,各文件系统通过安装点结构vfsmount间接地连接起来。

(3)分割文件系统名字空间,允许为每个名字空间建立一棵全局目录树,使进程仅能看到自己名字空间中的目录树,而不再是全系统的目录树。

安装点结构vfsmount描述文件系统之间的安装关系,每个已安装的文件系统都至少有一个安装点结构。若一个文件系统被安装多次,Linux会为其建立多个vfsmount结构。在安装点vfsmount中,mnt_sb指向子文件系统的超级块、mnt_root指向子文件系统的根目录,mnt_parent指向父文件系统的安装点结构、mnt_mountpoint指向位于父文件系统中的安装点目录,这四个域合起来表明子文件系统mnt_sb的根目录mnt_root被嫁接在父文件系统mnt_parent的目录mnt_mountpoint上。子文件系统的特殊安装需求记录在域mnt_flags中。一个文件系统上可以安装多个子文件系统,子文件系统上还可以再安装子文件系统,因而系统中的vfsmount自然地形成了一种树形组织结构。域mnt_child将各兄弟文件系统的vfsmount串成一个队列,队头是父vfsmount结构中的域mnt_mounts。从根文件系统的vfsmount开始,搜索vfsmount树,可以找到已安装的任意一个子文件系统,但速度较慢。为了加快文件系统的查找速度,Linux另创建了一个vfsmount结构的Hash表mount_hashtable,其Hash值由父文件系统的vfsmount和安装点目录的dentry算出。给出一个安装点目录,查mount_hashtable,可以找到安装在其上的第一层子文件系统。安装点结构vfsmount中的域mnt_ns指向文件系统所属的名字空间mnt_namespace。安装在同一名字空间中的所有文件系统的vfsmount结构被它们的mnt_list域串成一个链表,表头为mnt_namespace结构中的list。文件系统安装点结构的组织形式与物理文件系统的实际安装方式一一对应,如图11.8所示。

在图11.8中,文件系统1安装在文件系统0的A目录上,文件系统2安装在文件系统0的B目录上。B目录的内容被文件系统2的根目录覆盖,其中的文件f2和目录C被隐藏,在B目录中看到的是文件系统2的F和G子目录。文件系统3安装在文件系统1的D目录和文件系统2的根目录上,覆盖掉了D目录和整个文件系统2的内容,在D和B目录中看到的都是文件系统3的H和I子目录。文件系统1和2是文件系统0的子文件系统,文件系统3是文件系统1和2的子文件系统,文件系统1和2是兄弟文件系统。

图11.8文件系统安装结构物理文件系统只能由超级用户安装,安装时需提供多个参数,如物理文件系统所在块设备的特殊文件名dev_name、安装点目录的路径名dir_name、文件系统类型名type、特殊的安装要求flags等。文件系统的安装过程大致如下:

(1)解析安装点目录的路径名,获得它的目录项结构dentry和所在文件系统的安装点结构vfsmount。新文件系统的安装点目录必须在已安装的目录树上,且必须是一个目录。如果所给的安装点目录上已安装了其它文件系统,那么此处获得的vfsmount描述的应该是在该目录上的最新一次安装,或者说是覆盖在安装点之上的最上一层文件系统,dentry应该是覆盖在安装点目录上的最上一层文件系统的根目录。如果安装点目录是图11.8中的目录/B,那么解析所得的安装点应该是文件系统3的根目录。

(2)搜索队列file_systems队列,找到名为type的file_system_type结构。如果名为type的文件系统类型还未注册,则请求模块加载程序将该类型的物理文件系统实现模块插入到内核中,并注册其文件系统类型。

(3)申请一个空的安装点结构vfsmount。

(4)执行file_system_type结构中的get_sb操作,为新安装的物理文件系统创建超级块结构super_block,并为其根目录创建dentry结构和inode结构。

(5)为新建的vfsmount结构建立社会关系,如图11.9所示。图11.9新安装结构的社会关系特别地,类型为rootfs的根文件系统是在系统初始化时由函数init_mount_tree()安装的,是系统中的第一个文件系统。根文件系统是一种Ramfs,其管理信息都是动态生成的。由于根文件系统不需要嫁接在其它文件系统之上,因而其安装过程是从第(2)步开始的,其中的参数type是“rootfs”,与之对应的file_system_type结构中的get_sb操作是rootfs_get_sb(),完成的工作如下:

(1)创建一个超级块结构,对其作如下设置:

标志s_flags中包含MS_NOUSER,表示该文件系统不为用户所见;设备号s_dev是动态生成的,主设备号为0,域s_bdev为空;

块尺寸s_blocksize是4096字节,文件的最大尺寸s_maxbytes是文件页缓存的最大尺寸(在32位机器上为243-1,在64位机器上为263-1);

超级块操作集s_op是ramfs_ops。

(2)创建一个根inode结构,对其作如下设置:

i_ino为0;

inode操作集是ramfs_dir_inode_operations;

文件操作集是simple_dir_operations;

地址空间i_mapping中的操作集a_ops是ramfs_aops。

(3)创建一个根目录项结构dentry,对其作如下设置:

名称为“/”,操作集d_op为空,d_parent指向自己;

标志d_flags中包含DCACHE_UNHASHED,表示该目录项不在Hash表dentry_hashtable中。

在根文件系统的安装点结构vfsmount中,mnt_parent指向自身,mnt_mountpoint指向自己的根目录项,mnt_child等队列都是空的。值得一提的是,根文件系统的vfsmount结构并未插入到Hash表mount_hashtable中。事实上,由于根文件系统是整个名字空间的根(mnt_namespace结构中的root指向根文件系统的vfsmount),未嫁接在任何文件系统之上,也没有查找其vfsmount结构的必要。

在安装点结构的社会关系中,Hash表mount_hashtable的主要作用是方便vfsmount的查找,以便快速确定嫁接在某一安装点上的子文件系统。在早期的Linux中,安装点与子文件系统的根目录是绑定在一起的,从安装点目录可以直接找到子文件系统的根目录,如图11.7所示。这种绑定安装方式方便了路径名的解析,但却降低了安装的灵活性,如无法将一个文件系统同时嫁接在多个安装点上。Hash表mount_hashtable的引入解除了安装点与根目录之间的绑定关系,极大地提高了安装的灵活性,如:

(1)为一个物理文件系统建立多个vfsmount结构之后,可将其同时嫁接在多个安装点上,如图11.8中的文件系统3就被同时嫁接在两个安装点上。

(2)修改安装结构和它所处的社会关系,即可将已安装的文件系统从一个安装点移到另一个安装点。

(3)新建一个vfsmount结构,即可将已安装文件系统的某个子目录树嫁接到一个新的安装点上,使其可在多个位置同时被访问到。

(4)修改安装结构中的标志mnt_flags即可改变文件系统的安装方式(称为重装remount),如由只读安装到读写安装等。

(5)能够提供满足特殊需求的新式安装,如共享安装、主从安装等。11.2.3文件系统卸载

除根文件系统rootfs之外,其它文件系统都是动态安装的,也可以被动态卸载。在从计算机系统上将可移动介质(如U盘)拔下之前应该先卸载其上的文件系统。在卸载文件系统时,用户需提供文件系统的安装点目录和特殊的卸载需求。如果用户提供的是块设备特殊文件名,需先查出其安装点目录。如果一个块设备上的文件系统被同时安装在多个目录上,用块设备特殊文件名卸载文件系统会引起歧义。

正在使用的文件系统(如其上有未关闭的文件、有作为进程pwd的目录、有未卸载的子文件系统等)称为忙文件系统,不能卸载。

文件系统卸载操作umount大致完成如下几件工作:

(1)解析安装点目录的路径名,做必要的合法性检查。

(2)如果用户要强行卸载文件系统且超级块操作集中提供了umount_begin操作,则执行该操作。

(3)如果要卸载的文件系统是当前进程的根,则将其改装成只读文件系统。

(4)断开文件系统的安装点结构vfsmount的社会关系,并将其释放。(5)如果文件系统已无其它安装位置,则将其从系统中清除,包括执行文件系统类型中的kill_sb操作、释放它的超级块结构等。

卸载过程中的许多实质性、事务性的工作都在kill_sb操作中完成,如将对该文件系统的所有修改全部写回到底层块设备中(称为文件系统同步)、释放属于该文件系统的所有目录项和inode、执行超级块操作集中的put_super操作、关闭底层块设备等。

VFS的文件系统管理子系统提供的是对全局目录树的粗粒度管理,如其中的安装操作用于将新的目录子树拼接在全局目录树上,卸载操作用于从全局目录树中删除不再使用的目录子树,所管理的单位是子树,无法对单个节点(如文件、目录等)实施管理。负责全局目录树细粒度管理的子系统称为文件管理子系统,其主要工作包括:11.3文件管理

(1)与物理文件系统合作,解析节点的路径名,获得文件或目录的物理描述信息,从中抽取出VFS关心的元数据,建立inode和dentry结构。

(2)将最近使用的文件与目录的路径信息缓存起来,形成统一的目录项缓存,以加快路径名的解析速度,并维护缓存信息与物理文件系统的一致性。

(3)与物理文件系统合作,向用户提供文件与目录的创建、删除、移动等服务。

VFS通过全局目录树来组织、管理系统中的文件,不管它们来自哪个物理文件系统。然而作为最高一级的管理机构,VFS并不管理文件的实现细节,如文件在块设备中的存储位置等。事实上,文件的真正管理者仍然是物理文件系统,VFS的作用仅仅是屏蔽各类物理文件系统的管理差异,为用户提供统一的文件管理接口。11.3.1路径名解析

VFS实现文件管理的主要依据是全局目录树。在全局目录树中,任何一个节点,不管它属于哪个物理文件系统,都有一个路径名。从根目录到任一特定节点的路径称为绝对路径,将绝对路径上的所有目录名串连起来(用‘/’隔开)就构成了节点的绝对路径名。从当前工作目录到特定节点的路径称为相对路径,将相对路径上所有的目录名串连起来就构成了节点的相对路径名。绝对路径名和相对路径名都可用于标识文件和目录。

当要访问一个文件或目录时,进程可以提供绝对路径名,也可以提供相对路径名。对一个进程来说,绝对路径名是相对于其主目录(home目录)的路径名,相对路径名是相对于其当前工作目录的路径名。在进程的管理结构task_struct中,fs域总是指向一个fs_struct结构,其中的root是进程的主目录,pwd是进程的当前工作目录。在图11.10中,B是进程的主目录,H是进程的当前工作目录。虽然全局目录树很大,但进程所能看到的仅有其中的一部分,即以B为根的目录子树。在进程运行过程中,它的主目录和当前工作目录都可以改变,但新目录必须在全局目录树中。系统调用chroot用于改变进程的主目录,chdir用于改变进程的当前工作目录。

图11.10进程的主目录和当前工作目录在访问一个文件或目录之前,首要的任务是找到路径名所标识的实体,并为其建立inode和dentry结构,这一过程称为路径名解析。Linux用path结构描述实体的路径信息,其中包含一个vfsmount和一个dentry结构,定义如下:

structpath{

structvfsmount *mnt; //实体所在文件系统的安装点结构

structdentry *dentry; //实体本身的目录项结构

};

Linux用path_lookup()系列的函数实现路径名解析。要解析的路径名是一个由‘/’分隔的字符串,如“/./usr/bin/../local/././bin//emacs”(等价于“/usr/local/bin/emacs”)。除最后一个子串之外,其余各子串都是路径上的目录名。最后一个子串可能是文件名,也可能是目录名。路径名中的一个子串称为一个子路径名。

由于进程给出的路径名都是相对路径名,因而在解析之前需要先确定此次解析的参考点或出发点。一般情况下,如果路径名的首字符是‘/’,参考点应该是进程的主目录;如果路径名的首字符不是‘/’,参考点应该是进程的当前工作目录。在新版本中,Linux还允许进程自己指定解析的参考点。所谓路径名解析实际就是从参考点出发,依照各子路径名的指示,沿着全局目录树逐步前行,直到最后一个子路径名所指示的节点。所以路径名的解析过程实际就是对各子路径名进行顺序分析的过程。

为记录路径名解析的结果,需定义一个path结构,将其初值设为参考点目录。此后,每解析一个子路径名,就对path结构做一次调整,将已解析出的中间实体记录在其中。在解析过程中,path总是指向当前目录;在解析完成后,path指向解析的结果(整个路径名所标识的实体)。设name是下一个要解析的子路径名,解析的过程如下:

(1)检查当前目录的权限。当前进程必须拥有当前目录的执行权限。

(2)在当前目录附近找名为name的dentry。name的值可能是下列三种之一:

①“.”表示当前目录,不需要前行,也不需要调整path结构。②“..”表示当前目录的父目录,需调整path结构,让它指向父目录。一般情况下,dentry结构中的d_parent就是其父目录,但也有特例:

●如果当前目录是当前进程的主目录,那么其父目录仍是自己。

●如果当前目录是某个子文件系统的根目录,则需向上跨越安装点。

●如果父目录是一个安装点目录,则需向下跨越安装点。③其余格式的子路径名表示当前目录中的一个子目录或文件。由于该子目录或文件可能已被解析过,其dernty结构可能已在目录项缓存中,因而应先根据name的Hash值查目录项的Hash表dentry_hashtable,找父目录为path.dentry、名字为name的dentry结构,结果有二:

●在缓存中。执行目录项操作集中的d_revalidate操作,以确保该dentry结构的有效性,而后调整path结构。

●不在缓存中。创建一个新的dentry结构,执行当前目录inode操作集中的lookup操作,请求物理文件系统在当前目录中查找名为name的实体、读入其管理信息并创建inode结构,而后调整path结构。

(3)跨越安装点。如果找到的dentry是一个安装点目录,则需向下跨越安装点。

(4)跨越符号链接。如果找到的dentry是一个符号链接,则需跨越该符号链接。

(5)让name指向下一个子路径名。如果name不空,则从(1)开始解析下一段子路径名。如果name为空,path中记录的就是解析结果。

判断目录是否为安装点的依据是其dentry结构中的d_mounted标志。若d_mounted的值大于0,说明该目录上安装有文件系统,其内容已被覆盖,不能作为路径中的一个节点,也不应该在其上停留,而应向下跨越该安装点目录,走到安装在其上的文件系统的根目录上。根据已获得的安装点目录的path结构,查Hash表mount_hashtable,可以找到安装在其上的文件系统及其根目录,path应指向该根目录。由于新找到的根目录仍然可能是安装点,因而这种向下跨越可能需要多次,如图11.11所示。

图11.11安装点跨越在图11.11中,在目录B上安装着文件系统2,在目录C上安装着文件系统3。当解析A目录下的B子目录时,解析程序发现B是安装点,因而需查mount_hashtable获得安装在B上的子文件系统的根C。由于C也是安装点,因而需再查mount_hashtable获得安装在C上的子文件系统的根D。D才是对A目录下B子目录的解析结果。

当需要查找当前目录的父目录时,有可能需要向上跨越安装点,原因是当前目录可能为某个子文件系统的根目录。由于根目录项的d_parent总是指向自身,无法通过它找到其父目录,只能向上跨越安装点。若path是已解析出的当前目录,且该目录是某子文件系统的根,则path.mnt->mnt_parent指向父安装点结构,path.mnt->mnt_mountpoint指向安装点目录。由于新找到的安装点目录仍然可能是某子文件系统的根目录,因而这种向上跨越有可能需要多次。向上跨越之后,path应指向最底层安装点目录的父目录。在图11.11中,目录D的父目录是A,它跨越了C和B。

符号链接是一种特殊文件,其内容是另一个文件或目录的路径名,因而更像是一个指针。如果要解析的路径名中包含符号链接,则整个路径名的解析过程应被分成三步:

(1)解析符号链接之前的路径名,让path指向符号链接自身。

(2)跨越符号链接,过程是先读出符号链接文件的内容,再从头开始解析其中的路径名,让path指向目标实体,如图11.12所示。

(3)解析符号链接之后的路径名,让path指向最终的实体。

图11.12符号链接跨越符号链接实体的inode操作集中肯定包含一个follow_link操作,用于读出其中的路径名。如果符号链接的目标实体仍然是符号链接,则需要再次跨越。因此对符号链接的跨越是一个递归的过程,Linux限制了递归的深度,如8层。

在跨越符号链接时,path结构被重新初始化,其中的前期解析结果被丢弃,因而符号链接只能向前跨越,不能回溯。如路径名“/B/C/..”的解析结果是目录“F”而不是“B”。

若路径名的最终解析结果是符号链接,则可跨越也可不跨越,由参数声明。如果在目录项缓存中找不到需要的dentry结构,说明该目录项在近期内未被访问过,因而需要为其新建dentry和inode结构。当然,新建的dentry结构要被插入到目录项缓存中,包括Hash表dentry_hashtable和目录项树,如图11.5所示;新建的inode结构要被插入到inode缓存(Hash表inode_hashtable)中。新目录项及其inode结构中的信息来源于物理文件系统。通过父目录的inode操作集中的lookup操作可请求物理文件系统从该父目录中获取指定名称的物理实体,并获取其管理信息。如果要解析的实体也不在物理文件系统之中,说明该实体还未被创建,其管理结构还不存在,那么新目录项的d_inode域应该为空,但新目录项仍要被插入到目录项缓存中。由路径名的解析过程还可以看出,进程无法向上跨越自己的主目录。因而,只要给不同的进程设定不同的主目录,就可以使它们仅看到自己的目录子树,从而区分它们的文件系统名字空间,避免进程之间的相互干扰。

当不再需要某个dentry或inode结构时,可通过函数dput()或iput()将其释放。释放操作递减dentry或inde结构上的引用计数,只有当引用计数被减到0时才真正将它们销毁。当然,在销毁之前需要将结构中的内容同步到物理文件系统中。值得注意的是,销毁dentry或inode结构仅仅意味着内核不再缓存与之对应的实体信息,并非销毁物理实体本身。11.3.2文件管理操作

以全局目录树和路径名解析为基础,VFS提供了多种文件管理服务,如子目录的创建与删除、硬链接的创建与删除、文件与符号链接的创建、实体的移动、实体属性的设置等。Linux用户可以利用这些服务来组织、管理自己的VFS实体,包括目录、普通文件、设备特殊文件、符号链接、命名管道等。VFS按大致相同的方式实现文件管理服务,其操作过程大致分为三步,一是解析路径名以获得实体所在父目录及实体本身的dentry和inode结构,并进行权限检查;二是执行父目录的inode操作集中的相应操作,请求物理文件系统完成实体的物理管理操作;三是调整相关实体的dentry结构,以便在目录项缓存和全局目录树中反映实体的变化。文件管理操作的流程如图11.13所示。

c

图11.13文件管理操作流程

(1)硬链接创建link或linkat。创建一个硬链接就是为实体起一个别名。可以为一个实体创建多个硬链接,它们可以位于不同的目录下,但必须在同一个物理文件系统中。硬链接创建操作需要两个路径名,其中老路径名标识目标实体,必须存在且不能是目录,新路径名是给实体起的新名称。VFS为新链接创建一个新的目录项,将其插入目录项缓存中,并执行其父目录的inode操作集中的link操作,以请求物理文件系统完成硬链接的物理创建。如果老路径名所标识的是一个符号链接,那么新硬链接所指向的是符号链接本身而不是符号链接的目标实体。

(2)硬链接删除unlink或unlinkat。删除一个硬链接就是删除实体的一个路径名。工作有二,一是释放实体的dentry结构;二是执行父目录的inode操作集中的unlink操作,请求物理文件系统删除目录项,并递减实体的硬链接计数i_nlink。如果实体的i_nlink已被减到0,说明实体的最后一个访问路径已被删除,应释放它的inode结构。如果实体的inode结构已没有用户,应执行超级块操作集中的delete_inode操作,请求物理文件系统释放它所占用的外存空间和管理结构,以完成实体的物理删除。如果路径名所标识的是一个符号链接,该操作仅删除符号链接本身而不影响链接的目标实体。

(3)子目录创建mkdir或mkdirat。Linux用户可以创建新的子目录来更好地组织自己的文件。创建子目录操作需要两个参数,一是新子目录的路径名,二是新子目录的访问权限。真正的创建操作由父目录的inode操作集中的mkdir完成,该操作请求物理文件系统完成子目录的物理创建。新子目录的dentry应出现在目录树中。

(4)子目录删除rmdir。要删除的子目录应该是空的,而且不能是安装点,也不能是进程的主目录或当前工作目录。父目录的inode操作集中的rmdir操作会请求物理文件系统完成子目录的物理删除。被删除之后,子目录的dentry和inode结构也要释放。

(5)文件创建creat。要创建一个新文件至少需要两个参数,一是文件的路径名,二是文件的访问权限。父目录的inode操作集中的create操作会请求物理文件系统完成新文件的物理创建。新文件的dentry应出现在目录树中。

(6)特殊文件创建mknod或mknodat。特殊文件包括字符设备特殊文件、块设备特殊文件、命名管道、Socket等,其作用是借助文件的描述与组织方式来管理这些输入/输出实体,并非为了在其中存储数据。在创建设备特殊文件时需要提供路径名、访问权限和设备号。父目录的inode操作集中的mknod操作会请求物理文件系统完成新文件的物理创建。新文件的dentry应出现在目录树。

(7)符号链接创建symlink或symlinkat。符号链接是一种特殊文件,其正文中保存的不是数据而是全局目录树中另一个实体的路径名。符号链接所指实体可以不存在(称为虚悬链接)。创建符号链接就是创建一个符号链接类型的新文件。父目录的inode操作集中的symlink操作会请求物理文件系统完成符号链接文件的物理创建。

符号链接的内容可以通过readlink操作读出,符号链接的删除由unlink操作完成。

(8)实体移动rename或renameat。移动实体就是将文件系统中的某个指定实体从当前位置移动到新的位置,或者说改变实体的路径名。实体的新老位置必须在同一个物理文件系统中。如果老路径名是目录,那么新路径名要么不存在,要么必须是空目录。如果新路径名已存在,它将被覆盖。如果老路径名是符号链接,那么移动的是符号链接本身而不是它所指的实体。移动操作仅修改实体的目录项,不改变实体本身。移动工作分三步进行,一是执行老路径中父目录的inode操作集中的rename操作,请求物理文件系统删除老目录项、添加新目录项,完成实体的物理移动;二是更换老目录项的名称,并按新名称和新位置将其重新插入到目录项缓存和目录项树中;三是释放新目录项(可能会将其删除)。

(9)实体属性设置。文件系统实体的属性包括属主(uid、gid)、访问权限(读、写、执行)等。当实体被创建时,其uid被设为进程的fsuid,gid被设为进程的fsgid或父目录的gid,访问权限由创建者提供。在实体被创建出来之后,其属性还可以改变。Linux提供了多个系统调用用于设置实体的属性,其中chown类操作用于设置实体的属主,chmod类操作用于设置实体的访问权限。当然,设置属性操作也需要一定的权限,通常只能由属主或超级用户执行。如果父目录的inode操作集中提供了setattr操作,则由该操作完成属性的物理设置;否则,Linux仅修改实体inode结构中的属性并将其标记为脏的,实体物理属性的改变要等到inode被同步时才能完成。

管理文件的目的是为了更方便、更高效地使用文件。从用户的角度看,文件的内容千差万别,其使用方式也各种各样。但从VFS的角度看,对文件的使用方式只要两种,即向文件中写数据和从文件中读数据。因此,VFS的核心任务是提供文件I/O服务,以便更加高效地读写文件中的数据。可以说,VFS的其它子系统,如文件系统管理、文件管理等,都是为文件I/O操作服务的。11.4文件I/O操作要读写文件中的数据至少需要知道文件的路径名、数据在文件中的开始位置、数据在内存中的开始位置、数据长度等,所以每次I/O操作都需要路径名解析和权限检查。由于经常需要反复读写同一个文件,因而重复的路径名解析和权限检查会严重影响文件I/O操作的性能。为了减少路径名解析的次数,VFS在读写操作之外又提供了文件打开和关闭操作,由打开操作专门负责文件路径名的解析和权限检查。11.4.1文件描述符表

打开与关闭操作的核心工作是维护进程的文件描述符表。进程对文件的所有读写操作都必须经过其文件描述符表。

有了打开和关闭操作之后,文件I/O操作一般分三步完成,即打开文件、反复读写文件、关闭文件。在对文件进行读写操作之前,进程通过打开操作声明自己要操作的文件(路径名)及要操作的方式。VFS在收到进程的打开请求之后,解析文件的路径名,找到文件的inode结构,对其进行权限检查。一旦通过权限检查,VFS就为进程创建一个file结构,在其中记录此次路径名解析的结果,并将其插入到进程的文件描述符表中。file结构在文件描述符表中的索引称为文件描述符(filedescriptor)。此后,当需要读写已打开的文件时,进程只需在读写操作中提供文件的描述符即可,不需要再提供文件的路径名。VFS根据文件描述符查进程的文件描述符表,即可找到与之对应的file结构,利用file结构即可完成进程请求的所有文件I/O操作。在完成文件I/O操作之后,进程再通过关闭操作释放与之对应的file结构。因此,增加了打开与关闭操作之后,不管进程对文件进行多少次读写操作,系统仅需解析一次路径名。由于文件描述符表的存在,繁琐的路径名解析工作变成了简单的查表工作,极大地提高了文件I/O操作的性能。

由于文件读写操作都是由进程发起的,而不同的进程可能以不同的方式读写不同的文件,因而Linux为每个进程都维护了一张文件描述符表。在进程的task_struct结构中,有一个指向files_struct结构的指针(见7.1节),其中的主要内容是一个file结构的指针数组,也就是进程的文件描述符表。换句话说,进程的文件描述符表实际就是一个file结构的指针数组,一个文件描述符就代表着一个file结构。一个file结构描述一个进程对一个文件的一种I/O操作方式,称为打开文件对象。显然,file结构是VFS用于实现文件I/O操作的核心。在Linux的发展过程中,file结构的定义发生了一些变化,但其主要内容被保留了下来,大致有以下几个:

(1)文件路径f_path。VFS用结构path描述文件的路径,其中的域dentry指向文件的目录项,目录项中的d_inode指向文件的inode。因此,通过f_path可找到目标文件的所有描述信息,或者说f_path就是文件路径名的解析结果。

(2)读写头位置f_pos。文件的读写方式有两种,一是顺序,二是随机。缺省情况下,Linux按顺序方式读写文件,下一次读写操作的开始位置记录在f_pos中。每一次成功的I/O操作之后,f_pos的值都会被调整。由于f_pos的存在,用户省掉了位置记录工作,并可在读写操作中少提供一个位置参数。

(3)文件操作模式f_mode。记录进程在打开时申请并经VFS批准的文件操作方式,如只读、只写、既读又写等。进程对文件的I/O操作方式必须遵循该模式的约定。

(4)文件操作集f_op。文件操作集是一个file_operations结构(见11.1.3),其中记录着各种文件操作方法,如open、release、llseek、read、write、mmap等。文件被打开之后,对它的所有操作都由该操作集中的对应方法实现。

(5)地址空间f_mapping。地址空间由结构address_space描述,实际是一个文件页的缓存,用于暂存来自文件或即将写入文件的数据页,见11.5。

(6)引用计数f_count。引用计数用于记录file结构的当前用户数。当f_count为0时,file结构应该被释放或回收。

进程每打开一个文件,VFS都会为其创建一个file结构。同一进程多次打开同一个文件会创建多个file结构,不同进程打开同一个文件也会创建不同的file结构。为一个进程创建的所有file结构都记录在它的files_struct结构中。在早期的版本中,Linux在files_struct结构中为每个进程预建了一个一页大小的文件描述符表,可在其中记录1024个file结构。通常情况下,进程的文件描述符表都会有一些浪费,但当进程同时打开太多文件时,其描述符表又不够用。新版本的Linux改进了文件描述符表的管理方式,专门定义了一个fdtable结构来描述进程的描述符表。缺省情况下,文件描述符表的大小为32或64。在运行过程中,VFS会根据需要为进程更换fdtable结构,从而调整其文件描述符表的大小。结构fdtable和files_struct的定义如下:

structfdtable{

unsignedint max_fds; //文件描述符表的大小

structfile **fd; //文件描述符表

fd_set *close_on_exec; //需要在加载时关闭的文件描述符

fd_set *open_fds; //各文件描述符的状态

structrcu_head rcu; //RCU回调节点

structfdtable *next;

};

structfiles_struct{

atomic_t count; //引用计数

structfdtable *fdt; //当前使用的fdtable

structfdtable fdtab; //缺省的fdtable

spinlock_t file_lock; //保护锁

int next_fd; //下一个可用的文件描述符

structembedded_fd_set close_on_exec_init; //缺省的close_on_exec

structembedded_fd_set open_fds_init; //缺省的open_fds

structfile *fd_array[NR_OPEN_DEFAULT];

};

在files_struct中,fd_array是缺省的文件描述符表,大小为NR_OPEN_DEFAULT(一个长整数中的位数,如32或64);类型embedded_fd_set和fd_set都是位图,其中的一位描述一个文件描述符的使用情况,0表示空闲。

只有第0号进程(即init_struct)的files_struct结构是静态建立的,其余各进程的files_struct结构都是从创建者进程中复制的。第0号进程的files_struct结构定义如下:

structfiles_struct init_files={

.count =ATOMIC_INIT(1),

.fdt =&init_files.fdtab,

.fdtab ={

.max_fds =NR_OPEN_DEFAULT,

.fd =&init_files.fd_array[0],

.close_on_exec =(fd_set*)&init_files.close_on_exec_init,

.open_fds =(fd_set*)&init_files.open_fds_init,

.rcu =RCU_HEAD_INIT,

},

.file_lock =_

_SPIN_LOCK_UNLOCKED(init_task.file_lock),

};

由此可见,第0号进程使用的是缺省的fdtable结构、缺省的文件描述符表fd_array和缺省的位图close_on_exec_init与open_fds_init。在创建之初,新进程要么与创建者进程共用同一个files_struct结构,要么从创建者进程中复制一个files_struct结构。值得注意的是,所复制的内容不包括file结构。因而在复制之后,创建者进程的各file结构会同时出现在新老进程的文件描述符表中,两个进程用同一个描述符所访问的file结构是相同的,它们对文件的操作会相互影响(如读写头的位置等)。复制完成之后,不管是创建者进程还是被创建者进程,新打开的文件都会使用独立的file结构,不会自动与其它进程共用。缺省情况下,进程在加载新程序时会保留已打开的文件,如标准输入、标准输出、标准错误等,仅有一些特别声明的文件(在位图close_on_exec中)会在加载时关闭。

在进程运行过程中,若它同时打开的文件数超过了其文件描述符表的容量,VFS会采用RCU方式(见9.4节)自动为其更换新的、更大的文件描述符表,过程如下:

(1)为进程创建新的fdtable结构,同时为其创建新的描述符表fd及新的位图open_fds和close_on_exec。新描述符表应比老表大一倍,新位图的位数应与新表的长度一致。新的fd、close_on_exec和open_fds的内容是从老表中复制的。

(2)让进程files_struct结构中的fdt指向新的fdtable结构,使其成为进程新的当前描述符表;

(3)如果老的fdtable结构不是缺省的fdtab,则通过call_rcu()向RCU注册一个回调函数,让它在宽限期结束时释放老的fdtable结构。图11.14是进程文件描述符表的一个示意图,其中的虚线为缺省部分,实线为当前使用部分。在为进程更换文件描述符表之后,定义在files_struct结构中的缺省描述符表fdtab以及fd_array、close_on_exec_init、open_fds_init等都会被搁置不用,是一种小的浪费。当然可以将缺省的文件描述符表定义在files_struct结构之外,从而节省这部分内存空间。然

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论