『壹』 八种数据结构特点
数据结构:计算机存储、组织数据的方式。程序员的目标是为当前的问题选择最优的数据结构。
八种数据结构:数组,栈,链表,队列,堆,图,树,散列表,每种数据结构都有其特殊的存储方式。
概念:
一维数组:数组元素+数组索引
多维数组:数组的元素也是数组
基本操作:insert,get,delete(删除某个索引处的数组),size(获取数组长度)
题目:
查找数组第二小的元素
查找第一个没有重复的数组元素
合并2个排序号的数据
重新排列数组中的正数和负数
特点:栈是一种特殊的线性表,仅能在线性表的一端操作,栈顶允许操作,栈底不允许操作。 栈的特点是:先进后出,或者说是后进先出。栈中的元素采用LIFO (Last In First Out),即后进先出。
基本操作:Push(栈顶插入元素),Pop(返回栈最上方的元素,并删除),isEmpty(查询栈是否为空),Top(返回最上方元素,并不删除)
题目:使用栈计算后缀表达式、使用栈为栈中的元素排序、检查字符串中的括号是否匹配正确
使用场景:栈常应用于实现递归功能方面的场景,例如斐波那契数列。撤回,即Ctrl+Z,是我们最常见的操作之一,大多数应用都会支持这个功能。你知道它是怎么实现的吗?答案是这样的:把之前的应用状态(限制个数)保存到内存中,最近的状态放到第一个。这时,我们需要栈(stack)来实现这个功能。
概念:队列(Queue)与栈类似,都是采用线性结构存储数据。它们的区别在于,栈采用LIFO方式,而队列采用先进先出,即FIFO(First in First Out)。
使用场景:因为队列先进先出的特点拆闭,在多线程阻塞队列管理中非常适用。
基本操作:Enqueue—在队列末尾插入元素,Dequeue—将队列第一个元素删除i,sEmpty—查询队列是否为空,Top—返回队列的第一个元素
习题:使用队列实现栈,倒转队列的前K个元素,使用队列将1到n转换为二进制。
概念“链表(Linked List)也是线性结构,它与数组看起来非常像,但是它们的内存分配方式、内部结构和插入删除操作方式都不一样。链表是一系列节点组成的链,每一个节点保存了数据以及指向下一个节点的指针。链表头指针指向第一个节点,如果链表为空,则头指针为空或者为null。链表分为:单向链表,双向链表。
使用场景:链表可以用来实现文件系统、哈正迹希表和邻接表。
基本操作:InsertAtEnd—在链表结尾插入元素,InsertAtHead—在链表开头插入元素,Delete—删除链表的指定元素,DeleteAtHead—删除链表第一个元素,Search—在链表中查询指定元素,isEmpty—查询链表是否为空
题目:倒转1个链表,检查链表中是否存在循环,返回链表倒数第N个元素,移除链表中的重复元素
概念:图(graph)由多个节点(vertex)构成,节点之间阔以互相连接组成一个网络。(x, y)表示一条边(edge),它表示节点x与y相连。边可能会有权值(weight/cost)。
分类:无向图,有向图
表现形式:邻接矩阵(Adjacency Matrix),邻接表(Adjacency List)
遍历图的两种算法:广度优先搜索(Breadth First Search),深度优先搜索(Depth First Search)
常见题目:
实现广度优先搜索,实现深度优先搜索,检查图是否为树,统计图中边的个数,使用Dijkstra算法查找两个节点之间的最短距离。
树(Tree)是一个分层的数据结构,由节点和连接节点的边组成。树是一种特殊的图,它与图最大的区别是没有循环。树被广泛应用在人工智能和一些复杂算法中,用来提供高效的存储结构。
常见树:N叉树(N-ary Tree),平衡树(Balanced Tree),二叉树(Binary Tree),二叉查找树(Binary Search Tree),平衡二叉举御并树(AVL Tree),红黑树(Red Black Tree),2-3树(2–3 Tree)
题目:计算树的高度,查找二叉平衡树中第K大的元素,查找树中与根节点距离为k的节点,查找二叉树中某个节点所有祖先节点。
哈希(Hash)将某个对象变换为唯一标识符,该标识符通常用一个短的随机字母和数字组成的字符串来代表。哈希可以用来实现各种数据结构,其中最常用的就是哈希表(hash table)。
哈希表通常由数组实现。
哈希表的性能取决于3个指标:
哈希函数哈希表的大小哈希冲突处理方式
题目:查找数组中对称的组合,确认某个数组的元素是否为另一个数组元素的子集,确认给定的数组是否互斥。
前缀树(Prefix Trees或者Trie) 与树类似,用于处理字符串相关的问题时非常高效。它可以实现快速检索,常用于字典中的单词查询,搜索引擎的自动补全甚至IP路由。
参考资料: http://www.cnblogs.com/williamjie/p/9558015.html
『贰』 基于flash存储器的文件系统有哪些
Flash 存储器( Flash Memory) 是一种高可靠性、高密度的固态存储器件。 其存储方式是完全非易失性的,掉电后可以保存数据;可以在线写入,并可按页连续字节写入,存取速度快,所以嵌入式系统通常使用Flash 存储器作为存储设备。 但Flash存储器也存在着两个主要缺陷:一是在重写之前必须进行擦除,因为Flash 存储器划分成很多擦除块(SectorOErase) ,对任何一位数据进行修改必须先擦除整个块(Sector) ;二是擦除块的擦除次数有限,当一个块提前达到擦除次数上限时, 将导致整个Flash 存储器无法使用。 所以,目前PC 机上很多成熟的基于磁盘的文件系统在Flash 存储器上使用都存在着不足。
嵌入式系统应具有的特点: 一是高可靠性,在恶劣环境下系统仍能正常工作;二是低消耗,受成本限制系统设计必须量体裁衣,去除冗余;三是高效率,在占用较少资源情况下保证功能需求,这样就要求算法简单,效率高。 而日志文件系统(Log-St ruct ured File System) 在数据更新时无需将数据写入原存储区域,适应Flash 存储器无法进行重写这一特点。 目前,针对Flash 存储器的缺陷而设计的linux 下的J FFS 文件系统,就是采用简化的日志文件系统。 J FFS 文件系统将磨损均衡集成于清除机制之中,在带来掉电可恢复功能的同时,大大减少了块擦除的次数,提高了文件系统的存取速度和效率。 但是,J FFS 文件系统无法单独使用,或者使用于其它实时操作系统中。 对由于受成本和实时性限制而无法使用Linux 的一些嵌入式系统,也就无法使用J FFS 文件系统。基于上述分析,该嵌入式文件系统适合在开源实时操作系统(如μC/OS-II) 和无操作系统的情况下使用。
嵌入式文件系统原理
在日志文件系统中,一个文件被修改后不是被写入到原来的存储空间,而是被加到所有内容的后面,象日志一样被更新,这就是日志文件系统的基本原理。 由于同一个文件在文件系统中会留下不同的版本,所以系统需要设置一张表标注文件的最新与以前的版本。 在内容不断添加时为不将存储空间占满,系统设计了一种回收机制,回收无效内容占用的空间。
日志文件系统在文件更新时不用将文件写回原来的地址,这对Flash 存储器这种存储介质最为适合。 文中所设计的嵌入式文件系统采用了日志文件系统的设计原理,以及J FFS 文件系统将磨损均衡集成于清除机制之中的方法。 该系统将一个可擦写块平分为多个簇,文件的读写以簇为单位进行。簇的状态有3 种:脏、干净和空。 脏表示所存内容已被置为无效;干净表示所存数据有效;空表示可以写入数据。 文件和目录在该系统中被作为节点,一个节点占用若干个簇,节点中的内容连续存储,但不能越过块边界存储。 该系统设置一个索引节点,保存整个系统的信息,其中包含保存有各簇状态的簇状态表。
每一次文件更新后内容都将被添加至末尾处,索引节点也被更新,总是占用最末尾的干净簇。 回收脏簇时,将所要擦除块中的干净簇重写到空簇中,再进行块擦除。 当内容写至存储体末端,则从头部重新开始循环存储。 所设计的文件系统的操作过程见图1。
ic72新闻中心
嵌入式文件系统设计
Flash 存储器中的存储结构
Flash 存储器中的存储结构见图2。 该存储器中每个簇的第一个字作为簇的状态字,表示此簇是否为一个节点的首簇或空簇。 每个节点的首部存放此节点属性(文件/目录/索引节点) 和节点标识号。
ic72新闻中心
索引节点
索引节点存放该文件系统的大部分信息。 包括32 位的索引节点更新号、一张簇状态表、下一个要被擦除块的块号、给下一个新建节点(文件或目录) 的节点编号、系统根目录信息表。系统每一次更新都会产生新的索引节点,索引节点更新号加1。 按照Flash 存储器的使用寿命10 年计算,需要每秒更新136 次以上,才能达到索引节点更新号的上限,所以认为拥有最大更新号的索引节点为最新的索引节点。 簇状态表中对应每一个簇有两个Bit 位,表示各个簇的状态(干净01 ,脏11 ,空00) 。 根目录信息表存放根目录下的各个目录项,每个目录项包括:属性(文件0x1/目录0x0) 、文件名或目录名、节点编号、此文件(或目录) 对应节点的起始簇地址、根目录表的大小可变。
目录节点
目录节点存放的内容有目录名,目录项个数,及所有目录项信息。 文件节点存放文件名,文件大小,文件属性及文件内容,内存中的目录结构见图3。
ic72新闻中心
内存数据结构及基本操作
该文件系统载入(Mount ) 后,会在内存中建立一个系统的映象。 该映象包括:索引节点中的信息、目录及文件信息、每个可擦写块中包含的节点信息、未存盘的节点信息。 簇状态表、索引节点更新号、新节点编号、下一擦除块号等索引节点中的内容,在内存中均作为不同的变量。 内存中为每个文件和目录都建立了映象,数据结构见图4 和图5。
ic72新闻中心
ic72新闻中心
内存中的文件节点不包含文件真正的数据,而使用指针。 文件被打开时,在内存中创建一块新存储区域存放数据,数据指针便指向此存储区,未被打开时,此指针指向空。 对于每个目录有1 个目录层数,表示此目录的深度,如根目录的目录层数为0 ,根目录的下一级目录则为1 ,依此类推。 存储地址保存文件或目录在Flash 中的地址。 文件和目录都被存在上一级目录下,所属目录指针即指向上一级目录在内存中的数据结构,根目录的所属目录指针即为空。 对于同目录下的不同节点,在内存中使用链表将其串联,同目录文件指针即联成链表。 链表的首指针保存在上一级目录中,首目录项指针即指向链表的首项。 为提高块擦写的效率,存储在同一个可擦写块中的各个节点在内存中也建立一个链表,块队列指针即用于连成此链表。 为标识被修改的节点,利用一个未保存队列,未保存队列指针即用来建立此队列。
该文件系统载入(mount ) 时,首先顺序扫描Flash 中的每个索引节点,查找出最大的索引节点更新号,此更新号对应的索引节点即为最新的索引节点。 查找到最新索引节点后,将簇状态表等信息映射到内存的数据结构中。 依据索引节点中的根目录信息,遍历所有节点,建立内存中的目录文件结构,并将节点添加到对应的擦写块队列中。 对一个文件编辑并保存的过程见图6。
ic72新闻中心
文件打开时,先在内存中分配一块空间作为数据区,将内容写入,并定位文件节点的数据指针指向该内存中的数据区。 如果文件内容被修改,就将文件节点添加到未存盘队列,依次写入Flash 存储器中,并修改簇状态表。 保存时将内存中数据区内容写入Fhttp://www.xiupin365.net/sitemap.html?lash 中,释放申请的内存空间,修改节点中的数据指针和簇状态表,再将文件的所有上级目录重新写入Flash ,最后将更新后的索引节点内容写入Flash。 如果文件未被修改,则只需修改数据指针即可。
节点加入未存盘队列的顺序按照目录层数的大小排列,文件节点排在队列首,目录层数最大的排在其后,目录层数为1 的排在队列末尾,根目录不加入未存盘队列。
嵌入式文件系统特殊处理机制
均衡擦写机制
为了避免任意一个可擦除块因擦写次数过多而过早报废,文件系统对Flash擦写时采用了均衡擦写机制。 考虑到系统的精简性,擦写在整片Flash 的各块中依次进行,一块擦写完后,下一个被擦写的块即为后一个块,在系统的索引节点中保存了下一个要擦除的块号。 当文件系统中的剩余空间减少到设定值时,系统会擦除此块,以回收脏簇占用的空间。 对应每个可擦写块都有一个节点队列,此块中包含的节点都加入其中。块擦除的流程见图7。
ic72新闻中心
首先,将未保存于队列中的节点保存,清未保存队列。 然后将块队列中的所有文件节点转移到空簇中,同时将文件路径上的各级目录加入到未存盘队列中。 对于块队列中的目录节点,则将它和其路径上的各级目录加入未存盘队列中,按照未保存队列的顺序,依次将各个目录写入Flash 中,最后写入最新的索引节点。 因为目录节点加入未存盘队列时,按照目录层数的大小排列,所以按照未保存队列的顺序写入时,可以保证当一个目录要被写入Flash 时,它的所有下级目录已被写入Flash 中。 所有下级目录在Flash 中的存储地址都已确定。当该文件系统的空间将达到存储上限时,可能会出现特殊情况,即废簇回收时,空簇的空间不足,无法将所有干净簇重写。 文件系统为此建立了应急机制,先将文件节点内容存在内存中,这时新建一个临时未保存队列,专门保存文件节点,在块擦写完成后,将剩余的文件节点写入新的空簇中,其算法与图7 所示流程大致相同。 但是,一旦在擦写时断电,会导致该块上的所有数据丢失。
断电错误处理机制
当系统遭遇断电重新启动后,索引节点中的信息会与系统中的状态不符,这时便需要错误处理机制。 错误一般是索引节点中标注的空簇已被写入了数据,错误处理就是将此簇标志为脏簇,并查找下一个空簇重新写入。
多任务处理机制
该文件系统允许同时打开多个文件,在多任务操作系统下,为了避免冲突建立了多任务处理机制。 系统允许打开的多个文件在内存中同时被编辑修改,但是对Flash 写入操作有限制。 处理方法是设立Flash 写入保护区,在此区中只允许当前正在执行的任务执行Flash 写入操作。 实现Flash 写入保护区的方法是建立一个初始值为1 的信号量,当一个节点需要Flash 写入时,首先申请信号量,完成后再释放信号量。 Flash 写入保护区见图6 、图7。在图6 中,空操作语句是用来对多个文件的保存进行同步。 例如,有文件1 和文件2 需要保存,先将文件1 的内容写入Flash 中,文件1 路径下的目录节点被添加到未保存队列中,再将文件2 的内容写入Flash 中,文件2 路径下的目录节点也被添加到未保存队列中,最后将未保存队列中的所有节点都写入Flash 中。 这样,如果同一路径下的两个文件同时存盘,可避免路径下的相同目录节点被写入两次,从而提高了效率。 不足之处在于,如果很多文件同时存盘,会导致索引节点在一段时间内都无法写入Flash 存储器,有断电丢失的危险。 但对于一般嵌入式系统来说,很少会碰到这种情况。 当进行Flash存储器擦写时,在取块队列首节点至索引节点写入完成这段时间内都不允许进行其他Flash 存储器的写入操作,这是为了保证数据的完整性,同时也提高了文件系统的稳定性。
无目录文件系统的优化
许多嵌入式系统设计中虽没有目录管理的要求,但是对执行效率和资源消耗的要求较高。 对于不要求有目录管理的精简文件系统,在设计时也进行了优化。 精简文件系统在Flash 中的存储格式与上述设计相同,文件系统中的所有文件信息都保存在索引节点的根目录信息表中。 精简文件系统在内存中的映象则要简单很多,只包含索引节点中的信息,包括簇状态表、下一个擦除块、下一个新节点的标号和根目录信息,而不用为每个文件都建立内存中的映象,节省大量的内存空间。 文件的编辑存盘过程简化为:打开文件、编辑、将文件写入Flash 存储器、将修改后的索引节点写入Flash 存储器。 擦写则只需通过查询根目录信息表中的各个目录项,将块中的所有文件节点写入空簇即可。在无目录管理的情况下,精简文件系统占用的内存资源可以减少,操作也可便捷,提高了效率。 对于大量只需要按名存取的简单文件管理的小型嵌入式系统而言,针对Flash 存储器的简单文件系统将占用资源少,执行效率高,有很大的应用价值。
嵌入式文件系统实现及性能分析
该文件系统的实现采用了分层方法,分为3 层4 个部分:应用程序接口、文件系统核心、操作系统调用接口、Flash 存储器驱动,实现结构见图8。
ic72新闻中心
实现平台中RTOS 为μC/OSOII 实时操作系统,CPU 使用三星S4510B作为处理器,Flash 存储器芯片为FUJ ITSU 的29LV160 TE。 针对不同的实时操作系统和Flash 存储器芯片需要实现不同的操作系统接口和Flash 存储器驱动。
针对μC/ OSOII 编写操作系统调用接口,包括5个函数: ①系统调用接口初始化FS_Sys_Interface_Init ( ) ,创建互斥信号量和内存分区; ② Flash 写入关闭FS_Sys_Write_Lock ( ) ,禁止Flash 写入操作,调用μC/OS-II 中OSMutePend ( ) ; ③ Flash写入打开FS_Sys_Write_Unlock ( ) ,重新允许Flash 写入操作,调用μC/OS-II 中OSMutePost() ; ④内存空间申请FS_Sys_Mem_Alloc( ) 和内存空间添加FS_Sys_Mem_Add ( ) , 都调用OSMemGet ( ) 来完成; ⑤内存空间释放FS_Sys_Mem_Free ( ) ,调用OSMemPut ( ) 完成,将申请的内存块全部释放。针对29LV160 TE 这款Flash 存储器芯片,定义一个FlashDef 结构体的全局变量, 用于存储Flash 器件信息,并且编写针对此款Flash 的块擦写函数FS_Device_Sector_Erase ( ) 和数据写入函数FJ FS_Device_Write ( ) 。
完成这两部分的实现后,该系统就可运行调试。 测试应用程序接口(API) 。 应该提供的各部分功能,并在突然断电情况下,测试文件系统的恢复情况。无目录管理的精简文件系统的载入,可在2μs内完成,文件写入耗时主要为闪存的等待时间,系统本身只占用不到200 个字节的内存,产生的代码段大小为7 K。 完整的文件系统载入时,需要建立内存中映象,耗时根据文件数量的多少而不同,一般为10μs ,产生的代码段大小为11 K。 系统写入效率较高,在无目录管理的配置下尤其明显。 试验中系统在多次断电的情况下,系统仍能恢复至上次存盘的状态,虽会导致个别文件未更新,但不会导致文件系统崩溃。
『叁』 不同操作系统支持不同的文件系统,谁能帮我介绍一下不同文件系统有何区别,
文件系统就是建立在存储器件(如硬盘)对物理存储介质的读写之上的。文件系统不关心存储介质的绝对物理位置。
简单说下FAT吧 相信你把州友这个弄明白了 其他的就简单多了。
首先,文逗闷件系统的建立是为了使文件管理和调用、存储介质管理等事务的效率提高。不管什么文件系统读取文件都得从某一地址读出。
拿硬盘来说吧。你先去弄明白几个概念。扇区、磁头和柱面。
fat就是把一系列连续的存储区域(扇区)划分为几个逻辑盘。每个逻辑盘有分区DBR,FAT,FDT,数据区等等,中间还有一些做其他用途的存储空间。
物理第一扇区有MBR表(主引导记录)和分区表DPT。这两个表是系统启动有关的。所谓表,也就是按规定顺序放置的一系列信息。此扇区是操作系统不可存取的。分区表记录了硬盘分区的信息、系统安装盘的信息等等。通过BPB可以找到安装系统的盘(叫做活动分区,在BPB表中有相关描述)的信息,然后到那个分区读取第一个扇区,即含DBR表(dos引导记录)的扇区。然后查找系统文件,开始载入系统。
DBR后有FAT,即文件分配表。系统把数据区分为簇来管理。每个簇对应一个FAT表项。把表项编号带入计算式既可以得到扇区号。
FDT,即根目录表,这个表由多个FDT项构成。每个项有文件的相关信息。如文件名、扩展名、创建日期、起始簇号等。如果文件比较小,放在一个簇里,通过起始簇号,计算出文件的内容的册指槐扇区号,然后就可以读出来。有的文件由多个簇存放。则对应的FAT中存放的下一个簇的簇号,类似与链表的机制。
其实要深究起来的话。内容可多了去了。有不明白的可以跟我留言交流。
『肆』 程序员必备知识(操作系统5-文件系统)
本篇与之前的第三篇的内存管理知识点有相似的地方
对于运行的进程来说,内存就像一个纸箱子, 仅仅是一个暂存数据的地方, 而且空间有限。如果我们想要进程结束之后,数据依然能够保存下来,就不能只保存在内存里,而是应该保存在 外部存储 中。就像图书馆这种地方,不仅空间大,而且能够永久保存。
我们最常用的外部存储就是 硬盘 ,数据是以文件的形式保存在硬盘上的。为了管理这些文件,我们在规划文件系统的时候,需要考虑到以下几点。
第一点,文件系统要有严格的组织形式,使得文件能够 以块为单位进行存储 。这就像图书馆里,我们会给设置一排排书架,然后再把书架分成一个个小格子,有的项目存放的资料非常多,一个格子放不下,就需要多个格子来进行存放。我们把这个区域称为存放原始资料的 仓库区 。
第二点,文件系统中也要有 索引区 ,用来方便查找一个文件分成的多个块都存放在了什么位置。这就好比,图书馆的书太多了,为了方便查找,我们需要专门设置一排书架,这里面会写清楚整个档案库有哪些资料,资料在哪个架子的哪个格子上。这样找资料的时候就不用跑遍整个档案库,在这个书架上找到后,直奔目标书架就可以了。
第三点,如果文件系统中有的文件是热点文件,近期经常被读取和写入,文件系统应该有 缓存层 。这就相当于图书馆里面的热门图书区,这里面的书都是畅销书或者是常常被借还的图书。因为借还的次数比较多,那就没必要每次有人还了之后,还放回遥远的货架,我们可以专门开辟一个区域, 放置这些借还频次高的图书。这样借还的效率就会提高。
第四点,文件应该用 文件夹 的形式组织起来,方便管理和查询。这就像在图书馆里面,你可以给这些资料分门别类,比如分成计算机类.文学类.历史类等等。这样你也容易管理,项目组借阅的时候只要在某个类别中去找就可以了。
在文件系统中,每个文件都有一个名字,这样我们访问一个文件,希望通过它的名字就可以找到。文件名就是一个普通的文本。 当然文件名会经常冲突,不同用户取相同的名字的情况还是会经常出现的。
要想把很多的文件有序地组织起来,我们就需要把它们成为 目录 或者文件夹。这样,一个文件夹里可以包含文件夹,也可以包含文件,这样就形成了一种 树形结构 。而我们可以将不同的用户放在不同的用户目录下,就可以一定程度上避免了命名的冲突问题。
第五点,Linux 内核要在自己的内存里面维护一套数据结构,来保存哪些文件被哪些进程打开和使用 。这就好比,图书馆里会有个图书管理系统,记录哪些书被借阅了,被谁借阅了,借阅了多久,什么时候归还。
文件系统是操作系统中负责管理持久数据的子系统,说简单点,就是负责把用户的文件存到磁盘硬件中,因为即使计算机断电了,磁盘里的数据并不会丢失,所以可以持久化的保存文件。
文件系统的基本数据单位是 文件 ,它的目的是对磁盘上的文件进行组织管理,那组织的方式不同,就会形成不同的文件系统。
Linux最经典的一句话是:“一切皆文件”,不仅普通的文件和目录,就连块设备、管道、socket 等,也都是统一交给文件系统管理的。
Linux文件系统会为每个文件分配两个数据结构: 索引节点(index node) 和 目录项(directory entry) ,它们主要用来记录文件的元信息和目录层次结构。
●索引节点,也就是inode, 用来记录文件的元信息,比如inode编号、文件大小访问权限、创建时间、修改时间、 数据在磁盘的位置 等等。 索引节点是文件的唯一标识 ,它们之间一一对应, 也同样都会被 存储在硬盘 中,所以索引节点同样占用磁盘空间。
●目录项,也就是dentry, 用来记录文件的名字、索引节点指针以及与其他目录项的层级关联关系。多个目录项关联起来,就会形成 目录结构 ,但它与索引节点不同的是,目录项是由内核维护的一个数据结构,不存放于磁盘,而是 缓存在内存 。
由于索引节点唯一标识一个文件,而目录项记录着文件的名,所以目录项和索引节点的关系是多对一,也就是说,一个文件可以有多个别字。比如,硬链接的实现就是多个目录项中的索引节点指向同一个文件。
注意,目录也是文件,也是用索引节点唯一标识,和普通文件不同的是,普通文件在磁盘里面保存的是文件数据,而目录文件在磁盘里面保存子目录或文件。
(PS:目录项和目录不是一个东西!你也不是一个东西(^_=), 虽然名字很相近,但目录是个文件。持久化存储在磁盘,而目录项是内核一个数据结构,缓存在内存。
如果查询目录频繁从磁盘读,效率会很低,所以内核会把已经读过的目录用目录项这个数据结构缓存在内存,下次再次读到相同的目录时,只需从内存读就可以,大大提高了 文件系统的效率。
目录项这个数据结构不只是表示目录,也是可以表示文件的。)
磁盘读写的最小单位是 扇区 ,扇区的大小只有512B大小,很明显,如果每次读写都以这么小为单位,那这读写的效率会非常低。
所以,文件系统把多个扇区组成了一个 逻辑块 ,每次读写的最小单位就是逻辑块(数据块) , Linux中的逻辑块大小为4KB,也就是一次性读写 8个扇区,这将大大提高了磁盘的读写的效率。
以上就是索引节点、目录项以及文件数据的关系,下面这个图就很好的展示了它们之间的关系:
索引节点是存储在硬盘上的数据,那么为了加速文件的访问,通常会把索引节点加载到内存中。
另外,磁盘进行格式化的时候,会被分成三个存储区域,分别是超级块、索引节点区和数据块区。
●超级块,用来存储文件系统的详细信息,比如块个数、块大小、空闲块等等。
●索引节点区,用来存储索引节点;
●数据块区,用来存储文件或目录数据;
我们不可能把超级块和索引节点区全部加载到内存,这样内存肯定撑不住,所以只有当需要使用的时候,才将其加载进内存,它们加载进内存的时机是不同的.
●超级块:当文件系统挂载时进入内存;
●索引节点区:当文件被访问时进入内存;
文件系统的种类众多,而操作系统希望 对用户提供一个统一的接口 ,于是在用户层与文件系统层引入了中间层,这个中间层就称为 虚拟文件系统(Virtual File System, VFS) 。
VFS定义了一组所有文件系统都支持的数据结构和标准接口,这样程序员不需要了解文件系统的工作原理,只需要了解VFS提供的统一接口即可。
在Linux文件系统中,用户空间、系统调用、虚拟机文件系统、缓存、文件系统以及存储之间的关系如下图:
Linux支持的文件系统也不少,根据存储位置的不同,可以把文件系统分为三类:
●磁盘的文件系统,它是直接把数据存储在磁盘中,比如Ext 2/3/4. XFS 等都是这类文件系统。
●内存的文件系统,这类文件系统的数据不是存储在硬盘的,而是占用内存空间,我们经常用到的/proc 和/sys文件系统都属于这一类,读写这类文件,实际上是读写内核中相关的数据。
●网络的文件系统,用来访问其他计算机主机数据的文件系统,比如NFS. SMB等等。
文件系统首先要先挂载到某个目录才可以正常使用,比如Linux系统在启动时,会把文件系统挂载到根目录。
在操作系统的辅助之下,磁盘中的数据在计算机中都会呈现为易读的形式,并且我们不需要关心数据到底是如何存放在磁盘中,存放在磁盘的哪个地方等等问题,这些全部都是由操作系统完成的。
那么,文件数据在磁盘中究竟是怎么样的呢?我们来一探究竟!
磁盘中的存储单元会被划分为一个个的“ 块 ”,也被称为 扇区 ,扇区的大小一般都为512byte.这说明即使一块数据不足512byte,那么它也要占用512byte的磁盘空间。
而几乎所有的文件系统都会把文件分割成固定大小的块来存储,通常一个块的大小为4K。如果磁盘中的扇区为512byte,而文件系统的块大小为4K,那么文件系统的存储单元就为8个扇区。这也是前面提到的一个问题,文件大小和占用空间之间有什么区别?文件大小是文件实际的大小,而占用空间则是因为即使它的实际大小没有达到那么大,但是这部分空间实际也被占用,其他文件数据无法使用这部分的空间。所以我们 写入1byte的数据到文本中,但是它占用的空间也会是4K。
这里要注意在Windows下的NTFS文件系统中,如果一开始文件数据小于 1K,那么则不会分配磁盘块来存储,而是存在一个文件表中。但是一旦文件数据大于1K,那么不管以后文件的大小,都会分配以4K为单位的磁盘空间来存储。
与内存管理一样,为了方便对磁盘的管理,文件的逻辑地址也被分为一个个的文件块。于是文件的逻辑地址就是(逻辑块号,块内地址)。用户通过逻辑地址来操作文件,操作系统负责完成逻辑地址与物理地址的映射。
不同的文件系统为文件分配磁盘空间会有不同的方式,这些方式各自都有优缺点。
连续分配要求每个文件在磁盘上有一组连续的块,该分配方式较为简单。
通过上图可以看到,文件的逻辑块号的顺序是与物理块号相同的,这样就可以实现随机存取了,只要知道了第一个逻辑块的物理地址, 那么就可以快速访问到其他逻辑块的物理地址。那么操作系统如何完成逻辑块与物理块之间的映射呢?实际上,文件都是存放在目录下的,而目录是一种有结构文件, 所以在文件目录的记录中会存放目录下所有文件的信息,每一个文件或者目录都是一个记录。 而这些信息就包括文件的起始块号和占有块号的数量。
那么操作系统如何完成逻辑块与物理块之间的映射呢? (逻辑块号, 块内地址) -> (物理块号, 块内地址),只需要知道逻辑块号对应的物理块号即可,块内地址不变。
用户访问一个文件的内容,操作系统通过文件的标识符找到目录项FCB, 物理块号=起始块号+逻辑块号。 当然,还需要检查逻辑块号是否合法,是否超过长度等。因为可以根据逻辑块号直接算出物理块号,所以连续分配支持 顺序访问和随机访问 。
因为读/写文件是需要移动磁头的,如果访问两个相隔很远的磁盘块,移动磁头的时间就会变长。使用连续分配来作为文件的分配方式,会使文件的磁盘块相邻,所以文件的读/写速度最快。
连续空间存放的方式虽然读写效率高,但是有 磁盘空间碎片 和 文件长度不易扩展 的缺陷。
如下图,如果文件B被删除,磁盘上就留下一块空缺,这时,如果新来的文件小于其中的一个空缺,我们就可以将其放在相应空缺里。但如果该文件的大小大于所
有的空缺,但却小于空缺大小之和,则虽然磁盘上有足够的空缺,但该文件还是不能存放。当然了,我们可以通过将现有文件进行挪动来腾出空间以容纳新的文件,但是这个在磁盘挪动文件是非常耗时,所以这种方式不太现实。
另外一个缺陷是文件长度扩展不方便,例如上图中的文件A要想扩大一下,需要更多的磁盘空间,唯一的办法就只能是挪动的方式,前面也说了,这种方式效率是非常低的。
那么有没有更好的方式来解决上面的问题呢?答案当然有,既然连续空间存放的方式不太行,那么我们就改变存放的方式,使用非连续空间存放方式来解决这些缺陷。
非连续空间存放方式分为 链表方式 和 索引方式 。
链式分配采取离散分配的方式,可以为文件分配离散的磁盘块。它有两种分配方式:显示链接和隐式链接。
隐式链接是只目录项中只会记录文件所占磁盘块中的第一块的地址和最后一块磁盘块的地址, 然后通过在每一个磁盘块中存放一个指向下一 磁盘块的指针, 从而可以根据指针找到下一块磁盘块。如果需要分配新的磁盘块,则使用最后一块磁盘块中的指针指向新的磁盘块,然后修改新的磁盘块为最后的磁盘块。
我们来思考一个问题, 采用隐式链接如何将实现逻辑块号转换为物理块号呢?
用户给出需要访问的逻辑块号i,操作系统需要找到所需访问文件的目录项FCB.从目录项中可以知道文件的起始块号,然后将逻辑块号0的数据读入内存,由此知道1号逻辑块的物理块号,然后再读入1号逻辑块的数据进内存,此次类推,最终可以找到用户所需访问的逻辑块号i。访问逻辑块号i,总共需要i+ 1次磁盘1/0操作。
得出结论: 隐式链接分配只能顺序访问,不支持随机访问,查找效率低 。
我们来思考另外一个问题,采用隐式链接是否方便文件拓展?
我们知道目录项中存有结束块号的物理地址,所以我们如果要拓展文件,只需要将新分配的磁盘块挂载到结束块号的后面即可,修改结束块号的指针指向新分配的磁盘块,然后修改目录项。
得出结论: 隐式链接分配很方便文件拓展。所有空闲磁盘块都可以被利用到,无碎片问题,存储利用率高。
显示链接是把用于链接各个物理块的指针显式地存放在一张表中,该表称为文件分配表(FAT, File Allocation Table)。
由于查找记录的过程是在内存中进行的,因而不仅显著地 提高了检索速度 ,而且 大大减少了访问磁盘的次数 。但也正是整个表都存放在内存中的关系,它的主要的缺点是 不适 用于大磁盘 。
比如,对于200GB的磁盘和1KB大小的块,这张表需要有2亿项,每一项对应于这2亿个磁盘块中的一个块,每项如果需要4个字节,那这张表要占用800MB内存,很显然FAT方案对于大磁盘而言不太合适。
一直都在,加油!(*゜Д゜)σ凸←自爆按钮
链表的方式解决了连续分配的磁盘碎片和文件动态打展的问题,但是不能有效支持直接访问(FAT除外) ,索引的方式可以解决这个问题。
索引的实现是为每个文件创建一个 索引数据块 ,里面存放的 是指向文件数据块的指针列表 ,说白了就像书的目录一样,要找哪个章节的内容,看目录查就可以。
另外, 文件头需要包含指向索引数据块的指针 ,这样就可以通过文件头知道索引数据块的位置,再通过索弓|数据块里的索引信息找到对应的数据块。
创建文件时,索引块的所有指针都设为空。当首次写入第i块时,先从空闲空间中取得一个块, 再将其地址写到索引块的第i个条目。
索引的方式优点在于:
●文件的创建、增大、缩小很方便;
●不会有碎片的问题;
●支持顺序读写和随机读写;
由于索引数据也是存放在磁盘块的,如果文件很小,明明只需一块就可以存放的下,但还是需要额外分配一块来存放索引数据,所以缺陷之一就是存储索引带来的开销。
如果文件很大,大到一个索引数据块放不下索引信息,这时又要如何处理大文件的存放呢?我们可以通过组合的方式,来处理大文件的存储。
先来看看 链表+索引 的组合,这种组合称为 链式索引块 ,它的实现方式是在 索引数据块留出一个存放下一个索引数据块的指针 ,于是当一个索引数据块的索引信息用完了,就可以通过指针的方式,找到下一个索引数据块的信息。那这种方式也会出现前面提到的链表方式的问题,万一某个指针损坏了,后面的数据也就会无法读取了。
还有另外一种组合方式是 索引+索引 的方式,这种组合称为多级索引块,实现方式是通过一个索引块来存放多个索引数据块,一层套一层索引, 像极了俄罗斯套娃是吧๑乛◡乛๑
前面说到的文件的存储是针对已经被占用的数据块组织和管理,接下来的问题是,如果我要保存一个数据块, 我应该放在硬盘上的哪个位置呢?难道需要将所有的块扫描一遍,找个空的地方随便放吗?
那这种方式效率就太低了,所以针对磁盘的空闲空间也是要引入管理的机制,接下来介绍几种常见的方法:
●空闲表法
●空闲链表法
●位图法
空闲表法
空闲表法就是为所有空闲空间建立一张表,表内容包括空闲区的第一个块号和该空闲区的块个数,注意,这个方式是连续分配的。如下图:
当请求分配磁盘空间时,系统依次扫描空闲表里的内容,直到找到一个合适的空闲区域为止。当用户撤销一个文件时,系统回收文件空间。这时,也需顺序扫描空闲表,寻找一个空闲表条目并将释放空间的第一个物理块号及它占用的块数填到这个条目中。
这种方法仅当有少量的空闲区时才有较好的效果。因为,如果存储空间中有着大量的小的空闲区,则空闲表变得很大,这样查询效率会很低。另外,这种分配技术适用于建立连续文件。
空闲链表法
我们也可以使用链表的方式来管理空闲空间,每一个空闲块里有一个指针指向下一个空闲块,这样也能很方便的找到空闲块并管理起来。如下图:
当创建文件需要一块或几块时,就从链头上依次取下一块或几块。反之,当回收空间时,把这些空闲块依次接到链头上。
这种技术只要在主存中保存一个指针, 令它指向第一个空闲块。其特点是简单,但不能随机访问,工作效率低,因为每当在链上增加或移动空闲块时需要做很多1/0操作,同时数据块的指针消耗了一定的存储空间。
空闲表法和空闲链表法都不适合用于大型文件系统,因为这会使空闲表或空闲链表太大。
位图法
位图是利用二进制的一位来表示磁盘中一个盘块的使用情况,磁盘上所有的盘块都有一个二进制位与之对应。
当值为0时,表示对应的盘块空闲,值为1时,表示对应的盘块已分配。它形式如下:
在Linux文件系统就采用了位图的方式来管理空闲空间,不仅用于数据空闲块的管理,还用于inode空闲块的管理,因为inode也是存储在磁盘的,自然也要有对其管理。
前面提到Linux是用位图的方式管理空闲空间,用户在创建一个新文件时, Linux 内核会通过inode的位图找到空闲可用的inode,并进行分配。要存储数据时,会通过块的位图找到空闲的块,并分配,但仔细计算一下还是有问题的。
数据块的位图是放在磁盘块里的,假设是放在一个块里,一个块4K,每位表示一个数据块,共可以表示4 * 1024 * 8 = 2^15个空闲块,由于1个数据块是4K大小,那么最大可以表示的空间为2^15 * 4 * 1024 = 2^27个byte,也就是128M。
也就是说按照上面的结构,如果采用(一个块的位图+ 一系列的块),外加一(个块的inode的位图+一系列的inode)的结构能表示的最大空间也就128M,
这太少了,现在很多文件都比这个大。
在Linux文件系统,把这个结构称为一个 块组 ,那么有N多的块组,就能够表示N大的文件。
最终,整个文件系统格式就是下面这个样子。
最前面的第一个块是引导块,在系统启动时用于启用引导,接着后面就是一个一个连续的块组了,块组的内容如下:
● 超级块 ,包含的是文件系统的重要信息,比如inode总个数、块总个数、每个块组的inode个数、每个块组的块个数等等。
● 块组描述符 ,包含文件系统中各个块组的状态,比如块组中空闲块和inode的数目等,每个块组都包含了文件系统中「所有块组的组描述符信息」。
● 数据位图和inode位图 ,用于表示对应的数据块或inode是空闲的,还是被使用中。
● inode 列表 ,包含了块组中所有的inode, inode 用于保存文件系统中与各个文件和目录相关的所有元数据。
● 数据块 ,包含文件的有用数据。
你可以会发现每个块组里有很多重复的信息,比如 超级块和块组描述符表,这两个都是全局信息,而且非常的重要 ,这么做是有两个原因:
●如果系统崩溃破坏了超级块或块组描述符,有关文件系统结构和内容的所有信息都会丢失。如果有冗余的副本,该信息是可能恢复的。
●通过使文件和管理数据尽可能接近,减少了磁头寻道和旋转,这可以提高文件系统的性能。
不过,Ext2 的后续版本采用了稀疏技术。该做法是,超级块和块组描述符表不再存储到文件系统的每个块组中,而是只写入到块组0、块组1和其他ID可以表示为3、5、7的幂的块组中。
在前面,我们知道了一个普通文件是如何存储的,但还有一个特殊的文件,经常用到的目录,它是如何保存的呢?
基于Linux 一切切皆文件的设计思想,目录其实也是个文件,你甚至可以通过vim打开它,它也有inode, inode 里面也是指向一些块。
和普通文件不同的是, 普通文件的块里面保存的是文件数据,而目录文件的块里面保存的是目录里面一项一项的文件信息 。
在目录文件的块中,最简单的保存格式就是 列表 ,就是一项一项地将目录下的文件信息(如文件名、文件inode.文件类型等)列在表里。
列表中每一项就代表该目录下的文件的文件名和对应的inode,通过这个inode,就可以找到真正的文件。
通常,第一项是「则」,表示当前目录,第二项是.,表示上一级目录, 接下来就是一项一项的文件名和inode。
如果一个目录有超级多的文件,我们要想在这个目录下找文件,按照列表一项一项的找,效率就不高了。
于是,保存目录的格式改成 哈希表 ,对文件名进行哈希计算,把哈希值保存起来,如果我们要查找一个目录下面的文件名,可以通过名称取哈希。如果哈希能够匹配上,就说明这个文件的信息在相应的块里面。
Linux系统的ext文件系统就是采用了哈希表,来保存目录的内容,这种方法的优点是查找非常迅速,插入和删除也较简单,不过需要一些预备措施来避免哈希冲突。
目录查询是通过在磁盘上反复搜索完成,需要不断地进行/0操作,开销较大。所以,为了减少/0操作,把当前使用的文件目录缓存在内存,以后要使用该文件时只要在内存中操作,从而降低了磁盘操作次数,提高了文件系统的访问速度。
感谢您的阅读,希望您能摄取到知识!加油!冲冲冲!(发现光,追随光,成为光,散发光!)我是程序员耶耶!有缘再见。<-biubiu-⊂(`ω´∩)
『伍』 请问 电脑向移动硬盘考大量电影时(上百G),为什么复制再删除 要比 剪切速度快很多
因碰让为剪切是复制的时候同时在删纳孙除,两种同时进行当然慢了
这和电脑有关,受硬盘读取写入的速率影响,如果你的硬盘速率高的话笑茄局就不会有太大差别了
『陆』 jffs2的详述
节点头部定义和兼容性
JFFS2 将文件系统的数据和原数据以节点的形式存储在闪存上,具体来说节点头部的定义如下:
图迹纤一
幻数屏蔽位:0x1985 用来标识 JFFS2 文件系统。
节点类型:JFFS2 自身定义了三种节点类型,但是考虑到文件系统可扩展性和兼容性,JFFS2从 ext2 借鉴了经验,节点类型的最高两位被用来定义节点的兼容属性,具体来说有下面几种兼容属性:
JFFS2_FEATURE_INCOMPAT:当 JFFS2 发现了一个不能识别的节点类型,并且它的兼容属性是 JFFS2_FEATURE_INCOMPAT,那么 JFFS2 必须拒绝挂载(mount)文件系统。
JFFS2_FEATURE_ROCOMPAT:当 JFFS2 发现了一个不能识别的节点类型,并且它的兼容属性是 JFFS2_FEATURE_ROCOMPAT,那么 JFFS2 必须以只读的方式挂载文件系统。
JFFS2_FEATURE_RWCOMPAT_DELETE:当 JFFS2 发现了一个不能识别的节点类型,并且它的兼容属性是 JFFS2_FEATURE_RWCOMPAT_DELETE,那么在垃圾回收的时候,这个节点可以被删除。
JFFS2_FEATURE_RWCOMPAT_COPY:当 JFFS2 发现了一个不能识别的节点类型,并且它的兼容属性是 JFFS2_FEATURE_RWCOMPAT_COPY,那么在垃圾回收的时候,这个节点要被拷贝到新的位置。
节点总长度:包括节点头和数据的长度。
节点头部 CRC 校验:包含节点头部的校验码岁键,为文件系统的可靠性提供了支持。
节点类型
JFFS2 定义了三种节点类型:
JFFS2_NODETYPE_INODE: INODE 节点包含了i-节点的原数据(i节点号,文件的组 ID, 属主 id, 访问时间,偏移,长度等),文件数据被附在 INODE 节点之后。除此之外,每个 INODE 节点还有一个版本号,它被用来维护属于一个i-节点的所有 INODE 节点的全序关系。下面举例来说明这个全序关系在 JFFS2 的使用:
图二
因此,当文件系统从闪存上读节点信息后,会生成下面的映射信息:
图三
根据这个映射信息表,文件系统就知道到相应的 INODE 节点去读取相应的文件内容。 最后要说明的是,JFFS2 支持文件数据的压缩存储,因此在 INODE 节点中还包含了所使用的压缩算法,在读取数据的时候选择相应的压缩算法来解压缩。
JFFS2_NODETYPE_DIRENT:DIRENT 节点就是把文件名与 i 节点对应起来。在 DIRENT节点中也有一个版本号,这个版本号的作用主要是用来删除一个dentry。具体来说,当我们要乎州巧从一个目录中删除一个dentry时,我们要写一个 DIRENT 节点,节点中的文件名与被删除的 dentry 中的文件名相同,i 节点号置为 0,同时设置一个更高的版本号。
JFFS2_NODETYPE_CLEANMARKER:当一个擦写块被擦写完毕后,CLEANMARKER 节点会被写在 NOR flash 的开头,或 NAND flash 的 OOB(Out-Of-Band) 区域来表明这是一个干净,可写的擦写块。在 JFFS v1 中,如果扫描到开头的 1K 都是 0xFF 就认为这个擦写块是干净的。但是在实际的测试中发现,如果在擦写的过程中突然掉电,擦写块上也可能会有大块连续 0xFF,但是这并不表明这个擦写块是干净的。于是我们需要 CLEANMARKER 节点来确切的标识一个干净的擦写块。
JFFS2节点,擦写块在内存中的表示和操作
JFFS2 维护了几个链表来管理擦写块,根据擦写块上的内容,一个擦写块会在不同的链表上。具体来说,当一个擦写块上都是合法(valid)的节点时,它会在 clean_list 上;当一个擦写块包含至少一个过时(obsolete)的节点时,它会在 dirty_list 上;当一个擦写块被擦写完毕,并被写入 CLEANMARKER 节点后,它会在 free_list 上。
通常情况下,JFFS2 顺序的在擦写块上写入不同的节点,直到一个擦写块被写满。此时 JFFS2 从 free_list 上取下一个擦写块,继续从擦写块的开头开始写入节点。当 free_list 上擦写块的数量逐渐减少到一个预先设定的阀值的时候,垃圾回收就被触发了,为文件系统清理出更多的可用擦写块。 为了减少对内存的占用,JFFS2 并没有把 i 节点所有的信息都保留在内存中,而只是把那些在请求到来时不能很快获得的信息保留在内存中。具体来说,对于在闪存上的每个 i 节点,在内存里都有一个 struct jffs2_inode_cache 与之对应,这个结构里保存了 i 节点号,指向 i 节点的连接数,以及一个指向属于这个 i 节点的物理节点链表的指针。所有的 struct jffs2_inode_cache 存储在一个哈希表中。闪存上的每个节点在内存中由一个 struct jffs2_raw_node_ref 表示,这个结构里保存了此节点的物理偏移,总长度,以及两个指向 struct jffs2_raw_node_ref 的指针。一个指针指向此节点在物理擦写块上的下一个节点,另一个指针指向属于同一个 i-节点的物理节点链表的下一个节点。
图四
在闪存上的节点的起始偏移都是 4字节对齐的,所以 struct jffs2_inode_cache 中flash_offset 的最低两位没有被用到。JFFS2 正好利用最低位作为此节点是否过时的标记。
下面举一例来说明 JFFS2 是如何使用这些数据结构的。VFS 调用 iget() 来得到一个 i 节点的信息,当这个 i 节点不在缓存中的时候,VFS 就会调用 JFFS2 的 read_inode() 回调函数来得到 i 节点信息。传给 read_inode() 的参数是 i 节点号,JFFS2 用这个 i 节点号从哈希表中查找相应的 struct jffs2_inode_cache,然后利用属于这个 i 节点的节点链表从闪存上读入节点信息,建立类似于表三的映射信息。
JFFS2 挂载过程
JFFS2 的挂载过程分为四个阶段:
1) JFFS2 扫描闪存介质,检查每个节点 CRC 校验码的合法性,同时分配了 struct jffs2_inode_cache 和 struct jffs2_raw_node_ref
2) 扫描每个 i 节点的物理节点链表,标识出过时的物理节点;对每一个合法的dentry节点,将相应的 jffs2_inode_cache 中的 nlink 加一。
3 找出 nlink 为 0 的 jffs2_inode_cache,释放相应的节点。
4 释放在扫描过程中使用的临时信息。
JFFS2 垃圾回收机制
当 free_list 上的擦写块数太少了,垃圾回收就会被触发。垃圾回收主要的任务就是回收那些已经过时的节点,但是除此之外它还要考虑磨损平衡的问题。因为如果一味的从 dirty_list上选取擦写块进行垃圾回收,那么 dirty_list 上的擦写块将先于 clean_list 上的擦写块被磨损坏。JFFS2 的处理方式是以 99% 的概率从 dirty_list,1% 的概率从 clean_list 上取一个擦写块下来。由此可以看出 JFFS2 的设计思想是偏向于性能,同时兼顾磨损平衡。对这个块上每一个没有过时的节点执行相同的操作:
1 找出这个节点所属的 i 节点号(见图五)。
2 调用 iget(),建立这个 i 节点的文件映射表。
3 找出这个节点上没有过时的数据内容,并且如果合法的数据太少,JFFS2 还会合并相邻的节点。
4 将数据读入倒缓存里,然后将它拷贝到新的擦写块上。
5 将回收的节点置为过时。
当擦写块上所有的节点都被置为过时,就可以擦写这个擦写块,回收使用它。
『柒』 如何实现一个文件系统
摘要:本文目的是分析在Linux系统中如何实现新的文件系统。在介绍文件系统具体实现前先介绍文件系统的概念和作用,抽象出了文件系统概念模型。熟悉文件系统的内涵后,我们再近一步讨论Linux系统中和文件系统的特殊风格和具体文件系统在Linux中组成结构,为读者勾画出Linux中文件系统工作的全景图。最后,我们再通过Linux中最简单的Romfs作实例分析实现文件系统的普遍步骤。(我们假定读者已经对Linux文件系统初步了解)
什么是文件系统
首先要谈的概念就是什么是文件系统,它的作用到底是什么。
文件系统的概念虽然许多人都认为是再清晰不过的了,但其实我们往往在谈论中或多或少地夸大或片缩小了它的实际概念(至少我时常混淆),或者说,有时借用了其它概念,有时说的又不够全面。
比如在操作系统中,文件系统这个术语往往既被用来描述磁盘中的物理布局,比如有时我们说磁盘中的“文件系统”是EXT2或说把磁盘格式化成FAT32格式的“文件系统”等——这时所说的“文件系统”是指磁盘数据的物理布局格式;另外,文件系统也被用来描述内核中的逻辑文件结构,比如有时说的“文件系统”的接口或内核支持Ext2等“文件系统”——这时所说的文件系统都是内存中的数据组织结构而并非磁盘物理布局。还有些时候说“文件系统”负责管理用户读写文件——这时所说的“文件系统”往往描述操作系统中的“文件管理系统”,也就是文件子系统。
虽然上面我们列举了混用文件系统的概念的几种情形,但是却也不能说上述说法就是错误的,因为文件系统概念本身就囊括众多概念,几乎可以说在操作系统中自内存管理、系统调度到I/O系统、设备驱动等各个部分都和文件系统联系密切,有些部分和文件系统甚至未必能明确划分——所以不能只知道文件系统是系统中数据的存储结构,一定要全面认识文件系统在操作系统中的角色,才能具备自己开发新文件系统的能力。
为了澄清文件系统的概念,必须先来看看文件系统在操作系统中处于何种角色,分析文件系统概念的内含外延。所以我们先抛开Linux文件系统的实例,而来看看操作系统中文件系统的普遍体系结构,从而增强对文件系统的理论认识。
下面以软件组成的结构图[1]的方式描述文件系统所涉及的内容。
我们针对各层做以简要分析:
首先我们来分析最低层——设备驱动层,该层负责与外设——磁盘等——通讯。基于磁盘的文件系统都需要和存储设备打交道,而系统操作外设离不开驱动程序。所以内核对文件的最后操作行为就是调用设备驱动程序完成从主存(内存)到辅存(磁盘)的数据传输。文件系统相关的多数设备都属于块设备,常见的块设备驱动程序有磁盘驱动,光驱驱动等,之所以称它们为块设备,一个原因是它们读写数据都是成块进行的,但是更重要的原因是它们管理的数据能够被随机访问——不需要向字符设备那样必须顺序访问。
设备驱动层的上一层是物理I/O层,该层主要作为计算机外部环境和系统的接口,负责系统和磁盘交换数据块。它要知道据块在磁盘中存储位置,也要知道文件数据块在内存缓冲中的位置,另外它不需要了解数据或文件的具体结构。可以看到这层最主要的工作是标识别磁盘扇区和内存缓冲块[2]之间的映射关系。
再上层是基础I/O监督层,该层主要负责选择文件 I/O需要的设备,调度磁盘请求等工作,另外分配I/O缓冲和磁盘空间也在该层完成。由于块设备需要随机访问数据,而且对速度响应要求较高,所以操作系统不能向对字符设备那样简单、直接地发送读写请求,而必须对读写请求重新优化排序,以能节省磁盘寻址时间,另外也必须对请求提交采取异步调度(尤其写操作)的方式进行。总而言之,内核对必须管理块设备请求,而这项工作正是由该层负责的。
倒数第二层是逻辑I/O层,该层允许用户和应用程序访问记录。它提供了通用的记录(record)I/O操作,同时还维护基本文件数据。由于为了方便用户操作和管理文件内容,文件内容往往被组织成记录形式,所以操作系统为操作文件记录提供了一个通用逻辑操作层。
和用户最靠近的是访问方法层,该层提供了一个从用户空间到文件系统的标准接口,不同的访问方法反映了不同的文件结构,也反映了不同的访问数据和处理数据方法。这一层我们可以简单地理解为文件系统给用户提供的访问接口——不同的文件格式(如顺序存储格式、索引存储格式、索引顺序存储格式和哈希存储格式等)对应不同的文件访问方法。该层要负责将用户对文件结构的操作转化为对记录的操作。
对比上面的层次图我们再来分析一下数据流的处理过程,加深对文件系统的理解。
假如用户或应用程序操作文件(创建/删除),首先需要通过文件系统给用户空间提供的访问方法层进入文件系统,接着由使用逻辑I/O层对记录进行给定操作,然后记录将被转化为文件块,等待和磁盘交互。这里有两点需要考虑——第一,磁盘管理(包括再磁盘空闲区分配文件和组织空闲区);第二,调度块I/O请求——这些由基础I/O监督层的工作。再下来文件块被物理I/O层传递给磁盘驱动程序,最后磁盘驱动程序真正把数据写入具体的扇区。至此文件操作完毕。
当然上面介绍的层次结构是理想情况下的理论抽象,实际文件系统并非一定要按照上面的层次或结构组织,它们往往简化或合并了某些层的功能(比如Linux文件系统因为所有文件都被看作字节流,所以不存在记录,也就没有必要实现逻辑I/O层,进而也不需要在记录相关的处理)。但是大体上都需要经过类似处理。如果从处理对象上和系统独立性上划分,文件系统体系结构可以被分为两大部分:——文件管理部分和操作系统I/O部分。文件管理系统负责操作内存中文件对象,并按文件的逻辑格式将对文件对象的操作转化成对文件块的操作;而操作系统I/O部分负责内存中的块与物理磁盘中的数据交换。
数据表现形式再文件操作过程中也经历了几种变化:在用户访问文件系统看到的是字节序列,而在字节序列被写入磁盘时看到的是内存中文件块(在缓冲中),在最后将数据写入磁盘扇区时看到的是磁盘数据块[3]。
本文所说的实现文件系统主要针对最开始讲到第二种情况——内核中的逻辑文件结构(但其它相关的文件管理系统和文件系统磁盘存储格式也必须了解),我们用数据处理流图来分析一下逻辑文件系统主要功能和在操作系统中所处的地位。
其中文件系统接口与物理布局管理是逻辑文件系统要负责的主要功能。
文件系统接口为用户提供对文件系统的操作,比如open、close、read、write和访问控制等,同时也负责处理文件的逻辑结构。
物理存储布局管理,如同虚拟内存地址转化为物理内存地址时,必须处理段页结构一样,逻辑文件结构必须转化到物理磁盘中,所以也要处理物理分区和扇区的实际存储位置,分配磁盘空间和内存中的缓冲也要在这里被处理。
所以说要实现文件系统就必须提供上面提到的两种功能,缺一不可。
在了解了文件系统的功能后,我们针对Linux操作系统分析具体文件系统如何工作,进而掌握实现一个文件系统需要的步骤。
Linux 文件系统组成结构
Linux 文件系统的结构除了我们上面所提到的概念结构外,最主要有两个特点,一个是文件系统抽象出了一个通用文件表示层——虚拟文件系统或称做VFS。另外一个重要特点是它的文件系统支持动态安装(或说挂载、登陆等),大多数文件系统都可以作为根文件系统的叶子接点被挂在到根文件目录树下的子目录上。另外Linux系统在文件读写的I/O操作上也采取了一些先进技术和策略。
我们先从虚拟文件系统入手分析linux文件系统的特性,然后介绍有关文件系统的安装、注册和读写等概念。
虚拟文件系统
虚拟文件系统为用户空间程序提供了文件系统接口。系统中所有文件系统不但依赖VFS共存,而且也依靠VFS系统协同工作。通过虚拟文件系统我们可以利用标准的UNIX文件系统调用对不同介质上的不同文件系统进行读写操作[4]。
虚拟文件系统的目的是为了屏蔽各种各样不同文件系统的相异操作形式,使得异构的文件系统可以在统一的形式下,以标准化的方法访问、操作。实现虚拟文件系统利用的主要思想是引入一个通用文件模型——该模型抽象出了文件系统的所有基本操作(该通用模型源于Unix风格的文件系统),比如读、写操作等。同时实际文件系统如果希望利用虚拟文件系统,既被虚拟文件系统支持,也必须将自身的诸如,“打开文件”、“读写文件”等操作行为以及“什么是文件”,“什么是目录”等概念“修饰”成虚拟文件系统所要求的(定义的)形式,这样才能够被虚拟文件系统支持和使用。
我们可以借用面向对象的一些思想来理解虚拟文件系统,虚拟文件系统好比一个抽象类或接口,它定义(但不实现)了文件系统最常见的操作行为。而具体文件系统好比是具体类,它们是特定文件系统的实例。具体文件系统和虚拟文件系统的关系类似具体类继承抽象类或实现接口。而在用户看到或操作的都是抽象类或接口,但实际行为却发生在具体文件系统实例上。至于如何将对虚拟文件系统的操作转化到对具体文件系统的实例,就要通过注册具体文件系统到系统,然后再安装具体文件系统才能实现转化,这点可以想象成面向对象中的多态概念。
我们个实举例来说明具体文件系统如何通过虚拟文件系统协同工作。
例如:假设一个用户输入以下shell命令:
$ cp /hda/test1 /removable/test2
其中 /removable是MS-DOS磁盘的一个安装点,而 /hda 是一个标准的第二扩展文件系统( Ext2)的目录。cp命令不用了解test1或test2的具体文件系统,它所看到和操作的对象是VFS。cp首先要从ext3文件系统读出test1文件,然后写入MS-DOS文件系统中的test2。VFS会将找到ext3文件系统实例的读方法,对test1文件进行读取操作;然后找到MS-DOS(在Linux中称VFAT)文件系统实例的写方法,对test2文件进行写入操作。可以看到 VFS是读写操作的统一界面,只要具体文件系统符合VFS所要求的接口,那么就可以毫无障碍地透明通讯了。
Unix风格的文件系统
虚拟文件系统的通用模型源于Unix风格的文件系统,所谓Unix风格是指Unix传统上文件系统传统上使用了四种和文件系统相关的抽象概念:文件(file)、目录项(dentry)、索引节点(inode)和安装点(mount point)。
文件——在Unix中的文件都被看做是一有序字节串,它们都有一个方便用户或系统识别的名称。另外典型的文件操作有读、写、创建和删除等。
目录项——不要和目录概念搞混淆,在Linux中目录被看作文件。而目录项是文件路径中的一部分。一个文件路径的例子是“/home/wolfman/foo”——根目录是/,目录home,wolfman和文件foo都是目录项。
索引节点——Unix系统将文件的相关信息(如访问控制权限、大小、拥有者、创建时间等等信息),有时被称作文件的元数据(也就是说,数据的数据)被存储在一个单独的数据结构中,该结构被称为索引节点(inode)。
安装点——在Unix中,文件系统被安装在一个特定的安装点上,所有的已安装文件系统都作为根文件系统树中的叶子出现在系统中。
上述概念是Unix文件系统的逻辑数据结构,但相应的Unix文件系统(Ext2等)磁盘布局也实现了部分上述概念,比如文件信息(文件数据元)存储在磁盘块中的索引节点上。当文件被载如内存时,内核需要使用磁盘块中的索引点来装配内存中的索引接点。类似行为还有超级块信息等。
对于非Unix风格文件系统,如FAT或NTFS,要想能被VFS支持,它们的文件系统代码必须提供这些概念的虚拟形式。比如,即使一个文件系统不支持索引节点,它也必须在内存中装配起索引节点结构体——如同本身固有一样。或者,如果一个文件系统将目录看作是一种特殊对象,那么要想使用VFS,必须将目录重新表示为文件形式。通常,这种转换需要在使用现场引入一些特殊处理,使得非Unix文件系统能够兼容Unix文件系统的使用规则和满足VFS的需求。通过这些处理,非Unix文件系统便可以和VFS一同工作了,是性能上多少会受一些影响[5]。这点很重要,我们实现自己文件系统时必须提供(模拟)Unix风格文件系统的抽象概念。
Linux文件系统中使用的对象
Linux文件系统的对象就是指一些数据结构体,之所以称它们是对象,是因为这些数据结构体不但包含了相关属性而且还包含了操作自身结构的函数指针,这种将数据和方法进行封装的思想和面向对象中对象概念一致,所以这里我们就称它们是对象。
Linux文件系统使用大量对象,我们简要分析以下VFS相关的对象,和除此还有和进程相关的一些其它对象。
VFS相关对象
这里我们不展开讨论每个对象,仅仅是为了内容完整性,做作简要说明。
VFS中包含有四个主要的对象类型,它们分别是:
超级块对象,它代表特定的已安装文件系统。
索引节点对象,它代表特定文件。
目录项对象,它代表特定的目录项。
文件对象,它代表和进程打开的文件。
每个主要对象中都包含一个操作对象,这些操作对象描述了内核针对主要对象可以使用的方法。最主要的几种操作对象如下:
super_operations对象,其中包括内核针对特定文件系统所能调用的方法,比如read_inode()和sync_fs()方法等。
inode_operations对象,其中包括内核针对特定文件所能调用的方法,比如create()和link()方法等。
dentry_operations对象,其中包括内核针对特定目录所能调用的方法,比如d_compare()和d_delete()方法等。
file对象,其中包括,进程针对已打开文件所能调用的方法,比如read()和write()方法等。
除了上述的四个主要对象外,VFS还包含了许多对象,比如每个注册文件系统都是由file_system_type对象表示——描述了文件系统及其能力(如比如ext3或XFS);另外每一个安装点也都利用vfsmount对象表示——包含了关于安装点的信息,如位置和安装标志等。
其它VFS对象
系统上的每一进程都有自己的打开文件,根文件系统,当前工作目录,安装点等等。另外还有几个数据结构体将VFS层和文件的进程紧密联系,它们分别是:file_struct 和fs_struct
file_struct结构体由进程描述符中的files项指向。所有包含进程的信息和它的文件描述符都包含在其中。第二个和进程相关的结构体是fs_struct。该结构由进程描述符的fs项指向。它包含文件系统和进程相关的信息。每种结构体的详细信息不在这里说明了。
缓存对象
除了上述一些结构外,为了缩短文件操作响应时间,提高系统性能,Linux系统采用了许多缓存对象,例如目录缓存、页面缓存和缓冲缓存(已经归入了页面缓存),这里我们对缓存做简单介绍。
页高速缓存(cache)是 Linux内核实现的一种主要磁盘缓存。其目的是减少磁盘的I/O操作,具体的讲是通过把磁盘中的数据缓存到物理内存中去,把对磁盘的I/O操作变为对物理内存的I/O操作。页高速缓存是由RAM中的物理页组成的,缓存中每一页都对应着磁盘中的多个块。每当内核开始执行一个页I/O操作时(通常是对普通文件中页大小的块进行磁盘操作),首先会检查需要的数据是否在高速缓存中,如果在,那么内核就直接使用高速缓存中的数据,从而避免了访问磁盘。
但我们知道文件系统只能以每次访问数个块的形式进行操作。内核执行所有磁盘操作都必须根据块进行,一个块包含一个或多个磁盘扇区。为此,内核提供了一个专门结构来管理缓冲buffer_head。缓冲头[6]的目的是描述磁盘扇区和物理缓冲之间的映射关系和做I/O操作的容器。但是缓冲结构并非独立存在,而是被包含在页高速缓存中,而且一个页高速缓存可以包含多个缓冲。我们将在文件后面的文件读写部分看到数据如何被从磁盘扇区读入页高速缓存中的缓冲中的。
文件系统的注册和安装
使用文件系统前必须对文件系统进行注册和安装,下面分别对这两种行为做简要介绍。
文件系统的注册
VFS要想能将自己定义的接口映射到实际文件系统的专用方法上,必须能够让内核识别实际的文件系统,实际文件系统通过将代表自身属性的文件类型对象(file_system_type)注册(通过register_filesystem()函数)到内核,也就是挂到内核中的文件系统类型链表上,来达到使文件系统能被内核识别的目的。反过来内核也正是通过这条链表来跟踪系统所支持的各种文件系统的。
我们简要分析一下注册步骤:
struct file_system_type {
const char *name; /*文件系统的名字*/
int fs_flags; /*文件系统类型标志*/
/*下面的函数用来从磁盘中读取超级块*/
struct super_block * (*read_super) (struct file_system_type *, int,
const char *, void *);
struct file_system_type * next; /*链表中下一个文件系统类型*/
struct list_head fs_supers; /*超级块对象链表*/
};
其中最重要的一项是read_super()函数,它用来从磁盘上读取超级块,并且当文件系统被装载时,在内存中组装超级块对象。要实现一个文件系统首先需要实现的结构体便是file_system_type结构体。
注册文件系统只能保证文件系统能被系统识别,但此刻文件系统尚不能使用,因为它还没有被安装到特定的安装点上。所以在使用文件系统前必须将文件系统安装到安装点上。
文件系统被实际安装时,将在安装点创建一个vfsmount结构体。该结构体用代表文件系统的实例——换句话说,代表一个安装点。
vfsmount结构被定义在<linux/mount.h>中,下面是具体结构
―――――――――――――――――――――――――――――――――――――――
struct vfsmount
{
struct list_head mnt_hash; /*哈希表*/
struct vfsmount *mnt_parent; /*父文件系统*/
struct dentry *mnt_mountpoint; /*安装点的目录项对象*/
struct dentry *mnt_root; /*该文件系统的根目录项对象*/
struct super_block *mnt_sb; /*该文件系统的超级块*/
struct list_head mnt_mounts; /*子文件系统链表*/
struct list_head mnt_child; /*和父文件系统相关的子文件系统*/
atomic_t mnt_count; /*使用计数*/
int mnt_flags; /*安装标志*/
char *mnt_devname; /*设备文件名字*/
struct list_head mnt_list; /*描述符链表*/
};
――――――――――――――――――――――――――――――――――――――
文件系统如果仅仅注册,那么还不能被用户使用。要想使用它还必须将文件系统安装到特定的安装点后才能工作。下面我们接着介绍文件系统的安装[7]过程。
安装过程
用户在用户空间调用mount()命令——指定安装点、安装的设备、安装类型等——安装指定文件系统到指定目录。mount()系统调用在内核中的实现函数为sys_mount(),该函数调用的主要例程是do_mount(),它会取得安装点的目录项对象,然后调用do_add_mount()例程。
do_add_mount()函数主要做的是首先使用do_kern_mount()函数创建一个安装点,再使用graft_tree()将安装点作为叶子与根目录树挂接起来。
整个安装过程中最核心的函数就是do_kern_mount()了,为了创建一个新安装点(vfsmount),该函数需要做一下几件事情:
l 1 检查安装设备的权利,只有root权限才有能力执行该操作。
l 2 Get_fs_type()在文件链表中取得相应文件系统类型(注册时被填加到练表中)。
l 3 Alloc_vfsmnt()调用slab分配器为vfsmount结构体分配存储空间,并把它的地址存放在mnt局部变量中。
l 4 初始化mnt->mnt_devname域
l 5 分配新的超级块并初始化它。do_kern_mount( )检查file_system_type描述符中的标志以决定如何进行如下操作:根据文件系统的标志位,选择相应的方法读取超级块(比如对Ext2,romfs这类文件系统调用get_sb_dev();对于这种没有实际设备的虚拟文件系统如 ramfs调用get_sb_nodev())——读取超级块最终要使用文件系统类型中的read_super方法。
安装过程做的最主要工作是创建安装点对象,挂接给定文件系统到根文件系统的指定接点下,然后初始化超级快对象,从而获得文件系统基本信息和相关操作方法(比如读取系统中某个inode的方法)。
总而言之,注册过程是告之内核给定文件系统存在于系统内;而安装是请求内核对给定文件系统进行支持,使文件系统真正可用。
转载
『捌』 简述微软的FAT32文件系统
FAT32是分区格式的一种。这种格式采用32位的文件分配表,使其对磁盘的管理能力大大增强,突破了FAT16对每一个分区的容量只有2 GB的限制。由于现在的硬盘生产成本下降,其容量越来越大,运用FAT32的分区格式后,我们可以将一个大硬盘定义成一个分区而不必分为几个分区使用,大大方便了对磁盘的管理。但由于FAT32分区内无法存放大于4GB的单个文件,且性能不佳,易产生磁盘碎片。目前已被性能更优异的NTFS分区格式所取代。
分区标识: 0x0B, 0x0C(MBR)EBD0A0A2-B9E5-4433-87C0-68B6B72699C7(GPT)
最大单文件大小: 4 GB (Fat16分区是2 GB )
最大文件数量: 268,435,437
最长档名限制: 8.3 或者 长文件名255个字符
最大卷大小: 8 TB (在windows 2000和windows XP环境下格式化程序只能创建最大32GBFAT32文件系统,不过可以用如PQ等分区软件分出大于32GB的FAT32分区,大于32GB的FAT32分区在WIN2000/XP下使用完全正常)
记录日期: 创建、修改、访问
日期范围: 1980年1月1日至2107年12月31日
属性: 只读,隐藏,系统,卷标,子目录,档案
透明加密:不支持
透明压缩:不支持
访问许可:无限制
性能特点
编辑
FAT32(File Allocation Table)具有一个最大的优点:在一个不超过8GB 的分区中,FAT32分区格式的每个簇容量都固定为4KB,与FAT16相比,可以大大地减少磁盘的浪费,提高磁盘利用率。目前,支持这一磁盘分区格式的操作系统有Win95、Win98、Win2000、Win2003、Win Vista、Win7和Win10。部分智能手机也支持对FAT32格式存储器的直接读写(OTG)。但是,这种分区格式也有它的缺点,首先是采用FAT32格式分区的磁盘,由于文件分配表的扩大,运行速度比采用FAT16格式分区的磁盘要慢。
约束
编辑
windows 2000和windows XP能够读写任何大小的FAT32文件系统,但是这些平台上的格式化程序只能创建最大32GB的FAT32文件系统。
在dos系统下可直接访问fat32分区,而ntfs分区则不能在dos下直接访问。
缺点
编辑
但FAT有一个严重的缺点:当文件删除后写入新资料,FAT不会将档案整理成完整片段再写入,长期使用后会使档案资料变得逐渐分散,而减慢了读写速度。硬盘碎片整理是一种解决方法,但必须经常整理来保持FAT文件系统的效率。还有就是FAT32分区因为设计缺陷,无法支持超过4GB的单个文件.
磁盘结构
编辑
主启动区 文件 分配表 #1 文件 分配表 #2 根目录 其他所有资料...剩下磁盘空间
一个FAT文件系统包括四个不同的部分。
保留扇区
位于最开始的位置。第一个保留扇区是引导区(分区启动记录)。它包括一个称为基本输入输出参数块的区域(包括一些基本的文件系统信息尤其是它的类型和其它指向其它扇区的指针),通常包括操作系统的启动调用代码。保留扇区的总数记录在引导扇区中的一个参数中。引导扇区中的重要信息可以被DOS和OS/2中称为驱动器参数块的操作系统结构访问。
FAT区域
它包含有两份文件分配表,这是出于系统冗余考虑,尽管它很少使用,即使是磁盘修复工具也很少使用它。它是分区信息的映射表,指示簇是如何存储的。
根目录区域
它是在根目录中存储文件和目录信息的目录表。在FAT32下它可以存在分区中的任何位置,但是在早期的版本中它永远紧随FAT区域之后。
数据区域
这是实际的文件和目录数据存储的区域,它占据了分区的绝大部分。通过简单地在FAT中添加文件链接的个数可以任意增加文件大小和子目录个数(只要有空簇存在)。然而需要注意的是每个簇只能被一个文件占有,这样的话如果在32KB大小的簇中有一个1KB大小的文件,那么31KB的空间就浪费掉了。
启动扇区
编辑
格式如下
偏移(字节) 长度(字节) 说明
0x00 3 跳转指令(跳过开头一段区域)
0x03 8 OEM名称(空格补齐)。 MS-DOS检查这个区域以确定使用启动记录中的哪一部分数据。常见值是IBM 3.3(在“IBM”和“3.3”之间有两个空格)和MSDOS5.0.
0x0b 2 每个扇区的字节数。基本输入输出系统参数块从这里开始。
0x0d 1 每簇扇区数
0x0e 2 保留扇区数(包括启动扇区)
0x10 1 文件分配表数目
0x11 2 最大根目录条目个数
0x13 2 总扇区数(如果是0,就使用偏移0x20处的4字节值)
0x15 1 介质描述 0xF8 单面、每面80磁道、每磁道9扇区
0xF9 双面、每面80磁道、每磁道9扇区
0xFA 单面、每面80磁道、每磁道8扇区
0xFB 双面、每面80磁道、每磁道8扇区
0xFC 单面、每面40磁道、每磁道9扇区
0xFD 双面、每面40磁道、每磁道9扇区
0xFE 单面、每面40磁道、每磁道8扇区
0xFF 双面、每面40磁道、每磁道8扇区
同样的介质描述必须在重复复制到每份FAT的第一个字节。有些操作系统(MSX-DOS 1.0版)全部忽略启动扇区参数,而仅仅使用FAT的第一个字节的介质描述确定文件系统参数。
0x16 2 每个文件分配表的扇区(FAT16)
0x18 2 每磁道的扇区
0x1a 2 磁头数
0x1c 4 隐藏扇区
0x20 4 总扇区数(如果超过65535,参见偏移0x13)
0x24 4 每个文件分配表的扇区(FAT32)。扩展基本输入输出系统参数块从这里开始。
0x24 1 物理驱动器个数(FAT16)
0x25 1 当前磁头(FAT16)
0x26 1 签名(FAT16)
0x27 4 ID (FAT16)
0x28 2 Flags (FAT32)
0x2a 2 版本号 (FAT32)
0x2c 4 根目录启始簇 (FAT32)
0x2b 11 卷标(非FAT32)
0x30 2 FSInfo 扇区 (FAT32)
0x32 2 启动扇区备份 (FAT32)
0x34 2 保留未使用 (FAT32)
0x36 8 FAT文件系统类型(如FAT、FAT12、FAT16)
0x3e 2 操作系统自引导代码
0x40 1 BIOS设备代号 (FAT32)
0x41 1 未使用 (FAT32)
0x42 1 标记 (FAT32)
0x43 4 卷序号 (FAT32)
0x47 11 卷标(FAT32)
0x52 8 FAT文件系统类型(FAT32)
0x1FE 2 扇区结束符(0x55 0xAA)
这里描述的启动扇区能在如OS/2 1.3的启动盘上看到。早期的版本使用一个较短的基本输入输出系统参数块,它们的启动代码在前面开始(如OS/2 1.1中是偏移0x2b)。
分配表
编辑
一个分区分成同等大小的簇,也就是连续空间的小块。簇的大小随着FAT文件系统的类型以及分区大小而不同,典型的簇大小介于2KB到32KB之间。每个文件根据它的大小可能占有一个或者多个簇;这样,一个文件就由这些(称为单链表)簇链所表示。然而,这些链并不一定一个接着一个在磁盘上存储,它们经常是在整个数据区域零散的储存。
文件分配表(FAT)是映射到分区每个簇的条目列表。每个条目记录下面五种信息中的一种。
●链中下一个簇的地址
●一个特殊的文件结束符(EOF)符号指示链的结束
●一个特殊的符号标示坏簇
●一个特殊的符号标示保留簇
●0来表示空闲簇
FAT32条目值:
FAT32 描述
0x?0000000 空闲簇
0x?0000001 保留簇
0x?0000002 - 0x?FFFFFEF 被占用的簇;指向下一个簇
0x?FFFFFF0 - 0x?FFFFFF6 保留值
0x?FFFFFF7 坏簇
0x?FFFFFF8 - 0x?FFFFFFF 文件最后一个簇
注意FAT32只使用32位中的28位。高4位通常是0但它们是保留位,不要更改它们。在上面的表中它们用问号表示。
目录表
编辑
目录表是一个表示目录的特殊类型文件(现今通常称为文件夹)。它里面保存的每个文件或目录使用表中的32位条目表示。每个条目记录名字、扩展名、属性(档案、目录、隐藏、只读、系统和卷)、创建的日期和时间、文件/目录数据第一个簇的地址,最后是文件/目录的大小。
除了FAT12和FAT16文件系统中的根目录表占据特殊的根目录区域位置之外,所有其它的目录表都存在数据区域。
合法的DOS文件名包括下面一些字符:
●大写字母A-Z
●数字0-9
●空格(尽管结尾的空格被作为填充而不是文件名的一部分)
●! # $ amp;amp; ( ) - @ ^ _ ` { } ~ '
●数值 128-255
●DOS文件名位于OEM字符集。
长文件名(LFN)使用一个技巧存储在FAT文件系统上——在目录表中添加假的条目。这些条目使用一个普通文件无法使用的卷标属性标识,普通文件无法使用是由于它们被大多数旧的MS-DOS程序忽略。很显然,一个只包含卷标的目录被当作空卷,这样就允许删除;使用长文件名创建的文件在从普通的DOS删除就会发生这样的情形。
校验和也允许检验长文件名是否与8.3文件名匹配;当一个文件删除之后使用DOS在同一个目录位置重新创建之后就会出现不匹配现象。校验和使用下面的算法计算。(注意pFcbName是指向如正常目录条目中所显示的文件名的指针,例如前八个字符是文件名,最后三个是扩展名。点是隐含的。文件名中没有使用的空间将使用空格(ASCII 0x20)补齐。例如,“Readme.txt”将记录为"README TXT"。
特点
编辑
FAT32实际上是文件分区表采取的一种形式,它是相对于FAT16而言的。众所周知,Dos和Windows95采用的都是FAT16格式。至于FAT32,准确地说是在Windows95OSR2中第一次出现的,之所以没有宣传是因为当时该文件系统还不够成熟,尚处于试探阶段。那么为什么一定要推出FAT32呢?这主要是由其自身的优越性决定的。
首先,它可以大大地节约磁盘空间。文件在磁盘上是以簇的方式存放的,簇里存放了一个文件就不能再存放另外的文件。假如一个磁盘的分区大小为512MB,基于FAT16的系统的簇的大小为8KB,而FAT32系统的簇的大小仅是4KB,那么,我们存放一个3KB的文件,FAT16系统就会有5KB的空间被浪费,而FAT32的浪费则会少一些。如果分区达到1GB,FAT16的簇为16KB,而FAT32还是4KB,节省的也就更多了。
在推出FAT32文件系统之前,通常PC机使用的文件系统是FAT16。像基于MS-DOS,Win 95等系统都采用了FAT16文件系统。在Win 9X下,FAT16支持的分区最大为2GB。我们知道计算机将信息保存在硬盘上称为“簇”的区域内。使用的簇越小,保存信息的效率就越高。在FAT16的情况下,分区越大簇就相应的要增大,存储效率就越低,势必造成存储空间的浪费。并且随着计算机硬件和应用的不断提高,FAT16文件系统已不能很好地适应系统的要求。在这种情况下,推出了增强的文件系统FAT32。同FAT16相比,FAT32主要具有以下特点:
1. 同FAT16相比FAT32最大的优点是可以支持的磁盘大小达到2TB(2048GB),但是不能支持小于512MB的分区。基于FAT32的Win 2000可以支持分区最大为32GB;而基于 FAT16的Win 2000支持的分区最大2GB。
2. 由于采用了更小的簇,FAT32文件系统可以更有效率地保存信息。如两个分区大小都为2GB,一个分区采用了FAT16文件系统,另一个分区采用了FAT32文件系统。采用FAT16的分区的簇大小为32KB,而FAT32分区的簇只有4KB的大小。这样FAT32就比FAT16的存储效率要高很多,通常情况下可以提高15%。
3. FAT32文件系统可以重新定位根目录和使用FAT的备份副本。另外FAT32分区的启动记录被包含在一个含有关键数据的结构中,减少了计算机系统崩溃的可能性。
稍作补充:
功能
编辑
与以前的 FAT 文件系统实现相比,FAT32 提供了以下增强功能:FAT32 支持最大为 2 TB 的驱动器。
注意:Microsoft Windows 2000 仅能支持最大为 32 GB 的 FAT32 分区。
· FAT32 可以更高效地使用空间。FAT32 使用较小的簇(即,对于大小在 8 GB 以内的驱动器,使用 4 KB 的簇),这与大的 FAT 或 FAT16 驱动器相比,可将磁盘空间的使用率提高10% 到 15%。
· FAT32 更稳定可靠。FAT32 可以重新定位根文件夹,而且它使用文件分配表的备份副本,而不是使用默认副本。此外,FAT32 驱动器上的引导记录也得到扩展,包括了关键数据结构的备份副本。因此,与现有的 FAT16 驱动器相比,FAT32 驱动器不容易受单点故障的影响。
· FAT32 更灵活。FAT32 驱动器上的根文件夹是普通的簇链,因此它可以位于驱动器上的任何位置。以前对根文件夹数量的限制不复存在。此外,可以禁用文件分配表镜像,这样就可以让文件分配表的副本而不是让第一个文件分配表处于活动状态。这些功能允许您动态重调 FAT32 分区的大小。不过要注意,虽然 FAT32 的设计允许这一功能,但 Microsoft 将不在初始版本中实现此功能。
兼容性
编辑
软硬件的兼容问题
为了与现有程序、网络和设备驱动程序保持最大程度的兼容性,FAT32 尽可能不改动现有的 Windows 体系结构、内部数据结构、应用程序编程接口(API) 和磁盘上的格式。然而,因为需要 4 字节来存储簇值,所以许多内部的和磁盘上的数据结构以及发布的 API 都作了修改或扩展。某些情况下,现有的 API 无法在 FAT32 驱动器上运行。大多数程序不会受这些更改的影响。现有工具和驱动程序在 FAT32 驱动器上应能够继续正常运行。不过,MS-DOS 块设备驱动程序(例如 Aspidisk.sys)和磁盘工具需要经过修改才能支持 FAT32 驱动器。
所有 Microsoft 捆绑磁盘工具(格式化、Fdisk、碎片整理、基于 MS-DOS 和 Windows 的磁盘扫描)已经过修改,可以在 FAT32 中正常运行。此外,Microsoft 正在与一些主要设备驱动程序和磁盘工具厂商协作,帮助他们修改其产品以支持 FAT32。
注意:不能使用 Microsoft DriveSpace 或 DriveSpace 3 压缩 FAT32 卷。
FAT32 性能
转换到 FAT32 文件系统是您可以在基于 Windows 98 的计算机上实现的最大的一种性能增强。
双启动计算机
目前,在 Microsoft 操作系统中,只有 Windows 95 OSR2、Windows 98、Windows 2000 和 Windows Me 可以访问 FAT32 卷。MS-DOS、Windows 95 原始版和 Windows NT 4.0不识别 FAT32 分区,它们无法从 FAT32 卷启动。此外,如果使用其他操作系统(例如,Windows 95 或 MS-DOS 启动盘)启动计算机,也无法正确访问 FAT32 卷。
Windows 95 OSR2 和 Windows 98 可以在实模式下启动(例如为了运行游戏)并可以使用 FAT32 卷。
创建 FAT32 驱动器
在 Windows 95 OSR2、Windows 98 和 Windows Me 中,如果在大小超过 512 兆字节 (MB) 的硬盘上运行 Fdisk 工具,Fdisk 将提示您是否启用大磁盘支持。如果回答“是”(启用大磁盘支持),您创建的任何大于 512 MB 的分区都将被标记为 FAT32 分区。
Windows 98 和 Windows Me 中还带有一种 FAT32 转换工具,您可以使用该工具将现有驱动器转换为 FAT32 文件系统。要使用该转换工具,请按照下列步骤操作: 1. 单击开始,依次指向程序、附件、系统工具,然后单击驱动器转换器 (FAT32)。
2. 单击下一步。
3. 单击希望转换为 FAT32 文件系统的驱动器,然后单击下一步。
4. 按照屏幕上的说明操作。
支持范围
Microsoft 将支持 FAT32 文件系统的功能,以实现无错读取和在实模式或保护模式下保存文件。Microsoft 支持 Windows 95 中所带的实模式和保护模式工具。
对于旧式(较早的)程序,如果它们无法在 FAT32 卷上安装,或者无法正确保存文件或读取文件,则您必须与软件包的生产厂商联系。
注意:虽然 FAT32 文件系统所支持的硬盘容量最高可以达到 2 TB,但有些硬盘可能会因为计算机基本输入/输出系统 (BIOS) INT13 接口的限制而无法包含大于 7.8 GB 的可启动分区。请与硬件制造商联系,以确定您计算机的 BIOS 是否支持更新后的 INT13 扩展。
Windows 操作系统使用 FAT32 文件系统时存在以下限制: · 簇不能是 64 千字节 (KB) 或更大。如果簇为 64 KB 或更大,有些程序(例如安装程序)在计算磁盘空间时可能会出错。
· 卷必须包含至少 65,527 个簇,才能使用 FAT32 文件系统。您不能增加使用 FAT32 文件系统的卷上的簇大小,以致于该卷的最终簇数小于 65,527。
· 在使用 FAT32 文件系统的卷上,簇最多是 268,435,445 个。对于文件分配表 (FAT) 的空间,每簇最大为 32 KB,据此计算出的最大磁盘大小大约为 8 太字节 (TB)。
· Microsoft Windows 95 和 Microsoft Windows 98 附带的“磁盘扫描”工具是 16 位程序。这样的程序有一个内存块,该内存块的最大分配大小等于 16 MB 减去 64 KB。因此,Windows 95 或 Windows 98 的“磁盘扫描”工具无法处理使用 FAT32 文件系统(其 FAT 大小大于 16 MB 和 64 KB 之差)的卷。使用 FAT32 文件系统的卷上的 FAT 项使用 4 字节,因此 ScanDisk 无法处理使用定义的簇数大于 4,177,920(包括两个保留的簇)的 FAT32 文件系统的卷上的 FAT。将 FAT 本身包括在内,按每簇最大 32 KB 计算,卷大小为 127.53 吉字节 (GB)。
· 您不能减少使用 FAT32 文件系统的卷上的簇大小,以致于 FAT 的最终大小大于 16 MB 与 64 KB 之差。
· 在 Windows 2000 中您无法使用 FAT32 文件系统格式化大于 32 GB 的卷。Windows 2000 FastFAT 驱动程序可以安装和支持使用 FAT32 文件系统且大于 32 GB的卷(受到其他限制),但是您不能使用格式化工具创建一个这样的卷。这种现象是设计使然。如果需要创建大于 32 GB 的卷,请改用 NTFS 文件系统。
备注:在尝试格式化大于 32 GB 的 FAT32 分区时,在进程快结束时格式化会失败,并出现以下错误:
Logical Disk Manager:Volume size too big.
Windows XP 中 FAT32 文件系统的限制
当您在 Windows XP 中使用 FAT32 文件系统时,请注意下列限制: · 簇不能等于或大于 64 KB。如果簇大小为 64 KB 或更大,则有些程序(例如安装程序)可能会不正确地计算磁盘空间。
· FAT32 卷必须至少包含 65,527 个簇。您不能增加使用 FAT32 文件系统的卷上的簇大小,这样会导致卷上的簇数少于 65,527 个。
· 如果考虑到以下可变因素,则最大磁盘大小大约为 8 TB:一个 FAT32 卷上允许的最大簇数为 268,435,445,每个簇最大为 32 KB,另外还有文件分配表 (FAT) 所需的空间。
· 您不能减少 FAT32 卷上的簇大小,这样会导致 FAT 的大小大于 16 MB 减去 64 KB。
· 在 Windows XP 安装过程中,您不能使用 FAT32 文件系统格式化大于 32 GB 的卷。Windows XP 可以装入和支持大于 32 GB 的 FAT32 卷(受到其他限制),但是您不能在安装期间使用 Format 工具创建大于 32 GB 的 FAT32 卷。如果您需要格式化大于 32 GB 的卷,请使用 NTFS 文件系统来格式化。另一种方法是从 Microsoft Windows 98 或 Microsoft Windows Millennium Edition (Me) 启动盘启动,然后使用该磁盘上包含的 Format 工具。
有关如何使用 Microsoft Windows 98 或 Microsoft Windows Millennium Edition (Me) 启动盘格式化硬盘的其他信息,请单击下面的文章编号,以查看 Microsoft 知识库中相应的文章:
255867 如何使用 Fdisk 和 Format 工具对硬盘进行分区或重新分区
注意:当您在 Windows XP 安装过程中试图格式化大于 32GB 的 FAT32 分区时,在格式化过程快结束时操作将会失败,并且您可能会收到以下错误信息:
Logical Disk Manager:Volume size too big.
· MS-DOS(Microsoft Windows 95 的原始版本)和 Microsoft Windows NT 4.0 及更低版本都不能识别 FAT32 分区,因此无法从 FAT32 卷启动。
· 您不能在 FAT32 分区上创建大于 (2^32)-1 字节(即 4 GB 减去 1 个字节)的文件。
FAT32格式理论上可以支持128TB的磁盘,即簇的大小乘簇的数量32K*(2^32),但由于受到软硬件的限制和其他方面的原因,不可能达到理论值。
FAT32数据恢复原理
由于FAT与FDT对数据的链式存储管理的,可以通过链式搜索查找。只有FAT表和FDT配合使用,才能可以统一管理整个磁盘文件,也就可以准确定位被删除的文件的确切位置。
在 Window操作系统下对文件的删除工作,是将FDT中该文件的第一个字符改“E5”来表示该文件已经删除,同时改写引导扇区的第二个扇区中表示该分区点用空间大小的相应信息。因此,在设计的时候,根据其存储结构,对相应的地方进行修改,搜索E5删除标志,再按被删文件数据链进行恢复。
具体步骤如下:
(1) 获取磁盘分区类型,分区存储空间,FAT、FDT、文件等存储的起始扇区。
(2) 获取FAT、FDT表项内的簇信息,即簇的删除、未使用、坏簇、以及文件占用簇的起始扇区信息等。
(3) 通过FAT、FDT计算不同文件在物理扇区的起始位置,包括有标识E5的删除文件起始位置、文件类型、占用大小和存储的所在簇。
(4) 改变删除位,通过第3步得到的结果,并按FAT表对文件进行链式查找并缓存,当达到文件所在簇尾时,停止操作。
(5) 对存储的数据进行转换,生成文件。
(6) 对文件进行异区存储。 而操作系统在读写文件时,会根据FDT中的起始单元,结合FAT表就可以知道文件在磁盘的具体位置,然后顺序读取每个簇的内容,文件的查找如图4-1所示。具体的方法如下:
(1)在文件目录项FDT中找到存放在FAT表中的文件File的地址M(2)2*M得到FAT表中File文件数据链表首地址B。
(3)在FAT表中的B地址中,存放有文件FILE数据链表头P。
(4)P*4得到在FAT表项中的下一个偏移位置,即下一个数据链表。
(5)如果下一个数据链表内的信息为0FFFFFFH,则表示文件存储已经结束图4-1查找文件流程需要说明的是,新建文件时,系统在FAT表中查找未用的簇记录在新文件目录项中做为首簇;FDT文件目录项中,第0~7字节为文件名(若有剩余字节用20填充)第1字节表明了该文件的状态,第1字节又表明了文件的状态,00HE5H及其它字符。当该字节为00H时,表示该目录项是空表项,可以使用。当为E5H时,表示该目录项以前用过,但是文件被已经删除了。
小知识
编辑
在FAT32下,一个目录只能够容纳65536个文件。微软的官方文档没有给出。
『玖』 文件系统中分配存储空间的基本单位是什么
以块为单位进行的。
为了提高存储空间的利用率,对存储空间的分配,通常是采用离散分配方式,以减少外存零头,并以盘块为基本分配单位。盘块的大小通常为 1~8KB。
文件系统中,用于文件的描述和控制并与文件一一对应的是文件控制块。文件存储空间的管理就是块空间的管理,包括空闲块的分配、回收和组织等几个问题。
(9)链表文件系统扩展阅读:
位示图是利用二进制的一位来表示磁盘中一个盘块的使用情况。当其值为“0”时,表示对应的盘块空闲;为“1”时,表示已分配。有的系统把“0”作为盘块已分配的标志,把“1”作为空闲标志。(它们在本质上是相同的,都是用一位的两种状态来标志空闲和已分配两种情况。)
磁盘上的所有盘块都有一个二进制位与之对应,这样,由所有盘块所对应的位构成一个集合,称为位示图。通常可用 m × n 个位数来构成位示图,并使 m × n 等于磁盘的总块数。位示图也可描述为一个二维数组 map:Var map: array of bit;
『拾』 在linux操作系统内核实现里经常使用的红黑树
在linux操作系统内核实现里经常使用的红黑树如下:
二叉树,按中序遍历后为一递增数组,自平衡意味着树的高度有一个上限,对于红黑树,其为2log(n+1),所以时间复杂度为最差为Olog(n)。
赋予二叉搜索树自平衡特性的方法有多种,红黑树通过一下4条约束实现自平衡:
Every node is either red or black.
All NIL nodes (figure 1) are considered black.
A red node does not have a red child.
Everypathfrom a given node to any of its descendant NIL nodes goes through the same number of black nodes.
其中根节点为黑色。
红黑树的搜索与二叉搜索树无异,但是插入和删除可能会违背上述四条原则。需要用到左旋右旋操作。左旋右旋上图,可以看到左旋右旋本身不改变二叉搜索树的特性,旋转后必要时改变节点的颜色可消除插入或者删除带来的红冲突和黑冲突,有时红黑树的重新平衡需要迭代进行。
红黑树比较适合的应用场景:
需要动态插入、删除、查找的场景,包括但不限于:
某些数据库的增删改查,比如select * from xxx where 这类条件检索。
linux内核中进程通过红黑树组织管理,便于快速插入、删除、查找进程的task_struct。
linux内存中内存的管理:分配和回收。用红黑树组织已经分配的内存块,当应用程序调用free释放内存的时候,可以根据内存地址在红黑树中快速找到目标内存块。
hashmap中(key,value)增、删、改查的实现;java 8就采用了RBTree替代链表。
Ext3文件系统,通过红黑树组织目录项。