TShopping

 找回密碼
 註冊
搜索
查看: 1100|回復: 3

[分享] Ext3 mount過程分析

[複製鏈接]
發表於 2013-10-24 13:18:32 | 顯示全部樓層 |閱讀模式
 
Push to Facebook Push to Plurk Push to Twitter 
Ext3 mount原理

本質上,Ext3 mount的過程實際上是inode被替代的過程。例如,/dev/sdb塊設備被mount到/mnt/alan目錄。那麼mount這個過程所需要解決的問題就是將/mnt/alan的dentry目錄項所指向的inode屏蔽掉,然後重新定位到/dev/sdb所表示的inode索引節點。在沒有分析閱讀linux vfs mount代碼的時候,我的想法是修改dentry所指向的inode索引節點,以此實現mount文件系統的訪問。經過分析,在實際的vfs mount實現過程中,還是和我原始的想法略有差別,但是,基本目標還是相同的。

Linux VFS的mount過程基本原理如下圖所示:

當用戶輸入”mount /dev/sdb /mnt/alan”命令後,Linux會解析/mnt/alan字符串,並且從Dentry Hash表中獲取相關的dentry目錄項,然後將該目錄項標識成DCACHE_MOUNTED。一旦該dentry被標識成DCACHE_MOUNTED,也就意味著在訪問路徑上對其進行了屏蔽。

在mount /dev/sdb設備上的ext3文件系統時,內核會創建一個該文件系統的superblock對象,並且從/dev/sdb設備上讀取所有的superblock信息,初始化該內存對象。Linux內核維護了一個全局superblock對象鍊錶。s_root是superblock對象所維護的dentry目錄項,該目錄項是該文件系統的根目錄。即新mount的文件系統內容都需要通過該根目錄進行訪問。在mount的過程中,VFS會創建一個非常重要的vfsmount對象,該對象維護了文件系統mount的所有信息。Vfsmount對象通過HASH表進行維護,通過path地址計算HASH值,在這裡vfsmount的HASH值通過“/mnt/alan”路徑字符串進行計算得到。Vfsmount中的mnt_root指向superblock對象的s_root根目錄項。因此,通過/mnt/alan地址可以檢索VFSMOUNT Hash Table得到被mount的vfsmount對象,進而得到mnt_root根目錄項。

例如,/dev/sdb被mount之後,用戶想要訪​​問該設備上的一個文件ab.c,假設該文件的地址為:/mnt/alan/ab.c。在打開該文件的時候,首先需要進行path解析。在解析到/mnt/alan的時候,得到/mnt/alan的dentry目錄項,並且發現該目錄項已經被標識為DCACHE_MOUNTED。之後,會採用/mnt/alan計算HASH值去檢索VFSMOUNT Hash Table,得到對應的vfsmount對象,然後採用vfsmount指向的mnt_root目錄項替代/mnt/alan原來的dentry,從而實現了dentry和inode的重定向。在新的dentry的基礎上,解析程序繼續執行,最終得到表示ab.c文件的inode對象。

關鍵數據結構說明
Linux VFS mount所涉及的關鍵數據結構分析如下。
Vfsmount數據結構

Vfsmount數據結構是vfs mount最為重要的數據結構,其維護了一個mount點的所有信息。該數據結構描述如下:

  1. struct vfsmount {  
  2.     struct list_head mnt_hash; /* 連接到VFSMOUNT Hash Table */  
  3.     struct vfsmount *mnt_parent; /* 指向mount樹中的父節點*/  
  4.     struct dentry *mnt_mountpoint; /* 指向mount點的目錄項*/  
  5.     struct dentry *mnt_root; /* 被mount的文件系統根目錄項*/  
  6.     struct super_block *mnt_sb; /* 指向被mount的文件系統superblock */  
  7. #ifdef CONFIG_SMP  
  8.     struct mnt_pcp __percpu *mnt_pcp;  
  9.     atomic_t mnt_longterm; /* how many of the refs are longterm */  
  10. #else  
  11.     int mnt_count;  
  12.     int mnt_writers;  
  13. #endif  
  14.     struct list_head mnt_mounts; /* 下級(child)vfsmount對象鍊錶*/  
  15.     struct list_head mnt_child; /* 鏈入上級vfsmount對象的鍊錶點*/  
  16.     int mnt_flags;  
  17.     /* 4 bytes hole on 64bits arches without fsnotify */  
  18. #ifdef CONFIG_FSNOTIFY  
  19.     __u32 mnt_fsnotify_mask;  
  20.     struct hlist_head mnt_fsnotify_marks;  
  21. #endif  
  22.     const char *mnt_devname; /* 文件系統所在的設備名字,例如/dev/sdb */  
  23.     struct list_head mnt_list;  
  24.     struct list_head mnt_expire; /* link in fs-specific expiry list */  
  25.     struct list_head mnt_share; /* circular list of shared mounts */  
  26.     struct list_head mnt_slave_list;/* list of slave mounts */  
  27.     struct list_head mnt_slave; /* slave list entry */  
  28.     struct vfsmount *mnt_master; /* slave is on master- > mnt_slave_list */  
  29.     struct mnt_namespace *mnt_ns; /* containing namespace */  
  30.     int mnt_id; /* mount identifier */  
  31.     int mnt_group_id; /* peer group identifier */  
  32.     int mnt_expiry_mark; /* true if marked for expiry */  
  33.     int mnt_pinned;  
  34.     int mnt_ghosts;  
  35. };
複製代碼

在Linux內核中不僅存在VFSMOUNT的Hash Table,而且還維護了一棵Mount對象樹,通過該mount樹,我們可以了解到各個文件系統之間的關係。該mount樹描述如下:

上圖所示為三層mount文件系統樹。第一層為系統根目錄“/”;第二層有兩個mount點,一個為/mnt/a,另一個是/mnt/b;第三層在/mnt/a的基礎上又創建了兩個mount點,分別為/mnt/a/c和/mnt/a/d。通過mount樹,可以對整個系統的mount結構一目了然。

Superblock數據結構

每個文件系統都會擁有一個superblock對像對其基本信息進行描述。對於像ext3之類的文件系統而言,在磁盤上會持久化存儲一份superblock元數據信息,內存的superblock對象由磁盤上的信息初始化。對於像block device 之類的“偽文件系統”而言,在mount的時候也會創建superblock對象,只不過很多信息都是臨時生成的,沒有持久化信息。Vfs superblock數據結構定義如下:

  1. struct super_block {  
  2.     struct list_head s_list; /* 鏈入全局鍊錶的對象*/  
  3.     dev_t s_dev; /* search index; _not_ kdev_t */  
  4.     unsigned char s_dirt;  
  5.     unsigned char s_blocksize_bits;  
  6.     unsigned long s_blocksize;  
  7.     loff_t s_maxbytes; /* Max file size */  
  8.     struct file_system_type *s_type;  
  9.     const struct super_operations *s_op; /* superblock操作函數集*/  
  10.     const struct dquot_operations *dq_op;  
  11.     const struct quotactl_ops *s_qcop;  
  12.     const struct export_operations *s_export_op;  
  13.     unsigned long s_flags;  
  14.     unsigned long s_magic;  
  15.     struct dentry *s_root; /* 文件系統根目錄項*/  
  16.     struct rw_semaphore s_umount;  
  17.     struct mutex s_lock;  
  18.     int s_count;  
  19.     atomic_t s_active;  
  20. #ifdef CONFIG_SECURITY  
  21.     void *s_security;  
  22. #endif  
  23.     const struct xattr_handler **s_xattr;  

  24.     struct list_head s_inodes; /* all inodes */  
  25.     struct hlist_bl_head s_anon; /* anonymous dentries for (nfs) exporting */  
  26. #ifdef CONFIG_SMP  
  27.     struct list_head __percpu *s_files;  
  28. #else  
  29.     struct list_head s_files;  
  30. #endif  
  31.     /* s_dentry_lru, s_nr_dentry_unused protected by dcache.c lru locks */  
  32.     struct list_head s_dentry_lru; /* unused dentry lru */  
  33.     int s_nr_dentry_unused; /* # of dentry on lru */  

  34.     /* s_inode_lru_lock protects s​​_inode_lru and s_nr_inodes_unused */  
  35.     spinlock_t s_inode_lru_lock ____cacheline_aligned_in_smp;  
  36.     struct list_head s_inode_lru; /* unused inode lru */  
  37.     int s_nr_inodes_unused; /* # of inodes on lru */  

  38.     struct block_device *s_bdev;  
  39.     struct backing_dev_info *s_bdi;  
  40.     struct mtd_info *s_mtd;  
  41.     struct list_head s_instances;  
  42.     struct quota_info s_dquot; /* Diskquota specific options */  

  43.     int s_frozen;  
  44.     wait_queue_head_t s_wait_unfrozen;  

  45.     char s_id[32]; /* Informational name */  
  46.     u8 s_uuid[16]; /* UUID */  

  47.     void *s_fs_info; /* Filesystem private info */  
  48.     fmode_t s_mode;  

  49.     /* Granularity of c/m/atime in ns.  
  50.        Cannot be worse than a second */  
  51.     u32 s_time_gran;  

  52.     /*  
  53.      * The next field is for VFS *only*. No filesystems have any business  
  54.      * even looking at it. You had been warned.  
  55.      */  
  56.     struct mutex s_vfs_rename_mutex; /* Kludge */  

  57.     /*  
  58.      * Filesystem subtype. If non-empty the filesystem type field  
  59.      * in /proc/mounts will be "type.subtype"  
  60.      */  
  61.     char *s_subtype;  

  62.     /*  
  63.      * Saved mount opt​​ions for lazy filesystems using  
  64.      * generic_show_options()  
  65.      */  
  66.     char __rcu *s_options;  
  67.     const struct dentry_operations *s_d_op; /* default d_op for dentries */  

  68.     /*  
  69.      * Saved pool identifier for cleancache (-1 means none)  
  70.      */  
  71.     int cleancache_poolid;  

  72.     struct shrinker s_shrink; /* per-sb shrinker handle */  
  73. };  
複製代碼


代碼流程分析

Linux中實現mount操作需要一定的代碼量,下面對Linux VFS Mount代碼進行分析說明,整個分析過程按照mount操作函數調用流程進行。代碼分析基於Linux-3.2版本。

當用戶在用戶層執行mount命令時,會執行系統調用從用戶態陷入linux內核,執行如下函數(namespace.c):
  1. SYSCALL_DEFINE5(mount, char __user *, dev_name, char __user *, dir_name,  
  2.         char __user *, type, unsigned long, flags, void __user *, data)  
  3. {  
  4.     int ret;  
  5.     char *kernel_type;  
  6.     char *kernel_dir;  
  7.     char *kernel_dev;  
  8.     unsigned long data_page;  
  9.     /* 獲取mount類型 */  
  10.     ret  =  copy_mount_string (type, &kernel_type);  
  11.     if (ret  <  0 )  
  12.         goto out_type;  
  13.     /* 獲取mount點目錄字符串 */  
  14.     kernel_dir  =  getname (dir_name);  
  15.     if (IS_ERR(kernel_dir)) {  
  16.         ret  =  PTR_ERR (kernel_dir);  
  17.         goto out_dir;  
  18.     }  
  19.     /* 獲取設備名稱字符串 */  
  20.     ret  =  copy_mount_string (dev_name, &kernel_dev);  
  21.     if (ret  <  0 )  
  22.         goto out_dev;  
  23.     /* 獲取其它選項 */  
  24.     ret  =  copy_mount_options (data, &data_page);  
  25.     if (ret  <  0 )  
  26.         goto out_data;  
  27.     /* 主要函數,執行掛載文件系統的具體操作*/  
  28.     ret  =  do_mount (kernel_dev, kernel_dir, kernel_type, flags,  
  29.         (void *) data_page);  

  30.     free_page(data_page);  
  31. out_data:  
  32.     kfree(kernel_dev);  
  33. out_dev:  
  34.     putname(kernel_dir);  
  35. out_dir:  
  36.     kfree(kernel_type);  
  37. out_type:  
  38.     return ret;  
  39. }
複製代碼

do_mount()函數是mount操作過程中的核心函數,在該函數中,通過mount的目錄字符串找到對應的dentry目錄項,然後通過do_new_mount()函數完成具體的mount操作。do_mount()函數分析如下:


  1. long do_mount(char *dev_name, char *dir_name, char *type_page,  
  2.           unsigned long flags, void *data_page)  
  3. {  
  4.     struct path path;  
  5.     int  retval  =  0 ;  
  6.     int  mnt_flags  =  0 ;  

  7. 。。。  

  8.     /* 通過mount目錄字符串獲取path,path結構中包含有mount目錄的dentry目錄對象*/  
  9.     retval  =  kern_path (dir_name, LOOKUP_FOLLOW, &path);  
  10.     if (retval)  
  11.         return retval;  

  12.     。。。  

  13.     /* Separate the per-mountpoint flags */  
  14.     if (flags & MS_NOSUID)  
  15.         mnt_flags |= MNT_NOSUID;  
  16.     if (flags & MS_NODEV)  
  17.         mnt_flags |= MNT_NODEV;  
  18.     if (flags & MS_NOEXEC)  
  19.         mnt_flags |= MNT_NOEXEC;  
  20.     if (flags & MS_NOATIME)  
  21.         mnt_flags |= MNT_NOATIME;  
  22.     if (flags & MS_NODIRATIME)  
  23.         mnt_flags |= MNT_NODIRATIME;  
  24.     if (flags & MS_STRICTATIME)  
  25.         mnt_flags &= ~(MNT_RELATIME | MNT_NOATIME);  
  26.     if (flags & MS_RDONLY)  
  27.         mnt_flags |= MNT_READONLY;  

  28.     flags &= ~(MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_ACTIVE | MS_BORN |  
  29.            MS_NOATIME | MS_NODIRATIME | MS_RELATIME| MS_KERNMOUNT |  
  30.            MS_STRICTATIME);  

  31.     /* remount操作 */  
  32.     if (flags & MS_REMOUNT)  
  33.         retval  =  do_remount (&path, flags & ~MS_REMOUNT, mnt_flags,  
  34.                     data_page);  
  35.     else if (flags & MS_BIND)  
  36.         retval  =  do_loopback (&path, dev_name, flags & MS_REC);  
  37.     else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE))  
  38.         retval  =  do_change_type (&path, flags);  
  39.     else if (flags & MS_MOVE)  
  40.         retval  =  do_move_mount (&path, dev_name);  
  41.     else  
  42.         /* 正常的mount操作,完成具體的mount操作*/  
  43.         retval  =  do_new_mount (&path, type_page, flags, mnt_flags,  
  44.                       dev_name, data_page);  
  45. dput_out:  
  46.     path_put(&path);  
  47.     return retval;  
  48. }
複製代碼


do_new_mount()函數主要分成兩大部分:第一部分建立vfsmount對象和superblock對象,必要時從設備上獲取文件系統元數據;第二部分將vfsmount對象加入到mount樹和Hash Table中,並且將原來的dentry對象無效掉。do_new_mount函數說明如下:


  1. static int do_new_mount(struct path *path, char *type, int flags,  
  2.             int mnt_flags, char *name, void *data)  
  3. {  
  4.     struct vfsmount *mnt;  
  5.     int err;  

  6.     。。。  

  7.     /* 在內核建立vfsmount對象和superblock對象*/  
  8.     mnt  =  do_kern_mount (type, flags, name, data);  
  9.     if (IS_ERR(mnt))  
  10.         return PTR_ERR(mnt);  
  11.     /* 將vfsmount對象加入系統,屏蔽原有dentry對象*/  
  12.     err  =  do_add_mount (mnt, path, mnt_flags);  
  13.     if (err)  
  14.         mntput(mnt);  
  15.     return err;  
  16. }
複製代碼


do_new_mount()中的第一步調用do_kern_mount()函數,該函數的主幹調用路徑如下:
do_kern_mount--> vfs_kern_mount--> mount_fs
在mount_fs()函數中會調用特定文件系統的mount方法,如果mount是ext3文件系統,那麼在mount_fs函數中最終會調用ext3的mount方法。Ext3的mount方法定義在super.c文件中:

  1. static struct file_system_type  ext3_fs_type  = {  
  2.     .owner       =  THIS_MODULE ,  
  3.     .name        =  "ext3" ,  
  4.     .mount       =  ext3_mount , /* ext3文件系統mount方法*/  
  5.     .kill_sb     =  kill_block_super ,  
  6.     .fs_flags    =  FS_REQUIRES_DEV ,  
  7. };
複製代碼


Ext3 mount函數主幹調用路徑為:ext3_mount--> mount_bdev。Mount_bdev()函數主要完成superblock對象的內存初始化,並且加入到全局superblock鍊錶中。該函數說明如下:


  1. struct dentry *mount_bdev(struct file_system_type *fs_type,  
  2.     int flags, const char *dev_name, void *data,  
  3.     int (*fill_super)(struct super_block *, void *, int))  
  4. {  
  5.     struct block_device *bdev;  
  6.     struct super_block *s;  
  7.     fmode_t  mode  =  FMODE_READ  | FMODE_EXCL;  
  8.     int  error  =  0 ;  

  9.     if (!(flags & MS_RDONLY))  
  10.         mode |= FMODE_WRITE;  
  11.     /* 通過設備名字獲取被mount設備的bdev對象*/  
  12.     bdev  =  blkdev_get_by_path (dev_name, mode, fs_type);  
  13.     if (IS_ERR(bdev))  
  14.         return ERR_CAST(bdev);  

  15.     /*  
  16.      * once the super is inserted into the list by sget, s_umount  
  17.      * will protect the lockfs code from trying to start a snapshot  
  18.      * while we are mounting  
  19.      */  
  20.     mutex_lock(&bdev- > bd_fsfreeze_mutex);  
  21.     if (bdev- > bd_fsfreeze_count  >  0) {  
  22.         mutex_unlock(&bdev- > bd_fsfreeze_mutex);  
  23.         error  = -EBUSY;  
  24.         goto error_bdev;  
  25.     }  
  26.     /* 查找或者創建superblock對象 */  
  27.     s  =  sget (fs_type, test_bdev_super, set_bdev_super, bdev);  
  28.     mutex_unlock(&bdev- > bd_fsfreeze_mutex);  
  29.     if (IS_ERR(s))  
  30.         goto error_s;  

  31.     if (s- > s_root) {  
  32.         /* 被mount文件系統的根目錄項已經存在*/  
  33.         if ((flags ^ s- > s_flags) & MS_RDONLY) {  
  34.             deactivate_locked_super(s);  
  35.             error  = -EBUSY;  
  36.             goto error_bdev;  
  37.         }  

  38.         /*  
  39.          * s_umount nests inside bd_mutex during  
  40.          * __invalidate_device(). blkdev_put() acquires  
  41.          * bd_mutex and can't be called under s_umount. Drop  
  42.          * s_umount temporarily. This is safe as we're  
  43.          * holding an active reference.  
  44.          */  
  45.         up_write(&s- > s_umount);  
  46.         blkdev_put(bdev, mode);  
  47.         down_write(&s- > s_umount);  
  48.     } else {  
  49.         /* 文件系統根目錄項不存在,通過filler_super函數讀取磁盤上的superblock元數據信息,並且初始化superblock內存結構*/  
  50.         char b[BDEVNAME_SIZE];  

  51.         s- > s_flags  = flags | MS_NOSEC;  
  52.         s- > s_mode  = mode;  
  53.         strlcpy(s- > s_id, bdevname(bdev, b), sizeof(s- > s_id));  
  54.         sb_set_blocksize(s, block_size(bdev));  
  55.         /* 對於ext3文件系統,調用ext3_fill_super函數*/  
  56.         error  =  fill_super (s, data, flags & MS_SILENT ? 1 : 0);  
  57.         if (error) {  
  58.             deactivate_locked_super(s);  
  59.             goto error;  
  60.         }  

  61.         s- > s_flags |= MS_ACTIVE;  
  62.         bdev- > bd_super  = s;  
  63.     }  
  64.     /* 正常返回被mount文件系統根目錄項*/  
  65.     return dget(s- > s_root);  

  66. error_s:  
  67.     error  =  PTR_ERR (s);  
  68. error_bdev:  
  69.     blkdev_put(bdev, mode);  
  70. error:  
  71.     return ERR_PTR(error);  
  72. }
複製代碼


do_new_mount()函數的第二步是將創建的vfsmount對象加入到mount樹和VFSMOUNT Hash Table中,並且將老的dentry目錄項無效掉。該過程主幹函數調用過程如下所示:
do_new_mount--> do_add_mount--> g​​raft_tree--> attach_recursive_mnt
attach_recursive_mnt()函數完成第二步過程的主要操作。至此,文件系統的mount操作已經完成。Mount完成之後,如果用戶想要訪​​問新mount文件系統中的文件,那麼需要在path解析過程中重定位dentry,該過程主要在follow_managed()函數中完成。在該函數中會判斷一個dentry是否已經被標識成DCACHE_MOUNTED,如果該標誌位已經被設置,那麼通過VFSMOUNT Hash Table可以重定位dentry。

如有不對之處,敬請指出更正(tl_wzj@yahoo.com.cn )。
本文出自“ 存儲之道 ”博客,請務必保留此出處http://alanwu.blog.51cto.com/3652632/1105681

 

臉書網友討論
發表於 2013-11-9 00:59:43 | 顯示全部樓層
謝謝分享

版主招募中

發表於 2013-11-9 00:59:43 | 顯示全部樓層
ding   支持  


發表於 2013-11-9 00:59:43 | 顯示全部樓層
好人一個  


您需要登錄後才可以回帖 登錄 | 註冊 |

本版積分規則



Archiver|手機版|小黑屋|免責聲明|TShopping

GMT+8, 2016-12-5 20:34 , Processed in 0.061474 second(s), 23 queries .

本論壇言論純屬發表者個人意見,與 TShopping綜合論壇 立場無關 如有意見侵犯了您的權益 請寫信聯絡我們。

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回復 返回頂部 返回列表