您的位置:首页 > 运维架构 > Linux

Linux ext2, ext3, ext4 文件系统解读[2]

2017-04-22 17:38 543 查看


目录与文件:

从前面Inode的结构中我们可以看到,其中并没有定义文件名称,文件名称实际是存放在目录中的,目录也是一类特殊的文件,在ext2文件系统中,目录是由ext2_dir_entry结构组成的列表,目录是变长的结构,这样可以避免磁盘空间的浪费。对于单个文件名,长度不能超过255字符,且文件名的长度会自动使用'\0'字符填充为4个整数倍。目录中有文件和子目录。

ext2_dir_entry结构:

 /*
 
* Structure of a directory entry
 
*/
 
#define EXT2_NAME_LEN 255
 
struct ext2_dir_entry {
       
__u32   inode;                 
/* Inode number */
       
__u16   rec_len;               
/* Directory entry length */
       
__u16   name_len;              
/* Name length */
       
char    name[EXT2_NAME_LEN];   
/* File name */
};
上面是老版本的定义,新版本的结构定义:

/*
 
* The new version of the directory entry.  Since EXT2 structures are
 
* stored in intel byte order, and the name_len field could never be
 
* bigger than 255 chars, it's safe to reclaim the extra byte for the
 
* file_type field.
 
*/
 
struct ext2_dir_entry_2 {
        
__u32   inode;                 
/* Inode number */
        
__u16   rec_len;               
/* Directory entry length */
        
__u8    name_len;              
/* Name length */
        
__u8    file_type;
        
char    name[EXT2_NAME_LEN];   
/* File name */
};
可以看到,新版本中,将文件名的长度的属性从16比特修改为8比特,将剩余的8比特用于记录文件类型。

文件类型定义:

/ *
 
* Ext2 directory file types.  Only the low 3 bits are used. 
The
 
* other bits are reserved for now.
 
*/
 
enum {
        
EXT2_FT_UNKNOWN,        /*未知*/
        
EXT2_FT_REG_FILE,       /*普通文件*/
        
EXT2_FT_DIR,           /*目录文件*/
        
EXT2_FT_CHRDEV,         /*字符设备*/
        
EXT2_FT_BLKDEV,         /*块设备*/
        
EXT2_FT_FIFO,          /*管道*/
        
EXT2_FT_SOCK,          /*套接字*/
        
EXT2_FT_SYMLINK,            /*符号链接*/
        
EXT2_FT_MAX             /*文件类型枚举的结尾标识*/
};
无论何种类型的文件,都对应一个Inode,Inode号在结构中inode属性记录。不同类型的文件使用Data
Block的方式并不相同。有的文件不用来存放数据,不使用Data Block。下面是不同类型的文件使用Data
Block的说明:

普通文件:

普通文件在创建时是空的,再向其中写入数据的时候,会为其分配Data Block。

目录文件:

目录文件的Data Block中存放的就是上面的ext2_dir_entry结构(或者是新的结构),name长度可变,最大不会超过255。rec_len记录了目录的大小,将它与这个目录的起始地址相加,就得到了下一个有效目录的起始地址,所以rec_len也可以看做是下一个有效目录的指针。要删除当前这个目录,只需要将inode置为0,并增加前一个目录的rec_len的值,以“跳过”当前这个删除的目录。

符号链接(软连接):
如果符号链接的路径名称小于60个字符,则使用Inode的i_block数组直接存放其值(15*4字节=60字节),如果超出60了字符,则需要为其分配一个Block,并将i_block中的指针指向对应的Block。

设备文件,管道,套接字:

这些文件类型不需要Block,所有信息都存放在Inode中。

 

下面是一个小例子,说明了ext2文件系统中,是如何利用目录来读出一个文件内容的,顺便也熟悉一下debugfs调试工具:

我们将1023GB的磁盘(分区:/dev/sdd1)使用ext2格式化,然后挂载到/mnt/sdd下,在下面创建了几个目录和文件,结构如下:

/mnt/sdd
/mnt/sdd/root_inode_debugfs
/mnt/sdd/test/new1.txt
/mnt/sdd/test/testlnk.txt -> test.txt
/mnt/sdd/test/test.txt
/mnt/sdd/test/test1/2.txt
/mnt/sdd/test/test2/2.txt
首先,输入debugfs,进入调试:

[root@DanCentOS65 daniel]# debugfs
debugfs 1.41.12 (17-May-2010)
debugfs:
接着使用open打开要调试的设备:

debugfs: 
open /dev/sdd1
查看根目录的详细信息:

debugfs: 
ls -l
     
2   40755 (2)      0     
0    4096 21-Apr-2017 03:21 .
     
2   40755 (2)      0     
0    4096 21-Apr-2017 03:21 ..
    
11   40700 (2)      0     
0   16384 20-Apr-2017 14:16 lost+found
    
12  100644 (1)      0     
0    4096 21-Apr-2017 02:49 root_inode_debugfs
 855681  
40755 (2)      0      0   
4096 21-Apr-2017 07:30 test
每一列的说明:

第一列为Inode号

第二列表示文件权限(前三位是suid/sgid/ticky,后三位的User/Group/Others的权限)

第三列是文件类型,(2)对应目录,(1)对应普通文件,具体可以参考一下前面ext2文件类型的枚举

第四列表示文件/目录的ownerid

第五列表示文件/目录的groupid

第六列表示文件占用的大小,可以除以Block大小得到文件占用的Block数量

第七列表示创建时间

最后一列是目录的名称
注意到上面输出结果中有两个特殊的目录“.”和“..”,这两个目录不能删除,用于相对路径的查找,“.”表示当前目录,“..”表示父目录。

言归正传,我们以/mnt/sdd/test/test2/2.txt来说明文件的读取过程:

我们当前查看的目录其实就是/dev/sdd1的挂载点的目录,即/mnt/sdd,所以在结果中看到的“.”目录对应的Inode号为2,也就是/mnt/sdd的根Inode号是2,进一步查看Inode
2的内容,首先通过命令将Inode 2的内容输出到文件中:

debugfs: 
dump_inode <2> dump1
接着在另一个终端中使用vi -b打开dump1文件(注意,上面dump1文件的存放目录是进入debugfs之前的那个目录,而不是我们调试的那个目录):

[root@DanCentOS65 daniel]# vi -b dump1
 
^B^@^@^@^L^@^A^B.^@^@^@^B^@^@^@^L^@^B^B..^@^@^K^@^@^@$^@
^Blost+found^@^@^@^@^@^@^P^@^E^A1.txt^@^@^@^L^@^@^@D^@^R^Aroot_inode_debugfs^F^A^@^@^@^@
(^@^L^Adir1_debugfs^@^@^@^@^T^@^K^Adev_debugfs^@<81>^N^M^@<80>^O^D^Btest^@^@^@^@^@^@^@^@
^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@
^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@
^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@
^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@
……
上面的结果中显示了目前Inode 2中存放的内容,其中有一些乱码先忽略掉,我们先查看可读的文件名。细心的读者可能会发现除了我们在目录中看到的lost+found目录,test目录以及root_inode_debugfs文件之外,还有1.txt和dir1_debugfs两个文件,其实这两个文件之前确实是存在的,后来删除掉了,由于删除其实仅仅是修改对应的Inode和Block的标记位,在没有新的内容覆盖掉其内容之前,这些旧的条目会一直都在磁盘上,所以这里我们才能看到对应的内容,这也是一些磁盘数据恢复工具的原理所在。

当我们尝试打开/mnt/sdd/test/test2/2.txt时,文件系统首先会根据目录/mnt/sdd得到其Inode号2,并且确认其权限有读权限,从中读出Block中的内容,也就是上面这些子目录和文件的内容,从中找到我们要访问的下一个目录test,并找到对应的Inode号码855681(从上面的结果中可以看到)。接着系统会读取Inode
855681的内容:

 
.                                                                                                                                                                                                                             
ntest2.tx
<81>^N^M^@^L^@^A^B.^@^@^@^B^@^@^@^L^@^B^B..^@^@<85>^N^M^@^P^@^H^Anew1.txt<82>^N^M^@^P^@^E^Btest1t.s^A^
O^M^@^P^@^E^Btest2.tx<83>^N^M^@^X^@^K^Gtestlnk.txtwp^@^@^@<84>^N^M^@<a0>^O^H^Atest.txtt.swx^@^@^@^@^@^
@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^
@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^
@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^
@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^
@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@......
同理,文件系统会从中找到下一级目录test2,并找到他的Inode编号855809:

debugfs: 
ls -l test
 855681  
40755 (2)      0      0   
4096 21-Apr-2017 08:09 .
     
2   40755 (2)      0     
0    4096 21-Apr-2017 09:14 ..
 855685 
100644 (1)      0      0     
15 21-Apr-2017 06:54 new1.txt
 855682  
40755 (2)      0      0   
4096 21-Apr-2017 05:04 test1
 855809  
40755 (2)      0      0   
4096 21-Apr-2017 05:05 test2
 855683 
120777 (7)      0      0      
8 21-Apr-2017 08:09 testlnk.txt
 855684 
100644 (1)      0      0     
58 21-Apr-2017 07:30 test.txt
继续读取其中内容:

^A^O^M^@^L^@^A^B.^@^@^@<81>^N^M^@^L^@^B^B..^@^@^D^O^M^@<e8>^O^E^A2.txt^@^@^@^@^@^@^@<d8>^O
^A.2.txt.swp^@^@^@^@^@^@<c4>^O^F^A2.txt~.swx^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^
@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^
@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@
 进一步得到2.txt的Inode号,最终从Data
Block中读出文件数据。
调试完成后,可以使用close关闭打开的设备:

debugfs: 
close
需要注意的是,在调试过程中,如果我们在其他终端中对这个文件系统的文件或者目录进行调整,是不会反映到当前Debug的过程中的,想要重新载入修改的目录和文件,需要退出debugfs,重新mount一下分区,再次打开debugfs即可。

 

除了上面列出的命令之外,还有很多其他命令可以使用,例如可以使用features查看superblock中定义的功能特性,使用stat
<inode number>查看对应的Inode的结构信息,使用stats可以查看superblock的结构等等,有兴趣的读者可以使用?查看全部命令(也可以使用man
debugfs查看更详细的用户手册):

debugfs: 
?
Available debugfs requests:
 
show_debugfs_params, params
                        
Show debugfs parameters
open_filesys, open      
Open a filesystem
close_filesys, close    
Close the filesystem
feature, features       
Set/print superblock features
dirty_filesys, dirty    
Mark the filesystem as dirty
init_filesys            
Initialize a filesystem (DESTROYS DATA)
show_super_stats, stats 
Show superblock statistics
ncheck                  
Do inode->name translation
icheck                  
Do block->inode translation
change_root_directory, chroot
                        
Change root directory
change_working_directory, cd
                        
Change working directory
list_directory, ls      
List directory
show_inode_info, stat   
Show inode information
dump_extents, extents, ex
                        
Dump extents information
link, ln                
Create directory link
unlink                  
Delete a directory link
mkdir                   
Create a directory
rmdir                   
Remove a directory
rm                      
Remove a file (unlink and kill_file, if appropriate)
kill_file               
Deallocate an inode and its blocks
clri                    
Clear an inode's contents
freei                   
Clear an inode's in-use flag
seti                    
Set an inode's in-use flag
testi                   
Test an inode's in-use flag
freeb                   
Clear a block's in-use flag
setb                    
Set a block's in-use flag
testb                   
Test a block's in-use flag
modify_inode, mi        
Modify an inode by structure
find_free_block, ffb    
Find free block(s)
find_free_inode, ffi    
Find free inode(s)
print_working_directory, pwd
                        
Print current working directory
expand_dir, expand      
Expand directory
mknod                   
Create a special file
list_deleted_inodes, lsdel
                        
List deleted inodes
undelete, undel         
Undelete file
write                   
Copy a file from your native filesystem
dump_inode, dump        
Dump an inode out to a file
cat                     
Dump an inode out to stdout
lcd                     
Change the current directory on your native filesystem
rdump                   
Recursively dump a directory to the native filesystem
set_super_value, ssv    
Set superblock value
set_inode_field, sif    
Set inode field
set_block_group, set_bg 
Set block group descriptor field
logdump                 
Dump the contents of the journal
htree_dump, htree       
Dump a hash-indexed directory
dx_hash, hash           
Calculate the directory hash of a filename
dirsearch               
Search a directory for a particular filename
bmap                    
Calculate the logical->physical block mapping for an inode
imap                    
Calculate the location of an inode
dump_unused             
Dump unused blocks
set_current_time        
Set current time to use when setting filesystme fields
supported_features      
Print features supported by this version of e2fsprogs
help                    
Display info on command or topic.
list_requests, lr, ?    
List available commands.
quit, q                 
Leave the subsystem.
 

除了上面说的dumpe2fs和debugfs中的调试命令之外,下面也是一些常用的查看命令:
通过-i参数在输出结果中查看Inode编号:

[root@DanCentOS65 test]# ls -il
total 16
855685 -rw-r--r-- 1 root root  
15 Apr 21 06:54 new1.txt
855682 drwxr-xr-x 2 root root 4096 Apr 21 05:04 test1
855809 drwxr-xr-x 2 root root 4096 Apr 21 05:05 test2
855683 lrwxrwxrwx 1 root root   
8 Apr 21 08:09 testlnk.txt -> test.txt
855684 -rw-r--r-- 1 root root  
58 Apr 21 07:30 test.txt
stat命令查看目录或文件对应的Inode信息:

[root@DanCentOS65 test]# stat test2
 
File: `test2'
 
Size: 4096              Blocks: 8         
IO Block: 4096   directory
Device: 831h/2097d        Inode: 855809     
Links: 2
Access: (0755/drwxr-xr-x) 
Uid: (    0/    root)  
Gid: (    0/    root)
Access: 2017-04-21 05:35:58.000000000 +0000
Modify: 2017-04-21 05:05:08.000000000 +0000
Change: 2017-04-21 05:05:08.000000000 +0000
查看Inode使用情况:

[root@DanCentOS65 daniel]# df -ih
Filesystem    
Inodes IUsed IFree IUse% Mounted on
/dev/sda1       
1.9M   98K  1.8M   
6% /
tmpfs           
873K     1  873K   
1% /dev/shm
/dev/sdb1        
18M    12   18M   
1% /mnt/resource
/dev/sdf1       
1.9M   36K  1.9M   
2% /mnt/ubuntu
/dev/sdd1      
1023K    20 1023K    1% /mnt/sdd
 

顺便再补充一下软连接和硬链接的知识:

我们接着上面的例子,再创建一个硬链接:

[root@DanCentOS65 daniel]# ll /mnt/sdd/test
total 20
-rw-r--r-- 2 root root  
15 Apr 21 06:54 new1HDlnk
-rw-r--r-- 2 root root  
15 Apr 21 06:54 new1.txt
drwxr-xr-x 2 root root 4096 Apr 21 05:04 test1
drwxr-xr-x 2 root root 4096 Apr 21 05:05 test2
lrwxrwxrwx 1 root root   
8 Apr 21 08:09 testlnk.txt -> test.txt
-rw-r--r-- 1 root root  
58 Apr 21 07:30 test.txt
上面的目录中new1HDlnk文件是new1.txt的硬链接,testlnk.txt是test.txt的软连接,我们还是用debugfs来看一下二者的区别:

debugfs: 
ls -l test
 855681  
40755 (2)      0      0   
4096 21-Apr-2017 09:47 .
     
2   40755 (2)      0     
0    4096 21-Apr-2017 09:14 ..
 855685 
100644 (1)      0      0     
15 21-Apr-2017 06:54 new1.txt
 855682  
40755 (2)      0      0   
4096 21-Apr-2017 05:04 test1
 855809  
40755 (2)      0      0   
4096 21-Apr-2017 05:05 test2
 855683 
120777 (7)      0      0      
8 21-Apr-2017 08:09 testlnk.txt
 855684 
100644 (1)      0      0     
58 21-Apr-2017 07:30 test.txt
 855685 
100644 (1)      0      0     
15 21-Apr-2017 06:54 new1HDlnk
可以明显看到,硬链接new1HDlnk实际上仅仅是源文件的一个别名,写到了上级目录的Data Block中,它是没有自己的Inode的,所以它的Inode号855685其实也就是源文件new1.txt的Inode号。而软连接不同,软连接是一类特殊的文件,有自己的Inode和内容。上面例子中,源文件test.txt的文件名小于60字节,所以我们可以看到testlnk.txt的Inode中并没有分配Data
Block:

debugfs: 
stat <855683>
Inode: 855683  
Type: symlink    Mode:  0777  
Flags: 0x0
Generation: 2285856085   
Version: 0x00000000
User:    
0   Group:     0  
Size: 8
File ACL: 0   
Directory ACL: 0
Links: 1  
Blockcount: 0
Fragment: 
Address: 0    Number: 0    Size: 0
ctime: 0x58f9be38 -- Fri Apr 21 08:09:28 2017
atime: 0x58f9be39 -- Fri Apr 21 08:09:29 2017
mtime: 0x58f9be38 -- Fri Apr 21 08:09:28 2017
Size of extra inode fields: 0
Fast_link_dest: test.txt
我们在做一个测试,创建一个超过60字节文件名的文件file0123456789012345678901234567890123456789012345678901234567890123456789.txt,并为其创建一个软连接largerlnk:

[root@DanCentOS65 test]# ll
total 28
-rw-r--r-- 1 root root  
71 Apr 22 08:35 file0123456789012345678901234567890123456789012345678901234567890123456789.txt
lrwxrwxrwx 1 root root  
78 Apr 22 08:36 largerlnk -> file0123456789012345678901234567890123456789012345678901234567890123456789.txt
-rw-r--r-- 2 root root  
15 Apr 21 06:54 new1HDlnk
-rw-r--r-- 2 root root  
15 Apr 21 06:54 new1.txt
drwxr-xr-x 2 root root 4096 Apr 21 05:04 test1
drwxr-xr-x 2 root root 4096 Apr 21 05:05 test2
lrwxrwxrwx 1 root root   
8 Apr 21 08:09 testlnk.txt -> test.txt
-rwxr-xr-x 1 root root  
58 Apr 21 07:30 test.txt
再来看一下largerlnk对应的Inode:

debugfs: 
ls -l
 855681  
40755 (2)     0     0   
4096 22-Apr-2017 08:36 .
     
2   40755 (2)     0    
0    4096 21-Apr-2017 09:14 ..
 855685 
100644 (1)     0     0     
15 21-Apr-2017 06:54 new1.txt
 855682  
40755 (2)     0     0   
4096 21-Apr-2017 05:04 test1
 855809  
40755 (2)     0     0   
4096 21-Apr-2017 05:05 test2
 855683 
120777 (7)     0     0      
8 21-Apr-2017 08:09 testlnk.txt
 855684 
100755 (1)     0     0     
58 21-Apr-2017 07:30 test.txt
 855685 
100644 (1)     0     0     
15 21-Apr-2017 06:54 new1HDlnk
 855687 
120777 (7)     0     0     
78 22-Apr-2017 08:36 largerlnk
 855688 
100644 (1)     0     0     
71 22-Apr-2017 08:35 file0123456789012345678901234567890123456789012345678901234567890123456789.txt
debugfs: 
stat <855687>
Inode: 855687  
Type: symlink    Mode:  0777  
Flags: 0x0
Generation: 2439924333   
Version: 0x00000000
User:    
0   Group:     0  
Size: 78
File ACL: 0   
Directory ACL: 0
Links: 1  
Blockcount: 8
Fragment: 
Address: 0    Number: 0    Size: 0
ctime: 0x58fb1607 -- Sat Apr 22 08:36:23 2017
atime: 0x58fb1608 -- Sat Apr 22 08:36:24 2017
mtime: 0x58fb1607 -- Sat Apr 22 08:36:23 2017
Size of extra inode fields: 0
BLOCKS:
(0):219056128
TOTAL: 1
可以看到,当指向的文件名超过60字节时,就不能在Inode的i_block中直接存放指向的文件名了,此时文件系统会分配一个Data
Block,我们看到Data Block的编号为219056128,查看一下其中的内容(当然直接在debugfs中通过cat也可以查看,只不过这里演示一下如何使用dd来读取Data
Block的数据):

[root@DanCentOS65 test]# dd if=/dev/sdc1 of=block.txt skip=219056128 ibs=4096 bs=4096 count=1
1+0 records in
1+0 records out
4096 bytes (4.1 kB) copied, 0.00018398 s, 22.3 MB/s
[root@DanCentOS65 test]# vi block.txt

file0123456789012345678901234567890123456789012345678901234567890123456789.txt^@^@^@^@^@^@^@^@^
@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^
@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^
@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^
@^@^@^@^@^@^@^@^@^@^@^@^@……
可以看到,Data Block中存放的就是指向的文件的名称。

注:dd命令参数简要说明:

if=file
                                                   #输入文件名,缺省为标准输入

of=file
                                                   #输出文件名,缺省为标准输出
ibs=bytes 
                                              #一次读入 bytes 个字节(即一个块大小为 bytes 个字节)
obs=bytes 
                                              #一次写 bytes 个字节(即一个块大小为 bytes 个字节)

bs=bytes
                                                 #同时设置读写块的大小为
bytes ,可代替 ibs 和 obs 
cbs=bytes 
                                              #一次转换 bytes 个字节,即转换缓冲区大小
skip=blocks
                                            #从输入文件开头跳过 blocks 个块后再开始复制
seek=blocks
                                            #从输出文件开头跳过 blocks 个块后再开始复制
count=blocks
                                          #仅拷贝 blocks 个块,块大小等于 ibs 指定的字节数
conv=conversion[,conversion...] #用指定的参数转换文件
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息