02.07.2013 Views

文件系统检查工具—fsck 研究

文件系统检查工具—fsck 研究

文件系统检查工具—fsck 研究

SHOW MORE
SHOW LESS

Create successful ePaper yourself

Turn your PDF publications into a flip-book with our unique Google optimized e-Paper software.

第一部分 理论<br />

<strong>文件系统检查工具—fsck</strong> <strong>研究</strong><br />

导致一个文件系统 corrupt 的原因有可能有几种,而最经常的就是非正常关机流程和硬<br />

件的错误造成的。<br />

造成 corrupt 的主要原因就是在停止 CPU 之前没有同步系统数据。如果非正常的启动没<br />

有被检测到,比如没有检查文件系统的一致性,没有修复不一致的数据,允许使用一个 corrupt<br />

的文件系统将是一种灾难。另外,一部分硬件有可能在任何时间出错,如在磁盘上的一个坏<br />

的块,或者是磁盘控制器没有响应等。<br />

一般情况下 fsck 会在非交互的方式下运行。这种方式只会修正由 unclean halt 所引起的<br />

corrupt。这些操作一般是在交互方式下运行操作的子集。这份文档中我们默认 fsck 是在交<br />

互方式下运行的,所有可能的错误都会遇到。在这种方式下发现不一致的情况,则会将其汇<br />

报给操作者选择修正的方式。<br />

在执行 fsck 时,文件系统必须处于一种 quiescent state,因为 fsck 是一个 multi-pass 的<br />

程序。<br />

以下部分分别讨论发现数据不一致的方法,修正 cylinder group blocks,inodes, indirect<br />

blocks 和包含有目录项的 data blocks 的方法。<br />

1. Super block checking<br />

在一个文件系统中最经常 corrupt 掉的是 super block 中的汇总信息。原因是这些信息在<br />

文件系统的 block 或者 inode 的每项改动,都需要在汇总信息中做相应的修改。因此,经常<br />

会 corrupt(汇总信息与实际的文件系统信息不一致)。<br />

Super block 的一致性检查包括文件系统大小,inode 数量,空闲的 block 块,空闲的 inode<br />

数量等。文件系统的大小必须大于 super block 和 inode 使用的 block 数的和。文件系统的大<br />

小和布局信息是对 fsck 而言至关重要的信息。但并没有一种可以实际检查这些大小,因为<br />

他们是由 newfs 静态决定的,fsck 可以检查这些大小在一个合理的范围之内。如果 fsck 在默<br />

认的 super block 中的静态的参数中检查到 corrupt,它就会要求提供备用的 super block 所存<br />

放的地址。<br />

2. Free block checking<br />

Fsck 会检查所有在 cylinder group block maps (注:cylinder group 即对应于 fs 的 partions。)<br />

中标记为 free 的块,即没有被文件占用的块。Fsck 会检查 free 的块的数量与 inode 中声明使<br />

用的块的数量的和是否与整个文件系统的所有块数相等。<br />

如果在 block allocation maps 中有任何错误,fsck 将根据其计算的 allocated blocks 进行重<br />

新组建 block allocation maps。<br />

Super block 中也存有所有 free 块的数量信息,fsck 会把自己检查的结果与 super block 中<br />

的信息进行比较。如果这两个数不等,则 fsck 会将检查得到的结果更新到 super block 中。<br />

对文件系统中的 free inode 的数量,也会进行类似的处理。<br />

3. Checking the inode state


当文件系统中有很多 inode 存在的时候(即很多文件),有可能会有几个 inode corrupt。<br />

文件系统中的 inode 链表是从 inode2 开始顺序被检查的(inode0 标记没有用过的 inode,inode1<br />

用来将来的扩展),直到文件系统中的最后一个 inode。Inode 的状态检查包括:format and type,<br />

link count, duplicated blocks, bad blocks, and inode size。<br />

每个 inode 都有一个 mode word,它描述了 inode 的 type 和 state。Inode 必须处于六种类<br />

型之一:普通 inode,目录 inode,symbolink inode,special block inode,special character inode,<br />

或者是 socket inode。<br />

Indoe 有三种 allocation 状态:unallocated,allocated 和不属于前两种情况的情况。在第<br />

三种状态的 inode 就是不正确的 inode,当 inodes 链表被写入坏的数据的时候,inode 有可能<br />

进入这种状态。唯一可能修复的方法是 fsck 清空这个 inode(在链表中删除之)。<br />

4. inode links<br />

连接数是计算每一个 inode 与其相连的目录项的数目。fsck 从文件系统的 root 目录开始<br />

检查每一个 inode 的连接数,并沿着目录树依次查找。每个 inode 的实际 link count 在遍历的<br />

时候计算得到。<br />

如果存储的 link count 非 0,而计算的 link count 为 0,则此 inode 没有对应的目录项。这<br />

种情况下,fsck 将把这个对应的文件放入 lost+fount 目录中。如果存储的 link count 与实际<br />

计算所得的值非 0 且不相等,那么可能是 inode 的 link count 在有一个目录加入或删除的时<br />

候没有被相应更新。这种情况下,fsck 会用计算得到的值更新存储的值。<br />

每个 inode 都包含一个列表或者是列表的指针,上面记录着这个 inode 所使用的数据块。<br />

因为 inode 是这些列表的拥有者,因此,当这些列表存在不一致的情况时,就直接影响到拥<br />

有它的 inode。<br />

Fsck 会将一个 inode 声明的 block number 与列表中已经分配的 block number 比较。如果<br />

另一个 inode 已经声明了一个 block number,那么这个 block number 就被加入到一个 duplicate<br />

block 链表中。否则,已分配的 block list 中会将这个 block number 加入。<br />

对任何 duplicate blocks,fsck 将会遍历 inode list 找到拥有 duplicated block 的 inode。一般<br />

而言,拥有最早修改时间的 inode 坏掉的可能性比较大,需要被 clear。如果是这种情况,fsck<br />

将执行操作,clear 这两个 inodes。操作必须决定,哪个该留,哪个该 clear。<br />

Fsck 检查每个 inode 声明的 block number 的范围,如果 block number 比文件系统中第一<br />

个数据块的块号低,或者比文件系统中的最后一个数据块的块号大,则称为 bad block<br />

number。一个 inode 中许多的 bad blocks 经常是由于一个 indirect block 没有被写入到文件系<br />

统中,发生这种情况的前提是由于有硬件异常的产生。如果一个 inode 含有 bad block<br />

numbers,fsck 会将其 clear。<br />

5. inode data size<br />

每个 inode 包含一定数量的 data blocks。实际 data block 的数量是所有 allocated data blocks<br />

和 indirect blocks 的总和。Fsck 计算实际的 data blocks 的数量,并与 inode 所记录的数值进<br />

行比较。如果两者不一致,fsck 会进行修正。<br />

每个 inode 包含了一个 32 位的 size 域。这个数是 inode 对应的文件所含有的字节数。这<br />

个 size 域的一致性检查是通过计算与 inode 对应的最大数量的 blocks 数,与实际 inode 保存<br />

的数值比较。<br />

6. checking the data with an inode<br />

一个 inode 可以直接或间接的 reference 三种类型的 data blocks。所有的 referenced blocks<br />

必须是同种类型。这三种类型是:plain data blocks, symbolic link data blocks 和 directory data<br />

blocks。Plain data blocks 包含文件中保存的信息。symbolic link data blocks 包含一个 link 中


包含的路径名。Directory data blocks 包含目录项。Fsck 只能检查 directory data blocks 的有效<br />

性。<br />

Fsck 会检查每个 directory data block 的几种一致性:directory inode 指向 unallocated<br />

inodes,directory inodes 的数量比文件系统中的 inode 数量大,不正确的“.”“..”directory inode<br />

numbers,没有结合在文件系统中的 directories。如果一个 directory data block 中的 inode<br />

number references 一个 unallocated inode, fsck 将移除这个 directory entry(目录项)。这种<br />

情况只发生在存在硬件异常的情况下。<br />

Fsck 检查与 unallocated blocks(holes)对应的 directories。这些 directories 应当从不被创<br />

建。当发现这些 directory 存在时,fsck 将通过缩短这些 directory 大小到上一个 allocated block<br />

结尾的 hole 来提示用户去调整这些 directory。但同时这又意味着另一轮的第一部分检查需<br />

要执行---super blockchecking。Fsck 将提示用户在修改一个含有 unlocated block 的 directory<br />

后重新执行 fsck。(Whenfound, fsck will prompt the user to adjust the length of the offending<br />

directory which is done by shortening the size of the directory to the end of the last allocated block<br />

preceeding the hole. Unfortunately, this means that another Phase 1 run has to be done. Fsck will<br />

remind the user to rerun fsck after repairing a directory containing an unallocated block.)<br />

如果一个 directory entry 的 inode number reference 不在 inode list 中,fsck 会移除这个<br />

directory entry。这一般发生在 bad data 被写入到 a directory data block 的情况下。<br />

“.”的 directory inode number entry 必须是 directory data block 的第一个 entry。“.”的 inode<br />

number 必须 reference 它自己。比如,它必须等于 directory data block 的 inode number。“..”<br />

的 directory inode number entry 必须是 directory data block 的第二个 entry,它的值必须与这个<br />

directory entry 的父目录的 inode number 相等(如果是 root directory 的“..”,则与其 directory<br />

data block 的 inode number 相等)。如果 directory inode numbers 不正确,fsck 将用正确的值<br />

取代它。如果目录有许多 hard links,第一个被认为是“..”指向的真正的父目录,fsck 会倾<br />

向于删除其后出现的名称。(recommends deletion for the subsequently discovered names.(不<br />

懂这句话)<br />

7. File system connectivity<br />

Fsck 检查文件系统的 general connectivity。如果目录不被 link 到文件系统中,fsck 会 link<br />

directory 到文件系统中的 lost+found directory。这个条件只有在发生硬件异常的时候成立。<br />

翻译文献:Fsck - The UNIX File System Check Program Marshall Kirk McKusick<br />

第二部分 dosfsck 实现分析<br />

1. Main 函数:<br />

Main()<br />

{<br />

解析输入参数;<br />

fs_open();//打开设备文件,并取得其 fd 以全局使用<br />

read_boot();//读取并检查 boot_sector 内容<br />

while (read_fat(&fs), scan_root(&fs)) qfree(&mem_queue);//检查分区表, 根目录以及所有子目<br />

录文件<br />

if (test) fix_bad(&fs); //扫描所有的 cluster,如果一个 cluster 没有 owener,且在 fat 表中记录


的值不合法,则进一步判断不可读,在 fat 表中将其标示为“bad”。<br />

if (salvage_files) reclaim_file(&fs); //-f 位设定的话,将 cluster chain 中的 free cluster 归为文件<br />

所有,<br />

else reclaim_free(&fs);// 否则归 free disk 所有。<br />

free_clusters = update_free(&fs); // 读取磁盘上的 free cluster 数目,并更新到 fs 结构中。<br />

file_unused();//??????<br />

qfree(&mem_queue);//问题:memory 是怎样使用的?<br />

if (verify) {<br />

printf("Starting verification pass.\n");<br />

read_fat(&fs);<br />

scan_root(&fs);<br />

reclaim_free(&fs); //reclaim unused cluster<br />

qfree(&mem_queue);<br />

}<br />

if (fs_changed()) {<br />

}<br />

if (rw) {<br />

}<br />

else<br />

}<br />

if (interactive)<br />

rw = get_key("yn","Perform changes ? (y/n)") == 'y';<br />

else printf("Performing changes.\n");<br />

printf("Leaving file system unchanged.\n");<br />

printf( "%s: %u files, %lu/%lu clusters\n", argv[optind],<br />

n_files, fs.clusters - free_clusters, fs.clusters );<br />

return fs_close(rw) ? 1 : 0;<br />

2. read_boot()<br />

主要完成的工作是检查 boot sector 中的内容。取得相应的文件系统信息。<br />

Read_boot(){<br />

读取分区的 boot sector;<br />

If(扇区字节数为 0,或者 cluster 大小为 0)<br />

{<br />

}<br />

退出<br />

If(分区表的数目>2 或者


检查 root_cluster 和 root_entries;<br />

如果是 FAT32,则检查 check_backup_boot().<br />

检查总 cluster 数与 fat 表能表示的最大数是否冲突;<br />

检查 root directory 的大小是否为 0;<br />

检查 root entry 是否占据整数个扇区;<br />

检查逻辑扇区是否是物理扇区的整数倍。<br />

检查 boot sector 记录的是否非法磁盘格式;<br />

If(verbose)<br />

Dump_boot();//把 boot sector 信息输出到内存中。<br />

}<br />

3. read_fat<br />

主要完成的工作是检查 FAT 的有效性。一般 fat 文件系统会有 2 个 fat,因此可以检查哪个<br />

可用,或者采用交互的方式由用户决定采用哪个 fat。<br />

Read_fat()<br />

{<br />

将 fat 表拷贝到内存中。<br />

如果含有 2 个分区表,则比较这 2 个表是否一致。<br />

If(2 个分区表不同)<br />

}<br />

{<br />

判断 2 个分区表的有效性(判断依据需要进一步探讨);<br />

只有一个有效则用有效的一个覆盖无效的一个;<br />

两个都有效<br />

If(用户交互模式)<br />

{根据用户的选择}<br />

Else{选择第二个有效}<br />

Else 选择第一个。<br />

如果 2 个分区表都无效,则文件系统崩溃,退出检查。(无法修复)<br />

检查 fat 表的内容,如果出现越界(超过能标示 cluster 号的最大值),则设置为 eof。<br />

4. scan_root<br />

主要完成<br />

函数逻辑:<br />

Scan_root(){<br />

Check_dir(); //root<br />

Check_files() //root<br />

Subdirs(); //sub directories and files<br />

}<br />

(1) check_dir<br />

check_dir()<br />

{<br />

检查目录下的所有文件(包括文件夹)的文件名。<br />

如果一个文件夹下的文件包含太多的坏文件名,则对其进行截断操作。


遍历所有文件{<br />

If(是 . 或者 ..)<br />

{<br />

}<br />

Handle_dot();<br />

If(文件夹属性非 ATTR_VOLUME,而且文件名不合法)<br />

}<br />

{<br />

}<br />

If(用户交互模式)<br />

{<br />

}<br />

1) Drop file\n2) Rename file\n3) Auto-rename\n 4) Keep it<br />

检查是否含有相同名字的文件,有则"1) Drop first\n2) Drop second\n3) Rename first\n"<br />

}遍历结束<br />

检查是否含有. 和..<br />

}<br />

(2) check_files<br />

"4) Rename second\n5) Auto-rename first\n"<br />

"6) Auto-rename second\n");。<br />

static int check_files(DOS_FS *fs,DOS_FILE *start)<br />

{<br />

}<br />

while (start) {<br />

if (check_file(fs,start)) return 1;<br />

start = start->next;<br />

}<br />

return 0;<br />

(3) check_file()<br />

if(文件是目录)<br />

{<br />

if(文件的 dentry 大小不为 0)<br />

{<br />

}<br />

if(是”.”)<br />

{<br />

录的 cluster 号;<br />

}<br />

if(是”..”)<br />

设置文件的 dentry 大小为 0;<br />

开始的 cluster 号是否与 parent 相同。如果不同则将“.”的 cluster 号改为 parent 目


{<br />

开始的 cluster 号是否与 parent 的 parent 目录相同。如果不同则将“.”的 cluster 号<br />

改为 parent 的 parent 目录的 cluster 号;<br />

}<br />

}<br />

if(FSTART(file,fs)==0)<br />

{<br />

}<br />

删除此指向 root 的目录。<br />

if(FSTART(file,fs) >= fs->clusters+2)<br />

{<br />

}<br />

超出文件系统的 cluster 界限,则改为 EOF????MODIFY_START(file,0,fs);<br />

遍历所有的 cluster<br />

{<br />

遇到“bad”cluster 则改为 EOF;MODIFY_START(file,0,fs);<br />

如果 root 含有 bad cluster,则退出;<br />

If(文件非目录 且文件记录的大小小于实际 cluster 大小“遍历 cluster 得到的数值”)<br />

{<br />

则对文件进行截短处理。修改文件的大小值,并修改 cluster chain,全部标记<br />

为 free,即只存在 entry 记录,无对应实体数据?<br />

}<br />

If(current cluster 有拥有者)<br />

{<br />

}<br />

有错误;<br />

需要选择保留哪个?<br />

设置 current cluster 的 owner。<br />

}(遍历结束)<br />

If(文件的实际大小大于 entry 记录中的文件大小)<br />

{<br />

}<br />

}<br />

修改 entry 记录文件大小的值为实际的大小。<br />

(4) subdirs()<br />

static int subdirs(DOS_FS *fs,DOS_FILE *parent,FDSC **cp)<br />

{<br />

DOS_FILE *walk;<br />

for (walk = parent ? parent->first : root; walk; walk = walk->next)<br />

if (walk->dir_ent.attr & ATTR_DIR)<br />

if (strncmp(walk->dir_ent.name,MSDOS_DOT,MSDOS_NAME) &&


}<br />

return 0;<br />

(5) scan_dir<br />

scan_dir()<br />

{<br />

}<br />

strncmp(walk->dir_ent.name,MSDOS_DOTDOT,MSDOS_NAME))<br />

if (scan_dir(fs,walk,file_cd(cp,walk->dir_ent.name))) return 1;<br />

Check_dir()<br />

Check_files()

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!