Linux VFS機制簡析(二)
接上一篇Linux VFS機制簡析(一),本篇繼續介紹有關Address space和address operations、file和file operations、dentry和dentry operations和dentry cache API。
Address Space
Address Space用於管理page caches里的page頁,它關聯某個文件的所有pages,並管理文件的內容到進程地址空間的映射。它還提供了內存管理接口(page回收等)、根據地址查找page、跟蹤page的tags(如dirty和writeback)等等功能。
VM模塊會調用->write_page方法去嘗試將臟頁刷盤,以及調用->releasepage方法將clean page釋放。帶有PagePrivate標記的clean page(引用為0)會被VM直接釋放而不通知Address Space。
為了實現這個功能,Address Space通過lru_cache_add()將page放入LRU,並通過mark_page_active()標記page正在使用。
Pages通過->index保存在一個radix樹里,該radix樹維護page的PG_Dirty和PG_Writeback信息,因此查找這兩個標識的pages變得非常快。
Dirty標記(PAGECACHE_TAG_DIRTY)主要由->writepages(默認方法mpage_writepages())方法使用。它使用該標記查找臟頁並調用->writepage方法。如果Address operations實現了自己的->writepages(不使用mpage_writepages),則Dirty標記將幾乎沒有作用。write_inode_now()和sync_inode()通過Dirty標記來檢查->writepages是否成功完成。
Writeback標記主要是由filemap_wait 方法和sync_page* 方法使用,通過調用filemap_fdatawait_range()等待所有的writeback完成。如果定義了->sync_page,則會調用它來等待所有需要writeback的page結束。
Address Space Handler可以通過page的private字段保存額外的數據,此時需要設置PG_Private標識。這樣VM的相關操作會調用address的handler處理這些數據。
上面說的這么多Page相關的管理,其實Address Space最核心的作用是充當存儲和應用程序的中間緩存。數據從存儲側以page為單位讀入address space,通過拷貝或者mapping的方式提供給應用層。應用寫入數據到address space,然后通過writeback機制寫入到存儲。
讀操作的核心是readpage()。寫操作稍微復雜些,可以通過write_begin/write_end或者set_page_dirty寫入數據到address space,再通過writepage、sync_page和writepages寫入數據到存儲。從address space里增加刪除page由inode的i_mutex鎖保護。
當數據寫入到page,需要設置PG_Dirty標識,當writepage准備寫入存儲時清除PG_Dirty,並設置PG_Writeback標識,知道數據完全寫入存儲后清除PG_Writeback。
struct address_space_operations
struct address_space_operations的定義如下:
struct address_space_operations {
int (*writepage)(struct page *page, struct writeback_control *wbc);
int (*readpage)(struct file *, struct page *);
int (*sync_page)(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);
int (*invalidatepage) (struct page *, unsigned long);
int (*releasepage) (struct page *, int);
void (*freepage)(struct page *);
ssize_t (*direct_IO)(int, struct kiocb *, const struct iovec *iov,
loff_t offset, unsigned long nr_segs);
struct page* (*get_xip_page)(struct address_space *, sector_t,
int);
/* migrate the contents of a page to the specified target */
int (*migratepage) (struct page *, struct page *);
int (*launder_page) (struct page *);
int (*error_remove_page) (struct mapping *mapping, struct page *page);
int (*swap_activate)(struct file *);
int (*swap_deactivate)(struct file *);
};
writepage:VM調用,用於將臟頁寫入后端存儲。參數wbc->sync_mode顯示是什么原因觸發,'sync'或者'flush'(釋放內存)。調用時PG_Dirty已經被清除,並且PageLocked已經設置。writepage開始寫入數據時需要設置PG_Writeback,並且寫入結束時清除該標記。無論是同步還是異步寫入,都要保證函數返回時page處於unlocked狀態。
如果wbc->sync_mode是WB_SYNC_NONE(不等待),則writepage遇到困難時可以不那么努力的寫入,而是返回AOP_WRITEPAGE_ACTIVATE,這樣VM不會老是來寫該page。
readpage:VM調用,用於從后端存儲讀取數據。調用時,page處於lock狀態,並且在讀取結束時需要設置為unlock狀態,並設置uptodate。如果readpage處理過程中需要unlock page,則unlcok之后需要返回AOP_TRUNCATED_PAGE,調用者將重新定位page並重新lock,成功之后會再次調用readpage。
sync_page:VM調用,用於通知后端存儲處理該page的I/O。該page所屬address space的其他Pages的I/O也可能被處理。該函數是可選的,僅用於等待PG_Writeback的page處理完成。
writepages:VM調用,將address space里所有Dirty的pages寫入后端存儲。如果wbc->sync_mode是WBC_SYNC_ALL,則writeback_control會選取一個范圍的pages必須寫入。如果是WBC_SYNC_NONE,則根據參數nr_to_write盡可能寫入這么多pages。如果沒有設置,則默認調用mpage_writepages()。
set_page_dirty:VM調用,用於設置page為dirty。通常用於address space里有新的數據寫入,如memory mapping的page被修改。該函數將設置PageDirty標記,並在Radix樹里設置PAGECACHE_TAG_DIRTY標識。
readpages:VM調用,用於讀取address space里的指定pages。主要是通過調用readpage將一組pages讀取。通常用於預讀,因此讀取失敗的錯誤碼可能會被忽略。
write_begin:由通用的buffered寫流程調用,寫入len長度數據到文件的offset處。address space可能需要申請額外的存儲空間來保證寫操作可以完成,或者需要從后端存儲讀取不在緩存里的pages。該函數返回的pagep要處於locked狀態,調用者將直接寫入數據。返回參數fsdata用於私有數據指針,它將傳遞給write_end函數。如果函數返回<0,則write_end將不會調用。
write_end:數據拷貝到write_begin返回的page后,調用write_end將page unlock,遞減引用計數並更新i_size字段。
bmap:VFS調用用於映射邏輯塊的偏移和物理塊編號。該方法由FIBMAP ioctl使用,並且是swap文件。swap系統不直接進入文件系統,而是通過BMAP方式建立內存地址和文件的塊映射,然后直接使用內存地址。
invalidatepage:如果設置了PagePrivate,則當Page部分或者全部從address space里刪除時調用該方法。通常是因為address space里執行了一個截斷或者是失效所有數據。和page關聯的私有信息需要更新,或者直接被釋放(如果失效的offset為0的話,整個page將被釋放)。
releasepage:用於將PagePrivate pages釋放,它將把私有數據釋放,然后清除PagePrivate標識。releasepage有兩種使用場景,一是VM發現沒有引用計數的clean page,想將其變成free page。通過調用releasepage將其從address space里摘掉變為clean page。二是有invalid請求需要將address space里的部分或全部pages失效。通常是fadvice系統調用或者文件系統自己認為緩存里的數據已經不是最新的了,此時通過調用invalidate_inode_pages2()將pages釋放。調動該函數前,需要保證pages已經是invalidate的。如果釋放私有數據失敗,則需要在返回錯誤之前將PageUptodate清除。
freepage:用於將不在pagecache里的page釋放,page必須不屬於任何address space。通常有內存回收處理程序調用。
direct_IO:由通用讀寫流程調用,繞過pagecache,DIO方式讀取數據。
get_xip_page:VM調用,將block number轉換為page。支持XIP(execute in place)的文件系統需要實現該函數。
migrate_page:在old page和new page之間遷移數據,通常用於內存整理(減少碎片)。遷移時需要將私有數據和引用一起遷移。
launder_page:在free之前調用,用於writeback dirty page。為了防止再次被設置dirty,操作過程中持有page lock。
error_remove_page:用於內存分配失敗的處理,如果address space支持truncation,通常設置為generic_error_remove_page()。
swap_activate and swap_deactivate:用於swapon在一個文件上時,分配空間並將block信息保存在內存中。以及swapoff時釋放空間。
File
一個File數據結構代表一個進程里打開的一個文件。所以File結構是跟進程相關的,不同的進程打開同一個文件會在每個進程里都有一個File對象,對應到進程的文件句柄。
同一個文件File結構指向的inode是同一個,所以通過pagecache緩存進行數據讀寫的時候,使用的是inode里同一個address space,保證文件數據在不同進程里的一致性。
struct file_operations
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 (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*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 *);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *);
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 *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long arg, struct file_lock **);
long (*fallocate)(struct file *, int mode, loff_t offset, loff_t len);
};
同樣,如果沒有特別說明,則所有操作都在沒有鎖持有的情況下調用。
file_operations里大部分函數跟POSIX文件系統接口語義一樣,就不單獨列出了。
Dentry
dcache(dentry cache)用於緩存dentry,每個dentry用於索引filename和inode number。dentry也有一套操作合集dentry operations用於管理dentry。底層文件系統可以選擇實現自己的dentry operations來替換默認的operations。
struct dentry_operations
struct dentry_operations的定義如下:
struct dentry_operations {
int (*d_revalidate)(struct dentry *, unsigned int);
int (*d_weak_revalidate)(struct dentry *, unsigned int);
int (*d_hash)(const struct dentry *, const struct inode *,
struct qstr *);
int (*d_compare)(const struct dentry *, const struct inode *,
const struct dentry *, const struct inode *,
unsigned int, const char *, const struct qstr *);
int (*d_delete)(const 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)(struct dentry *, bool);
};
d_revalidate:VFS用於檢查在dcache里找到的dentry是否有效。通常設置為NULL,則只要在dcache找到即認為是有效的。但對網絡文件系統如NFS來說,dentry可能在一段時間之后就會失效,因此需要實現該函數用於檢查是否有效。如果有效,函數需要返回一個正數。
d_revalidate可能在rcu-walk模式(flags & LOOKUP_RCU)下被調用。此時該函數里不能阻塞也不能寫入數據到dentry,並且d_parent和d_inode不能使用,因為他們可能瞬間就可能被修改。如果在rcu-walk模式遇到困難,則返回-ECHILD,將在ref-walk模式下重新調用。
d_weak_revalidate:用於檢查'jumped'的dentry,即那些不是通過lookup獲取的dentry,如'', '.'或者'..'。這種場景只需要檢查dentry對應inode是否OK即可。該函數不會在rcu-walk模式下調用,所以可以放心的使用inode。
d_hash:用於VFS將dentry放入HASH列表。並不清楚HASH表用來做啥,通常不需要設置它,使用VFS默認的即可。
d_compare:用於比較dentry name和指定的name。該函數必須是可重入的,即每次的返回結果一樣。
d_delete:用於引用計數遞減為0時調用,返回1則dcache立即刪除dentry,返回0則繼續緩存該dentry。默認為NULL,則總是將dentry進行緩存。該函數必須是可重入的,即每次的返回結果一樣。
d_release:用於釋放dentry資源。
d_iput:用於釋放dentry對應inode引用計數。該函數在釋放dentry之前調用。如果為NULL,則VFS默認調用iput()。
d_dname:用於生成dentry的pathname,主要是一些偽文件系統(sockfs, pipefs等)用於延遲生成pathname。一般文件系統不實現該函數,因為其dentry存在於dcache的hash表里(通過pathname做hash),所以並不希望pathname變化。
d_automount:可選函數,用於穿越到一個自動掛載的dentry。它會創建一個新的vfsmount記錄,並將其返回,成功后調用者將根據vfsmount去嘗試mount它到掛載點。
d_manage:可選函數,用於管理從dentry進行transition。
Directory Entry Cache API
以下函數是VFS提供給文件系統參與維護和管理的dentry cache的API接口。
dget:用於增加dentry引用計數。
dput:遞減引用計數,如果減為0,則調用d_delete判斷是否留在緩存里。如果判斷為否,或者該dentry已經不在其父目錄hash列表里,則將其刪除。如果判斷為是,則dentry放入LRU鏈表,並在觸發內存回收時刪除。
d_drop:將dentry從其父目錄的hash列表里刪除。隨后如果引用計數減為0,該dentry將被刪除。
d_delete:將dentry刪除。如果引用計數不為0,則調用d_drop。如果為0,則調用d_iput將dentry搞成nagtive dentry。注意該函數不是dentry operations->d_delete函數指針,而是VFS提供的API接口。
d_add:將dentry加入到父目錄的hash列表里,並調用d_instantiate。
d_instantiate:將dentry加入到對應的inode的hash列表里,並更新其d_inode字段。inode的引用計數i_count字段需要遞增。該函數通常用於新創建inode給一個nagtive dentry。
d_lookup:根據pathname,查找父目錄dentry下的某個dentry。如果找到,則增加引用計數並返回dentry。調用用完該dentry之后需要通過dput將引用計數遞減。
總結
VFS的角色包括:
- 管理可用的文件系統類型,將設備和文件系統實例進行關聯。
- 處理文件系統的相關操作,為應用程序提供標准文件系統接口。
VFS和具體的文件系統系統之間主要通過幾個數據結構:super_block, inode, dentry, file和address space以及對應的operations: sb_ops, i_ops, d_ops, f_ops和a_ops來實現文件系統的功能。