聊聊磁盤文件系統(tǒng)(三)
掛載到linux的VFS中
vfs對象
VFS采用了面向?qū)ο蟮脑O(shè)計思路,將一系列概念抽象出來作為對象而存在,它們包含數(shù)據(jù)的同時也包含了操作這些數(shù)據(jù)的方法。當(dāng)然,這些對象都只能用數(shù)據(jù)結(jié)構(gòu)來表示,而不可能超出C語言的范疇,不過即使在C++里面數(shù)據(jù)結(jié)構(gòu)和類的區(qū)別也僅僅在于類的成員默認私有,數(shù)據(jù)結(jié)構(gòu)的成員默認公有。VFS主要有如下4個對象類型。
(1)超級塊(struct super_block)。超級塊對象代表一個己安裝的文件系統(tǒng),存儲該文件系統(tǒng)的有關(guān)信息,比如文件系統(tǒng)的類型、大小、狀態(tài)等。對基于磁盤的文件系統(tǒng),這類對象通常存放在磁盤上的特定扇區(qū)。對于并非基于磁盤的文件系統(tǒng)(比如基于內(nèi)存的文件系統(tǒng)sysfs),它們會現(xiàn)場創(chuàng)建超級塊對象并將其保存在內(nèi)存中。
(2)索引節(jié)點(struct inode)。索引節(jié)點對象代表存儲設(shè)備上的一個實際的物理文件,存儲該文件的有關(guān)信息。Linux將文件的相關(guān)信息,比如訪問權(quán)限、大小、創(chuàng)建時間等信息,與文件本身區(qū)分開來。文件的相關(guān)信息又被稱為文件的元數(shù)據(jù)。
(3)目錄項(struct dentry)。目錄項對象描述了文件系統(tǒng)的層次結(jié)構(gòu),一個路徑的各個組成部分,不管是目錄(VFS將目錄當(dāng)作文件來處理)還是普通的文件,都是一個目錄項對象。比如,打開文件/home/test/test.c時,內(nèi)核將為目錄/、home、test和文件test.c都創(chuàng)建一個目錄項對象。
(4)文件(struct file)。文件對象代表已經(jīng)被進程打開的文件,主要用于建立進程和文件之間的對應(yīng)關(guān)系。它由open()系統(tǒng)調(diào)用創(chuàng)建,由close()系統(tǒng)調(diào)用銷毀,且僅當(dāng)進程訪問文件期間存在于內(nèi)存之中。同一個物理文件可能存在多個對應(yīng)的文件對象,但其對應(yīng)的索引節(jié)點對象卻是惟一的。
除了上述4個主要對象外,VFS還包含了其他很多對象,比如用于描述各種文件系統(tǒng)類型的struct file_system_type,用于描述文件系統(tǒng)安裝點的struct vfsmount等。
VFS各個對象間的關(guān)系不是孤立的,進程描述符的files字段記錄了進程打開的所有文件,這些文件的文件對象指針保存在struct file_struct的fd_array數(shù)組里。通過文件的file對象可以獲得它對應(yīng)的目錄項對象,再由目錄項對象的d_inode字段可以獲得它的inode對象,這樣就建立了文件對象與物理文件之間的關(guān)聯(lián)。一個文件被打開的時候,它的file對象是使用dentry、inode、vfsmount對象中的信息填充的,比如它對應(yīng)的文件操作f_op由inode對象的i_fop字段得到。
文件系統(tǒng)的掛載
內(nèi)核是不是支持某種類型的文件系統(tǒng),需要我們進行注冊才能知道。例如,咱們的 ext4 文件系統(tǒng),就需要通過 register_filesystem 進行注冊,傳入的參數(shù)是 ext4_fs_type,表示注冊的是 ext4 類型的文件系統(tǒng)。這里面最重要的一個成員變量就是 ext4_mount。記住它,這個我們后面還會用。
- 如果一種文件系統(tǒng)的類型曾經(jīng)在內(nèi)核注冊過,這就說明允許你掛載并且使用這個文件系統(tǒng)。
- register_filesystem(&ext4_fs_type);
- static struct file_system_type ext4_fs_type = {
- .owner = THIS_MODULE,
- .name = "ext4",
- .mount = ext4_mount,
- .kill_sb = kill_block_super,
- .fs_flags = FS_REQUIRES_DEV,
- };
ext4文件系統(tǒng)的掛載是通過ext4_mount完成的,后者調(diào)用mount_bdev(block device)實現(xiàn),mount_bdev判斷兩次掛載是否為同一個文件系統(tǒng)的依據(jù)是:是否為同一個塊設(shè)備(test_bdev_super),也就是同一個塊設(shè)備只有一個super_block與之對應(yīng),即使掛載多次。
- static struct dentry *ext4_mount(struct file_system_type *fs_type, int flags,
- const char *dev_name, void *data)
- {
- return mount_bdev(fs_type, flags, dev_name, data, ext4_fill_super);
- }
- struct dentry *mount_bdev(struct file_system_type *fs_type,
- int flags, const char *dev_name, void *data,
- int (*fill_super)(struct super_block *, void *, int))
- {
- struct block_device *bdev;
- struct super_block *s;
- fmode_t mode = FMODE_READ | FMODE_EXCL;
- int error = 0;
- if (!(flags & MS_RDONLY))
- mode |= FMODE_WRITE;
- 獲取設(shè)備
- bdev = blkdev_get_by_path(dev_name, mode, fs_type);
- if (IS_ERR(bdev))
- return ERR_CAST(bdev);
- /*
- * once the super is inserted into the list by sget, s_umount
- * will protect the lockfs code from trying to start a snapshot
- * while we are mounting
- */
- mutex_lock(&bdev->bd_fsfreeze_mutex);
- if (bdev->bd_fsfreeze_count > 0) {
- mutex_unlock(&bdev->bd_fsfreeze_mutex);
- error = -EBUSY;
- goto error_bdev;
- }
- s = sget(fs_type, test_bdev_super, set_bdev_super, flags | MS_NOSEC,
- bdev);
- mutex_unlock(&bdev->bd_fsfreeze_mutex);
- if (IS_ERR(s))
- goto error_s;
- if (s->s_root) {
- if ((flags ^ s->s_flags) & MS_RDONLY) {
- deactivate_locked_super(s);
- error = -EBUSY;
- goto error_bdev;
- }
- /*
- * s_umount nests inside bd_mutex during
- * __invalidate_device(). blkdev_put() acquires
- * bd_mutex and can't be called under s_umount. Drop
- * s_umount temporarily. This is safe as we're
- * holding an active reference.
- */
- up_write(&s->s_umount);
- blkdev_put(bdev, mode);
- down_write(&s->s_umount);
- } else {
- s->s_mode = mode;
- snprintf(s->s_id, sizeof(s->s_id), "%pg", bdev);
- sb_set_blocksize(s, block_size(bdev));
- error = fill_super(s, data, flags & MS_SILENT ? 1 : 0);
- if (error) {
- deactivate_locked_super(s);
- goto error;
- }
- s->s_flags |= MS_ACTIVE;
- bdev->bd_super = s;
- }
- return dget(s->s_root);
- error_s:
- error = PTR_ERR(s);
- error_bdev:
- blkdev_put(bdev, mode);
- error:
- return ERR_PTR(error);
- }
掛載ext4文件系統(tǒng)最終由ext4_fill_super完成,它會讀取磁盤中的ext4_super_block,創(chuàng)建并初始化ext4_sb_info對象,建立它們和super_block的關(guān)系。ext4_sb_info的結(jié)構(gòu)如下:
它的實現(xiàn)比較復(fù)雜,主要邏輯如下:
ext4_sb_info的建立是在ext4_fill_super函數(shù)中完成的,代碼如下:
- struct ext4_sb_info {
- struct buffer_head * s_sbh; /* Buffer containing the super block */
- struct ext4_super_block *s_es; /* Pointer to the super block in the buffer */
- struct buffer_head **s_group_desc;
- };
- static int ext4_fill_super(struct super_block *sb, void *data, int silent)
- {
- struct ext4_sb_info *sbi;
- struct buffer_head *bh;
- struct ext4_super_block *es = NULL;
- //1
- bh = sb_bread_unmovable(sb, logical_sb_block)
- //2
- es = (struct ext4_super_block *) (bh->b_data + offset);
- sbi->s_sbh = bh;
- sbi->s_es = es;
- sb->s_fs_info = sbi;
- sbi->s_sb = sb;
- //3
- blocks_count = (ext4_blocks_count(es) -
- le32_to_cpu(es->s_first_data_block) +
- EXT4_BLOCKS_PER_GROUP(sb) - 1);
- do_div(blocks_count, EXT4_BLOCKS_PER_GROUP(sb));
- sbi->s_groups_count = blocks_count;
- sbi->s_blockfile_groups = min_t(ext4_group_t, sbi->s_groups_count,
- (EXT4_MAX_BLOCK_FILE_PHYS / EXT4_BLOCKS_PER_GROUP(sb)));
- db_count = (sbi->s_groups_count + EXT4_DESC_PER_BLOCK(sb) - 1) /
- EXT4_DESC_PER_BLOCK(sb);
- sbi->s_group_desc = ext4_kvmalloc(db_count *
- sizeof(struct buffer_head *),
- GFP_KERNEL);
- for (i = 0; i < db_count; i++) {
- block = descriptor_loc(sb, logical_sb_block, i);
- sbi->s_group_desc[i] = sb_bread_unmovable(sb, block);
- }
- //4
- if (!ext4_check_descriptors(sb, logical_sb_block, &first_not_zeroed)) {
- ret = -EFSCORRUPTED;
- goto error;
- }
- //5
- root = ext4_iget(sb, EXT4_ROOT_INO);
- //6
- if (ext4_setup_super(sb, es, sb->s_flags & MS_RDONLY))
- sb->s_flags |= MS_RDONLY;
- if (sbi->s_inode_size > EXT4_GOOD_OLD_INODE_SIZE) {
- sbi->s_want_extra_isize = sizeof(struct ext4_inode) -
- EXT4_GOOD_OLD_INODE_SIZE;
- if (ext4_has_feature_extra_isize(sb)) {
- if (sbi->s_want_extra_isize <
- le16_to_cpu(es->s_want_extra_isize))
- sbi->s_want_extra_isize =
- le16_to_cpu(es->s_want_extra_isize);
- if (sbi->s_want_extra_isize <
- le16_to_cpu(es->s_min_extra_isize))
- sbi->s_want_extra_isize =
- le16_to_cpu(es->s_min_extra_isize);
- }
- ext4_set_resv_clusters(sb);
- err = ext4_setup_system_zone(sb);
- ext4_ext_init(sb);
- err = ext4_mb_init(sb);
- block = ext4_count_free_clusters(sb);
- ext4_free_blocks_count_set(sbi->s_es,
- EXT4_C2B(sbi, block));
- err = percpu_counter_init(&sbi->s_freeclusters_counter, block,
- GFP_KERNEL);
- if (!err) {
- unsigned long freei = ext4_count_free_inodes(sb);
- sbi->s_es->s_free_inodes_count = cpu_to_le32(freei);
- err = percpu_counter_init(&sbi->s_freeinodes_counter, freei,
- GFP_KERNEL);
- }
- err = percpu_counter_init(&sbi->s_dirs_counter,
- ext4_count_dirs(sb), GFP_KERNEL);
- err = percpu_counter_init(&sbi->s_dirtyclusters_counter, 0,
- GFP_KERNEL);
- err = percpu_init_rwsem(&sbi->s_journal_flag_rwsem);
- return 0;
- }
ext4_fill_super主要分六步,均用標(biāo)號標(biāo)出。
第1步,讀取ext4_super_block對象,此時并不知道文件系統(tǒng)的block大小,也不知道它起始于第幾個block,只知道它起始于磁盤的第1024字節(jié)(前1024字節(jié)存放x86啟動信息等)。所以在第1步中先給定一個假設(shè)值,一般假設(shè)block大小為1024字節(jié),ext4_super_block始于block 1(sb_block)。由sb_min_blocksize計算得到的block大小如果小于1024,就以它作為新的block大小得到block號logical_sb_block和block內(nèi)的偏移量offset。讀取logical_sb_block的內(nèi)容,加上計算得到的偏移量,得到的就是ext4_super_block對象(es),但因為block大小可能小于1024,所以有可能讀到的只是ext4_super_block的一部分,所以為了保險起見,接下來只能訪問它的一部分字段,主要是一些簡單的驗證工作。所幸s_log_block_size字段的偏移量0x18并不大,步驟1完成后,可以得到實際的block大小(2^(10+s_log_block_size))。
第2步,block大小最小為1024,最大為65536,我的磁盤中為4096,所以步驟2中會重新計算logical_sb_block和offset分別為0和1024。然后讀取block 0,得到的數(shù)據(jù)加上1024就是完整的ext4_super_block對象。
第3步,根據(jù)得到es為ext4_sb_info字段賦值,代碼段中保留了s_group_desc字段的賦值過程,其余字段省略。
第4步,檢查所有的group descriptors數(shù)據(jù)的合法性,初始化flex_bg相關(guān)的信息。
第5步,調(diào)用ext4_iget獲取ext4的root文件,并調(diào)用d_make_root創(chuàng)建對應(yīng)的dentry,為sb->s_root賦值。
第6步,調(diào)用ext4_setup_super,將控制權(quán)轉(zhuǎn)移到ext4_setup_super,它將進行幾項最后的檢查并輸出適當(dāng)?shù)木嫘畔?。最后將超級快的變更?nèi)容寫回到磁盤上,更新掛載計數(shù)器和上一次掛載的日期。
這樣就將磁盤掛載到linux的VFS文件文件系統(tǒng)中了。其中,file_system_type用于描述具體文件系統(tǒng)的類型,struct vfsmount用于描述一個文件系統(tǒng)的安裝實例。Linux所支持的文件系統(tǒng),都會有且僅有一個file_system_type結(jié)構(gòu)(比如,Ext2對應(yīng)ext2_fs_type,Ext3對應(yīng)ext3_fs_type,Ext4對應(yīng)ext4_fs_type),而不管它有零個或多個實例被安裝到系統(tǒng)中。每當(dāng)一個文件系統(tǒng)被安裝時,就會有一個vfsmount結(jié)構(gòu)被創(chuàng)建,它代表了該文件系統(tǒng)的一個安裝實例,也代表了該文件系統(tǒng)的一個安裝點。下圖是超級塊、安裝點和具體的文件系統(tǒng)之間的關(guān)系。不同類型的文件系統(tǒng)通過next字段形成一個鏈表,同一種文件系統(tǒng)類型的超級塊通過s_instances字段鏈接在一起,并掛入fs_supers鏈表中。
關(guān)于ext4還有很多內(nèi)容,源碼鏈接:https://elixir.bootlin.com/linux/v4.8/source/fs/ext4/,有興趣的大家可以去看看。
恢復(fù)刪除的文件并不神秘
存儲介質(zhì)上的數(shù)據(jù)可以分為兩部分:表征文件的數(shù)據(jù)(可以稱為元數(shù)據(jù),metadata)和文件的內(nèi)容。不僅僅ext4文件系統(tǒng)如此,多數(shù)基于磁盤的文件系統(tǒng)都離不開這兩部分。為了恢復(fù)刪除的文件,需要先了解刪除的數(shù)據(jù)屬于哪個類型,多數(shù)文件系統(tǒng)刪除的是文件的信息,也就是表示文件和它所屬目錄的關(guān)系、文件本身信息的數(shù)據(jù),至于文件的內(nèi)容,一般是不會覆蓋的。這么做最大的優(yōu)點是效率高,比如我們在ext4文件系統(tǒng)中,刪除一個幾個G字節(jié)大小的文件并不會比刪除幾個字節(jié)的文件所用的時間長很多。缺點也是明顯的,就是所謂的刪除并沒有對文件的內(nèi)容造成影響,只要沒有被后續(xù)的文件覆蓋,就有被恢復(fù)的可能,有安全的風(fēng)險。