全部博文(28)
分类: LINUX
2010-07-16 15:45:45
通过读毛德操先生的linux内核源代码情景分析,感触比较深,对于作者能从代码的字里行间找出各种数据结构之间的复杂关系,将代码分析的那么透彻,感到深表钦佩。书刚买回来时,身边好多同事对于我是否能将书读完表示怀疑。其实我读的时候也发觉了,想读完确实不是一般的难度,但是后来想想,人家书都写出来,难道我读还读不完,于是乎就硬着头皮往下读。
由于前阶段一直在搞文件系统,所以我是先读该书的文件系统部分。基本将第5章读了一遍。下面做一些总结。
1、概述
对于普通文件,文件系统层最终要通过磁盘或其他存储设备的驱动程序从存储介质上读或写。从磁盘文件的角度来看,对存储介质的访问可以涉及到四种不同的目标那就是:
(1)、文件中的数据,包括目录的内容,即目录项数据结构;
(2)、文件的组织与管理信息,即索引节点数据结构;
(3)、磁盘的超级块。如果物理的磁盘被划分成若干分区,那就包括每个‘逻辑磁盘’的超级块。
(4)、引导块。
涉及的有关数据结构有:
(1)、文件操作跳转表,即file_operations数据结构:file结构中的指针f_op指向具体的file_operations结构,如ext2就有这样的数据结构,分别用于普通文件和目录文件。
(2)、目录项操作跳转表,即dentry_operations数据结构:dentry结构中的指针d_op指向具体的dentry_operations数据结构,这是内核中hash()、compare()等内部操作的跳转表。
(3)、索引节点跳转表,即inode_operations数据结构:inode结构中的指针i_op指向具体的inode_operations数据结构,这是mkdir()、mknod()等文件操作以及lockup()、permission()等内部函数的跳转表。同样,一种文件系统也并不只限于一个file_operations结构。
(4)、超级快操作跳转表,即super_operations数据结构:super_block结构中的指针s_op指向具体的super_operations数据结构,这是read_inode()、write_node()、delete_inode()等内部操作跳转表。
(5)、超级快本身也因为文件系统而异。
虽然每个文件都有目录项和索引节点在磁盘上,但是只有在需要时才在内存中为之建立起相应的denty和inode数据结构。
2、从路径名到目标节点
主要涉及两个函数,即path_init()和path_walk()以及它们下面的一些底层函数。二者合在一起就可以根据给定的文件路径名在内存中找到或建立代表着目标文件或目录的dentry结构和inode结构。
path_init(),内部涉及到替换根目录的一些判断,成功返回后nameidata结构中的指针dentry指向路径搜索的起点,接着就是通过path_walk()顺着路径名的指引进行搜索了。其中path_walk涉及到了,如路径中包含“.””..”和一些link文件的判断,根据其中的标记进行寻找等。
结构ext2_inode_info中的i_data[]是一块很重要的数据。对于有存储内容的文件(普通文件和目录文件),这里存放着一些指针,直接或间接地(take care)指向磁盘上存储着该文件的所有记录块(在文件的读写章节中将会详细介绍)。所谓索引节点即因此而得名。至于代表着符合连接的节点,则并没有文件内容(数据),所以正好用这块空间来存储连接目标的路径名。
在找到了或建立了所需的inode结构后,就返回到ext2_lookup,在那里还要通过d_add()将inode结构与dentry结构挂上钩,并将dentry结构挂入杂凑表的某个队列。两个数据结构之间的联系是双向的。一方面是dentry结构的指针d_inode指向inode结构,这是一对一的关系,因为一个目录项只代表着一个文件。可是,反过来就不一样了,同一个文件可以有多个不同的文件名或路径(link),所以从inode结构到dentry结构的方向可以是一对多的关系。因此,inode结构中的i_dentry是个队列,dentry结构通过其队列头部d_alias挂入相应inode结构的队列中。
从path_walk()返回时,函数值为0表示搜索成功,此时nameidata结构中的指针dentry指向目标节点的dentry结构,指针mnt指向目标节点所在设备的安装结构。同时这个结构的last_type表示最后一个节点类型,节点名则在qstr结构last中。如果失败的话,则函数值为一负的出错代码,而nameidata结构中则提供失败的节点名等信息。
根据给定路径名找到目标节点的dentry结构(以及inode)的过程,涉及与文件系统有关的几乎所有数据结构以及这些数据结构间的关系,搞懂了这个过程就对文件系统有了基本的理解。
3、访问权限与文件的安全性
内核在执行用户进程访问文件的请求时要对比进程(和用户)的uid、gid以及文件的访问模式,以决定该进程是否对此文件具有所要求的访问权限。
除访问权限外,每个文件可以有另外的一些属性,“不可更改”(IS_IMMUTABLE)即是其中之一,这些属性也像访问权限一样以标记位的形式存储在文件的索引节点中,但是不像访问权限那样区分文件主、文件主的同组用户以及公众,而是另成体系,并且凌驾于访问权限之上。而且一旦设置了这些属性,即使是特权用户也不能在系统还在正常的多用户环境下运行时将这些属性去除,这样就算黑客偷到了特权用户的口令,对这些文件也就无能为力了。I_IMMUTABLE要有另外一种授权(CAP_LINUX_IMMUTABLE)的进程才能设置。
4、文件系统的安装和拆卸
系统在初始化时将一个“根设备”安装到节点“/”上,这个设备上的文件系统就成了整个系统中原始的、基本的文件系统。此后,就可以由超级用户进程通过系统调用mount把其他的子系统安装到已经存在于文件系统中的空闲节点上,使整个文件系统得以扩展,当不再需要使用某个子系统时,或者在关闭系统之前,则通过系统调用umount把已经安装的设备逐个拆卸下来。
其中提到了lookback,从系统角度来看,它似乎是一种设备,但实际上它只是提供了一条lookback(回接)到某个可访问普通文件或块设备的手段。
安装过程:确定安装点(dentry),读取待安装设备的super_block,然后将super_block与安装点的dentry数据结构联系在一起,即所谓的‘安装’,这是通过add_vfsmnt()完成。
在拆卸过程中,为了提高效率,块设备的输入/输出一般都是有缓冲的,无论是对超级块的改变还是对某个索引节点的改变,或者对某个数据块的改变,都只是对它们在内存中映像的改变,而不一定马上就写回设备上,现在设备要卸下来了,当然要先把已经改变了但是尚未写回设备的内容写回去。这称为‘同步’。同步完,如果没有其它进程在使用文件系统就可以卸载了。
5、文件的打开与关闭
sys_open,其中调用参数filename实际上是文件的路径名(绝对路径或相对路径);mode表示打开的模式,如“只读”等等;而flag则包含了许多标记位,用以表示打开模式以外的一些属性和要求。函数通过getname()从用户空间把文件的路径名拷贝到系统空间,并通过get_unused_fd()从当前进程的“打开文件表”中找到一个空闲的表项,该表项的下标即为“打开文件号”。然后,根据文件名通过file_open()找到或创建一个连接,或者说读/写该文件的上下文。文件读写的上下文是有file数据结构代表和绘制的。
sys_close,先检查文件号对应的file结构指针filp是否为0,然后释放打开的文件号,然后调用filp_close(),把文件中已经改变过的内容写回设备上即file_operations数据结构中提供相应的函数指针flush,同时移除POSIX锁,最后调用fput,递减file结构中的共享计数,如果递减后达到了0就释放该file结构。
6、文件的写与读
为了提高效率,稍微复杂一些的操作系统对文件的读/写都是带缓冲的,linux当然也不列外。用户空间和设备都可以映射到同一个缓冲页面,这样文件系统的缓冲机制和文件的内存映射机制巧妙地结合在一起了。
本节主要记录下关于文件的记录块原理。linux采用了直接和间接相结合的寻址方法。其方法是把整个文件的记录块寻址分成几个部分来实现。第一部分是个以文件内块号为下标的数组,这是采用直接映射的部分,对于较小的文件这一部分就够用了。由于根据文件的内块号就可以在inode结构里的数组中直接找到相应的设备上块号,所以效率很高。至于较大的文件,其开头那一部分记录块号同样直接就可以找到,但是当文件的大小超出这一部分的容量时,超出的那一部分就要采用间接寻址了。ext2文件系统的大小为12个记录块,即数组大小为12。当记录块大小为1K字节时,相应的文件大小为12k字节。在ext2文件系统的ext2_inode_info结构中,有个大小为15的整型数组i_data[],其开头12个元素即用于此项目。当文件大小超过这一部分的容量时,该数组中的第13个元素指向一个记录块,这个记录块的内容也是一个整型数组,其中的每个元素都指向一个设备上记录块。如果记录块大小为1k字节,则该数组的大小为256,也就是说间接寻址容量为256个记录块,即256k字节。这样,两个部分的总容量为12k+256k。可是,更大的文件还是容纳不下,所以超过此容量的部分要进一步采用双重(二层)间接寻址。此时inode结构里i_data[]数组中的第14个元素指向另一个记录块,该记录块的内容也是一个数组,但是每个元素都指向另一个记录块中的数组,那才是文件内块号至设备上的块号映射表。这么一来,双重间接寻址部分的能力为256*256个记录块即64M字节。以此类推,数组的第15个元素用于三重间接寻址,这一部分的容量可达256*256*256个记录块,也就是16GB,所以,对于32位结构的系统,当记录块大小为1k字节时,文件的最大容量为16G+64M+256K+12K。如果设备的容量大于这个数值,就得采用的记录块大小了。
7、其它文件操作
如sys_lseek,sys_dup,sys_ioctl
8、特殊文件系统 /proc
这个目录下的内容主要包括如下几类:
(1)、系统中的每个进程都有一个以其pid为名的子目录,而每个子目录中则包括关于该进程执行的命令行、所有环境变量、cpu占用时间、内存映射表、已打开文件的文件号以及进程状态等特殊文件。
(2)、系统中各种资源的管理信息,如/proc/slabinfo就是内存管理中关于各个slab缓冲块队列的信息,/proc/swaps就是关于系统的swap设备信息,/proc/partitions就是关于各个磁盘分区的信息,等等。
(3)、系统中的各种设备的有关信息,如/proc/pci就是关于系统的pci总线上所有设备的一份清单等等。
(4)、文件系统的信息,如/proc/mounts就是系统中已经安装的各个文件系统设备清单,而/proc/filesystems则是系统中已经登记的每种文件系统(类型)的清单。
(5)、中断的使用,/proc/interrupts是一份关于中断源和它们的中断向量编号的清单。
(6)、与动态安装模块有关的信息,/proc/modules是一份系统中已安装动态模块的清单,而/proc/ksyms则是内核中供可安装模块动态连接的符合名及其地址的清单。
(7)、与前述/dev/mem类似的内存访问手段,如/proc/kcore.
(8)、系统的版本号以及其他各种统计与状态信息。