Ext2文件系统初步
鸣谢:http://blog.youkuaiyun.com/kai_ding/article/details/9173903
一、磁盘布局
1.1概述
任何Ext2分区中的第一个块从不受Ext2文件系统的管理,因为这一块是为分区的引导扇区所保留的。Ext2分区的其余部分被分割成块组(blockgroup),每个块组的分布图如下图1所示。正如你从图中所看到的,一些数据结构正好可以放在一块中,而另一些可能需要更多的块。在Ext2文件系统中的所有块组大小相同并被顺序存放,因此,内核可以从块组的整数索引很容易地得到磁盘中一个块组的位置。
图1ext2磁盘布局
由于内核尽可能地把属于同一个文件的数据块存放在同一块组中,所以块组可有效地提高文件连续性。每个块组均包含如下方面的信息:
假定blocksize=4k=4096bytes
内容 | 大小(以block计) | 计算 | 使用结构 |
SuperBlock(超级块) | 1 |
| ext2_super_block |
GDT(组描述符表) | n |
| ext2_group_desc |
Datablock bitmap | 1 | 1*blocksize*8 |
|
Inodebitmap | 1 | 1*blocksize*8 |
|
Inodetable | m | (inode_per_group*inode_size+block_size-1)/block_size | ext2_inode |
Datablocks | p |
|
|
block_group_count=partition_capacity/(block_size*data_block_per_group)
block_per_group=block_size*8;
以下计算GDT所占blocks.
1.计算group descriptor number,即blockgroup的数量.
number_of_gdt=partition_block_count/block_per_group;
2,计算GDT占的block数。
2.a求出每个block能容纳多少GDT结构
gdt_per_block=block_size/sizeof(structext2_group_desc);
2.b求出所有GDT需要占多少个block。
blocks_of_gdt=number_of_gdt/gdt_per_block;
3.保留的GDTblock数量
即结构ext2_super_block的s_padding1字段值。
GDT所占的block为保留的block数量与现有GDT所占block数量之和。
事实上,只有块组0中所包含超级块和组描述符才由内核使用,而其余的超级块和组描述符都保持不变,内核甚至不考虑它们。当e2fsck程序对Ext2文件系统的状态执行一致性检查时,就引用存放在块组0中的超级块和组描述符,然后将它们拷贝到其他所有的块组中。如果出现数据损坏,并且块组0中的主超级块和主描述符变为无效,那么,系统管理员就可以命令e2fsck引用存放在某个块组(除了第一个块组)中的超级块和组描述符的旧拷贝。通常情况下,这些多余的拷贝所存放的信息足以让e2fsck把Ext2分区带回到一个一致的状态。
使用dumpe2fs/dev/sda1命令,可以看到,只有group0, group 1包含Superblock和Groupdescriptor,其余的group不含superblock和GDT. Group 0的superblock为主超级块,Group1的superblock为备份超级块。
那么每个ext2文件系统到底有多少块组呢?这取决于分区的大小和块的大小。其主要限制在于块位图,块位图用来标识一个块组中块的占用和空闲状况,而且ext2块位图只占据一个单独的数据块。所以,每组中至多可包含8×b个块,b是以字节为单位的块大小。例如,一个块大小是1024Byte,那么,一个块的位图就有8192个位,正好就对应8192个块。因此,块组的总数大约是c/8×b,这里c是指分区所包含的总块组数。
举例说明,让我们考虑一下32GB的Ext2分区,换算成KB就是33554432,假定块大小为4KB。在这种情况下,每个4KB的块位图描述32KB个数据块,即128MB。因此,最多需要33554432/ 4096 * 32 =256个块组。显然,块越大,块组数越小。
inode表用于存储块组内文件的inode信息,而且只存储该块组内存储文件的inode,我们知道inode是一个文件元数据的全部,是文件身份的象征,没有inode,文件在磁盘上就是不可见的。ext2文件系统格式化时,块组内的inode表空间也一并分配好,也就意味着该块组内能存储多少个文件是确定的,那么这个值是如何确定的呢,依稀记得mkfs.ext2在格式化时,会将块组存储文件的平均大小设定为16KB,这样,知道了块组大小和文件平均大小就可以计算块组内存储文件数量了。
inode位图用来管理块组内空闲inode分配情况。
由亍(1)一个inode占用128bytes ,(2)总共有1641- 629 + 1(629本身)= 1013个 block
花在 inodetable 上,(3)每个block癿大小为4096bytes(4K)。由这些数据可以算出inode
癿数量共有1013* 4096 / 128 = 32416个 inode啦!
数据块用来存储块组内的文件数据,使用位图方式管理空闲数据块。
至此,我们已经大概了解了ext2文件系统的磁盘布局,ext2这种布局方式是源自FFS的设计思想:尽量将文件的数据元数据连续存放,同时尽量将相关文件连续存放,所谓的相关文件诸如相同目录下的所有文件,ext2的块组思想就是连续的最好体现,关于ext2的文件创建和数据块分配后续还会仔细研究。
二、数据结构
头文件:/usr/include/linux/ext2_fs.h
2.1 ext2超级块
这里的超级块指的是ext2文件系统存储在磁盘上的超级块结构,之所以这么说是因为每个文件系统除了存储在磁盘上的超级块外,还在内存中也存储了一个超级块结构,基本上内存中的超级块是在磁盘超级块的基础上增加了一些额外的管理信息而成,因此,在这里我们主要关注的是ext2存储在磁盘上的超级块的数据结构。
ext2磁盘超级块的定义如下:
/*
*Structure of the super block
*/
structext2_super_block {
__le32 s_inodes_count; /* Inodes count */
__le32 s_blocks_count; /* Blocks count */
__le32 s_r_blocks_count; /* Reserved blocks count */
__le32 s_free_blocks_count; /* Free blocks count */
__le32 s_free_inodes_count; /* Free inodes count */
__le32 s_first_data_block; /* First Data Block */
__le32 s_log_block_size; /* Block size */
__le32 s_log_frag_size; /* Fragment size */
__le32 s_blocks_per_group; /* # Blocks per group */
__le32 s_frags_per_group; /* # Fragments per group */
__le32 s_inodes_per_group; /* # Inodes per group */
__le32 s_mtime; /* Mount time */
__le32 s_wtime; /* Write time */
__le16 s_mnt_count; /* Mount count */
__le16 s_max_mnt_count; /* Maximal mount count */
__le16 s_magic; /* Magic signature */
__le16 s_state; /* File system state */
__le16 s_errors; /* Behaviour when detecting errors */
__le16 s_minor_rev_level; /* minor revision level */
__le32 s_lastcheck; /* time of last check */
__le32 s_checkinterval; /* max. time between checks */
__le32 s_creator_os; /* OS */
__le32 s_rev_level; /* Revision level */
__le16 s_def_resuid; /* Default uid for reserved blocks */
__le16 s_def_resgid; /* Default gid for reserved blocks */
/*
*These fields are for EXT2_DYNAMIC_REV superblocks only.
*
*Note: the difference between the compatible feature set and
*the incompatible feature set is that if there is a bit set
*in the incompatible feature set that the kernel doesn't
*know about, it should refuse to mount the filesystem.
*
*e2fsck's requirements are more strict; if it doesn't know
*about a feature in either the compatible or incompatible
*feature set, it must abort and not try to meddle with
*things it doesn't understand...
*/
__le32 s_first_ino; /* First non-reserved inode */
__le16 s_inode_size; /* size of inode structure */
__le16 s_block_group_nr; /* block group # of this superblock */
__le32 s_feature_compat; /* compatible feature set */
__le32 s_feature_incompat; /* incompatible feature set */
__le32 s_feature_ro_compat; /* readonly-compatible feature set */
__u8 s_uuid[16]; /* 128-bit uuid for volume */
char s_volume_name[16]; /* volume name */
char s_last_mounted[64]; /* directory where last mounted */
__le32 s_algorithm_usage_bitmap; /* For compression */
/*
*Performance hints. Directory preallocation should only
*happen if the EXT2_COMPAT_PREALLOC flag is on.
*/
__u8 s_prealloc_blocks; /* Nr of blocks to try to preallocate*/
__u8 s_prealloc_dir_blocks; /* Nr to preallocate for dirs */
__u16 s_padding1;
/*
*Journaling support valid if EXT3_FEATURE_COMPAT_HAS_JOURNAL set.
*/
__u8 s_journal_uuid[16]; /* uuid of journal superblock */
__u32 s_journal_inum; /* inode number of journal file */
__u32 s_journal_dev; /* device number of journal file */
__u32 s_last_orphan; /* start of list of inodes to delete */
__u32 s_hash_seed[4]; /* HTREE hash seed */
__u8 s_def_hash_version; /* Default hash version to use */
__u8 s_reserved_char_pad;
__u16 s_reserved_word_pad;
__le32 s_default_mount_opts;
__le32 s_first_meta_bg; /* First metablock block group */
__u32 s_reserved[190]; /* Padding to the end of the block */
};
可以看到,ext2磁盘超级块结构中大部分是描述整个文件系统的信息,如文件系统中块组的数量,inode数量,磁盘块的数量等等,不一而足,基本上从代码的注释我们就能比较清楚各个成员的含义,而且在后续的文章中我们或多或少地也会遇到这些成员。
2.2 ext2块组描述符
前面的描述中我们知道,ext2文件系统将磁盘(分区)划分成大小相等的块组,以提高文件存取的连续性。而且块组中存在inode表,inode位图,数据块位图中众多信息,因此,有必要对每个块组生成一个描述符来管理块组,在ext2中,该数据结构如下定义:
/*
*Structure of a blocks group descriptor
*/
structext2_group_desc
{
__le32 bg_block_bitmap; /* Blocks bitmap block */
__le32 bg_inode_bitmap; /* Inodes bitmap block */
__le32 bg_inode_table; /* Inodes table block */
__le16 bg_free_blocks_count; /* Free blocks count */
__le16 bg_free_inodes_count; /* Free inodes count */
__le16 bg_used_dirs_count; /* Directories count */
__le16 bg_pad;
__le32 bg_reserved[3];
};
相对来说,块组描述符简单得多了,记录了块组中数据块位图和inode位图的块号,这些块号是相对于块组而言的,而非绝对块号,另外还记录了该块组中创建的目录数量,之所以记录这个是在后来创建目录时会将其作为考虑的参数,之所以这样做的目的是将目录分散在文件系统的所有块组中,避免某些块组过满而另外一些较为空闲的情况。
2.3 ext2文件目录项
熟悉文件系统的朋友都知道,在linux文件系统中是通过目录一级一级索引直至找到最终的文件。文件是被组织在目录下的,要先找到文件我们必须先定位其所在目录,而且在linux中,一切皆文件,目录也是一个文件,也有数据块,其数据块中保存的是该目录下所有文件和子目录的文件目录项,因此,linux下文件查找的过程便是读出目录的数据块,在其中查找感兴趣的文件的文件目录项,进而访问文件更详细的信息。
因此,对于ext2文件来说,每个文件的首先的元数据信息便是文件目录项,而且它是存储在磁盘上的,只不过它是存储在父目录的数据块中,但这并不影响其重要性,ext2文件系统的文件目录项结构如下:
#defineEXT2_NAME_LEN 255
structext2_dir_entry_2 {
__le32 inode; /* Inode number */
__le16 rec_len; /* Directory entry length */
__u8 name_len; /* Name length */
__u8 file_type;
char name[EXT2_NAME_LEN]; /* File name */
};
文件目录项主要是存储文件名至文件inode的映射关系,这样,根据文件名在父目录数据块中查找感兴趣文件就能获取该文件的inode号,进而可以得到该文件的所有信息。
在该结构中,inode代表该文件inode编号,rec_len表示本文件目录项的大小,为什么需要这个rec_len呢,结构体定义好了整个长度不也就确认了嘛?非也,这是因为该结构体的最后一个成员name并不是固定长度的,其最大可以支持256字节,因此必须要有一个长度域来保存当前目录项长度,name_len指的是文件名长度,既然已经有了rec_len,为什么还需要文件名长呢,岂不多此一举?这是考虑到存在文件名填充的问题。从效率上来考虑,每个structext2_dir_entry_2最终都会被填充成4字节整数倍,对于目录项不是4字节整数倍的,需要在最后name文件名后面填充若干个0,因此name_len中记录的便是name[]域中有效文件名长度(即不包含0)。考虑下图所示事例:
1.“.”和“..”文件名后都填充了‘\0’以使文件目录项总长度为4的整数倍;
2.music和src文件/目录均也填充了‘\0’以使文件目录项总长度为4的整数倍;
3.test.txt因为其文件目录项已经是16个字节,无需填充。
2.4 ext2索引节点
该数据结构可能是一个文件最重要的元数据信息了,因为描述文件的一切属性都保存在这里了(除了文件名),重要性不言而喻,而且索引节点也是持久化存储在磁盘之上,每个块组都有专门的inode表来存储文件索引节点,ext2的索引节点结构如下所描述:
/*
*Structure of an inode on the disk
*/
structext2_inode {
__le16 i_mode; /* File mode */
__le16 i_uid; /* Low 16 bits of Owner Uid */
__le32 i_size; /* Size in bytes */
__le32 i_atime; /* Access time */
__le32 i_ctime; /* Creation time */
__le32 i_mtime; /* Modification time */
__le32 i_dtime; /* Deletion Time */
__le16 i_gid; /* Low 16 bits of Group Id */
__le16 i_links_count; /* Links count */
__le32 i_blocks; /* Blocks count */
__le32 i_flags; /* File flags */
union{
struct{
__le32 l_i_reserved1;
}linux1;
struct{
__le32 h_i_translator;
}hurd1;
struct{
__le32 m_i_reserved1;
}masix1;
}osd1; /* OS dependent 1 */
__le32 i_block[EXT2_N_BLOCKS];/* Pointers to blocks */
__le32 i_generation; /* File version (for NFS) */
__le32 i_file_acl; /* File ACL */
__le32 i_dir_acl; /* Directory ACL */
__le32 i_faddr; /* Fragment address */
union{
struct{
__u8 l_i_frag; /* Fragment number */
__u8 l_i_fsize; /* Fragment size */
__u16 i_pad1;
__le16 l_i_uid_high; /* these 2 fields */
__le16 l_i_gid_high; /* were reserved2[0] */
__u32 l_i_reserved2;
}linux2;
struct{
__u8 h_i_frag; /* Fragment number */
__u8 h_i_fsize; /* Fragment size */
__le16 h_i_mode_high;
__le16 h_i_uid_high;
__le16 h_i_gid_high;
__le32 h_i_author;
}hurd2;
struct{
__u8 m_i_frag; /* Fragment number */
__u8 m_i_fsize; /* Fragment size */
__u16 m_pad1;
__u32 m_i_reserved2[2];
}masix2;
}osd2; /* OS dependent 2 */
};
索引节点记录了文件的各种属性,如文件大小,文件模式,文件各种时间信息,文件数据块位置信息等。而其中最重要的信息就得数文件数据块位置了。ext2文件系统采用了巧妙的办法来记录文件数据块,兼顾了效率和空间利用率,具体方法如下图所示:
ext2中,将文件的数据块索引组织成数组的形式。在ext2的索引结构中有一个i_block[],该数组共有15项,每项记录的都是物理磁盘块号,其中前12项记录的是一级索引,即该该索引记录的是文件数据块地址,因此,对于小于12个数据块的文件来说,只需要查一次索引即可获得文件数据块位置。i_block[]的第13项是一个二级索引,即其中的块号指向的并不是文件数据块,而是一个存储索引的数据块,该索引数据块中保存了文件数据块的块号,因此,对于大一点的文件,获取数据可能得经历两次索引查询,另外还有三级索引等等,ext2就是采取这种策略来组织文件数据,这种方式在后面的博客中还会有专门的篇幅来阐述,这里点到为止。
三、从ext2文件系统上读出超级块
本节将仔细分析如何从格式化为ext2文件系统的磁盘中读取超级块并填充内存超级块结构,每次将一个格式化了ext2文件系统的磁盘(分区)挂载到挂载点的时候会调用该方法,该方法在操作系统中的实现主要是函数ext2_fill_super。
实现
在之前的章节中描述了ext2的磁盘划分,所以读取超级块的过程也就显得比较简单,只是在读取完成后可能需要进行一些列的检查等。直接来看该函数的实现。下面分为几段来阐述其实现机理。
第一阶段:从磁盘读出超级块
staticint ext2_fill_super(struct super_block *sb, void *data, int silent)
{
structbuffer_head * bh;
structext2_sb_info * sbi;
structext2_super_block * es;
structinode *root;
unsignedlong block;
unsignedlong sb_block = get_sb_block(&data);
unsignedlong logic_sb_block;
unsignedlong offset = 0;
unsignedlong def_mount_opts;
longret = -EINVAL;
//defaultblock size is 1024B
intblocksize = BLOCK_SIZE;
intdb_count;
inti, j;
__le32features;
interr;
//allocatememory ext2_super_block in memory
sbi= kzalloc(sizeof(*sbi), GFP_KERNEL);
if(!sbi)
return-ENOMEM;
sbi->s_blockgroup_lock=
kzalloc(sizeof(structblockgroup_lock), GFP_KERNEL);
if(!sbi->s_blockgroup_lock) {
kfree(sbi);
return-ENOMEM;
}
//sbis vfs super_block
//sb->s_fs_infois specific file system super block
sb->s_fs_info= sbi;
sbi->s_sb_block= sb_block;
spin_lock_init(&sbi->s_lock);
/*
*See what the current blocksize for the device is, and
*use that as the blocksize. Otherwise (or if the blocksize
*is smaller than the default) use the default.
*This is important for devices that have a hardware
*sectorsize that is larger than the default.
*/
//theblock size can't be smaller than BLOCK_SIZE=1024B
//andblock size must be smaller than PAGE_SIZE = 4096B now
blocksize= sb_min_blocksize(sb, BLOCK_SIZE);
if(!blocksize) {
ext2_msg(sb,KERN_ERR, "error: unable to set blocksize");
gotofailed_sbi;
}
/*
*If the superblock doesn't start on a hardware sector boundary,
*calculate the offset.
*/
//blocksizemay bigger than BLOCK_SIZE=1024B
//becausewe read blocksize bytes data from disk
//Block0 is 1024B and super_block is also 1024B
//ifblocksize is not 1024B,it must be bigger than 1024B,for example,ifblocksize is 2048B
//wemust read block 0(first 2048B on disk),then we read offset 1024~2047as super block
if(blocksize != BLOCK_SIZE) {
logic_sb_block= (sb_block*BLOCK_SIZE) / blocksize;
offset= (sb_block*BLOCK_SIZE) % blocksize;
}else {
logic_sb_block= sb_block;
}
//readblock @logic_sb_block containg super block
if(!(bh = sb_bread(sb, logic_sb_block))) {
ext2_msg(sb,KERN_ERR, "error: unable to read superblock");
gotofailed_sbi;
}
/*
*Note: s_es must be initialized as soon as possible because
* some ext2 macro-instructions depend on its value
*/
es= (struct ext2_super_block *) (((char *)bh->b_data) + offset);
//sbiis ext2_super_block in memory while sbi->s_es is ext2_super_blockon disk
sbi->s_es= es;
第一部分的函数是核心,它负责从ext2的磁盘(分区)上读出超级块,那么这里的问题就产生了:
超级块的起始位置在哪?
超级块的大小是多少?
在实现中我们自己定义的块大小(默认1024)与磁盘设备的块大小如果不一致怎么办?
所以我们看上面的很多代码其实都是在处理这个问题。让我们一一来解答。
首先,超级块位于磁盘(分区)的第二个1024位置上,因为第一个1024字节默认作为引导块,文件系统并不使用。
其次,ext2的超级块大小也为1024字节,这在ext2超级块数据结构的定义中可以看出。
最后,因为读之前我们默认磁盘块大小是1024字节,但磁盘设备定义的块大小可能不同,有可能是2048,4096等等,而我们读磁盘数据的时候是以逻辑块为单位读取的(虽然最终的物理读取是以扇区为单位的),因此,我们必须确定到底块大小是多少,如果决定块大小是1024,那我们只需读出第二个磁盘块即可读出超级块,而如果块大小是2048,那我们读出第一个磁盘块,然后再取1024~2047这一段,下图比较清晰地阐述了这个过程:
另外,我们读出超级块是要缓存在内存中的,而内存中的超级块结构需要在磁盘超级块结构上增添一些管理成员。ext2内存超级块结构为structext2_sb_info。
ext2_fill_super所展示的第一段代码所做工作主要有:
分配ext2内存超级块结构structext2_sb_info,如果分配内存失败,则直接返回-ENOMEM;
确定逻辑磁盘块大小,比较默认逻辑块大小和真实逻辑块大小(根据磁盘设备的一些信息确定),将最大者设置为逻辑块大小,但注意:该最大者必须是2的次幂且不可大于4096
从磁盘上读出超级块根据2中计算的块大小确定超级块所在逻辑块号和块内偏移,读出超级块,存储在1中分配的内存超级块结构中sbi->s_es= es。
第二阶段:根据磁盘超级块初始化内存超级块的成员
上文描述的第一阶段从磁盘上读出了超级块内容,接下来我们就要根据磁盘上的超级块结构来初始化内存超级块结构,在这个过程中可能还伴随着磁盘超级块内容的检查,确认其是否已经损坏等。
sb->s_magic= le16_to_cpu(es->s_magic);
if(sb->s_magic != EXT2_SUPER_MAGIC)
gotocantfind_ext2;
/*Set defaults before we parse the mount options */
/*接下来这段根据磁盘超级块
**结构来设置内存超级块结构的部分选项
**相比较而言这些选项的重要性没那么高
*/
def_mount_opts= le32_to_cpu(es->s_default_mount_opts);
if(def_mount_opts & EXT2_DEFM_DEBUG)
set_opt(sbi->s_mount_opt,DEBUG);
if(def_mount_opts & EXT2_DEFM_BSDGROUPS)
set_opt(sbi->s_mount_opt,GRPID);
if(def_mount_opts & EXT2_DEFM_UID16)
set_opt(sbi->s_mount_opt,NO_UID32);
#ifdefCONFIG_EXT2_FS_XATTR
if(def_mount_opts & EXT2_DEFM_XATTR_USER)
set_opt(sbi->s_mount_opt,XATTR_USER);
#endif
#ifdefCONFIG_EXT2_FS_POSIX_ACL
if(def_mount_opts & EXT2_DEFM_ACL)
set_opt(sbi->s_mount_opt,POSIX_ACL);
#endif
/*这个选项决定了挂载出错时的处理方法
**如PANIC即指示出错就奔溃...
*/
if(le16_to_cpu(sbi->s_es->s_errors) == EXT2_ERRORS_PANIC)
set_opt(sbi->s_mount_opt,ERRORS_PANIC);
elseif (le16_to_cpu(sbi->s_es->s_errors) == EXT2_ERRORS_CONTINUE)
set_opt(sbi->s_mount_opt,ERRORS_CONT);
else
set_opt(sbi->s_mount_opt,ERRORS_RO);
sbi->s_resuid= le16_to_cpu(es->s_def_resuid);
sbi->s_resgid= le16_to_cpu(es->s_def_resgid);
set_opt(sbi->s_mount_opt,RESERVATION);
if(!parse_options((char *) data, sb))
gotofailed_mount;
sb->s_flags= (sb->s_flags & ~MS_POSIXACL) |
((EXT2_SB(sb)->s_mount_opt& EXT2_MOUNT_POSIX_ACL) ?
MS_POSIXACL: 0);
ext2_xip_verify_sb(sb);/* see if bdev supports xip, unset
EXT2_MOUNT_XIPif not */
if(le32_to_cpu(es->s_rev_level) == EXT2_GOOD_OLD_REV &&
(EXT2_HAS_COMPAT_FEATURE(sb,~0U) ||
EXT2_HAS_RO_COMPAT_FEATURE(sb,~0U) ||
EXT2_HAS_INCOMPAT_FEATURE(sb,~0U)))
ext2_msg(sb,KERN_WARNING,
"warning:feature flags set on rev 0 fs, "
"runninge2fsck is recommended");
/*
*Check feature flags regardless of the revision level, since we
*previously didn't change the revision level when setting the flags,
*so there is a chance incompat flags are set on a rev 0 filesystem.
*/
features= EXT2_HAS_INCOMPAT_FEATURE(sb, ~EXT2_FEATURE_INCOMPAT_SUPP);
if(features) {
ext2_msg(sb,KERN_ERR, "error: couldn't mount because of "
"unsupportedoptional features (%x)",
le32_to_cpu(features));
gotofailed_mount;
}
if(!(sb->s_flags & MS_RDONLY) &&
(features= EXT2_HAS_RO_COMPAT_FEATURE(sb, ~EXT2_FEATURE_RO_COMPAT_SUPP))){
ext2_msg(sb,KERN_ERR, "error: couldn't mount RDWR because of "
"unsupportedoptional features (%x)",
le32_to_cpu(features));
gotofailed_mount;
}
相对来说,这部分的代码重要性没那么高,我们无需花费太多的精力,简单阅读下注释即可。
第三阶段:根据磁盘超级块初始化内存超级块的成员
第二阶段初始化内存超级块的只是一些比较简单的选项,到了这个阶段,初始化的东西就比较重要了,它关乎着文件系统的正确性。因此我们作比较详细的分析。
/*
**超级块中可能记录着逻辑块大小,因此我们必须
**以此为准
*/
blocksize= BLOCK_SIZE << le32_to_cpu(sbi->s_es->s_log_block_size);
if(ext2_use_xip(sb) && blocksize != PAGE_SIZE) {
if(!silent)
ext2_msg(sb,KERN_ERR,
"error:unsupported blocksize for xip");
gotofailed_mount;
}
/*If the blocksize doesn't match, re-read the thing.. */
/*如果块大小和我们之前确定的不太一样
**我们有必要重新读一次超级块
**因为之前读的可能并不准确
*/
if(sb->s_blocksize != blocksize) {
brelse(bh);
if(!sb_set_blocksize(sb, blocksize)) {
ext2_msg(sb,KERN_ERR, "error: blocksize is too small");
gotofailed_sbi;
}
logic_sb_block= (sb_block*BLOCK_SIZE) / blocksize;
offset= (sb_block*BLOCK_SIZE) % blocksize;
bh= sb_bread(sb, logic_sb_block);
if(!bh){
ext2_msg(sb,KERN_ERR, "error: couldn't read"
"superblockon 2nd try");
gotofailed_sbi;
}
es= (struct ext2_super_block *) (((char *)bh->b_data) + offset);
sbi->s_es= es;
if(es->s_magic != cpu_to_le16(EXT2_SUPER_MAGIC)) {
ext2_msg(sb,KERN_ERR, "error: magic mismatch");
gotofailed_mount;
}
}
/*计算ext2最大可支持文件的大小*/
sb->s_maxbytes= ext2_max_size(sb->s_blocksize_bits);
if(le32_to_cpu(es->s_rev_level) == EXT2_GOOD_OLD_REV) {
sbi->s_inode_size= EXT2_GOOD_OLD_INODE_SIZE;
sbi->s_first_ino= EXT2_GOOD_OLD_FIRST_INO;
}else {
sbi->s_inode_size= le16_to_cpu(es->s_inode_size);
sbi->s_first_ino= le32_to_cpu(es->s_first_ino);
if((sbi->s_inode_size < EXT2_GOOD_OLD_INODE_SIZE) ||
!is_power_of_2(sbi->s_inode_size)||
(sbi->s_inode_size> blocksize)) {
ext2_msg(sb,KERN_ERR,
"error:unsupported inode size: %d",
sbi->s_inode_size);
gotofailed_mount;
}
}
/*对于逻辑块较大的ext2文件系统,为了
**减少块内碎片问题,设置了fragment,
**即每个磁盘块内可再细分成多个fragment
**这个思想源自FFS,对于1024大小的磁盘块
**也就没有必要再划分fragment了
**因为最小的fragment大小就是1024字节
*/
sbi->s_frag_size= EXT2_MIN_FRAG_SIZE <<
le32_to_cpu(es->s_log_frag_size);
if(sbi->s_frag_size == 0)
gotocantfind_ext2;
/*初始化一些静态信息*/
sbi->s_frags_per_block= sb->s_blocksize / sbi->s_frag_size;
sbi->s_blocks_per_group= le32_to_cpu(es->s_blocks_per_group);
sbi->s_frags_per_group= le32_to_cpu(es->s_frags_per_group);
sbi->s_inodes_per_group= le32_to_cpu(es->s_inodes_per_group);
if(EXT2_INODE_SIZE(sb) == 0)
gotocantfind_ext2;
sbi->s_inodes_per_block= sb->s_blocksize / EXT2_INODE_SIZE(sb);
if(sbi->s_inodes_per_block == 0 || sbi->s_inodes_per_group == 0)
gotocantfind_ext2;
sbi->s_itb_per_group= sbi->s_inodes_per_group /
sbi->s_inodes_per_block;
sbi->s_desc_per_block= sb->s_blocksize /
sizeof(struct ext2_group_desc);
sbi->s_sbh= bh;
sbi->s_mount_state= le16_to_cpu(es->s_state);
sbi->s_addr_per_block_bits=
ilog2(EXT2_ADDR_PER_BLOCK(sb));
sbi->s_desc_per_block_bits=
ilog2(EXT2_DESC_PER_BLOCK(sb));
if(sb->s_magic != EXT2_SUPER_MAGIC)
gotocantfind_ext2;
if(sb->s_blocksize != bh->b_size) {
if(!silent)
ext2_msg(sb,KERN_ERR, "error: unsupported blocksize");
gotofailed_mount;
}
/*目前仅支持块大小和fragmentsize大小相同*/
if(sb->s_blocksize != sbi->s_frag_size) {
ext2_msg(sb,KERN_ERR,
"error:fragsize %lu != blocksize %lu"
"(notsupported yet)",
sbi->s_frag_size,sb->s_blocksize);
gotofailed_mount;
}
if(sbi->s_blocks_per_group > sb->s_blocksize * 8) {
ext2_msg(sb,KERN_ERR,
"error:#blocks per group too big: %lu",
sbi->s_blocks_per_group);
gotofailed_mount;
}
if(sbi->s_frags_per_group > sb->s_blocksize * 8) {
ext2_msg(sb,KERN_ERR,
"error:#fragments per group too big: %lu",
sbi->s_frags_per_group);
gotofailed_mount;
}
if(sbi->s_inodes_per_group > sb->s_blocksize * 8) {
ext2_msg(sb,KERN_ERR,
"error:#inodes per group too big: %lu",
sbi->s_inodes_per_group);
gotofailed_mount;
}
if(EXT2_BLOCKS_PER_GROUP(sb) == 0)
gotocantfind_ext2;
sbi->s_groups_count= ((le32_to_cpu(es->s_blocks_count) -
le32_to_cpu(es->s_first_data_block)- 1)
/EXT2_BLOCKS_PER_GROUP(sb)) + 1;
db_count= (sbi->s_groups_count + EXT2_DESC_PER_BLOCK(sb) - 1) /
EXT2_DESC_PER_BLOCK(sb);
sbi->s_group_desc= kmalloc (db_count * sizeof (struct buffer_head *), GFP_KERNEL);
if(sbi->s_group_desc == NULL) {
ext2_msg(sb,KERN_ERR, "error: not enough memory");
gotofailed_mount;
}
bgl_lock_init(sbi->s_blockgroup_lock);
/*这个数据结构干嘛的现在还不得而知*/
sbi->s_debts= kcalloc(sbi->s_groups_count, sizeof(*sbi->s_debts),GFP_KERNEL);
if(!sbi->s_debts) {
ext2_msg(sb,KERN_ERR, "error: not enough memory");
gotofailed_mount_group_desc;
}
/*读出块组描述符信息*/
for(i = 0; i < db_count; i++) {
block= descriptor_loc(sb, logic_sb_block, i);
sbi->s_group_desc[i]= sb_bread(sb, block);
if(!sbi->s_group_desc[i]) {
for(j = 0; j < i; j++)
brelse(sbi->s_group_desc[j]);
ext2_msg(sb,KERN_ERR,
"error:unable to read group descriptors");
gotofailed_mount_group_desc;
}
}
if(!ext2_check_descriptors (sb)) {
ext2_msg(sb,KERN_ERR, "group descriptors corrupted");
gotofailed_mount2;
}
sbi->s_gdb_count= db_count;
get_random_bytes(&sbi->s_next_generation,sizeof(u32));
spin_lock_init(&sbi->s_next_gen_lock);
/*per fileystem reservation list head & lock */
//initsomething for reservation windows of every file
spin_lock_init(&sbi->s_rsv_window_lock);
sbi->s_rsv_window_root= RB_ROOT;
/*
*Add a single, static dummy reservation to the start of the
*reservation window list --- it gives us a placeholder for
*append-at-start-of-list which makes the allocation logic
*_much_ simpler.
*/
/*初始化内存超级块的预留窗口
**所谓的预留窗口是在分配数据块的时候
**每一次多分配一点,以提高文件数据存储
**的连续性
*/
sbi->s_rsv_window_head.rsv_start= EXT2_RESERVE_WINDOW_NOT_ALLOCATED;
sbi->s_rsv_window_head.rsv_end= EXT2_RESERVE_WINDOW_NOT_ALLOCATED;
sbi->s_rsv_window_head.rsv_alloc_hit= 0;
sbi->s_rsv_window_head.rsv_goal_size= 0;
ext2_rsv_window_add(sb,&sbi->s_rsv_window_head);
err= percpu_counter_init(&sbi->s_freeblocks_counter,
ext2_count_free_blocks(sb));
if(!err) {
err= percpu_counter_init(&sbi->s_freeinodes_counter,
ext2_count_free_inodes(sb));
}
if(!err) {
err= percpu_counter_init(&sbi->s_dirs_counter,
ext2_count_dirs(sb));
}
if(err) {
ext2_msg(sb,KERN_ERR, "error: insufficient memory");
gotofailed_mount3;
}
/*
*set up enough so that it can read an inode
*/
sb->s_op= &ext2_sops;
sb->s_export_op= &ext2_export_ops;
sb->s_xattr= ext2_xattr_handlers;
#ifdefCONFIG_QUOTA
sb->dq_op= &dquot_operations;
sb->s_qcop= &dquot_quotactl_ops;
#endif
这里面涉及到的细节问题比较多,但是都比较简单,主要是文件系统各种统计数据的计算等,这里不再赘述,请直接参考代码注释。
第四阶段:构造根目录
当超级块完全读出并构造内存超级块以后,接下来就是构造根目录了,让我们直接看代码:
/*读根目录的inode,inode号为默认值2
**读出后保存在内存inode结构中
*/
root= ext2_iget(sb, EXT2_ROOT_INO);
if(IS_ERR(root)) {
ret= PTR_ERR(root);
gotofailed_mount3;
}
if(!S_ISDIR(root->i_mode) || !root->i_blocks || !root->i_size){
iput(root);
ext2_msg(sb,KERN_ERR, "error: corrupt root inode, run e2fsck");
gotofailed_mount3;
}
/*分配根目录的内存目录项
**因为根目录没有父目录这个概念
**因此,没法从其父目录中读出其目录
**只能在内存中构造一个
*/
sb->s_root= d_alloc_root(root);
if(!sb->s_root) {
iput(root);
ext2_msg(sb,KERN_ERR, "error: get root inode failed");
ret= -ENOMEM;
gotofailed_mount3;
}
if(EXT2_HAS_COMPAT_FEATURE(sb, EXT3_FEATURE_COMPAT_HAS_JOURNAL))
ext2_msg(sb,KERN_WARNING,
"warning:mounting ext3 filesystem as ext2");
if(ext2_setup_super (sb, es, sb->s_flags & MS_RDONLY))
sb->s_flags|= MS_RDONLY;
/*在填充超级块时有可能会修改磁盘超级块
**因此有必要作一次写回操作
*/
ext2_write_super(sb);
return0;
到此,整个从磁盘读出超级块直至填充内存超级块结构的过程就结束了,整个流程虽然繁杂,但还算简单。
四、查看ext3文件系统分区的superblock
4.1查看磁盘分区
fdisk-l
下面查看/dev/sda3(这是一个devicefile)
4.2拷贝superblock数据到文件
从/dev/sda3拷贝1024B-2048B(这就是superblock的位置)到一个叫superblock的文件
#dd if=/dev/sda3 bs=1k skip=1 count=1of=superblock
这里我们用到了dd这个工具,它在对磁盘进行这样读写操作时非常有用,具体请见man。
bs=1k每次读写的块大小
skip=1偏移多少块开始读(这里块为bs=1k,偏移1,表示从1k开始读)
count=1读的大小(这里表示读1k)
if,of分别表示从哪里读和写到哪里去。
记住第一个Block的第一个1k处是存储的BootBlock,而superblock是从1k开始,大小为1k。
4.3查看superblock文件
用vim打开我们刚才从/dev/sda3复制出来的superblock文件,由于是二进制文件,我们在用vim打开之后要用:%!xxd转换成16进制形式。
到这里其实我们已经看到了在磁盘上superblock的形式了。下面我想验证一个这个superblock的正确性。
首先,通过查看/usr/include/linux/ext2_fs.h头文件中的structext2_super_block:
struct ext2_super_block {
__le32s_inodes_count; /* Inodes count */
__le32 s_blocks_count; /*Blocks count */
__le32 s_r_blocks_count; /* Reserved blocks count*/
__le32 s_free_blocks_count; /* Free blocks count */
__le32s_free_inodes_count; /* Free inodes count */
__le32s_first_data_block; /* First Data Block */
__le32s_log_block_size; /* Block size */
__le32 s_log_frag_size; /*Fragment size */
__le32 s_blocks_per_group; /* # Blocks per group*/
__le32 s_frags_per_group; /* # Fragments per group */
__le32s_inodes_per_group; /* # Inodes per group */
__le32 s_mtime; /*Mount time */
__le32 s_wtime; /* Write time */
__le16s_mnt_count; /* Mount count */
__le16 s_max_mnt_count; /* Maximalmount count */
__le16 s_magic; /* Magic signature */
__le16s_state; /* File system state */
__le16 s_errors;
....
我们可以知道在superblock的头上记录着inodes的个数和block的个数,它们分别是32位。
那从superblock中可以看出:
inodes_count:0000 3a04(注意:这里是小数端)–> 043a0000 = 70909952
blocks_count: fdc4 3904 –>0439c4fd = 70894845
那么该如何验证这个结果呢?linux为我们提供了dumpe2fs这个工具:
dumpe2fs/dev/sda3|less
我们可以看到如下的输出(一部分)
到这里,有关如果看superblock的方法,这里就全部都讲完了,同样我们可以用dd查看磁盘上任意的部分。
五 编程读取superblock结构体
Linux就是直接打开对应的设备文件,然后用通常的read函数读取即可。
下面为示例程序。
#include<fcntl.h>
#include<stdio.h>
#include<linux/fs.h>
#include<linux/ext2_fs.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<unistd.h>
intmain()
{
structext2_super_block esb;
intfileno;
fileno=open("/dev/sda1",O_RDONLY);
lseek(fileno,1024,SEEK_SET); //superblock起始于1024字节处。
read(fileno,&esb, sizeof(struct ext2_super_block));
printf("inodescount: %ld\n", esb.s_inodes_count);
printf("blockscount: %ld\n",esb.s_blocks_count);
printf("freeblocks count: %ld\n",esb.s_free_blocks_count);
printf("freeinodes count: %ld\n", esb.s_free_inodes_count);
printf("firstdata block: %ld\n", esb.s_first_data_block);
printf("blocksper group: %ld\n", esb.s_blocks_per_group);
printf("inodesper group: %ld\n", esb.s_inodes_per_group);
printf("firstinodes: %ld\n",esb.s_first_ino);
printf("inodesize: %d\n", esb.s_inode_size);
return0;
}
六、创建新文件/目录
本节主要阐述ext2文件系统创建文件和目录的实现原理。文件系统中每个文件/目录有两个结构来描述该文件的元数据信息,第一是文件目录项即structdentry,主要保存文件名至文件inode号的映射,第二是文件inode结构,保存文件元数据信息。而对应在磁盘上,每个文件目录项保存在父目录的数据块中,而文件inode在ext2文件系统中则保存在磁盘的特定区域(块组的inodetable中),每个磁盘inode均对应一个唯一的编号。
因此,总的说来,创建一个文件最主要的步骤就是:1.为文件创建一个文件目录项,2.为文件创建一个inode结构并分配inode号,将inode编号与文件名映射关系保存在1中分配的文件目录项中,3.将1中创建的文件目录项写入到父目录的数据区。而其中最主要和最复杂的过程是2。
实现
linux内核中创建文件的入口函数为sys_create()。该函数的实现如下:
SYSCALL_DEFINE2(creat,const char __user *, pathname, int, mode)
{
returnsys_open(pathname, O_CREAT | O_WRONLY | O_TRUNC, mode);
}
它只是对sys_open的简单封装,并将open的打开标志设置为O_CREAT| O_WRONLY | O_TRUNC。
[cpp]view plaincopy
SYSCALL_DEFINE3(open, const char __user *,filename, int, flags, int, mode)
{
long ret;
if(force_o_largefile())
flags |= O_LARGEFILE;
ret =do_sys_open(AT_FDCWD, filename, flags, mode);
/* avoid REGPARMbreakage on x86: */
asmlinkage_protect(3, ret, filename, flags,mode);
return ret;
}
sys_open的实现较为复杂,其复杂性体现在do_sys_open()函数,关于该函数我们不去深入钻研太多,在后面的博客中我们会仔细分析open的处理流程。
[cpp]view plaincopy
long do_sys_open(int dfd, const char __user*filename, int flags, int mode)
{
char *tmp =getname(filename);
int fd = PTR_ERR(tmp);
if(!IS_ERR(tmp)) {
fd = get_unused_fd_flags(flags);
if (fd >=0) {
struct file *f = do_filp_open(dfd, tmp, flags, mode, 0);
if (IS_ERR(f)) {
put_unused_fd(fd);
fd = PTR_ERR(f);
}else {
fsnotify_open(f);
fd_install(fd, f);
}
}
putname(tmp);
}
return fd;
}
do_sys_open()接着调用了do_filp_open()。这一路走下去,对于文件创建的分支,最后调用的函数为vfs_create(),至于这其中的逻辑和函数跳转,我们就不再仔细描述,在后续的博客中会有详细描述。
intvfs_create(struct inode *dir, struct dentry *dentry, int mode,
struct nameidata *nd)
{
int error = may_create(dir,dentry);
if (error)
return error;
if(!dir->i_op->create)
return -EACCES; /* shouldn't it beENOSYS? */
mode &= S_IALLUGO;
mode |= S_IFREG;
error= security_inode_create(dir, dentry, mode);
if (error)
returnerror;
error = dir->i_op->create(dir, dentry, mode, nd);
if (!error)
fsnotify_create(dir, dentry);
return error;
}
在vfs_create中,最核心的函数是调用了目录的i_op的create方法,对ext2文件系统来说,该方法的具体实现为ext2_create()。
staticint ext2_create (struct inode * dir, struct dentry * dentry, intmode, struct nameidata *nd)
{
struct inode *inode;
dquot_initialize(dir);
/* 首先创建内存inode结构
**并分配inode号
*/
inode = ext2_new_inode(dir, mode);
if (IS_ERR(inode))
return PTR_ERR(inode);
//ext2_create just create aregular file,not a dir ,not symlink either
inode->i_op =&ext2_file_inode_operations;
if (ext2_use_xip(inode->i_sb)){
inode->i_mapping->a_ops = &ext2_aops_xip;
inode->i_fop = &ext2_xip_file_operations;
} else if(test_opt(inode->i_sb, NOBH)) {
inode->i_mapping->a_ops= &ext2_nobh_aops;
inode->i_fop = &ext2_file_operations;
} else {
inode->i_mapping->a_ops = &ext2_aops;
inode->i_fop = &ext2_file_operations;
}
/*标记inode为脏*/
mark_inode_dirty(inode);
/* 将文件与其父目录挂钩*/
return ext2_add_nondir(dentry, inode);
}
如概述中描述,ext2中真正的文件创建包含以下3方面的工作:
1.为文件创建内存目录项;
2.分配内存inode结构并在磁盘上为其分配inode号;
3.文件目录项写回。
以下分别就这三个方面来详加描述。
创建内存目录项
其实ext2_create()的调用者在调用该函数之前就已经创建好了内存目录项,并作为第一个参数传入,只是目前该内存目录项里面的内容还是无效的,因为inode号都尚未分配。
创建inode
分配inode的含义有两层:1.分配一个特定文件系统的内存inode结构,如ext2文件系统的structext2_inode_info,其中包含了vfs层inode结构,因此可随便根据其中之一便可相互转化。2.为创建的内存inode在磁盘上的inode区分配一个尚未使用的inode编号。
创建内存inode
创建内存inode结构相对来说比较简单,一般来说,每种文件系统超级块均提供了一组特定的函数,在该函数中便存在一个create_inode函数,大致来说,该函数会从每种文件系统在模块初始化时创建的inode内存池中分配一个内存inode。这样做是因为每种文件系统其内存inode结构不尽相同,如果由操作系统来统一分配会产生这样的问题:操作系统无法自行知道要分配的inode大小。
分配inode号
这是inode分配的主要部分,因为它需要根据文件系统磁盘布局选择将inode存放在哪个group中,选择的inode应能最大化文件系统IO性能,具体特征是:1.文件inode尽量和文件数据连续,2.文件inode尽量和父目录inode连续,3.文件尽量均匀分布在磁盘上。
ext2将磁盘划分成多个blockgroup,每个块组均存在自己的inodetable。因此,分配inode的第一步也是最关键的一步是选择将文件inode存放在哪个group中。根据创建文件的类型不同,ext2采用的分配算法也不尽相同。ext2分配inode函数是ext2_new_inode。
创建目录
若创建的是一个目录,则分配算法调用的函数是find_group_orlov,总的来说,该分配算法主要思想和流程可如下描述:
若创建的目录是顶级目录,即在根目录”/”下创建子目录,那么此时需要考虑将该目录均匀分布到磁盘的所有group中。因此,其做法是首先随机选择一个group(真正的随机),从该group开始顺序扫描磁盘的所有group,直到遇到一个符合条件的group,即:1.拥有最少的文件数,2.大于平均数的空闲inode数,3.大于平均数的空闲block数。如果没有找到,转步骤3;
若创建的并非顶级目录,从父目录所在的块组开始搜索,找到第一个这样的group:小于阈值的dir数量,2.大于阈值的空闲inode数,3.大于阈值的空闲磁盘block数。若发现这样的group,立即返回,否则,转步骤3;
流程到此说明1和2没有成功,那么需要放松搜索条件,让我们从第0个group开始,直到发现这样一个group我们便可立即返回:该group中空闲inode数超过平均值。如果没有找到这样的group,那么转步骤4;
进一步放松条件,将平均空闲inode数设置为0,再转步骤3作再次搜索,如果还没有找到,那么只能放弃,说明系统没有空闲inode可用。返回失败。
创建普通文件
若创建的是普通文件,则调用函数find_group_other来分配group,该分配算法相对上面创建目录简单了许多,具体算法流程如下描述:
检查文件父目录inode所在的group是否有空闲block和空闲inode,如果有,那么直接返回该group号即可,如果没有,那么转步骤2,这样做是尽量将文件与父目录放在相同的group中,根据程序局部性原理,访问父目录以后很有可能继续访问父目录下的文件、子目录。将父目录与文件连续存放有利于加速这一访问过程;
根据一定算法挑选下一次搜索的group号,该算法无需仔细了解,判断该group是否有空闲inode和block,如果还是没有,那么转步骤3,否则直接返回;
至此,需要降低条件,还是继续从父目录inode所在group开始顺序搜索,找到第一个这样的group:有空闲inode即可,此时返回该group,如果该步骤也失败了,说明系统确实无空闲inode可用,只好返回失败。
上面确定了从哪个group去分配磁盘inode,接下来需要读出该group中inodetable的bitmap,每个inodetable的bitmap占据了1个磁盘块,因此为其分配一个buffer_head并读出inodetable的bitmap即可。关于vfs的缓存框架可参考另外一篇技术报告。读出inodetable的bitmap以后,从bitmap中寻找未使用的inode,并根据inode位置计算出该inode号,接下来即可根据该磁盘inode号初始化1中分配的内存inode结构,并将上述bitmap所在的buffer_head设置为dirty(因为其数据已被修改)。然后将内存inode结构也标记为脏。
至此,分配inode已经结束。
文件目录项写回
ext2在创建文件完成上述步骤后,最后一步便是修改父目录的文件目录项,因为此时在父目录下面又增加了一个文件,需要以文件目录项的形式反映到父目录中,因此,系统会调用函数ext2_add_link()来完成这最后一个步骤。
ext2_add_link主要步骤如下:
确定本文件目录项所需的空间大小,主要是根据文件名的长度来确定;
遍历该目录所有的文件目录项页面,对每个保存目录项的页面,首先调用ext2_get_page()从内存中找到或者磁盘上取出该文件目录项缓存页面;
在每个缓存页面中线性搜索文件目录项之间是否有空间可容纳这个全新的文件目录项结构,搜索的过程中还会判断拥有该文件名的文件是否已经存在,如果存在,返回错误代码-EXIST,如果不存在且有那么就将文件目录项写在该地方。
将文件目录项(主要是文件名)写入目录文件的特定页面之后,需要更新父目录的inode,将父目录inode标记为脏,甚至必要的时候会将父目录inode同步到磁盘上。调用了函数ext2_commit_chunk来完成写入之后的一系列工作。
总体来说,ext2文件系统的目录文件将所有子文件、子目录的文件目录项组织成线性结构,这样对于一个大目录下查找文件效率会非常低下,用脚趾头都能想明白。
七、恢复被删除的文件、
如果你在Linux下不小心用rm-rfxxx删除了文件或者整个目录,在ext2文件系统上是可以恢复的。因为文件数据没有被实际删除,只是索引被删除了,文件被误删除之后,应该马上停止创建、修改等文件操作,因为他们有可能会覆盖以前被删除文件所占用的磁盘空间,这样就没办法恢复了。
如果只有一个文件,那么运行:
debugfs/dev/sda2;///dev/sda2是你要查看的设备
>lsdel //查看被删除文件的inode,一般是最后一个,当然你可以根据lsdel不同的属性来判断。
>dump dst_file_path//inode表示具体的inode值,dst_file_path一定要一个具体的文件名
如果你删除了大量的文件,那么操作就很麻烦,如果进入debugfs交互模式,一次只能恢复一个文件,如果被删除了1w个,那基本没法一个一个的操作。debugfs有一个-f参数可以解决这个问题,-f表示可以传入一个命令文件让debugfs来执行。
先建立一个文件export:
echo"lsdel" > export;
然后执行debugfs-f export/dev/sda2,这时候会输出所有被删除的文件信息,分别为
InodeOwner Mode Size Blocks Timedeleted
而所有这些不一定是你想要恢复的文件,你可以根据你自己的需要用grep过滤一下,例如我要恢复uid=1用户今天(10号)12点删除的size不为0的文件,具体操作命令如下:
debugfs-f export/dev/sda2| grep ' 1 ' | grep ' 10 12:' | grep -v ' 0 ' | awk '{print "dump<"$1"> "$1".del"}' >cmd
这样得到了很多dumpdst_file_path命令存入cmd文件,然后再调用debugfs-f cmd/dev/sda2就可以恢复想要的文件了。
如果系统中有多个磁盘,那么恢复文件的时候最好从一个磁盘恢复到另外一个。不然dump的时候,有可能把还没开始dump的inode就覆盖了。