文件系统检查工具—fsck 研究
文件系统检查工具—fsck 研究
文件系统检查工具—fsck 研究
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()