Chinaunix首页 | 论坛 | 博客
  • 博客访问: 9726
  • 博文数量: 5
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 60
  • 用 户 组: 普通用户
  • 注册时间: 2014-10-09 17:57
文章分类
文章存档

2016年(5)

我的朋友
最近访客

分类: LINUX

2016-02-01 16:08:56

作者真是一个性情中人,他说这本书是以他妻子的名字命名的,真让人高兴,想来她妻子一定会觉得在作者写作期间自己的默默付出再没有比这更好的肯定和回报了。

我以前对文件系统只是有个模糊的认知,看linux 源码心中隐隐约约觉得文件系统就是如何在磁盘上安排文件的规则。但是这种认识只是局限在逻辑层面上的认识,知道是从根“/”开始寻找文件,要把根目录扇区读取出来,然后比对文件名,然后通过目录中保存的i节点号读取i节点所在的扇区,然后取得文件i节点,然后通过i节点里面记载的起始扇区号和扇区大小再读取相对应的扇区。这都是建立在linux系统中已经完善好的数据结构和磁盘驱动的基础上的认识。直到这次跟着作者开始规划磁盘,开始抽象数据结构,开始一点点看到磁盘驱动如何和文件层打交道,两者直接通讯的数据是什么样的,才算有一点点通畅的感觉。

还是用作者的话说吧,文件系统就是:

 

作者语文也比我好。

在实际的规划中,作者是把bximage 制造的硬盘规划为两个区,一个是主分区,一个是扩展分区。我以前一直以为扩展分区就是逻辑分区,这次才算明白过来,扩展分区也是主分区,因为扩展分区是在MBR中有记录的。这个主分区的意思是在MBR中被记录的分区条目。

首先我们要手动对磁盘进行分区,然后决定要在哪个分区上安装文件系统,以前一直不明白文件系统还能安装到不同的分区,总是以为一套规则应该要被用来管理操作系统下的所有文件。但是通过跟随作者实实在在的把ORANGE文件系统安装在一个逻辑分区上后,似乎隐隐有点明白如何在一个操作系统安装不同的文件的系统了。

随后在网上查看,不同的文件系统如何运行的机制,可以看到文件系统的超级块中也有了一套操作集合(可以看《Linux内核探秘:深入解析文件系统和设备驱动的架构与设计》),每次在生成文件的时候都会把对应的操作集合赋值给i节点的fops,这样随着linux调用的流程,不同文件系统下的操作会流向不同的操作函数。而对于设备文件也是如此,也抽象在一个庞大的虚拟文件接口下,生成设备文件的时候会将一个通用的设备文件操作集合赋值过去。这样就实现了多文件系统的协同机制。

在虚拟文件系统接口层和磁盘驱动层之间的文件系统的作用就是将i节点保存的的所在设备和起始扇区还有文件读写偏移做对应的计算,将结果转化为对应于本文件系统锁管理区域的偏移,然后传递给磁盘驱动以本区域的设备号和字节偏移,磁盘驱动会在它所保存的磁盘分区表中查找对应设备号的分区起始扇区,然后加上传递过来的偏移就是要读取数据整个磁盘上的偏移了,最后给磁盘硬件接口发送命令就可以了。

而在我们这个实例中,虽然没有可以实现多文件系统,但是框架都是一样的。作者和linux 0.12中的实现一样,文件操作是嵌入到操作系统的各个模块的,没有在i节点中保存操作集合指针。

设备编号

    而关于设备在内存中的编号,比如说linux0.110x301,是为了方便我们在编程上厘清逻辑。3是磁盘这个类型,这个3是一个索引,是文件操作找到对应驱动的索引,linux中有一个数组专门用来存放相应驱动的函数




struct dev_drv_map dd_map[] = {

/* driver nr. major device nr.

   ---------- ---------------- */

{INVALID_DRIVER}, /**< 0 : Unused */

{INVALID_DRIVER}, /**< 1 : Reserved for floppy driver */

{INVALID_DRIVER}, /**< 2 : Reserved for cdrom driver */

{TASK_HD}, /**< 3 : Hard disk */

{TASK_TTY}, /**< 4 : TTY */

{INVALID_DRIVER} /**< 5 : Reserved for scsi disk driver */

};

我们可以看到3就会对应到TASK_HD对应的处理程序中了。

01是分区编号。这个编号最后会交给磁盘驱动,驱动会在一张硬盘表中用01索引到一个分区的记录,里面存放的是该分区起始扇区,和扇区的大小。驱动用这个表中的数据和磁盘打交道。

也就是说,我们对硬盘分区标号,只是找到一种便于我们理解的方式去记录硬盘的分区信息。只要给驱动的编号能够索引到我们想要的分区就行了。

在作者写的书中,是用了两个数组来存放磁盘信息的,分别是主分区数组和扩展分区数组。编号从0x00---0x09给了两个硬盘的主分区用(0005代表两块硬盘各自整个硬盘数据),01-0406-09给了每个硬盘的四个主分区,扩展分区的编号是给每个主分区留了16个编号,

10-1f 给了01号主分区

20-2f 给了02号主分区

30-3f 给了03号主分区

40-4f 给了04号主分区

这样,我们在初始化硬盘的时候,将相应的记录填入到相应的下标处,那么磁盘驱动在工作的时候就能根据我们传入的编号正确找到数据了。

在初始化的时候我们读入MBR 0x1BE处的主引导记录,因为是第一块硬盘,将主分区记录填入01-04这个4个表项中,00项填充的是整个硬盘信息。书中的例子是0x20给了我们的活动分区。也就是根分区ROOT_DEV ==0x320,是扩展分区,那么再递归调用来填充扩展记录数组中相应的项。

但有一点要注意,我们的扩展分区编号是从10开始编号的,这个概念是存在脑海中的,实际用来存储扩展分区记录的数组的下标是从0开始的,所以我们使用这些编号的时候,要记得减去第一个扩展分区的编号0x10,才是从0开始的偏移。

等到我们读取磁盘扇区的时候,驱动根据0x20,判断得知第二个主分区是扩展分区(前面说了20-2f 给了02号主分区),在扩展分区记录数组的下标是0x20减掉0x10,也就是第16个(前十六个留给了第一个主分区,如果它是扩展分区它的信息就放在这个数组的前十六项里面)表项。取出起始扇区,就可以向硬盘发送读取的指令了。

也就说编号到底是怎么组织的没关系,只要能厘清其中的逻辑,能在脑海中知道如何根据编号索引到我们读取并保存下来的记录。

那么到这里就会明白,我们还要实现一个磁盘驱动,而磁盘驱动的主要逻辑就是上一篇胡乱讲的内容,要保存我们这个硬盘的整体分区表,并记录每个分区的起始地址和大小。以便文件系统传递过来的设备号和字节偏移能找到正确的位置。

文件i节点编号



上面是设备编号,下面说下文件i节点的编号和文件的找寻。


对于i节点,依然和linux0.12的内核中编号规则一样,0号不用,用来表示找寻失败的标志。

建立新文件,我们首先要为这个文件分配一个i节点号,要从磁盘中将i节点位图读进来的,然后循环遍历每一个位,直到遇到一个值为0的位或者遍历完位图。然后分配一段可用的扇区,也是读进扇区位图,遍历位图,直到遇到一个值为0的位或者遍历完位图,只不过i节点位图遇到0,置为1就好了;扇区位图遇到0要依次往后置0x800次,保证一个文件的大小不被其它文件占用。

假设我们可用i节点和扇区,就该为这个i节点在磁盘上的i节点数组分配一个空间,以用来保存文件的信息。我们根据i节点号读取所在扇区,然后计算在这个扇区的偏移,返回这个地址,然后对这一块内存当做i节点,将文件信息保存在其中,比如说起始扇区,文件大小,文件最大占用扇区数,文件的类型,然后写回磁盘。日后我们对文件的操作都最终反映在以这个为起始扇区的一块区域内。

文件已经创建好之后需要在其所在目录中表现出来,以便以后找这个文件的时候能够通过目录寻找。因为我们是单目录的文件系统,所以直接读取根目录所在扇区,然后遍历目录项,找到一个i节点号为0的目录项作为保存本次文件信息的目录。也就是把文件的名字和i节点号赋值过去,然后写回磁盘。

找已经存在的文件,在程序中我们是通过open来打开文件,传递给open的是一个路径,对于我们的文件系统,只有一个目录存在,所以方便了很多。

比如说给一个路径“/abc”,我们首先在进程的filp数组中找到一个可用的位置,以便进程来标识该文件,然后在内核文件描述符数组f_desc_table中找到一个可用的位置,内核用这个标识来表示已经打开的文件。因为我们知道只有一个目录,所以首先判断给出的路径第一个字符是不是“/”,如果是就剔除掉“/”后保存文件名,并且返回根目录的i节点指针,根据根目录i节点指定的根目录起始扇区号,读取目录扇区,然后遍历其中的目录项,和我们剔除掉“/”后的文件名做比较,如果找到则返回目录中记录的文件i节点号,没有找到返回0

假设我们找到abc的目录项,得到该文件的i节点号,那么我们就要将i节点的信息读取到内存中,并且为了便于管理要求从磁盘读取的i节点信息都统一放到i节点数组inode_table中。所以我们在读取磁盘上的信息之前要先查看i节点数组中是否已经保存了我们想要的信息,如果有可以直接用,当然要将引用计数加1;如果没有我们顺带着看看i节点数组中海油空闲位置给我们存放信息没有。

假设没有找到相关信息,并且有空闲位置可用。一个全新的i节点要读进内存,我们要给其中代表设备号,节点号,引用计数的变量分别赋值,然后根据节点号计算出该节点位于哪个扇区中,读取这个扇区,计算该节点在这个扇区的偏移,然后将其中保存的信息赋值给i节点数组中相应的成员。

得到i节点后,还要对全局f_desc_table(表示一个系统中最多可同时打开多少个文件)中的描述符进行赋值,毕竟i节点只是一个仓库,用来保存节点信息,真正让进程和节点关联起来的方式还是进程的filp[x](表示一个进程可以打开多少个文件)指向的描述符数组f_desc_table[y]。让描述符fd_inode变量(f_desc_table[y].fd_inode)指向刚创建的i节点,还有一些文件打开的模式,文件偏移指针,至于其中的fd_cnt对于普通的文件我感觉是没有多大的用处,因为两个进程即便是打开同一个文件由于fd_pos的不同就必须要重新生成一个描述符,我在网上查了下,应该是应对forkdup这样拷贝进程数据函数的一种方式。

到此,我们知道了abc文件在磁盘上的所有信息,除了文件的内容。我们知道它第一个数据扇区在我们管理的文件系统下的哪个扇区,有多大,我们可以依照这些信息将文件读进内存。

以上是手动在作者实现的文件系统中找寻或者创建文件的大体思路。再就是文件系统的建立了,因为我们在系统启动之后,对磁盘文件的操作都是依赖于根目录“/”的,也就是说,所谓的在磁盘上第一次建立文件系统,最重要无外乎写入文件系统的超级块、相应的位图、根目录数据等,以便于以后的操作能找到出发点。(当我现在再看《Linux内核探秘:深入解析文件系统和设备驱动的架构与设计》文件系统这一章节的时候,看超级块的结构不再觉得好像死的,而在尝试着解释为什么有这个变量,具体能起什么作用,实践和理论并重,走一程再走一程,总会有那么一丝融会贯通的感觉)

磁盘上具体的文件系统建立



何时建立文件系统

首先初始化文件系统将会用到的各种数据结构,例如i节点数组、系统文件描述符表、超级块结构。然后读取要建立文件系统的那个扇区的超级块,此时文件系统还没有建立,自然文件系统的MAGIC就不是想要的,此时需要建立磁盘文件系统。

读取该分区的信息(分区信息是在手工分区后已近写入MBR的分区记录表了,在文件系统初始化的时候将其读取出来保存在磁盘驱动的磁盘数组中),创建超级块,赋值为合适的值,写入该分区的第一块扇区(也就是超级块扇区,第零块是引导块)。接下来就是写入inode-map表,因为系统本身要用到几个文件(0号保留,1是“/”,还有标准输入、输出、错误输出),这几个文件占用的标号都要置为1。然后是sector-map的置位,系统开始的时候,只有根目录占用磁盘空间,而为了简单起见,每个文件的大小都是固定的,再加上第0位保留位,就是此时应该对sector-map置位的个数。随后是初始化磁盘上的inode数组,依然是上述的几个文件,不过要设置合适的属性罢了。最后是根目录项的初始化,将上述几个文件的名字和i节点号保存在目录项中就可以了。

以上的信息建立完成后,系统对文件的操作就有所依靠了,也就是解决了蛋和鸡的问题,手动构造一个开始,以这个构造作为以后操作的开始。
关于硬盘分区的知识,感觉这个很详细贴切:http://www.blogjava.net/galaxyp/archive/2010/04/25/319344.html

阅读(750) | 评论(0) | 转发(0) |
0

上一篇:没有了

下一篇:补充说明

给主人留下些什么吧!~~