鎖定

下面的文字描述了 VFS 相關方法的鎖定規則。它(據信)是最新的。,如果您更改原型或鎖定協議中的任何內容 - 請更新此檔案。並更新樹中的相關例項,不要將其留給檔案系統/裝置等的維護者。至少,將可疑案例列表放在此檔案的末尾。不要將其變成日誌 - 樹外程式碼的維護者應該能夠使用 diff(1)。

目前這裡缺少的東西:套接字操作。Alexey?

dentry_operations

原型

int (*d_revalidate)(struct inode *, const struct qstr *,
                    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)(struct dentry *);
int (*d_init)(struct dentry *);
void (*d_release)(struct dentry *);
void (*d_iput)(struct dentry *, struct inode *);
char *(*d_dname)((struct dentry *dentry, char *buffer, int buflen);
struct vfsmount *(*d_automount)(struct path *path);
int (*d_manage)(const struct path *, bool);
struct dentry *(*d_real)(struct dentry *, enum d_real_type type);
bool (*d_unalias_trylock)(const struct dentry *);
void (*d_unalias_unlock)(const struct dentry *);

鎖定規則

操作

rename_lock

->d_lock

可能阻塞

rcu-walk

d_revalidate

是 (ref-walk)

可能

d_weak_revalidate

d_hash

可能

d_compare

可能

d_delete

d_init

d_release

d_prune

d_iput

d_dname

d_automount

d_manage

是 (ref-walk)

可能

d_real

d_unalias_trylock

d_unalias_unlock

inode_operations

原型

int (*create) (struct mnt_idmap *, 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 mnt_idmap *, struct inode *,struct dentry *,const char *);
struct dentry *(*mkdir) (struct mnt_idmap *, struct inode *,struct dentry *,umode_t);
int (*rmdir) (struct inode *,struct dentry *);
int (*mknod) (struct mnt_idmap *, struct inode *,struct dentry *,umode_t,dev_t);
int (*rename) (struct mnt_idmap *, 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 *);
void (*truncate) (struct inode *);
int (*permission) (struct mnt_idmap *, struct inode *, int, unsigned int);
struct posix_acl * (*get_inode_acl)(struct inode *, int, bool);
int (*setattr) (struct mnt_idmap *, struct dentry *, struct iattr *);
int (*getattr) (struct mnt_idmap *, const struct path *, struct kstat *, u32, unsigned int);
ssize_t (*listxattr) (struct dentry *, char *, size_t);
int (*fiemap)(struct inode *, struct fiemap_extent_info *, u64 start, u64 len);
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 mnt_idmap *, struct inode *,
                struct file *, umode_t);
int (*fileattr_set)(struct mnt_idmap *idmap,
                    struct dentry *dentry, struct fileattr *fa);
int (*fileattr_get)(struct dentry *dentry, struct fileattr *fa);
struct posix_acl * (*get_acl)(struct mnt_idmap *, struct dentry *, int);
struct offset_ctx *(*get_offset_ctx)(struct inode *inode);
鎖定規則

全部可能阻塞

操作

i_rwsem(inode)

lookup

共享

create

獨佔

link

獨佔(兩者)

mknod

獨佔

symlink

獨佔

mkdir

獨佔

unlink

獨佔(兩者)

rmdir

獨佔(兩者)(見下文)

rename

獨佔(兩個父目錄,一些子目錄)(見下文)

readlink

get_link

setattr

獨佔

permission

否(如果在 rcu-walk 模式下呼叫,則可能不阻塞)

get_inode_acl

get_acl

getattr

listxattr

fiemap

update_time

atomic_open

共享(如果在開啟標誌中設定了 O_CREAT,則獨佔)

tmpfile

fileattr_get

否或獨佔

fileattr_set

獨佔

get_offset_ctx

此外,->rmdir()、->unlink() 和 ->rename() 在受害者上具有 ->i_rwsem 獨佔。跨目錄 ->rename() 具有(每個超級塊)->s_vfs_rename_sem。 ->unlink() 和 ->rename() 在所有涉及的非目錄上具有 ->i_rwsem 獨佔。 ->rename() 在任何更改父目錄的子目錄上具有 ->i_rwsem 獨佔。

有關目錄操作鎖定方案的更詳細討論,請參閱目錄鎖定

xattr_handler 操作

原型

bool (*list)(struct dentry *dentry);
int (*get)(const struct xattr_handler *handler, struct dentry *dentry,
           struct inode *inode, const char *name, void *buffer,
           size_t size);
int (*set)(const struct xattr_handler *handler,
           struct mnt_idmap *idmap,
           struct dentry *dentry, struct inode *inode, const char *name,
           const void *buffer, size_t size, int flags);
鎖定規則

全部可能阻塞

操作

i_rwsem(inode)

list

get

set

獨佔

super_operations

原型

struct inode *(*alloc_inode)(struct super_block *sb);
void (*free_inode)(struct inode *);
void (*destroy_inode)(struct inode *);
void (*dirty_inode) (struct inode *, int flags);
int (*write_inode) (struct inode *, struct writeback_control *wbc);
int (*drop_inode) (struct inode *);
void (*evict_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 (*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);
鎖定規則

全部可能阻塞 [不正確,見下文]

操作

s_umount

注意

alloc_inode

free_inode

從 RCU 回撥呼叫

destroy_inode

dirty_inode

write_inode

drop_inode

!!!inode->i_lock!!!

evict_inode

put_super

write

sync_fs

read

freeze_fs

write

unfreeze_fs

write

statfs

可能(read)

(見下文)

remount_fs

write

umount_begin

show_options

(namespace_sem)

quota_read

(見下文)

quota_write

(見下文)

當 ustat(2)(本機或相容)呼叫 ->statfs() 時,它具有 s_umount(共享),但這只是一個糟糕的 API 的意外;當我們只有使用者空間給我們的 dev_t 來識別超級塊時,s_umount 用於固定超級塊。其他所有內容(statfs()、fstatfs() 等)在呼叫 ->statfs() 時不持有它 - 超級塊透過解析傳遞給 syscall 的路徑名來固定。

->quota_read() 和 ->quota_write() 函式都保證是透過配額程式碼(透過 dqio_sem)在配額檔案上操作的唯一函式(除非管理員真的想搞砸一些東西並在啟用配額的情況下寫入配額檔案)。有關鎖定的其他詳細資訊,另請參閱 dquot_operations 部分。

file_system_type

原型

struct dentry *(*mount) (struct file_system_type *, int,
               const char *, void *);
void (*kill_sb) (struct super_block *);

鎖定規則

操作

可能阻塞

mount

kill_sb

->mount() 返回 ERR_PTR 或根 dentry;其超級塊應在返回時鎖定。

->kill_sb() 接受一個寫鎖定的超級塊,對其執行所有關閉工作,解鎖並刪除引用。

address_space_operations

原型

int (*read_folio)(struct file *, struct folio *);
int (*writepages)(struct address_space *, struct writeback_control *);
bool (*dirty_folio)(struct address_space *, struct folio *folio);
void (*readahead)(struct readahead_control *);
int (*write_begin)(struct file *, struct address_space *mapping,
                        loff_t pos, unsigned len,
                        struct folio **foliop, void **fsdata);
int (*write_end)(struct file *, struct address_space *mapping,
                        loff_t pos, unsigned len, unsigned copied,
                        struct folio *folio, void *fsdata);
sector_t (*bmap)(struct address_space *, sector_t);
void (*invalidate_folio) (struct folio *, size_t start, size_t len);
bool (*release_folio)(struct folio *, gfp_t);
void (*free_folio)(struct folio *);
int (*direct_IO)(struct kiocb *, struct iov_iter *iter);
int (*migrate_folio)(struct address_space *, struct folio *dst,
                struct folio *src, enum migrate_mode);
int (*launder_folio)(struct folio *);
bool (*is_partially_uptodate)(struct folio *, size_t from, size_t count);
int (*error_remove_folio)(struct address_space *, struct folio *);
int (*swap_activate)(struct swap_info_struct *sis, struct file *f, sector_t *span)
int (*swap_deactivate)(struct file *);
int (*swap_rw)(struct kiocb *iocb, struct iov_iter *iter);
鎖定規則

除 dirty_folio 和 free_folio 外,全部可能阻塞

操作

folio 已鎖定

i_rwsem

invalidate_lock

read_folio

是,解鎖

共享

writepages

dirty_folio

可能

readahead

是,解鎖

共享

write_begin

鎖定 folio

獨佔

write_end

是,解鎖

獨佔

bmap

invalidate_folio

獨佔

release_folio

free_folio

direct_IO

migrate_folio

是(兩者)

launder_folio

is_partially_uptodate

error_remove_folio

swap_activate

swap_deactivate

swap_rw

是,解鎖

->write_begin()、->write_end() 和 ->read_folio() 可以從請求處理程式 (/dev/loop) 呼叫。

->read_folio() 解鎖 folio,同步或透過 I/O 完成。

->readahead() 解鎖嘗試 I/O 的 folio,如 ->read_folio()。

->writepages() 用於定期回寫和 syscall 發起的同步操作。address_space 至少應針對 *nr_to_write 個頁面啟動 I/O。*nr_to_write 必須為寫入的每個頁面遞減。address_space 實現可能會寫入比 *nr_to_write 要求的更多(或更少)頁面,但它應該儘量接近。如果 nr_to_write 為 NULL,則必須寫入所有髒頁。

writepages 應該 _僅_ 寫入 mapping->i_pages 中存在的頁面。

->dirty_folio() 在核心中的各個位置呼叫,當目標 folio 被標記為需要回寫時。folio 不能被截斷,因為呼叫者持有 folio 鎖,或者呼叫者在持有頁表鎖時找到了 folio,這將阻止截斷。

->bmap() 當前由某些檔案系統提供的舊 ioctl() (FIBMAP) 和交換器使用。後者最終會消失。請保持這種方式,不要產生新的呼叫者。

->invalidate_folio() 在檔案系統必須嘗試從頁面中刪除一些或所有緩衝區時呼叫,當頁面被截斷時。成功時返回零。檔案系統必須獨佔獲取 invalidate_lock,然後才能在截斷/空洞打孔路徑中使頁面快取無效(從而呼叫 ->invalidate_folio),以阻止頁面快取無效和頁面快取填充函式(fault、read 等)之間的競爭。

->release_folio() 在 MM 想要更改將使檔案系統的私有資料無效的 folio 時呼叫。例如,它可能即將從 address_space 中刪除或拆分。folio 已鎖定且未進行回寫。它可能是髒的。gfp 引數通常不用於分配,而是指示檔案系統可以做什麼來嘗試釋放私有資料。檔案系統可以返回 false 以指示無法釋放 folio 的私有資料。如果它返回 true,則應已從 folio 中刪除私有資料。如果檔案系統未提供 ->release_folio 方法,則 pagecache 將假定私有資料是 buffer_heads 並呼叫 try_to_free_buffers()

->free_folio() 在核心從頁面快取中刪除 folio 時呼叫。

->launder_folio() 可以在釋放 folio 之前呼叫,如果發現它仍然是髒的。如果 folio 已成功清理,則返回零,否則返回錯誤值。請注意,為了防止 folio 被映射回去並重新變髒,需要鎖定它以進行整個操作。

->swap_activate() 將被呼叫以準備給定的檔案進行交換。它應執行任何必要的驗證和準備,以確保可以以最小的記憶體分配執行寫入。它應呼叫 add_swap_extent() 或 helper iomap_swapfile_activate(),並返回新增的範圍數。如果 IO 應透過 ->swap_rw() 提交,則應設定 SWP_FS_OPS,否則 IO 將直接提交到塊裝置 sis->bdev

->swap_deactivate() 將在 sys_swapoff() 路徑中呼叫,在 ->swap_activate() 返回成功後。

如果 ->swap_activate() 設定了 SWP_FS_OPS,則將為交換 IO 呼叫 ->swap_rw。

file_lock_operations

原型

void (*fl_copy_lock)(struct file_lock *, struct file_lock *);
void (*fl_release_private)(struct file_lock *);

鎖定規則

操作

inode->i_lock

可能阻塞

fl_copy_lock

fl_release_private

可能

可能[1]_

lock_manager_operations

原型

void (*lm_notify)(struct file_lock *);  /* unblock callback */
int (*lm_grant)(struct file_lock *, struct file_lock *, int);
void (*lm_break)(struct file_lock *); /* break_lease callback */
int (*lm_change)(struct file_lock **, int);
bool (*lm_breaker_owns_lease)(struct file_lock *);
bool (*lm_lock_expirable)(struct file_lock *);
void (*lm_expire_lock)(void);

鎖定規則

操作

flc_lock

blocked_lock_lock

可能阻塞

lm_notify

lm_grant

lm_break

lm_change

lm_breaker_owns_lease

lm_lock_expirable

lm_expire_lock

buffer_head

原型

void (*b_end_io)(struct buffer_head *bh, int uptodate);

鎖定規則

從中斷呼叫。換句話說,這裡需要格外小心。bh 已鎖定,但這就是我們在這裡擁有的所有保證。目前只有 RAID1、highmem、fs/buffer.c 和 fs/ntfs/aops.c 提供這些。塊裝置在 IO 完成後呼叫此方法。

block_device_operations

原型

int (*open) (struct block_device *, fmode_t);
int (*release) (struct gendisk *, fmode_t);
int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
int (*compat_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
int (*direct_access) (struct block_device *, sector_t, void **,
                        unsigned long *);
void (*unlock_native_capacity) (struct gendisk *);
int (*getgeo)(struct block_device *, struct hd_geometry *);
void (*swap_slot_free_notify) (struct block_device *, unsigned long);

鎖定規則

操作

open_mutex

open

release

ioctl

compat_ioctl

direct_access

unlock_native_capacity

getgeo

swap_slot_free_notify

否(見下文)

呼叫 swap_slot_free_notify 時會持有 swap_lock,有時也會持有頁面鎖。

file_operations

原型

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 (*iopoll) (struct kiocb *kiocb, bool spin);
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 *);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, loff_t start, loff_t end, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
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 *, int, loff_t, loff_t);
void (*show_fdinfo)(struct seq_file *m, struct file *f);
unsigned (*mmap_capabilities)(struct file *);
ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
                loff_t, size_t, unsigned int);
loff_t (*remap_file_range)(struct file *file_in, loff_t pos_in,
                struct file *file_out, loff_t pos_out,
                loff_t len, unsigned int remap_flags);
int (*fadvise)(struct file *, loff_t, loff_t, int);
鎖定規則

全部可能阻塞。

->llseek() 鎖定已從 llseek 移動到各個 llseek 實現。如果您的 fs 未使用 generic_file_llseek,則需要在 ->llseek() 中獲取和釋放相應的鎖。對於許多檔案系統,獲取 inode 互斥鎖或僅使用 i_size_read() 可能是安全的。注意:這不能保護 file->f_pos 免受併發修改,因為這是使用者空間必須處理的事情。

呼叫 ->iterate_shared() 時會持有 i_rwsem 以進行讀取,並獨佔持有檔案 f_pos_lock

->fasync() 負責維護 filp->f_flags 中的 FASYNC 位。大多數例項呼叫 fasync_helper(),它執行該維護,因此通常不需要擔心。VFS 層中 > 0 的返回值將對映為零。

必須更改目錄上的 ->readdir() 和 ->ioctl()。理想情況下,我們會將 ->readdir() 移動到 inode_operations 並使用單獨的方法用於目錄 ->ioctl() 或完全殺死後者。其中一個問題是,對於任何類似於聯合掛載的東西,我們都不會為所有元件提供 struct file。還有其他原因導致當前介面一團糟...

目錄上的 ->read 可能必須消失 - 我們應該在 sys_read() 及其朋友中強制執行 -EISDIR。

->setlease 操作應在個別檔案系統中設定租約之前或之後呼叫 generic_setlease() 以記錄操作結果

->fallocate 實現必須非常小心,以在打孔或執行其他使頁面快取內容無效的操作時保持頁面快取一致性。通常,檔案系統需要呼叫 truncate_inode_pages_range() 以使頁面快取的相關範圍無效。但是,檔案系統通常還需要更新其內部(和磁碟上)檔案偏移量 -> 磁碟塊對映的檢視。在此更新完成之前,檔案系統需要阻止頁面錯誤和讀取,從而從磁碟重新載入現在過時的頁面快取內容。由於 VFS 在從磁碟載入頁面(filemap_fault()filemap_read()、readahead 路徑)時以共享模式獲取 mapping->invalidate_lock,因此 fallocate 實現必須獲取 invalidate_lock 以防止重新載入。

->copy_file_range 和 ->remap_file_range 實現需要針對操作執行時的檔案資料修改進行序列化。對於透過 write(2) 和類似操作的阻塞更改,可以使用 inode->i_rwsem。要阻止在操作期間透過記憶體對映對檔案內容進行的更改,檔案系統必須獲取 mapping->invalidate_lock 以與 ->page_mkwrite 協調。

dquot_operations

原型

int (*write_dquot) (struct dquot *);
int (*acquire_dquot) (struct dquot *);
int (*release_dquot) (struct dquot *);
int (*mark_dirty) (struct dquot *);
int (*write_info) (struct super_block *, int);

這些操作旨在或多或少地成為包裝函式,以確保 wrt 檔案系統的正確鎖定並呼叫通用配額操作。

檔案系統應該從通用配額函式中獲得什麼

操作

FS 遞迴

呼叫時持有的鎖

write_dquot

dqonoff_sem 或 dqptr_sem

acquire_dquot

dqonoff_sem 或 dqptr_sem

release_dquot

dqonoff_sem 或 dqptr_sem

mark_dirty

write_info

dqonoff_sem

FS 遞迴意味著從超級塊操作呼叫 ->quota_read() 和 ->quota_write()。

有關配額鎖定的更多詳細資訊,請參見 fs/dquot.c。

vm_operations_struct

原型

void (*open)(struct vm_area_struct *);
void (*close)(struct vm_area_struct *);
vm_fault_t (*fault)(struct vm_fault *);
vm_fault_t (*huge_fault)(struct vm_fault *, unsigned int order);
vm_fault_t (*map_pages)(struct vm_fault *, pgoff_t start, pgoff_t end);
vm_fault_t (*page_mkwrite)(struct vm_area_struct *, struct vm_fault *);
vm_fault_t (*pfn_mkwrite)(struct vm_area_struct *, struct vm_fault *);
int (*access)(struct vm_area_struct *, unsigned long, void*, int, int);

鎖定規則

操作

mmap_lock

PageLocked(page)

open

write

close

read/write

fault

read

可以返回頁面鎖定的

huge_fault

可能-read

map_pages

可能-read

page_mkwrite

read

可以返回頁面鎖定的

pfn_mkwrite

read

access

read

->fault() 在即將錯誤地進入先前不存在的 pte 時呼叫。檔案系統必須在 vm_fault 結構中找到並返回與傳入的 “pgoff” 關聯的頁面。如果該頁面可能被截斷和/或無效,則檔案系統必須鎖定 invalidate_lock,然後確保該頁面尚未被截斷(invalidate_lock 將阻止後續截斷),然後以 VM_FAULT_LOCKED 和頁面鎖定狀態返回。VM 將解鎖頁面。

->huge_fault() 在不存在 PUD 或 PMD 條目時呼叫。這使檔案系統有機會安裝 PUD 或 PMD 大小的頁面。檔案系統還可以使用 ->fault 方法返回 PMD 大小的頁面,因此可能不需要實現此函式。特別是,檔案系統不應從 ->huge_fault() 呼叫 filemap_fault()。呼叫此方法時可能未持有 mmap_lock。

->map_pages() 在 VM 請求對映易於訪問的頁面時呼叫。檔案系統應找到並對映與從 “start_pgoff” 到 “end_pgoff” 的偏移量關聯的頁面。呼叫 ->map_pages() 時會持有 RCU 鎖,並且不得阻塞。如果無法在不阻塞的情況下訪問頁面,則檔案系統應跳過它。檔案系統應使用 set_pte_range() 設定頁表條目。與頁面關聯的條目的指標在 vm_fault 結構中的 “pte” 欄位中傳遞。其他偏移量的條目的指標應相對於 “pte” 進行計算。

->page_mkwrite() 在即將變為可寫的先前只讀的 pte 時呼叫。檔案系統再次必須確保沒有截斷/無效競爭或與諸如 ->remap_file_range 或 ->copy_file_range 等操作的競爭,然後以頁面鎖定狀態返回。通常,mapping->invalidate_lock 適合正確的序列化。如果頁面已被截斷,則檔案系統不應像 ->fault() 處理程式一樣查詢新頁面,而只需返回 VM_FAULT_NOPAGE,這將導致 VM 重試錯誤。

->pfn_mkwrite() 與 page_mkwrite 相同,但當 pte 是 VM_PFNMAP 或 VM_MIXEDMAP 且沒有頁面的條目時。預期的返回是 VM_FAULT_NOPAGE。或 VM_FAULT_ERROR 型別之一。在此呼叫之後的預設行為是使 pte 可讀寫,除非 pfn_mkwrite 返回錯誤。

->access() 在 access_process_vm() 中的 get_user_pages() 失敗時呼叫,通常用於透過 /proc/pid/mem 或 ptrace 除錯程序。此函式僅適用於 VM_IO | VM_PFNMAP VMA。


可疑的東西

(如果您破壞了某些東西或注意到它已損壞並且不自己修復它 - 至少將其放在此處)