檔案系統使用的資料結構
linux虛擬檔案系統四大物件:
1)超級塊(superblock)
2)索引節點(inode)
3)目錄項(dentry)
4)檔案物件(file)
super_block
/*超級塊代表了整個檔案系統,超級塊是檔案系統的控制塊,有整個檔案系統資訊,一個檔案系統所有的inode都要連線到超級塊上, 可以說,一個超級塊就代表了一個檔案系統 */ struct super_block { struct list_head s_list; /* Keep this first 一個雙向迴圈連結串列,把所有的super_block連線起來,一個super_block代表一個在linux上的檔案系統, 這個list上邊的就是所有的在linux上記錄的檔案系統。*/ dev_t s_dev; /* search index; _not_ kdev_t 包含該具體檔案系統的塊裝置識別符號。例如,對於 /dev/hda1,其裝置識別符號為 0x301*/ unsigned char s_blocksize_bits;//上面的size大小佔用位數,例如512位元組就是9 bits unsigned long s_blocksize;//檔案系統中資料塊大小,以位元組單位 loff_t s_maxbytes; /* Max file size允許的最大的檔案大小(位元組數)*/ struct file_system_type *s_type;//檔案系統型別 ext2還是fat32 ? “檔案系統”和“檔案系統型別”不一樣!一個檔案系統型別下可以包括很多檔案系統即很多的super_block const struct super_operations *s_op;//指向某個特定的具體檔案系統的用於超級塊操作的函式集合 const struct dquot_operations *dq_op;//指向某個特定的具體檔案系統用於限額操作的函式集合 const struct quotactl_ops *s_qcop;//用於配置磁碟限額的的方法,處理來自使用者空間的請求const struct export_operations *s_export_op; unsigned long s_flags; unsigned long s_iflags; /* internal SB_I_* flags */ unsigned long s_magic;//區別於其他檔案系統的標識 struct dentry *s_root;//指向該具體檔案系統安裝目錄的目錄項 struct rw_semaphore s_umount;//對超級塊讀寫時進行同步 int s_count;//對超級塊的使用計數 atomic_t s_active;//引用計數 #ifdef CONFIG_SECURITY void *s_security; #endif const struct xattr_handler **s_xattr; struct hlist_bl_head s_anon; /* anonymous dentries for (nfs) exporting */ struct list_head s_mounts; /* list of mounts; _not_ for fs use */ struct block_device *s_bdev;//指向檔案系統被安裝的塊裝置 struct backing_dev_info *s_bdi; struct mtd_info *s_mtd; struct hlist_node s_instances; unsigned int s_quota_types; /* Bitmask of supported quota types */ struct quota_info s_dquot; /* Diskquota specific options磁碟限額相關選項 */ struct sb_writers s_writers; char s_id[32]; /* Informational name */ u8 s_uuid[16]; /* UUID */ void *s_fs_info; /* Filesystem private info */ unsigned int s_max_links; fmode_t s_mode; /* Granularity of c/m/atime in ns. Cannot be worse than a second */ u32 s_time_gran; /* * The next field is for VFS *only*. No filesystems have any business * even looking at it. You had been warned. */ struct mutex s_vfs_rename_mutex; /* Kludge */ /* * Filesystem subtype. If non-empty the filesystem type field * in /proc/mounts will be "type.subtype" */ char *s_subtype; /* * Saved mount options for lazy filesystems using * generic_show_options() */ char __rcu *s_options; const struct dentry_operations *s_d_op; /* default d_op for dentries */ /* * Saved pool identifier for cleancache (-1 means none) */ int cleancache_poolid; struct shrinker s_shrink; /* per-sb shrinker handle */ /* Number of inodes with nlink == 0 but still referenced */ atomic_long_t s_remove_count; /* Being remounted read-only */ int s_readonly_remount; /* AIO completions deferred from interrupt context */ struct workqueue_struct *s_dio_done_wq; struct hlist_head s_pins; /* * Context in which to interpret filesystem uids, gids, * quotas, device nodes, extended attributes and security * labels. */ struct user_namespace *s_user_ns; /* * Keep the lru lists last in the structure so they always sit on their * own individual cachelines. */ struct list_lru s_dentry_lru ____cacheline_aligned_in_smp; struct list_lru s_inode_lru ____cacheline_aligned_in_smp; struct rcu_head rcu; struct work_struct destroy_work; struct mutex s_sync_lock; /* sync serialisation lock */ /* * Indicates how deep in a filesystem stack this SB is */ int s_stack_depth; /* s_inode_list_lock protects s_inodes */ spinlock_t s_inode_list_lock ____cacheline_aligned_in_smp; struct list_head s_inodes; /* all inodes */ };
Inode:
/* * Keep mostly read-only and often accessed (especially for * the RCU path lookup and 'stat' data) fields at the beginning * of the 'struct inode' inode有兩種,一種是VFS的inode,一種是具體檔案系統的inode。前者在記憶體中,後者在磁碟中。所以每次其實是將磁碟中的inode調進填充記憶體中的inode, 這樣才是算使用了磁碟檔案inode。 每個inode節點的大小,一般是128位元組或256位元組。inode節點的總數,在格式化時就給定(現代OS可以動態變化),一般每2KB就設定一個inode。 一般檔案系統中很少有檔案小於2KB的,所以預定按照2KB分,一般inode是用不完的。所以inode在檔案系統安裝的時候會有一個預設數量, 後期會根據實際的需要發生變化 inode號是唯一的,表示不同的檔案。其實在Linux內部的時候,訪問檔案都是通過inode號來進行的,所謂檔名僅僅是給使用者容易使用的。 當我們開啟一個檔案的時候,首先,系統找到這個檔名對應的inode號;然後,通過inode號,得到inode資訊,最後,由inode找到檔案資料所在的block, 現在可以處理檔案資料了。 inode和檔案的關係:當建立一個檔案的時候,就給檔案分配了一個inode。一個inode只對應一個實際檔案,一個檔案也會只有一個inode。 inodes最大數量就是檔案的最大數量。 */ struct inode { umode_t i_mode;//檔案的型別和訪問許可權 unsigned short i_opflags; kuid_t i_uid;//檔案擁有者標號 kgid_t i_gid;//檔案所在組標號 unsigned int i_flags; #ifdef CONFIG_FS_POSIX_ACL struct posix_acl *i_acl; struct posix_acl *i_default_acl; #endif const struct inode_operations *i_op;//索引節點操作函式集 struct super_block *i_sb;//inode所屬檔案系統的超級塊指標 struct address_space *i_mapping;//表示向誰請求頁面 於描述頁快取記憶體中的頁面的 #ifdef CONFIG_SECURITY void *i_security; #endif /* Stat data, not accessed from path walking */ unsigned long i_ino;//索引節點號,每個inode都是唯一的 /* * Filesystems may only read i_nlink directly. They shall use the * following functions for modification: * * (set|clear|inc|drop)_nlink * inode_(inc|dec)_link_count */ union { const unsigned int i_nlink; unsigned int __i_nlink; }; dev_t i_rdev;//實際的裝置標識 loff_t i_size;//inode所代表的的檔案的大小,以位元組為單位 struct timespec i_atime;//檔案最後一次訪問時間 struct timespec i_mtime;//檔案最後一次修改時間 struct timespec i_ctime;//inode最後一次修改時間 spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */ unsigned short i_bytes;//檔案中最後一個塊的位元組數 unsigned int i_blkbits;//塊大小,位元組單位 blkcnt_t i_blocks;//檔案所佔塊數 #ifdef __NEED_I_SIZE_ORDERED seqcount_t i_size_seqcount; #endif /* Misc */ unsigned long i_state; struct mutex i_mutex; unsigned long dirtied_when; /* jiffies of first dirtying */ unsigned long dirtied_time_when; struct hlist_node i_hash;//指向hash連結串列指標,用於inode的hash表 struct list_head i_io_list; /* backing dev IO list */ #ifdef CONFIG_CGROUP_WRITEBACK struct bdi_writeback *i_wb; /* the associated cgroup wb */ /* foreign inode detection, see wbc_detach_inode() */ int i_wb_frn_winner; u16 i_wb_frn_avg_time; u16 i_wb_frn_history; #endif struct list_head i_lru; /* inode LRU list */ struct list_head i_sb_list; union { struct hlist_head i_dentry;//指向目錄項鍊表指標,注意一個inodes可以對應多個dentry,因為一個實際的檔案可能被連結到其他的檔案, //那麼就會有另一個dentry,這個連結串列就是將所有的與本inode有關的dentry都連在一起 struct rcu_head i_rcu; }; u64 i_version;//版本號 atomic_t i_count;//引用計數 atomic_t i_dio_count; atomic_t i_writecount;//記錄多少程序以刻寫模式開啟此檔案 #ifdef CONFIG_IMA atomic_t i_readcount; /* struct files open RO */ #endif const struct file_operations *i_fop; /* former ->i_op->default_file_ops 檔案操作*/ struct file_lock_context *i_flctx; struct address_space i_data; struct list_head i_devices;//裝置連結串列。共用同一個驅動程式的裝置形成的連結串列 union { struct pipe_inode_info *i_pipe;//向管道檔案(如果檔案是管道檔案時使用) struct block_device *i_bdev;//指向塊裝置檔案指標(如果檔案是塊裝置檔案時使用) struct cdev *i_cdev;//指向字元裝置檔案指標(如果檔案是字元裝置時使用) char *i_link;//連結 }; __u32 i_generation; #ifdef CONFIG_FSNOTIFY __u32 i_fsnotify_mask; /* all events this inode cares about */ struct hlist_head i_fsnotify_marks; #endif void *i_private; /* fs or device private pointer */ };
dentry:
/* dentry則表示了不同層級之間的關係,也是連結所使用的結構體。dentry通過d_parent來和上級目錄構成連結關係, 通過d_op來儲存對應的實際檔案系統的檔案操作,如建立、刪除、開啟、讀寫等。d_sb指向實際檔案系統的超級塊, 該結構在上文已詳細介紹。d_inode指向對應的inode,d_name表示該檔案的檔名。 一個有效的dentry結構必定有一個inode結構,這是因為一個目錄項要麼代表著一個檔案,要麼代表著一個目錄,而目錄實際上也是檔案。 所以,只要dentry結構是有效的,則其指標d_inode必定指向一個inode結構。但是inode卻可以對應多個 */ struct dentry { /* RCU lookup touched fields */ unsigned int d_flags; /* protected by d_lock目錄項快取標識,可取 DCACHE_UNUSED DCACHE_REFERENCED 等 */ seqcount_t d_seq; /* per dentry seqlock */ struct hlist_bl_node d_hash; /* lookup hash list 核心使用dentry_hashtable對dentry進行管理,dentry_hashtable是由list_head組成的連結串列, 一個dentry建立之後,就通過d_hash連結進入對應的hash值的連結串列中。*/ struct dentry *d_parent; /*父目錄的目錄項 parent directory */ struct qstr d_name;//目錄項名稱 struct inode *d_inode; /* 與該目錄項關聯的inode Where the name belongs to - NULL is * negative */ unsigned char d_iname[DNAME_INLINE_LEN]; /* 存放短的檔名small names */ /* Ref lookup also touches following */ struct lockref d_lockref; /* per-dentry lock and refcount */ const struct dentry_operations *d_op;//目錄項操作函式集 struct super_block *d_sb; /* The root of the dentry tree 這個目錄項所屬的檔案系統的超級塊*/ unsigned long d_time; /* used by d_revalidate 重新變為有效的時間!注意只要操作成功這個dentry就是有效的,否則無效*/ void *d_fsdata; /* fs-specific data 檔案系統私有資料 */ struct list_head d_lru; /*最近未使用的目錄項的連結串列 LRU list */ struct list_head d_child; /* child of parent list 目錄項通過這個加入到父目錄的d_subdirs中*/ struct list_head d_subdirs; /* our children本目錄的所有孩子目錄連結串列頭 */ /* * d_alias and d_rcu can share memory 一個有效的dentry必然與一個inode關聯,但是一個inode可以對應多個dentry,因為一個檔案可以被連結到其他檔案, 所以,這個dentry就是通過這個欄位連結到屬於自己的inode結構中的i_dentry連結串列中的 */ union { struct hlist_node d_alias; /* inode alias list這個dentry就是通過這個欄位連結到屬於自己的inode結構中的i_dentry連結串列中的 */ struct rcu_head d_rcu; } d_u; };
files
struct path { struct vfsmount *mnt; struct dentry *dentry; };
struct file { union { struct llist_node fu_llist; struct rcu_head fu_rcuhead; } f_u;//用於通用檔案物件連結串列的指標 struct path f_path;//指出該檔案的已安裝的檔案系統vfsmount以及檔案相關的目錄項物件dentry struct inode *f_inode; /* cached value */ const struct file_operations *f_op;///*指向檔案操作表的指標 --函式指標*/ /* * Protects f_ep_links, f_flags. * Must not be taken from IRQ context. */ spinlock_t f_lock; atomic_long_t f_count;//表示開啟檔案的引用計數 unsigned int f_flags;//表示開啟檔案的許可權 fmode_t f_mode;//設定對檔案的訪問模式,例如:只讀,只寫 struct mutex f_pos_lock; loff_t f_pos;//表示當前讀寫檔案的位置 struct fown_struct f_owner;//該結構的作用是通過訊號進行I/O時間通知的資料 非同步操作;記錄一個程序ID,當某些事發送的時候傳送給該ID程序的訊號 const struct cred *f_cred; struct file_ra_state f_ra;//檔案預讀狀態,檔案預讀演算法使用的主要資料結構,當開啟一個檔案時,f_ra中出了perv_page(預設為-1)和ra_apges(對該檔案允許的最大預讀量)這兩個欄位外,其他的所有西端都置為0 u64 f_version;//記錄檔案的版本號,每次使用後都自動遞增 #ifdef CONFIG_SECURITY void *f_security; #endif /* needed for tty driver, and maybe others */ void *private_data; #ifdef CONFIG_EPOLL /* Used by fs/eventpoll.c to link all the hooks to this file */ struct list_head f_ep_links; struct list_head f_tfile_llink; #endif /* #ifdef CONFIG_EPOLL */ struct address_space *f_mapping; } __attribute__((aligned(4))); /* lest something weird decides that 2 is OK */
files_struct:
struct fdtable { unsigned int max_fds; struct file __rcu **fd; /* current fd array */ unsigned long *close_on_exec; unsigned long *open_fds; unsigned long *full_fds_bits; struct rcu_head rcu; }; /* * Open file table structure */ struct files_struct { /* * read mostly part */ atomic_t count;/* 共享該表的程序數 */ bool resize_in_progress; wait_queue_head_t resize_wait; /*包括一個struct fdtable變數例項和一個struct fdtable型別指標; 而struct fdtable中的成員變數close_on_exec,open_fds,fd又分別指向struct files_struct中 的成員close_on_exec_init,open_fds_init和fd_array*/ struct fdtable __rcu *fdt; struct fdtable fdtab; /* * written part on a separate cache line in SMP */ spinlock_t file_lock ____cacheline_aligned_in_smp; /* 保護以下的所有域,以免在tsk->alloc_lock中的巢狀*/ int next_fd;/*已分配的檔案描述符加1*/ unsigned long close_on_exec_init[1];/*指向執行exec( )時需要關閉的檔案描述符*/ unsigned long open_fds_init[1];/*指向開啟檔案描述符的指標*/ unsigned long full_fds_bits_init[1];/*檔案描述符的初值集合*/ struct file __rcu * fd_array[NR_OPEN_DEFAULT];;/* 檔案物件指標的初始化陣列 預設使用 不夠時動態分配*/ /*通常,fd欄位指向files_struct結構的fd_array欄位,該欄位包括32個檔案物件指標。 如果程序開啟的檔案數目多於32,核心就分配一個新的、更大的檔案指標陣列, 並將其地址存放在fd欄位中,核心同時也更新max_fds欄位的值。*/ };
linux系統中,一個程序開啟的檔案數是有初步限制的,即其檔案描述符數初始時有最大化定量,即一個程序一般只能開啟NR_OPEN_DEFAULT個檔案,該值在32位機上為32個,在64位機上為64個。上面的files_struct這種初始化正是體現了程序初步所能開啟檔案的核心結構描述。這裡需要說明的是,這不僅僅限於類似上面的靜態初始化,當init程序fork一個子程序時,也是如此(此時是files_struct是動態分配的)。
do_fork------>copy_process |------>dup_task_struct------>alloc_task_struct |------>copy_files------>dup_fd
即先呼叫alloc_task_struct分配一個task_struct結構例項,然後使用alloc_files來分配並初始化files_struct變數例項。
上面的 files_struct初始化和上面例子中的靜態初始化並無本質差別:
語句fdt = &newf->fdtab取出newf的struct fdtable例項變數fdtab的指標,然後通過rcu_assign_pointer函式將其賦值給newf->fdt,那麼newf->fdt還是指向其自身中的struct fdtable例項變數,fdt的成員close_on_exec、open_fds和fd也是如此。
當進行struct files_struct擴充時,會分配一個新的struct fdtable,為了敘述方便,下面該變數用指標用nfdt來表示。另外還分配了滿足擴充要求的fd陣列(即struct file陣列),以及與fd相對的bitmap描述close_on_exec,open_fds的儲存空間。
然後將新分配的close_on_exec,open_fds,fd空間指標賦值給nfdt->close_on_exec,nfdt->open_fds和nfdt->fd。注意,這裡的close_on_exec,open_fds和上面初始化時close_on_exec_init,open_fds_init的差別:
close_on_exec,open_fds的最大值按bit來說為__FDSET_LONGS,實際值為1024位,即檔案描述符的最大數為1024個。但它們也是按需分配,並和file陣列的大小一致,分配的實際值會同時賦值給nfdt->max_fds。
分配並初始化新的struct fdtable變數後,原先指向fdtab的struct files_struct指標成員fdt,會調整為指向新分配的struct fdtable變數。這時,struct files_struct例項變數中就包含兩個struct fdtable儲存區:一個是其自身的,一個新分配的,用fdt指向。
執行完上面的操作後,其關係如下圖:
上圖中同顏色的儲存區,代表的是兩者內容是一致的。即執行完上述的操作後,還要將舊的結構儲存區的內容拷貝到新的儲存區,這包括files_struct自身所包含的close_on_exec,open_fds,fd到新分配的close_on_exec,open_fds,fd的拷貝。
執行完上述拷貝之後,就要釋放舊的struct fdtable,