<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          一文讀懂|Linux 虛擬文件系統(tǒng)(VFS)

          共 10659字,需瀏覽 22分鐘

           ·

          2023-08-16 15:36

          前言

          虛擬文件系統(tǒng)是一個(gè)很龐大的架構(gòu),如果要分析的面面俱到,會(huì)顯得特別復(fù)雜而笨拙,讓人看著看著,就不知所云了(當(dāng)然主要還是筆者太菜),所以這篇博客,以 open() 函數(shù)為切入點(diǎn),來(lái)試著分析分析VFS文件系統(tǒng)的運(yùn)轉(zhuǎn)機(jī)理,本文的代碼來(lái)源于 linux3.4.2。

          基礎(chǔ)知識(shí)

          首先我們來(lái)看一張圖:

          (圖1)

          從這張圖中,我們可以看出,系統(tǒng)調(diào)用函數(shù)并不是直接操作真正的文件系統(tǒng),而是通過(guò)一層中間層,也就是我們說(shuō)的虛擬文件系統(tǒng),為什么要有虛擬文件系統(tǒng)?

          linux中常見(jiàn)的文件系統(tǒng)有三類:基于磁盤的文件系統(tǒng);基于內(nèi)存的文件系統(tǒng);網(wǎng)絡(luò)文件系統(tǒng),(這三類文件系統(tǒng)是共存于文件系統(tǒng)層,為不同類型的數(shù)據(jù)提供存儲(chǔ)服務(wù),這三類文件系統(tǒng)格式是不一樣的,也就是說(shuō)如果不通過(guò)虛擬文件系統(tǒng),直接對(duì)真正的文件系統(tǒng)進(jìn)行讀取,有種類型的文件系統(tǒng),你就得寫幾種相對(duì)應(yīng)的讀取函數(shù)),所以說(shuō)虛擬文件的出現(xiàn)(VFS)就是為了通過(guò)使用同一套文件 I/O 系統(tǒng) 調(diào)用即可對(duì) Linux 中的任意文件進(jìn)行操作而無(wú)需考慮其所在的具體文件系統(tǒng)格式。

          VFS的數(shù)據(jù)結(jié)構(gòu)

          VFS依靠四個(gè)主要的數(shù)據(jù)結(jié)構(gòu)和一些輔助的數(shù)據(jù)結(jié)構(gòu)來(lái)描述其結(jié)構(gòu)信息,這些數(shù)據(jù)結(jié)構(gòu)表現(xiàn)得就像是對(duì)象;每個(gè)主要對(duì)象中都包含由操作函數(shù)表構(gòu)成的操作對(duì)象,這些操作對(duì)象描述了內(nèi)核針對(duì)這幾個(gè)主要的對(duì)象可以進(jìn)行的操作。

          1、超級(jí)塊對(duì)象

          存儲(chǔ)一個(gè)已安裝的文件系統(tǒng)的控制信息,代表一個(gè)已安裝的文件系統(tǒng);每次一個(gè)實(shí)際的文件系統(tǒng)被安裝時(shí), 內(nèi)核會(huì)從磁盤的特定位置讀取一些控制信息來(lái)填充內(nèi)存中的超級(jí)塊對(duì)象。一個(gè)安裝實(shí)例和一個(gè)超級(jí)塊對(duì)象一一對(duì)應(yīng)。超級(jí)塊通過(guò)其結(jié)構(gòu)中的一個(gè)域s_type記錄它所屬的文件系統(tǒng)類型。

          struct super_block { //超級(jí)塊數(shù)據(jù)結(jié)構(gòu)
                  struct list_head s_list;                /*指向超級(jí)塊鏈表的指針*/
                  ……
                  struct file_system_type  *s_type;       /*文件系統(tǒng)類型*/
                 struct super_operations  *s_op;         /*超級(jí)塊方法*/
                  ……
                  struct list_head         s_instances;   /*該類型文件系統(tǒng)*/
                  ……
          };

          struct super_operations { //超級(jí)塊方法
                  ……
                  //該函數(shù)在給定的超級(jí)塊下創(chuàng)建并初始化一個(gè)新的索引節(jié)點(diǎn)對(duì)象
                  struct inode *(*alloc_inode)(struct super_block *sb);
                 ……
                  //該函數(shù)從磁盤上讀取索引節(jié)點(diǎn),并動(dòng)態(tài)填充內(nèi)存中對(duì)應(yīng)的索引節(jié)點(diǎn)對(duì)象的剩余部分
                  void (*read_inode) (struct inode *);
                 ……
          };

          2、索引節(jié)點(diǎn)對(duì)象

          索引節(jié)點(diǎn)對(duì)象存儲(chǔ)了文件的相關(guān)信息,代表了存儲(chǔ)設(shè)備上的一個(gè)實(shí)際的物理文件。當(dāng)一個(gè) 文件首次被訪問(wèn)時(shí),內(nèi)核會(huì)在內(nèi)存中組裝相應(yīng)的索引節(jié)點(diǎn)對(duì)象,以便向內(nèi)核提供對(duì)一個(gè)文件進(jìn)行操 作時(shí)所必需的全部信息;這些信息一部分存儲(chǔ)在磁盤特定位置,另外一部分是在加載時(shí)動(dòng)態(tài)填充的。

          struct inode {//索引節(jié)點(diǎn)結(jié)構(gòu)
                ……
                struct inode_operations  *i_op;     /*索引節(jié)點(diǎn)操作表*/
               struct file_operations   *i_fop;  /*該索引節(jié)點(diǎn)對(duì)應(yīng)文件的文件操作集*/
               struct super_block       *i_sb;     /*相關(guān)的超級(jí)塊*/
               ……
          };

          struct inode_operations { //索引節(jié)點(diǎn)方法
               ……
               //該函數(shù)為dentry對(duì)象所對(duì)應(yīng)的文件創(chuàng)建一個(gè)新的索引節(jié)點(diǎn),主要是由open()系統(tǒng)調(diào)用來(lái)調(diào)用
               int (*create) (struct inode *,struct dentry *,int, struct nameidata *);

               //在特定目錄中尋找dentry對(duì)象所對(duì)應(yīng)的索引節(jié)點(diǎn)
               struct dentry * (*lookup) (struct inode *,struct dentry *, struct nameidata *);
               ……
          };

          3、目錄項(xiàng)對(duì)象

          引入目錄項(xiàng)的概念主要是出于方便查找文件的目的。一個(gè)路徑的各個(gè)組成部分,不管是目錄還是 普通的文件,都是一個(gè)目錄項(xiàng)對(duì)象。如:在路徑 /home/source/test.c 中,目錄 /homesource 和文件 test.c都對(duì)應(yīng)一個(gè)目錄項(xiàng)對(duì)象。不同于前面的兩個(gè)對(duì)象,目錄項(xiàng)對(duì)象沒(méi)有對(duì)應(yīng)的磁盤數(shù)據(jù)結(jié)構(gòu),VFS 在遍歷路徑名的過(guò)程中現(xiàn)場(chǎng)將它們逐個(gè)地解析成目錄項(xiàng)對(duì)象。

          struct dentry {//目錄項(xiàng)結(jié)構(gòu)
               ……
               struct inode *d_inode;           /*相關(guān)的索引節(jié)點(diǎn)*/
              struct dentry *d_parent;         /*父目錄的目錄項(xiàng)對(duì)象*/
              struct qstr d_name;              /*目錄項(xiàng)的名字*/
              ……
               struct list_head d_subdirs;      /*子目錄*/
              ……
               struct dentry_operations *d_op;  /*目錄項(xiàng)操作表*/
              struct super_block *d_sb;        /*文件超級(jí)塊*/
              ……
          };

          struct dentry_operations {
              //判斷目錄項(xiàng)是否有效;
              int (*d_revalidate)(struct dentry *, struct nameidata *);
              //為目錄項(xiàng)生成散列值;
              int (*d_hash) (struct dentry *, struct qstr *);
              ……
          };

          4、文件對(duì)象

          文件對(duì)象是已打開(kāi)的文件在內(nèi)存中的表示,主要用于建立進(jìn)程和磁盤上的文件的對(duì)應(yīng)關(guān)系。它由 sys_open() 現(xiàn)場(chǎng)創(chuàng)建,由 sys_close() 銷毀。文件對(duì)象和物理文件的關(guān)系有點(diǎn)像進(jìn)程和程序的關(guān)系一樣。

          當(dāng)我們站在用戶空間來(lái)看待 VFS,我們像是只需與文件對(duì)象打交道,而無(wú)須關(guān)心超級(jí)塊,索引節(jié)點(diǎn)或目錄項(xiàng)。因?yàn)槎鄠€(gè)進(jìn)程可以同時(shí)打開(kāi)和操作 同一個(gè)文件,所以同一個(gè)文件也可能存在多個(gè)對(duì)應(yīng)的文件對(duì)象。

          文件對(duì)象僅僅在進(jìn)程觀點(diǎn)上代表已經(jīng)打開(kāi)的文件,它 反過(guò)來(lái)指向目錄項(xiàng)對(duì)象(反過(guò)來(lái)指向索引節(jié)點(diǎn))。一個(gè)文件對(duì)應(yīng)的文件對(duì)象可能不是惟一的,但是其對(duì)應(yīng)的索引節(jié)點(diǎn)和 目錄項(xiàng)對(duì)象無(wú)疑是惟一的。

          struct file {
              ……
               struct list_head        f_list;        /*文件對(duì)象鏈表*/
              struct dentry          *f_dentry;       /*相關(guān)目錄項(xiàng)對(duì)象*/
              struct vfsmount        *f_vfsmnt;       /*相關(guān)的安裝文件系統(tǒng)*/
              struct file_operations *f_op;           /*文件操作表*/
              ……
          };

          struct file_operations {
              ……
              //文件讀操作
              ssize_t (*read) (struct file *, char __user *, size_tloff_t *);
              ……
              //文件寫操作
              ssize_t (*write) (struct file *, const char __user *, size_tloff_t *);
              ……
              int (*readdir) (struct file *, void *, filldir_t);
              ……
              //文件打開(kāi)操作
              int (*open) (struct inode *, struct file *);
              ……
          };

          正篇

          經(jīng)過(guò)基礎(chǔ)知識(shí)點(diǎn)的介紹后,我們開(kāi)始來(lái)探究,當(dāng)我們通 open() 嘗試去打開(kāi)一個(gè)文件的時(shí)候,Linux 內(nèi)部是如何找到對(duì)應(yīng)的存儲(chǔ)在硬件上的該文件的數(shù)據(jù)。

          (圖2)

          (圖3)

          首先我們來(lái)看看上面這兩張圖,files_struct 主要就是一個(gè) file 指針數(shù)組,我們通常說(shuō)的文件描述符是一個(gè)整數(shù),而這個(gè)整數(shù)正好可以作為下標(biāo),從而從 files_struct 中獲得 file 結(jié)構(gòu)。

          task_struct 為進(jìn)程描述符,代表的是打開(kāi)文件的這么一個(gè)動(dòng)作,這里我想表達(dá)的知識(shí)點(diǎn):當(dāng)文件第一次被打開(kāi)時(shí)(打開(kāi)成功),會(huì)建立起如上圖所示的聯(lián)系,返回 fd 文件描述符就這樣和底層的存儲(chǔ)結(jié)構(gòu)聯(lián)系在了一起,fd 作為文件描述符,文件作為數(shù)據(jù)的載體,我們可以將它們理解為密碼和保險(xiǎn)柜之間的關(guān)系,第一打開(kāi)文件就是相當(dāng)初始化時(shí)設(shè)置密碼(建立起了密碼和保險(xiǎn)柜的聯(lián)系),當(dāng)我們以后再需要拿取保險(xiǎn)柜中的東西時(shí),只需要通過(guò)第一次設(shè)置的密碼就可以對(duì)保險(xiǎn)柜進(jìn)程操作。

          內(nèi)核中,對(duì)應(yīng)于每個(gè)進(jìn)程都有一個(gè)文件描述符表,表示這個(gè)進(jìn)程打開(kāi)的所有文件。文件描述表中每一項(xiàng)都是一個(gè)指針,指向一個(gè)用于描述打開(kāi)的文件的數(shù)據(jù)塊 ——— file 對(duì)象,file 對(duì)象中描述了文件的打開(kāi)模式,讀寫位置等重要信息,當(dāng)進(jìn)程打開(kāi)一個(gè)文件時(shí),內(nèi)核就會(huì)創(chuàng)建一個(gè)新的 file 對(duì)象。

          需要注意的是,file 對(duì)象不是專屬于某個(gè)進(jìn)程的,不同進(jìn)程的文件描述符表中的指針可以指向相同的 file 對(duì)象,從而共享這個(gè)打開(kāi)的文件。 file 對(duì)象有引用計(jì)數(shù),記錄了引用這個(gè)對(duì)象的文件描述符個(gè)數(shù),只有當(dāng)引用計(jì)數(shù)為0時(shí),內(nèi)核才銷毀 file 對(duì)象,因此某個(gè)進(jìn)程關(guān)閉文件,不影響與之共享同 一個(gè) file 對(duì)象的進(jìn)程.

          下面我們來(lái)分析具體的代碼。

          應(yīng)用層:

          應(yīng)用程序在操作任何一個(gè)文件之前,必須先調(diào)用 open() 來(lái)打開(kāi)該文件,即通知內(nèi)核新建一個(gè)代表該文件的結(jié)構(gòu),并且返回該文件的描述符(一個(gè)整數(shù)),該描述符在進(jìn)程內(nèi)唯一。所用到函數(shù)為 open()

          int open(const char * pathname,int oflag, mode_t mode )
              /*pathname:代表需要打開(kāi)的文件的文件名;

                 oflag:表示打開(kāi)的標(biāo)識(shí) (只讀打開(kāi)/只寫打開(kāi)/讀寫打開(kāi) ...........)
                 
                mode: 當(dāng)新創(chuàng)建一個(gè)文件時(shí),需要指定mode參數(shù)(設(shè)置權(quán)限)
               */

          內(nèi)核層:

          當(dāng) open() 系統(tǒng)調(diào)用進(jìn)入內(nèi)核時(shí)候,最終調(diào)用的函數(shù)為:

          SYSCALL_DEFINE3(open, const char __user , filename, int, flags, int,mode)

          該函數(shù)位于 fs/open.c 中,下面將會(huì)分析其具體的實(shí)現(xiàn)過(guò)程。

          SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, int, mode)
          {
           long ret;
           //判斷系統(tǒng)是否支持大文件,即判斷l(xiāng)ong的位數(shù),如果64則表示支持大文件; 
           if (force_o_largefile())
            flags |= O_LARGEFILE;
           
           //完成主要的open工作,AT_FDCWD表示從當(dāng)前目錄開(kāi)始查找
           ret = do_sys_open(AT_FDCWD, filename, flags, mode);
           /* avoid REGPARM breakage on x86: */
           asmlinkage_protect(3, ret, filename, flags, mode);
           return ret;
          }

          該函數(shù)主要調(diào)用 do_sys_open() 來(lái)完成打開(kāi)工作,do_sys_open() 的代碼分析如下。

          long do_sys_open(int dfd, const char__user *filename, int flags, int mode)
          {
           //將欲打開(kāi)的文件名拷貝到內(nèi)核中,該函數(shù)的分析見(jiàn)下文;
           char *tmp = getname(filename);
           int fd = PTR_ERR(tmp);

           if (!IS_ERR(tmp)) {
            //從進(jìn)程的文件表中找到一個(gè)空閑的文件表指針,如果出錯(cuò),則返回,見(jiàn)下文說(shuō)明;
            fd = fd = get_unused_fd();
            if (fd >= 0) {
             //執(zhí)行打開(kāi)操作,見(jiàn)下文說(shuō)明,dfd=AT_FDCWD;
             struct file *f = do_filp_open(dfdtmpflagsmode, 0);
             if (IS_ERR(f)) {
              put_unused_fd(fd);
              fd = PTR_ERR(f);
             } else {
              fsnotify_open(f);//作用是將 filp 的監(jiān)控點(diǎn)打開(kāi),并將其添加到監(jiān)控系統(tǒng)中
              //添加打開(kāi)的文件表f到當(dāng)前進(jìn)程的文件表數(shù)組中,見(jiàn)下文說(shuō)明;
              fd_install(fd, f);
             }
            }
            putname(tmp);
           }
           return fd;
          }

          (圖4)

          從代碼和流程圖的分析中我們知道了,fd 和 file 是如何建立聯(lián)系 (file 對(duì)象中包含一個(gè)指針,指向 dentry 對(duì)象。dentry 對(duì)象代表一個(gè)獨(dú)立的文件路徑,如果一個(gè)文件路徑被打開(kāi)多次,那么會(huì)建立多個(gè) file 對(duì)象,但它們都指向同一個(gè) dentry 對(duì)象。dentry 對(duì)象中又包含一個(gè)指向 inode 對(duì)象的指針。inode 對(duì)象代表一個(gè)獨(dú)立文件。因?yàn)榇嬖谟叉溄优c符號(hào)鏈接,因此不同的 dentry 對(duì)象可以指向相同的 inode 對(duì)象。inode 對(duì)象包含了最終對(duì)文件進(jìn)行操作所需的所有信息,如文件系統(tǒng)類型、文件的操作方法、文件的權(quán)限、訪問(wèn)日期等)。

          那我們反向思考一下,現(xiàn)在我們已經(jīng)得到 fd,如何找到對(duì)應(yīng) file, 在當(dāng)前進(jìn)程中我們保留著文件描述符,文件描述符中(files_structs),文件描述符中又保留著文件描述表(fatable),通過(guò)文件描述符表中 file 類型的指針數(shù)組對(duì)應(yīng)的 fd 的項(xiàng),我們可以找到 file

          這篇文章到這里就算結(jié)束了,還留有一個(gè)工作沒(méi)有完成,如 do_filp_open(dfd, tmp, flags, mode) 是如何得到 file?,這是一個(gè)很復(fù)雜的過(guò)程,以后有空,筆者會(huì)嘗試著去分析。

          原文鏈接:
          https://blog.csdn.net/KUNPLAYBOY/article/details/123191919


          瀏覽 1542
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日韩精品十区 | 欧美成人电影一区 | 免费看A A长 | 九九成人手机 | 中文字幕永久免费地址 |