Linux VFS

一罪發表於2019-05-26

翻譯自Linux文件中的vfs.txt

介紹

VFS(Virtual File System)是核心提供的檔案系統抽象層,其提供了檔案系統的操作介面,可以隱藏底層不同檔案系統的實現。

Directiry Entry Cache(dcache)

VFS通過open()stat()這些介面傳入的檔名搜尋dcache,快速找到檔名對應的dentry。dentry的結構是struct dentry,這只是一個記憶體結構,不會持久化到磁碟中,僅僅是為了提升效能而已。

dentry cache是整個檔案空間的檢視,但是大部分情況下並不能同時將所有dentry都載入到記憶體。VFS會在執行lookup()時,建立還不在記憶體中的dentry。

The Inode Object

inode代表真實儲存在檔案系統中的物件,比如檔案、目錄、FIFO等等。inode的結構是struct inode。一個dentry只能指向一個inode,但一個inode可以被多個dentry指向。

對於塊裝置的檔案系統,inode儲存在磁碟上,並在需要的時候拷貝到記憶體中,修改inode後又會寫回磁碟進行持久化。對於偽檔案系統,inode儲存在記憶體中。

VFS呼叫lookup()從指定路徑的第一級目錄的dentry開始查詢對應的inode。lookup()的真實實現是由inode所在的底層檔案系統提供的。

The File Object

開啟一個檔案實際就是建立一個file物件。file的結構是struct file,其有指向一個dentry的指標,和一組操作file的函式指標。這些資訊是從inode中獲取的。在開啟檔案的最後,file會被加入當前程式的檔案描述符表中。

使用者的讀、寫、關閉檔案都通過fd進行,核心會可以根據fd獲取到對應的file物件,這些操作最終都會呼叫到底層檔案系統提供的函式。

註冊和掛載檔案系統

註冊和登出檔案系統的函式如下

#include <linux/fs.h>

extern int register_filesystem(struct file_system_type *);
extern int unregister_filesystem(struct file_system_type *);

當掛載檔案系統時,VFS會呼叫底層檔案系統指定的mount()函式,mount()會返回一個dentry作為檔案系統的根目錄。所有已掛載的檔案系統,可以在/proc/filesystems中看到。

struct file_system_type

該結構用於描述檔案系統,自核心2.6.30,有如下定義

struct file_system_type {
    const char *name;
    int fs_flags;
    struct dentry *(*mount) (struct file_system_type *, int,
                    const char *, void *);
    void (*kill_sb) (struct super_block *);
    struct module *owner;
    struct file_system_type * next;
    struct list_head fs_supers;
    struct lock_class_key s_lock_key;
    struct lock_class_key s_umount_key;
};
  • name:檔案系統的名字,比如"ext2"等。
  • fs_flags:比如FS_REQUIRES_DEV、FS_NO_DCACHE等一些標誌。
  • mount:掛載檔案系統時呼叫。
  • kill_sb:解除安裝檔案系統時呼叫。
  • owner:在大部分情況下,應當初始化為THIS_MODULE。
  • next:應當初始化為NULL。
  • s_lock_key,s_umount_key:用於檢查死鎖。

mount()的引數如下

  • struct file_system_type* fs_type:描述檔案系統,其中部分底層檔案系統初始化。
  • int flags:掛載的標誌,如FS_REQUIRES_DEV,FS_NO_DCACHE等。
  • const char *dev_name:掛載的裝置名。
  • void *data:任意的選項,通常是ASCII字串。

mount()成功時,要求加鎖並獲得superblock的活動引用,返回dentry(可以是子目錄的dentry),失敗時返回ERR_PTR(error)

grab_super()是獲取活動引用的函式,即讓spuer_block::a_active加1

mount()會建立一個superblock,其結構是struct superblockstruct superblock中有一個指向struct super_operations的指標s_op,其由一系列操作檔案系統的函式指標組成。

VFS提供瞭如下方法掛載不同型別的檔案系統

  • mount_bdev():掛載基於塊裝置的檔案系統。
  • mount_nodev():掛載不基於裝置的檔案系統。
  • mount_single():掛載共享例項的檔案系統。

這些函式都有一個入參int (*fill_super)(struct super_block *, void *, int),其用於初始化struct superblock的部分欄位。

fill_super的引數如下

  • struct super_block *sb:指向superblock。
  • void *data:mount的引數,通常是一個ASCII字串,需要自行解析。
  • int silent:確定是否輸出錯誤資訊。

The Superblock Object

一個superblock代表了一個已掛載的檔案系統。

struct super_operations

VFS通過struct super_operations操作superblock

struct super_operations {
        struct inode *(*alloc_inode)(struct super_block *sb);
        void (*destroy_inode)(struct inode *);

        void (*dirty_inode) (struct inode *, int flags);
        int (*write_inode) (struct inode *, int);
        void (*drop_inode) (struct inode *);
        void (*delete_inode) (struct inode *);
        void (*put_super) (struct super_block *);
        int (*sync_fs)(struct super_block *sb, int wait);
        int (*freeze_fs) (struct super_block *);
        int (*unfreeze_fs) (struct super_block *);
        int (*statfs) (struct dentry *, struct kstatfs *);
        int (*remount_fs) (struct super_block *, int *, char *);
        void (*clear_inode) (struct inode *);
        void (*umount_begin) (struct super_block *);

        int (*show_options)(struct seq_file *, struct dentry *);

        ssize_t (*quota_read)(struct super_block *, int, char *, size_t, loff_t);
        ssize_t (*quota_write)(struct super_block *, int, const char *, size_t, loff_t);
    int (*nr_cached_objects)(struct super_block *);
    void (*free_cached_objects)(struct super_block *, int);
};

除非有額外的說明,否則VFS不會加鎖呼叫這些函式。這意味著這些函式阻塞時,不會影響到其他執行緒。所有的函式只會在程式上下文呼叫,不會在中斷上下文中呼叫。

  • alloc_inode:用於分配和初始化inode。如果未指定該方法,則使用純粹的struct inode。通常情況下,檔案系統會自定義自己的inode結構,除了包含struct inode外,還會包含和底層檔案系統相關的欄位。
  • destroy_inode:銷燬inode,必須和alloc_inode一起實現。
  • dirty_inode:將inode標記為髒inode
  • write_inode:將inode寫回到磁碟。該函式第二個引數用於指定是否同步寫入,並非所有檔案系統都檢查這個標誌
  • drop_inode:用於判斷是否從記憶體中移除inode。當inode引用計數為0時呼叫該函式,呼叫前會鎖定inode->i_lock。drop_inode應該是NULL(正常UNIX檔案系統語義)或者是generic_delete_inode()(不考慮引用計數,強制清除inode,適用於不想快取inode的檔案系統)。
  • delete_inode:從記憶體中移除inode。v2.6.39的struct super_operations就沒有這個欄位,取而代之的是evict_inode
  • put_super:釋放superblock呼叫前鎖定superblock lock
  • sync_fs:將superblock關聯的髒資料寫回到儲存。第二個引數用於指明是否等待資料寫完後再返回。
  • freeze_fs:鎖定檔案系統並迫使它進入一致狀態。目前被邏輯卷管理(LVM)會用到該函式。
  • unfreeze_fs:解鎖檔案系統,使其可以重新寫入。
  • statfs:獲取檔案系統統計資訊。
  • remount_fs:重新掛載檔案系統,主要用於更新掛載引數。呼叫前鎖定**kernel lock
  • clear_inode:標記不再使用該inode。v2.6.39的struct super_operations就沒有這個欄位
  • umount_begin:解除安裝檔案系統
  • show_options:用於在/proc//mounts裡輸出掛載選項
  • quota_read:讀quota file
  • quota_write:寫quota file
  • nr_cached_objects:返回可釋放的物件個數。
  • free_cache_objects:清理物件。需要和nr_cached_objects一起定義

如果這些函式內會執行批量任務,那麼必須包含支援重新排程的函式,這使得VFS無需擔心這些函式長時間處理的問題。

設定inode時必須初始化struct inodei_op欄位,其指向struct inode_operations,該結構包含了一系列操作inode的函式。

struct xattr_handler

當檔案系統需要支援擴充套件屬性時,可以指定superblocks_xattr欄位,其指向一個一NULL結尾的struct xattr_handler陣列。擴充套件屬性是一個name-value對。

struct xattr_handler {
    const char *name;
    const char *prefix;
    int flags;      /* fs private flags */
    bool (*list)(struct dentry *dentry);
    int (*get)(const struct xattr_handler *, struct dentry *dentry,
           struct inode *inode, const char *name, void *buffer,
           size_t size);
    int (*set)(const struct xattr_handler *, struct dentry *dentry,
           struct inode *inode, const char *name, const void *buffer,
           size_t size, int flags);
};
  • name:該結構中的函式用於處理該名字代表的屬性,比如"system.posix_acl_access"。指定name,則prefix必須是NULL
  • prefix:該結構中的函式用於處理該字首代表的屬性,比如"user."。指定了prefix,則name必須是NULL
  • list:確定是否應為特定的dentry列出與此處理函式匹配的屬性。
  • get:獲取擴充套件屬性的值。該方法在getxattr(2)流程中呼叫
  • set:設定擴充套件屬性的值。如果新值為NULL,則移除擴充套件屬性。該函式在setxattr(2)removexattr(2)流程中呼叫

當檔案系統沒有xattr的處理方法或者沒有匹配的屬性時,會返回-EOPNOTSUPP

The Inode Object

一個inode代表了一個檔案系統物件

struct inode_operations

struct inode_operations描述了VFS如何操作inode

struct inode_operations {
    int (*create) (struct inode *,struct dentry *, umode_t, bool);
    struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int);
    int (*link) (struct dentry *,struct inode *,struct dentry *);
    int (*unlink) (struct inode *,struct dentry *);
    int (*symlink) (struct inode *,struct dentry *,const char *);
    int (*mkdir) (struct inode *,struct dentry *,umode_t);
    int (*rmdir) (struct inode *,struct dentry *);
    int (*mknod) (struct inode *,struct dentry *,umode_t,dev_t);
    int (*rename) (struct inode *, struct dentry *,
            struct inode *, struct dentry *, unsigned int);
    int (*readlink) (struct dentry *, char __user *,int);
    const char *(*get_link) (struct dentry *, struct inode *,
                 struct delayed_call *);
    int (*permission) (struct inode *, int);
    int (*get_acl)(struct inode *, int);
    int (*setattr) (struct dentry *, struct iattr *);
    int (*getattr) (const struct path *, struct kstat *, u32, unsigned int);
    ssize_t (*listxattr) (struct dentry *, char *, size_t);
    void (*update_time)(struct inode *, struct timespec *, int);
    int (*atomic_open)(struct inode *, struct dentry *, struct file *,
            unsigned open_flag, umode_t create_mode);
    int (*tmpfile) (struct inode *, struct dentry *, umode_t);
};

除非有額外說明,否則所有方法都不會持鎖呼叫。

  • create:建立inode,由open(2)creat(2)呼叫。只有當需要支援常規檔案時,才實現該函式。入參dentry必須沒有指向任何inode的,最後需要呼叫d_instantiate()將新建立的inode加入到這個dentry中。
  • lookup:查詢inode。
    • 需要查詢的inode的名字記錄在dentry中。如果找到了則需要呼叫d_add()將inode加入到dentry中,並且inode的引用計數要加1。如果inode不存在,則需要將NULL加入到dentry,代表dentry是無效的。
    • 當錯誤發生時,必須返回錯誤。如果只是將dentry指向NULL,VFS會執行create(2), mknod(2), mkdir(2)去建立inode,但這些函式還是會因為lookup失敗的原因而再次失敗。
    • 如果需要重新設定dentry的操作函式,可以給d_dop賦值。這些函式被呼叫時,會使用目錄inode的訊號量。
  • link:建立硬連結。和create一樣,該函式中需要呼叫d_instantiate()
  • unlink:支援刪除inode。
  • symlink:建立符號連結。
  • mkdir:建立目錄。該函式中也需要呼叫d_instantiate()
  • rmdir:刪除目錄。
  • mknod:建立裝置inode、命名管道或socket。該函式中也需要呼叫d_instantiate()
  • rename:重新命名。當檔案系統不支援某些方法和flag時,必須返回-EINVAL。當前以下flag已經實現
    • RENAME_NOREPLACE:如果新檔名已經存在,則應當返回-EEXIST。但是目前VFS已經檢查了新檔名是否存在。
    • RENAME_EXCHANGE:兩個檔案必須都存在,只是交換一下檔名。
  • get_link:獲取符號連結所指向的inode。
  • readlink:讀取符號連結所指向的inode的檔名。正常情況下,檔案系統只需要實現get_link。
  • permission:檢查類POSIX檔案系統的訪問許可權。可能會在rcu-walk模式下呼叫。如果在rcu-walk模式下,檔案系統必須在不阻塞或者不儲存inode的情況下檢查許可權。如果在rcu-walk模式下,發生一個無法處理的情況,則返回-ECHILD,此後會在ref-walk模式下再嘗試一次。
  • setattr:設定檔案屬性,由chmod(2)和相關的系統呼叫觸發。
  • getattr:獲取檔案屬性,由stat(2)和相關的系統呼叫觸發。
  • listxattr:由VFS呼叫列出一個檔案的所有擴充套件屬性,由listxattr(2)觸發。
  • update_time:更新inode的時間(atime、ctime、mtime)或i_version。如果該方法未定義,那麼VFS會自行更新inode,然後呼叫mark_inode_dirty_sync()標記該inode為髒inode
  • atomic_open:在open操作的最後呼叫。使用這個可選的方法,檔案系統可以原子性地查詢、建立、開啟一個檔案。如果該方法想讓呼叫者去開啟檔案,則應當通過finish_no_open()通知呼叫者。該方法只在最後一步是無效或者需要lookup時才呼叫。快取有效的dentry需要在f_op->open()中完成。如果檔案建立成功了,則需要在file->f_mode設定FMODE_CREATED。如果指定了O_EXCL,該方法需要在檔案存在時返回失敗。返回成功就設定FMODE_CREATED
  • tmpfile:在open(O_TMPFILE)的最後被呼叫。這是一個可選的方法,等同於在一個指定的目錄下,原子性地建立、開啟、刪除一個檔案。

The Address Space Object

address space物件用於組織和管理page cache中的page。它可以追蹤一個檔案使用的page以及一個檔案被對映到程式空間的page。它可以根據地址查詢page,追蹤被標記為Dirty或Writeback的page。

VM可以呼叫writepage()來清理髒頁,或者設定PagePrivate後呼叫releasepage()來釋放乾淨的頁以便重新使用。如果幹淨的頁沒有設定PagePrivate或者沒有外部引用,就會在沒有通知address space的情況下被釋放掉。為了實現這些功能,page需要通過lru_cache_add()方法放到LRU中,當page被使用時需要呼叫mark_page_active()

page使用一個基數樹(radix tree)來儲存。這棵樹維護了所有page的PG_DiryPG_Writeback狀態,因此這些頁能被快速找到。

mpage_writepages()是預設的writepages,它使用Dirty標記查詢髒頁並寫回。如果沒有使用mpage_writepages()(可能是address space提供了自己的writepage),那麼PAGECACHE_TAG_DIRTY標記就不會被使用。write_inode_now()sync_inode()使用PAGECACHE_TAG_DIRTY來檢查writepages是否將整個address space寫回到儲存中。

filemap*wait*sync_page*會使用Writeback標記,並通過filemap_fdatawait_range()等待所有寫回操作完成。

address space的處理方法可以通過page::private附加一些額外的資訊到page中。如果附加了資訊,那麼必須設定PG_Private。VM會呼叫address space的額外函式來處理這些資料。

address space作為儲存和應用之間的中間層。每次從儲存讀取一頁的資料到address space中,供應用讀取。應用可以將任意大小的資料寫入address space,但最後以頁為單位寫入到儲存中。

讀程式只需要readpage。寫程式則複雜一點,需要write_begin/write_end將資料寫入到address space,還需要writepagewritepages將資料寫到儲存中。

address space增刪頁受inode::i_mutex的保護。

當資料被寫入到page中時,需要設定PG_Dirtyset_page_dirty())。當需要writepages時會設定PG_Writeback並清除PG_Dirty。標記為PG_Writeback的page可以在任意時間寫回到儲存,一旦寫回完成後,該標誌被清除。

寫回會使用struct writeback_controlwritepagewritepages提供寫回的請求和限制資訊,該結構也用於返回寫回的結果。

處理寫回的錯誤

大部分使用快取IO的應用都會週期性地呼叫同步介面(如fsync()fdatasync()msync()sync_file_rage())來保證資料被寫回到儲存中。如果寫回時發生錯誤,這些介面應當返回錯誤,但僅在第一次返回錯誤,後續應當返回0,除非又有新的資料寫入且再次呼叫同步介面時又發生寫回錯誤。

理想情況下,核心應當以檔案為單位,返回其寫回的錯誤。但實際上page cache並沒有以檔案為單位追蹤髒頁,因此當發生寫回錯誤時,核心無法知道是哪個檔案發生的寫回錯誤。當前當發生寫回錯誤時,核心給所有開啟的檔案都返回錯誤,即使這個檔案並沒有寫入或者已經寫回成功了。

想使用該框架的檔案系統應當呼叫mapping_set_error()來記錄錯誤。在寫回資料後,檔案系統還應當呼叫file_check_and_advance_wb_err()來確保struct file::f_wb_err記錄的是正確的寫回錯誤。

struct address_space_operations

VFS使用該結構操作檔案的page cache。

struct address_space_operations {
    int (*writepage)(struct page *page, struct writeback_control *wbc);
    int (*readpage)(struct file *, struct page *);
    int (*writepages)(struct address_space *, struct writeback_control *);
    int (*set_page_dirty)(struct page *page);
    int (*readpages)(struct file *filp, struct address_space *mapping,
            struct list_head *pages, unsigned nr_pages);
    int (*write_begin)(struct file *, struct address_space *mapping,
                loff_t pos, unsigned len, unsigned flags,
                struct page **pagep, void **fsdata);
    int (*write_end)(struct file *, struct address_space *mapping,
                loff_t pos, unsigned len, unsigned copied,
                struct page *page, void *fsdata);
    sector_t (*bmap)(struct address_space *, sector_t);
    void (*invalidatepage) (struct page *, unsigned int, unsigned int);
    int (*releasepage) (struct page *, int);
    void (*freepage)(struct page *);
    ssize_t (*direct_IO)(struct kiocb *, struct iov_iter *iter);
    /* isolate a page for migration */
    bool (*isolate_page) (struct page *, isolate_mode_t);
    /* migrate the contents of a page to the specified target */
    int (*migratepage) (struct page *, struct page *);
    /* put migration-failed page back to right list */
    void (*putback_page) (struct page *);
    int (*launder_page) (struct page *);

    int (*is_partially_uptodate) (struct page *, unsigned long,
                    unsigned long);
    void (*is_dirty_writeback) (struct page *, bool *, bool *);
    int (*error_remove_page) (struct mapping *mapping, struct page *page);
    int (*swap_activate)(struct file *);
    int (*swap_deactivate)(struct file *);
};
  • writepage:基於資料完整性(sync)或者釋放記憶體(flush)的原因,VM會呼叫該方法將髒頁寫回到儲存。寫回過程:清除PG_Dirty,設定PageLockedtrue,然後writepage開始寫回,並設定PG_Writeback,然後在完成寫回後解鎖page。
    • 如果wbc->sync_modeWB_SYNC_NONE,那麼writepage在無法完成寫回指定頁的情況下,返回AOP_WRITEPAGE_ACTIVATE,這樣VM就不會一直為該頁呼叫writepage了。
  • readpage:VM呼叫該方法從儲存中讀取一頁的資料。該頁會被鎖定,但需要在讀取完成後,標記為uptodate並解鎖。如果readpage需要解鎖,也可以解鎖,並返回AOP_TRUNCATED_PAGE。在這種情況下,VM會重新安置和加鎖該頁,再次呼叫readpage
  • writepages:VM呼叫該方法將address space相關聯的頁都寫回到儲存。如果如果wbc->sync_modeWBC_SYNC_ALL,那麼writeback_control就會指定一組必須寫回的頁。如果是WBC_SYNC_NONE的話,則只要求儘可能寫入nr_to_write頁。如果該方法未定義,那麼會用mpage_writepages來替代。該方法會從address space中獲取所有標記為DIRTY的頁,然後傳遞給writepage
  • set_page_dirty:VM呼叫該方法設定髒頁。只有在address space中有私有資料的page且在頁變髒時需要更新這些私有資料時,才需要該方法。如果定義了該方法,那麼它必須設定PageDirty標誌和基數樹中的PAGECACHE_TAG_DIRTY標誌。
  • readpages:VM呼叫該方法讀取address space相關聯的頁。這個方法本質上是多次呼叫readpage。readpages只用於預讀,因此讀取錯誤被忽略了。
  • write_begin:由generic_perform_write()呼叫,用於告知檔案系統準備在指定偏移處寫len位元組的資料。address space應預留一些資源保證完成這次寫入操作。如果寫入要更新頁中的一部分,那麼需要將整頁塊讀取到記憶體中,即讀改寫。檔案系統通過pagep返回page cache中已鎖定的page。呼叫者會將資料寫入到該page中。需要支援實際寫入的資料長度小於傳給write_begin的長度的場景。可通過fsdata儲存需要傳遞給write_end的資料。
  • write_end:該函式必須在成功呼叫write_begin和拷貝資料後被呼叫。len是傳遞給write_begin的len,copied則是實際拷貝的長度。檔案系統必須釋放page的鎖和引用,並更新struct inode::i_size。失敗返回小於0,否則返回實際拷貝到page cache的長度。
  • bmap:VFS呼叫該方法將邏輯塊偏移對映為物理塊序號。該方法用於ioctl(FIBMAP)和swap檔案。為了swap一個檔案,檔案必須固定地對映到一個塊裝置上。swap系統會通過bmap來找到檔案所在的塊,然後直接儲存,而不通過檔案系統。
  • invalidatepage:如果page設定了PagePrivate,那麼當部分或全部的page從address space中被移除時,就會呼叫invalidatepage。
  • releasepage:釋放標記為PagePrivate的page,該方法需要移除page的私有資料,並清除PagePrivate。如果該方法失敗了則返回0。releasepage用於兩種場景
    • VM發現一個沒有活動使用者的乾淨page,並向釋放該page。如果釋放成功,那麼該頁就會從address space中移除,變為自由的page。
    • 需要讓address space中的部分或全部page失效。這種場景由fadvise(POSIX_FADV_DONTNEED)觸發,或者檔案系統明確請求(比如當nfs和9fs認為cache的資料已經與儲存不一致時,會呼叫invalidate_inode_pages2()
  • freepage:一旦page在page cache中不可見時,為了允許清除私有資料,就會呼叫該方法。因為該方法可能被記憶體回收者呼叫,所以該方法不能假設原始的address space還存在,也不應當阻塞。
  • direct_IO:由generic read/write類函式呼叫來執行direct IO。
  • isolate_page:VM在需要隔離一個可移動的非LRU的page時呼叫。如果成功了,VM會通過__SetPageIsolated將該頁標記為PG_isolated
  • migrate_page:該方法用於壓縮實體記憶體使用量。如果VM想重新放置page(可能是記憶體卡的故障訊號觸發的),就會傳遞一個老page和一個新page給這個方法。該方法需要傳輸私有資料,並更新所有引用。
  • putback_page:當已隔離的頁遷移失敗時,VM會呼叫該方法。
  • launder_page:在釋放頁前呼叫。該方法將髒頁寫回儲存並避免重新弄髒該頁,在整個過程中,都會加鎖。
  • is_partially_uptodate:塊大小不等於頁大小,一頁可能包含多個塊。如果VM讀取到所需的塊資料,那麼就無需等待整個頁讀取完畢。
  • is_dirty_writeback:當VM想回收page時呼叫。VM會根據dirty和writeback的值決定是否需要停頓回收頁,以便能完成某些IO。通常情況下,VM可以使用PageDirtyPageWriteback,但是某些檔案系統會有更復雜的狀態(比如NFS的unstable pages需要避免被回收),或者因為鎖的問題沒有設定這些標誌。該方法可以向VM表明該頁是髒頁或正在寫回的頁,讓VM停止回收該頁。
  • error_remove_page:如果truncation是正常的話,通常設定為generic_error_remove_page()。主要用於處理記憶體失敗。實現該方法,意味著你會處理那些頁,除非你已經鎖定或者增加了引用計數。
  • swap_activate:當檔案使用了swapon時呼叫,用於分配空間並將塊的查詢資訊儲存在記憶體中。返回0代表成功,也意味著該檔案可以被當做備份的交換空間。
  • swap_deactivate:當swap_activate成功後,呼叫該方法使得該檔案變為swapoff。

The File Object

一個file物件代表程式開啟的一個檔案。

struct file_operations

VFS使用struct file_operations操作一個開啟的檔案。

自v4.18,struct file_operations定義如下

struct file_operations {
    struct module *owner;
    loff_t (*llseek) (struct file *, loff_t, int);
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
    ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
    int (*iterate) (struct file *, struct dir_context *);
    int (*iterate_shared) (struct file *, struct dir_context *);
    __poll_t (*poll) (struct file *, struct poll_table_struct *);
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
    int (*mmap) (struct file *, struct vm_area_struct *);
    int (*open) (struct inode *, struct file *);
    int (*flush) (struct file *, fl_owner_t id);
    int (*release) (struct inode *, struct file *);
    int (*fsync) (struct file *, loff_t, loff_t, int datasync);
    int (*fasync) (int, struct file *, int);
    int (*lock) (struct file *, int, struct file_lock *);
    ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
    unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
    int (*check_flags)(int);
    int (*flock) (struct file *, int, struct file_lock *);
    ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
    ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
    int (*setlease)(struct file *, long, struct file_lock **, void **);
    long (*fallocate)(struct file *file, int mode, loff_t offset,
              loff_t len);
    void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
    unsigned (*mmap_capabilities)(struct file *);
#endif
    ssize_t (*copy_file_range)(struct file *, loff_t, struct file *, loff_t, size_t, unsigned int);
    int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t, u64);
    int (*dedupe_file_range)(struct file *, loff_t, struct file *, loff_t, u64);
    int (*fadvise)(struct file *, loff_t, loff_t, int);
};

除非額外說明,否則這些函式都不會持鎖呼叫。

  • read_iter:支援將檔案資料讀取到非連續的記憶體中
  • write_iter:支援將非連續記憶體中的資料寫入到檔案中。
  • iterate:讀取目錄內容
  • iterate_shared:當檔案系統支援併發的目錄迭代時,使用該函式讀取目錄內容
  • compat_ioctl:在64位核心上相容32位的系統呼叫
  • open:建立一個新的struct file,並初始化strutc file::private_data
  • flush:由close(2)呼叫
  • release:當檔案的引用計數為0時呼叫
  • fasync:檔案為非阻塞模式時,由fcntl(2)呼叫
  • lock:由fcntl(2)呼叫,執行F_GETLKF_SETLKF_SETLKW命令
  • fallocate:預分配block

Directory Entry Cache (dcache)

dentry屬於VFS和單個檔案系統,與裝置驅動無關。每個dentry都有一個指向其父dentry的指標,以及一個子dentry的hash連結串列。

struct dentry_operations

struct dentry_operations是可選的,可以設定為NULL,或是使用VFS預設的函式。

v2.6.22中,其定義如下

struct dentry_operations {
    int (*d_revalidate)(struct dentry *, unsigned int);
    int (*d_weak_revalidate)(struct dentry *, unsigned int);
    int (*d_hash)(const struct dentry *, struct qstr *);
    int (*d_compare)(const struct dentry *,
            unsigned int, const char *, const struct qstr *);
    int (*d_delete)(const struct dentry *);
    int (*d_init)(struct dentry *);
    void (*d_release)(struct dentry *);
    void (*d_iput)(struct dentry *, struct inode *);
    char *(*d_dname)(struct dentry *, char *, int);
    struct vfsmount *(*d_automount)(struct path *);
    int (*d_manage)(const struct path *, bool);
    struct dentry *(*d_real)(struct dentry *, const struct inode *);
};
  • d_revalidate:當在cache中找到dentry時,判斷dentry是否有效,返回正數代表還有效,返回0或負數代表無效。大多數本地檔案系統將其設為NULL,因為它們的dentry總是有效的。網路檔案系統則不同,因為服務端的變更,客戶端可能感知不到。
  • d_weak_revalidate
  • d_hash:計算hash值,根據hash值加入父dentry的hash表中
  • d_compare:比較dentry的名字,必須是常熟且冪等
  • d_delete:判斷是否刪除dentry。返回1表示立即刪除,返回0代表快取。d_delete必須是常熟且冪等
  • d_init:當分配dentry後呼叫,初始化dentry
  • d_release:釋放dentry
  • d_iput:歸還inode引用(在d_release前呼叫)。如果是NULL,VFS會呼叫iput(),否則需要自行呼叫iput()
  • d_dname:當需要生成dentry的路徑名時呼叫。對於偽檔案系統(sockfs,pipefs)延遲生成路徑名來說很有用。因為沒有加鎖,所以d_dname不能修改dentry本身

Directory Entry Cache API

以下是操作dentry的函式

  • dget():獲取一個已存在的dentry的引用
  • dput():歸還一個dentry的引用。如果引用計數為0,且該dentry還在其父dentry的hash表中,則呼叫d_delete()檢查該dentry是否還應該快取。如果需要快取,則放入LRU連結串列中。
  • d_drop():從父dentry的hash表中刪除dentry
  • d_delete():刪除一個dentry,如果沒有其他引用,則該dentry變為一個無效的dentry(即指向NULL inode),d_iput()就會被呼叫。如果還有引用,則呼叫d_drop()
  • d_add():將dentry加入到其父dentry的hash表中,然後呼叫d_instantiate()
  • d_instantiate():將dentry加入到inode的dentry連結串列中,並更新dentry指向的inode(即struct dentry::d_inode)。inode的引用計數也會增加。
  • d_lookup():查詢dentry,如果找到了則增加引用計數後返回。

相關文章