EXT4檔案系統學習(14)VFS之VFS inode

王二車發表於2019-02-18

不同的檔案系統inode也不一樣,對inode的操作函式也不一樣,VFS inode的作用就是隱藏下面具體檔案系統的inode差異,向上層提供統一的介面。

inode

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; 指定一組對inode的操作函式
	struct super_block	*i_sb;
	struct address_space	*i_mapping;

#ifdef CONFIG_SECURITY
	void			*i_security;
#endif

	/* Stat data, not accessed from path walking */
	unsigned long		i_ino;
	/*
	 * 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;
	struct timespec		i_atime;
	struct timespec		i_mtime;
	struct timespec		i_ctime;
	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;
	struct list_head	i_wb_list;	/* backing dev IO list */
	struct list_head	i_lru;		/* inode LRU list */
	struct list_head	i_sb_list;
	union {
		struct hlist_head	i_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;	指定對檔案內容本身的操作函式
	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;
	};

	__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 */
};

結構體大部分成員都是根據磁碟的inode初始化的,詳細見上一章。

根據inode編號獲取inode結構操作是很繁瑣的,因此核心使用hash表讓每一個inode通過i_hash連結到hash表。

VFS inode的i_op和i_fop指標操作具體檔案系統的inode,但是目錄檔案、連結檔案等是分開的,具體可分為4中情況:

普通檔案和目錄的inode_operations

  • 普通檔案
const struct inode_operations ext4_file_inode_operations = {
	.setattr	= ext4_setattr,
	.getattr	= ext4_getattr,
	.setxattr	= generic_setxattr,
	.getxattr	= generic_getxattr,
	.listxattr	= ext4_listxattr,
	.removexattr	= generic_removexattr,
	.get_acl	= ext4_get_acl,
	.set_acl	= ext4_set_acl,
	.fiemap		= ext4_fiemap,
};
  • 普通目錄
const struct inode_operations ext4_dir_inode_operations = {
	.create		= ext4_create, 建立檔案
	.lookup		= ext4_lookup,
	.link		= ext4_link, 硬連結
	.unlink		= ext4_unlink,
	.symlink	= ext4_symlink, 軟連結
	.mkdir		= ext4_mkdir, 建立目錄
	.rmdir		= ext4_rmdir,
	.mknod		= ext4_mknod, 建立裝置節點
	.tmpfile	= ext4_tmpfile,
	.rename2	= ext4_rename2,
	.setattr	= ext4_setattr,
	.setxattr	= generic_setxattr,
	.getxattr	= generic_getxattr,
	.listxattr	= ext4_listxattr,
	.removexattr	= generic_removexattr,
	.get_acl	= ext4_get_acl,
	.set_acl	= ext4_set_acl,
	.fiemap         = ext4_fiemap,
};

普通檔案和目錄都有一個對應的inode結構,在某個目錄下建立一個檔案,會呼叫目錄對應的inode結構的ext4_create()函式,ext4_create()會呼叫__ext4_new_inode從磁碟上分配一個空閒的inode,同時初始化ext4記憶體中的inode機構。

建立一個子目錄,使用函式ext4_mkdir,也會呼叫__ext4_new_inode從磁碟上分配一個空閒的inode,同時初始化ext4記憶體中的inode機構。

連結檔案的inode_operations

普通檔案的定位:找到direntry,然後從direntry中的檔名讀出inode號,最後讀出inode資訊。

硬連結檔案:因為硬連結檔案的inode號與原始檔一致的,所以定位比較簡單。

軟連結檔案:需要特殊處理,由於inode號不一致,所以需要先讀取inode號獲取出原始檔的路徑,再根據路徑定位出目標檔案。如果目標檔案路徑小於60位元組,那麼稱為Fast Symbol link,因為這樣的話路徑資訊就直接儲存在inode所在的block,就不需要額外的block了。

const struct inode_operations ext4_fast_symlink_inode_operations = {
	.readlink	= generic_readlink, 在inode的block中讀出目標檔案路徑
	.follow_link    = ext4_follow_fast_link, 把對連結檔案的操作直接轉到目標檔案
	.setattr	= ext4_setattr,
	.setxattr	= generic_setxattr,
	.getxattr	= generic_getxattr,
	.listxattr	= ext4_listxattr,
	.removexattr	= generic_removexattr,
};

目標檔案路徑大於60位元組的,稱為普通符號連結,需要根據inode中的資料塊地址讀出目標檔案的地址:

const struct inode_operations ext4_symlink_inode_operations = {
	.readlink	= generic_readlink,
	.follow_link    = ext4_follow_link,
	.put_link       = ext4_put_link,
	.setattr	= ext4_setattr,
	.setxattr	= generic_setxattr,
	.getxattr	= generic_getxattr,
	.listxattr	= ext4_listxattr,
	.removexattr	= generic_removexattr,
};

多出一個put_link函式,且follow_link與快速連結不同,follow_link把inode地址對應的資料資料讀出到記憶體解析,處理我拿出後由put_link來釋放記憶體。

根據前面分析的ext4_fill_super函式中ext4_iget對檔案和目錄的i_fop賦值也是不一樣的,下面繼續分析i_fop:

檔案的file_operations

const struct file_operations ext4_file_operations = {
	.llseek		= ext4_llseek, 調整檔案讀寫指標
	.read_iter	= generic_file_read_iter,
	.write_iter	= ext4_file_write_iter,
	.unlocked_ioctl = ext4_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl	= ext4_compat_ioctl, 向裝置檔案傳送ioctl命令
#endif
	.mmap		= ext4_file_mmap, 
	.open		= ext4_file_open,
	.release	= ext4_release_file,
	.fsync		= ext4_sync_file,
	.splice_read	= generic_file_splice_read,
	.splice_write	= iter_file_splice_write,
	.fallocate	= ext4_fallocate,
};

read使用的是通用檔案系統讀函式generic_file_read_iter,支援IOCB_DIRECT直接讀模式和快取模式,IOCB_DIRECT模式在open檔案時傳入模式引數,繞過快取直接對磁碟讀寫操作;使用快取模式是do_generic_file_read函式中體現,函式中會檢查資料是否已經快取,如果沒有就會預讀取,將資料載入入快取頁。

mmap函式把一個檔案內容對映到程式的虛擬地址空間中(利用頁表),這樣可以通過記憶體指標p[n]來訪問檔案內容。

open函式開啟檔案操作,建立相關的記憶體管理結構,如inode物件。

release函式減少檔案的引用計數,當引用計數為0時,會關閉檔案物件,同時釋放相關的記憶體管理結構。

fsync函式把記憶體中檔案內容資料寫入磁碟,splice_read/write用於管道操作。

目錄的dir_operations

const struct file_operations ext4_dir_operations = {
	.llseek		= ext4_dir_llseek,
	.read		= generic_read_dir,
	.iterate	= ext4_readdir,
	.unlocked_ioctl = ext4_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl	= ext4_compat_ioctl,
#endif
	.fsync		= ext4_sync_file,
	.release	= ext4_release_dir,
};

read是一個空函式,為啥呢?

iterate是readdir函式,從目錄的資料塊中把目錄項讀出來,目錄項direntry資料塊中儲存了目錄下儲存的檔名資訊等。

總結

每當初始化一個inode時,設定好i_op和i_fop後,就能夠正確的找到這個檔案的操作函式,每個inode對應一個檔案,耶就能在磁碟中讀寫到這個檔案。

相關文章