成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

深入理解Linux文件系統之文件系統掛載(上)

系統 Linux
Linux系統中我們經常將一個塊設備上的文件系統掛載到某個目錄下才能訪問這個文件系統下的文件,但是你有沒有思考過:為什么塊設備掛載之后才能訪問文件?掛載文件系統Linux內核到底為我們做了哪些事情?

[[402508]]

本文轉載自微信公眾號「Linux內核遠航者」,作者Linux內核遠航者。轉載本文請聯系Linux內核遠航者公眾號。

1.開場白

  • 環境:

處理器架構:arm64

內核源碼:linux-5.11

ubuntu版本:20.04.1

代碼閱讀工具:vim+ctags+cscope

我們知道,Linux系統中我們經常將一個塊設備上的文件系統掛載到某個目錄下才能訪問這個文件系統下的文件,但是你有沒有思考過:為什么塊設備掛載之后才能訪問文件?掛載文件系統Linux內核到底為我們做了哪些事情?是否可以不將文件系統掛載到具體的目錄下也能訪問?下面,本文將詳細講解Linxu系統中,文件系統掛載的奧秘。

注:本文主要講解文件系統掛載核心邏輯,暫不涉及掛載命名空間和綁定掛載等內容(后面的內容可能會涉及),且以ext2磁盤文件系統為例講解掛載。本專題文章分為上下兩篇,上篇主要介紹掛載全貌以及具體文件系統的掛載方法,下篇介紹如何通過掛載實例關聯掛載點和超級塊。

2. vfs 幾個重要對象

在這里我們不介紹整個IO棧,只說明和文件系統相關的vfs和具體文件系統層。我們知道在Linux中通過虛擬文件系統層VFS統一所有具體的文件系統,提取所有具體文件系統的共性,屏蔽具體文件系統的差異。VFS既是向下的接口(所有文件系統都必須實現該接口),同時也是向上的接口(用戶進程通過系統調用最終能夠訪問文件系統功能)。

下面我們來看下,vfs中幾個比較重要的結構體對象:

2.1 file_system_type

這個結構來描述一種文件系統類型,一般具體文件系統會定義這個結構,然后注冊到系統中;定義了具體文件系統的掛載和卸載方法,文件系統掛載時調用其掛載方法構建超級塊、跟dentry等實例。

文件系統分為以下幾種:

1)磁盤文件系統

文件在非易失性存儲介質上(如硬盤,flash),掉電文件不丟失。

如ext2,ext4,xfs

2)內存文件系統

文件在內存上,掉電丟失。

如tmpfs

3)偽文件系統

是假的文件系統,是利用虛擬文件系統的接口(可以對用戶可見如proc、sysfs,也可以對用戶不可見內核可見如sockfs,bdev)。

如proc,sysfs,sockfs,bdev

4)網絡文件系統

這種文件系統允許訪問另一臺計算機上的數據,該計算機通過網絡連接到本地計算機。

如nfs文件系統

結構體定義源碼路徑:include/linux/fs.h +2226

2.2 super_block

超級塊,用于描述塊設備上的一個文件系統總體信息(如文件塊大小,最大文件大小,文件系統魔數等),一個塊設備上的文件系統可以被掛載多次,但是內存中只能有個super_block來描述(至少對于磁盤文件系統來說)。

結構體定義源碼路徑:include/linux/fs.h +1414

2.3 mount

掛載描述符,用于建立超級塊和掛載點等之間的聯系,描述文件系統的一次掛載,一個塊設備上的文件系統可以被掛載多次,每次掛載內存中有一個mount對象描述。

結構體定義源碼路徑:fs/mount.h +39

2.4 inode

索引節點對象,描述磁盤上的一個文件元數據(文件屬性、位置等),有些文件系統需要從塊設備上讀取磁盤上的索引節點,然后在內存中創建vfs的索引節點對象,一般在文件第一次打開時創建。

結構體定義源碼路徑:include/linux/fs.h +610

2.5 dentry

目錄項對象,用于描述文件的層次結構,從而構建文件系統的目錄樹,文件系統將目錄當作文件,目錄的數據由目錄項組成,而每個目錄項存儲一個目錄或文件的名稱和索引節點號等內容。每當進程訪問一個目錄項就會在內存中創建目錄項對象(如ext2路徑名查找中,通過查找父目錄數據塊的目錄項,找到對應文件/目錄的名稱,獲得inode號來找到對應inode)。

結構體定義源碼路徑:include/linux/dcache.h +90

2.6 file

文件對象,描述進程打開的文件,當進程打開文件時會創建文件對象加入到進程的文件打開表,通過文件描述符來索引文件對象,后面讀寫等操作都通過文件描述符進行(一個文件可以被多個進程打開,會由多個文件對象加入到各個進程的文件打開表,但是inode只有一個)。

結構體定義源碼路徑:include/linux/fs.h +915

3. 掛載總體流程

3.1系統調用處理

用戶執行掛載是通過系統調用路徑進入內核處理,拷貝用戶空間傳遞參數到內核,掛載委托do_mount。

//fs/namespace.c

SYSCALL_DEFINE5(mount

參數:

dev_name 要掛載的塊設備

dir_name 掛載點目錄

type 文件系統類型名

flags 掛載標志

data 掛載選項

-> kernel_type = copy_mount_string(type); //拷貝文件系統類型名到內核空間

-> kernel_dev = copy_mount_string(dev_name) //拷貝塊設備路徑名到內核空間 -> options = copy_mount_options(data) //拷貝掛載選項到內核空間

-> do_mount(kernel_dev, dir_name, kernel_type, flags, options) //掛載委托do_mount

3.2 掛載點路徑查找

掛載點路徑查找,掛載委托path_mount

do_mount

-> user_path_at(AT_FDCWD, dir_name, LOOKUP_FOLLOW, &path) //掛載點路徑查找 查找掛載點目錄的 vfsmount和dentry 存放在 path

-> path_mount(dev_name, &path, type_page, flags, data_page) //掛載委托path_mount

3.3 參數合法性檢查

參數合法性檢查, 新掛載委托do_new_mount

path_mount

-> 參數合法性檢查

-> 根據掛載標志調用不同函數處理 這里講解是默認 do_new_mount

3.4 調用具體文件系統掛載方法

  1. do_new_mount 
  2. -> type = get_fs_type(fstype)  //根據傳遞的文件系統名  查找已經注冊的文件系統類型 
  3. -> fc = fs_context_for_mount(type, sb_flags) //為掛載分配文件系統上下文 struct fs_context 
  4.  -> alloc_fs_context 
  5.    -> 分配fs_context fc = kzalloc(sizeof(struct fs_context), GFP_KERNEL) 
  6.    ->  設置 ...  
  7.    ->  fc->fs_type     = get_filesystem(fs_type);  //賦值相應的文件系統類型 
  8.    ->  init_fs_context = **fc->fs_type->init_fs_context**;  //新內核使用fs_type->init_fs_context接口  來初始化文件系統上下文 
  9.     if (!init_fs_context)   //init_fs_context回掉 主要用于初始化 
  10.         init_fs_context = **legacy_init_fs_context**;    //沒有 fs_type->init_fs_context接口  
  11.    -> init_fs_context(fc)  //初始化文件系統上下文 (初始化一些回掉函數,供后續使用) 

來看下文件系統類型沒有實現init_fs_context接口的情況:

  1. //fs/fs_context.c 
  2. init_fs_context = legacy_init_fs_context 
  3. ->  fc->ops = &legacy_fs_context_ops   //設置文件系統上下午操作 
  4.                     ->.get_tree               = legacy_get_tree  //操作方法的get_tree用于  讀取磁盤超級塊并在內存創建超級塊,創建跟inode, 跟dentry 
  5.                         -> root = fc->fs_type->mount(fc->fs_type, fc->sb_flags, 
  6.                                          ¦     fc->source, ctx->legacy_data)  //調用文件系統類型的mount方法來讀取并創建超級塊 
  7.                         -> fc->root = root  //賦值創建好的跟dentry 
  8.  
  9.  
  10. 有一些文件系統使用原來的接口(fs_type.mount  = xxx_mount):如ext2,ext4等 
  11. 有一些文件系統使用新的接口(fs_type.init_fs_context =  xxx_init_fs_context):xfs, proc, sys 
  12.  
  13. 無論使用哪一種,都會在xxx_init_fs_contex中實現 fc->ops =  &xxx_context_ops 接口,后面會看的都會調用fc->ops.get_tree 來讀取創建超級塊實例 

繼續往下走:

  1. do_new_mount 
  2.     -> ... 
  3.     ->  fc = fs_context_for_mount(type, sb_flags) //分配 賦值文件系統上下文 
  4.     -> parse_monolithic_mount_data(fc, data)  //調用fc->ops->parse_monolithic  解析掛載選項 
  5.     -> mount_capable(fc) //檢查是否有掛載權限 
  6.     -> vfs_get_tree(fc)  //fs/super.c 掛載重點   調用fc->ops->get_tree(fc) 讀取創建超級塊實例 
  7.     ... 

3.5 掛載實例添加到全局文件系統樹

  1. do_new_mount 
  2.     ... 
  3.     ->  do_new_mount_fc(fc, path, mnt_flags)  //創建mount實例 關聯掛載點和超級塊  添加到命名空間的掛載樹中 

下面主要看下vfs_get_tree和do_new_mount_fc:

4.具體文件系統掛載方法

1)vfs_get_tree

  1. //以ext2文件系統為例 
  2. vfs_get_tree  //fs/namespace.c 
  3. -> fc->ops->get_tree(fc) 
  4.  -> legacy_get_tree   //上面分析過 fs_type->init_fs_context == NULL使用舊的接口(ext2為NULL
  5.   ->fc->fs_type->mount 
  6.    -> ext2_mount  //fs/ext2/super.c  調用到具體文件系統的掛載方法 

來看下ext2對掛載的處理:

啟動階段初始化->

  1. //fs/ext2/super.c 
  2. module_init(init_ext2_fs) 
  3. init_ext2_fs 
  4.  ->init_inodecache  //創建ext2_inode_cache 對象緩存 
  5.  ->register_filesystem(&ext2_fs_type) //注冊ext2的文件系統類型 
  6.  
  7.  
  8. static struct file_system_type ext2_fs_type = { 
  9.         .owner          = THIS_MODULE, 
  10.         .name           = "ext2"
  11.         .mount          = ext2_mount,   //掛載時調用  用于讀取創建超級塊實例 
  12.         .kill_sb        = kill_block_super,  //卸載時調用  用于釋放超級塊 
  13.         .fs_flags       = FS_REQUIRES_DEV,  //文件系統標志為  請求塊設備,文件系統在塊設備上 
  14. }; 

掛載時調用->

  1. // fs/ext2/super.c 
  2. static struct dentry *ext2_mount(struct file_system_type *fs_type,            
  3.         int flags, const char *dev_name, void *data) 
  4.         return mount_bdev(fs_type, flags, dev_name, data, ext2_fill_super); 

ext2_mount通過調用mount_bdev來執行實際文件系統的掛載工作,ext2_fill_super的一個函數指針作為參數傳遞給get_sb_bdev。該函數用于填充一個超級塊對象,如果內存中沒有適當的超級塊對象,數據就必須從硬盤讀取。

mount_bdev是個公用的函數,一般磁盤文件系統會使用它來根據具體文件系統的fill_super方法來讀取磁盤上的超級塊并在創建內存超級塊。

我們來看下mount_bdev的實現(**它執行完成之后會創建vfs的三大數據結構 super_block、根inode和根dentry **):

2)mount_bdev源碼分析

  1. //fs/super.c 
  2. mount_bdev 
  3. ->bdev = blkdev_get_by_path(dev_name, mode, fs_type)  //通過要掛載的塊設備路徑名 獲得它的塊設備描述符block_device(會涉及到路徑名查找和通過設備號在bdev文件系統查找block_device,block_device是添加塊設備到系統時創建的) 
  4. -> s = sget(fs_type, test_bdev_super, set_bdev_super, flags | sb_NOSEC,    
  5.          ¦bdev);  //查找或創建vfs的超級塊  (會首先在文件系統類型的fs_supers鏈表查找是否已經讀取過指定的超級塊,會對比每個超級塊的s_bdev塊設備描述符,沒有創建一個) 
  6. ->  if (s->s_root) {   //超級塊的根dentry是否被賦值? 
  7.   ... 
  8.  } else {   //沒有賦值說明時新創建的sb 
  9.   ... 
  10.   -> sb_set_blocksize(s, block_size(bdev)) //根據塊設備描述符設置文件系統塊大小 
  11.   ->  fill_super(s, data, flags & sb_SILENT ? 1 : 0)  //調用傳遞的具體文件系統的填充超級塊方法讀取填充超級塊等 如ext2_fill_super 
  12.   ->  bdev->bd_super = s  //塊設備bd_super指向sb 
  13.  } 
  14. -> return dget(s->s_root)  //返回文件系統的根dentry 

可以看到mount_bdev主要是:1.根據要掛載的塊設備文件名查找到對應的塊設備描述符(內核后面操作塊設備都是使用塊設備描述符);2.首先在文件系統類型的fs_supers鏈表查找是否已經讀取過指定的vfs超級塊,會對比每個超級塊的s_bdev塊設備描述符,沒有創建一個vfs超級塊; 3.新創建的vfs超級塊,需要調用具體文件系統的fill_super方法來讀取填充超級塊。

那么下面主要集中在具體文件系統的fill_super方法,這里是ext2_fill_super:

分析重點代碼如下:

3)ext2_fill_super源碼分析

  1. //fs/ext2/super.c 
  2. static int ext2_fill_super(struct super_block *sb, void *data, int silent)                
  3. {                                                                                                           
  4.         struct buffer_head * bh;    //緩沖區頭  記錄讀取的磁盤超級塊                                                      
  5.         struct ext2_sb_info * sbi;   //內存的ext2 超級塊信息                                                     
  6.         struct ext2_super_block * es;  //磁盤上的  超級塊信息                                                
  7.            ... 
  8.                                                          
  9.         sbi = kzalloc(sizeof(*sbi), GFP_KERNEL);     //分配 內存的ext2 超級塊信息結構                                    
  10.         if (!sbi)                                                                         
  11.                 goto failed;                                                              
  12.                                                                                           
  13.          ...                                                   
  14.         sb->s_fs_info = sbi;     //vfs的超級塊 的s_fs_info指向內存的ext2 超級塊信息結構                                                    
  15.         sbi->s_sb_block = sb_block;                                                       
  16.                                                         
  17.       if (!(bh = sb_bread(sb, logic_sb_block))) {  // 讀取磁盤上的超級塊到內存的 使用buffer_head關聯內存緩沖區和磁盤扇區                                    
  18.               ext2_msg(sb, KERN_ERR, "error: unable to read superblock");                  
  19.               goto failed_sbi;                                                             
  20.       }                                                                                    
  21.                     
  22.       es = (struct ext2_super_block *) (((char *)bh->b_data) + offset);  //轉換為struct ext2_super_block 結構                  
  23.       sbi->s_es = es; //  內存的ext2 超級塊信息結構的 s_es指向真正的ext2磁盤超級塊信息結構                                                                  
  24.       sb->s_magic = le16_to_cpu(es->s_magic); //獲得文件系統魔數   ext2為0xEF53                                         
  25.                                                                                            
  26.       if (sb->s_magic != EXT2_SUPER_MAGIC)    //驗證 魔數是否正確                                            
  27.               goto cantfind_ext2; 
  28.  
  29.     blocksize = BLOCK_SIZE << le32_to_cpu(sbi->s_es->s_log_block_size); //獲得磁盤讀取的塊大小    
  30.  
  31.                                                                                            
  32.         /* If the blocksize doesn't match, re-read the thing.. */                          
  33.         if (sb->s_blocksize != blocksize) {  //塊大小不匹配 需要重新讀取超級塊                                               
  34.                 brelse(bh);                                                                
  35.                                                                                            
  36.                 if (!sb_set_blocksize(sb, blocksize)) {                                    
  37.                         ext2_msg(sb, KERN_ERR,                                             
  38.                                 "error: bad blocksize %d", blocksize);                     
  39.                         goto failed_sbi;                                                   
  40.                 }                                                                          
  41.                                                                                            
  42.                 logic_sb_block = (sb_block*BLOCK_SIZE) / blocksize;                        
  43.                 offset = (sb_block*BLOCK_SIZE) % blocksize;                                
  44.                 bh = sb_bread(sb, logic_sb_block); //重新 讀取超級塊                                        
  45.                 if(!bh) {                                                                  
  46.                         ext2_msg(sb, KERN_ERR, "error: couldn't read"                      
  47.                                 "superblock on 2nd try");                                  
  48.                         goto failed_sbi;                                                   
  49.                 }                                                                          
  50.                 es = (struct ext2_super_block *) (((char *)bh->b_data) + offset);          
  51.                 sbi->s_es = es;                                                            
  52.                 if (es->s_magic != cpu_to_le16(EXT2_SUPER_MAGIC)) {                        
  53.                         ext2_msg(sb, KERN_ERR, "error: magic mismatch");                   
  54.                         goto failed_mount;                                                 
  55.                 }                                                                          
  56.         }                                                                                  
  57.                                                                                            
  58.         sb->s_maxbytes = ext2_max_size(sb->s_blocksize_bits);  //設置最大文件大小                             
  59.         ...                                                            
  60.         
  61.        //讀取或設置 inode大小和第一個inode號                                                                         
  62.        if (le32_to_cpu(es->s_rev_level) == EXT2_GOOD_OLD_REV) {                    
  63.                sbi->s_inode_size = EXT2_GOOD_OLD_INODE_SIZE;                       
  64.                sbi->s_first_ino = EXT2_GOOD_OLD_FIRST_INO;                         
  65.        } else {                                                                    
  66.                sbi->s_inode_size = le16_to_cpu(es->s_inode_size);                  
  67.                sbi->s_first_ino = le32_to_cpu(es->s_first_ino);                    
  68.               ...                           
  69.        }                                                                           
  70.                                                                                    
  71.       ...              
  72.                                                                                    
  73.        sbi->s_blocks_per_group = le32_to_cpu(es->s_blocks_per_group);    //賦值每個塊組 塊個數           
  74.        sbi->s_frags_per_group = le32_to_cpu(es->s_frags_per_group);                
  75.        sbi->s_inodes_per_group = le32_to_cpu(es->s_inodes_per_group);  //賦值每個塊組 inode個數              
  76.                                                                                    
  77.        sbi->s_inodes_per_block = sb->s_blocksize / EXT2_INODE_SIZE(sb);    //賦值每個塊 inode個數        
  78.        ...                      
  79.        sbi->s_desc_per_block = sb->s_blocksize /                                   
  80.                                        sizeof (struct ext2_group_desc);    //賦值每個塊 塊組描述符個數      
  81.        sbi->s_sbh = bh;  //賦值讀取的超級塊緩沖區                                                           
  82.        sbi->s_mount_state = le16_to_cpu(es->s_state);    //賦值掛載狀態                            
  83.      ...                                    
  84.                                                                                 
  85.     if (sb->s_magic != EXT2_SUPER_MAGIC)                                        
  86.             goto cantfind_ext2;                                                 
  87.     
  88.    //一些合法性檢查 
  89.      ...     
  90.      
  91.   //計算塊組描述符 個數 
  92.   sbi->s_groups_count = ((le32_to_cpu(es->s_blocks_count) -                
  93.                           le32_to_cpu(es->s_first_data_block) - 1)         
  94.                                   / EXT2_BLOCKS_PER_GROUP(sb)) + 1;        
  95.   db_count = (sbi->s_groups_count + EXT2_DESC_PER_BLOCK(sb) - 1) /         
  96.           ¦  EXT2_DESC_PER_BLOCK(sb);                                      
  97.   sbi->s_group_desc = kmalloc_array(db_count,                              
  98.                                   ¦  sizeof(struct buffer_head *),         
  99.                                   ¦  GFP_KERNEL);  //分配塊組描述符 bh數組                        
  100.     
  101.      
  102.     for (i = 0; i < db_count; i++) {      //讀取塊組描述符                                  
  103.           block = descriptor_loc(sb, logic_sb_block, i);                 
  104.           sbi->s_group_desc[i] = sb_bread(sb, block);   //讀取的 塊組描述符緩沖區保存 到sbi->s_group_desc[i]               
  105.           if (!sbi->s_group_desc[i]) {                                   
  106.                   for (j = 0; j < i; j++)                                
  107.                           brelse (sbi->s_group_desc[j]);                 
  108.                   ext2_msg(sb, KERN_ERR,                                 
  109.                           "error: unable to read group descriptors");    
  110.                   goto failed_mount_group_desc;                          
  111.           }                                                              
  112.   }                                                                      
  113.  
  114.                                                                      
  115.   sb->s_op = &ext2_sops;   //賦值超級塊操作 
  116.   ... 
  117.  root = ext2_iget(sb, EXT2_ROOT_INO); //讀取根inode  (ext2 根根inode號為2)  
  118.  
  119.  sb->s_root = d_make_root(root);  //創建根dentry  并建立根inode和根dentry關系 
  120.  ext2_write_super(sb);  //同步超級塊信息到磁盤 如掛載時間等 

可以看到ext2_fill_super主要工作為:

1.讀取磁盤上的超級塊;

2.填充并關聯vfs超級塊;

3.讀取塊組描述符;

4.讀取磁盤根inode并建立vfs 根inode;

5.創建根dentry關聯到根inode。

下面給出ext2_fill_super之后ext2相關圖解:

 

有了這些信息,雖然能夠獲得塊設備上的文件系統全貌,內核也能通過已經建立好的block_device等結構訪問塊設備,但是用戶進程不能真正意義上訪問到,用戶一般會通過open打開一個文件路徑來訪問文件,但是現在并沒有關聯掛載目錄的路徑,需要將文件系統關聯到掛載點,以至于路徑名查找的時候查找到掛載點后,在轉向文件系統的根目錄,而這需要通過do_new_mount_fc來去關聯并加入全局的文件系統樹中,下一篇我們將做詳細講解。

 

責任編輯:武曉燕 來源: Linux內核遠航者
相關推薦

2018-09-12 15:48:35

ext4Linux文件系統

2022-04-21 14:09:17

lsofLinux虛擬文件

2020-10-12 17:40:44

lsofLinux虛擬文件

2023-09-27 23:19:04

Linuxmount

2020-07-22 14:53:06

Linux系統虛擬文件

2011-01-13 14:10:30

Linux文件系統

2011-01-11 10:29:35

Linux文件

2021-05-27 07:12:20

Ext2路徑Linux

2010-01-26 13:41:50

Android文件系統

2022-04-15 08:00:00

FUSE開發Android

2009-12-22 13:15:59

Linux ueven

2021-06-06 16:55:22

Linux文件系統

2021-04-12 05:44:44

Linux文件系統

2013-10-09 11:07:31

日志文件系統

2010-03-02 13:27:17

LinuxXFS文件系

2019-09-20 10:04:45

Linux系統虛擬文件

2018-08-24 10:10:25

Linux文件系統技術

2010-01-08 18:01:03

Ubuntu硬盤操作

2009-12-22 15:12:33

Linux擴展文件系統

2009-12-25 09:58:46

linux劃分文件系統
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 自拍偷拍在线视频 | 中文字幕啪啪 | 欧美a在线 | 国产精品 欧美精品 | 在线观看国产91 | 国产一区二区毛片 | 看av网 | 国产精品久久久免费 | 嫩草伊人 | 日韩av美女电影 | 久久久青草婷婷精品综合日韩 | 蜜桃视频在线观看免费视频网站www | 色欧美片视频在线观看 | 成人三级视频 | 狠狠操狠狠干 | 国外成人在线视频网站 | 亚洲国产片 | 99精品欧美一区二区三区综合在线 | 天天操夜夜操 | 天堂视频免费 | 日日摸夜夜添夜夜添精品视频 | 热久色| 中文字幕日本一区二区 | 国产在线观看网站 | 日韩久久久久久久久久久 | 精品一级| 成人免费视频网站 | 国产精品视频久久 | 999久久精品 | 国产一级特黄真人毛片 | 毛片.com | 超级碰在线 | 成人精品鲁一区一区二区 | 亚洲一区二区三区四区av | 99精品在线 | 久久久精品网站 | 2021天天躁夜夜看 | 亚洲www啪成人一区二区麻豆 | 久久久久成人精品 | 日韩三| 美女激情av |