VFS剖析

VFS:爲各種文件系統提供了一個通用的接口,它使得上層進程在操作各種文件系統的時候可以使用同一組系統調用,但是系統調用在內核中可以根據不同的文件系統執行不同的操作。在一個Linux操作系統中,存在多種的文件系統,例如ext2,ext3,ext4等,每種文件系統都有自己的組織方式和操作方法,對於用戶來說,不可能所有的文件系統都瞭解,所以在Linux中在應用程序和各種文件系統之間添加了一層稱爲虛擬文件系統的機制,在系統不同分區存在各種不同的文件系統,用戶在操作這些文件系統的時候,會直接跟vfs打交道。vfs根據你操作實際文件系統,然後來進行適合該文件系統的相應的操作。
在這裏插入圖片描述

在這裏插入圖片描述
虛擬文件系統是一個內核軟件層,用來處理與Unix文件系統相關的所有系統調用,爲各種文件系統提供了一個通用的接口。

VFS支持的文件系統可以劃分爲三種類型:
從磁盤文件系統:用於管理本地磁盤可用的存儲空間或其他可以起到磁盤作用的設備。(ext2,sysv,vfat等)
網絡文件系統:
這些系統允許輕易地訪問屬於其他網絡計算機的文件系統所包含的文件,像NFS
特殊文件系統:
這些文件系統不管理本地或者遠程磁盤空間,/proc是一種特殊的文件系統。

VFS隱含的主要思想是引入了一個通用的文件模型,這個文件模型能表示所有支持的文件系統,要實現一個具體的文件系統必須要將其物理組織結構轉換成虛擬文件系統的通用文件模型。

在通用文件系統模型中,每個目錄可以被看做一個文件,可以包含若干文件和子目錄,但是存在幾個非Unix的基於磁盤的文件系統,他們利用文件分配表(FAT)存放文件在目錄樹中的位置,在這些文件系統中,存放的都是目錄,爲了符合VFS的通用文件模型,對上述基於FAT的文件系統的實現,Linux必須在必要時能夠創建對應於目錄的文件,這樣文件只作爲內核內存的對象而存在。

比如當我們的應用程序調用read系統調用,首先和正常系統調用相同,引起內核調用sys_read服務例程,因爲內核通過一個file對象來表示打開的文件,在內核read中,會在file中找一個f_op字段,這個字段包含了被操作文件的文件系統的各種操作的函數指針,read找到該函數指針,並且調用它,這樣一來,應用程序中read被轉化成特定文件系統的read。
調用過程如下:

file->f_op->read() ;

其他的操作與之類似。
簡而言之,就是內核將一組合適的指針分配給每個打開文件相關的file變量,然後負責調用每個具體文件系統的函數(f_op指向)。

通用文件系統由下面對象組成:

超級塊:
存放已經安裝文件系統的有關信息,對基於磁盤的文件系統,這類對象通常對應於存放在磁盤上的文件系統控制塊。

inode索引節點對象:
存放關於文件的一般信息,對基於磁盤的文件系統,這類對象通常對應於存放在磁盤上的文件控制塊,每個索引節點對象都有一個索引節點號,這個節點號唯一標示了文件系統中的文件。

文件對象:
存放打開文件與進程之間進行交互的有關信息,這類信息僅當進程訪問文件期存在於內核內存中。

目錄項對象:
存放目錄項與對應文件進行鏈接的有關信息,每個磁盤文件都以自己特有的方式將該類信息存在磁盤上。四種對象的關係:
在這裏插入圖片描述

vfs數據結構:
內核超級塊源代碼:

struct super_block {
	struct list_head	s_list;		/* Keep this first *///指向超級塊鏈表的指針
	//這樣的結構來將super_block中的s_list鏈接起來,那麼遍歷到s_list之後,直接讀取super_block這麼長的一個內存塊,就可以將這個
///super_block直接讀進來!這樣就很快捷方便!這也是爲什麼s_list必須放在第一個字段的原因。
	dev_t			s_dev;		/* search index; _not_ kdev_t *///設備標識符號
	//包含該具體文件系統的塊設備標識符。例如,對於 /dev/hda1,其設備標識符爲 0x301
	unsigned long		s_blocksize; // 以字節爲單位塊大小
	//
	unsigned long		s_old_blocksize; // 基於驅動程序中提到的以字節爲單位的塊大小
	unsigned char		s_blocksize_bits; //以位爲單位的塊大小
	unsigned char		s_dirt; //修改髒標誌
	unsigned long long	s_maxbytes;	/* Max file size *///文件的最長長度
	struct file_system_type	*s_type; //文件系統類型 
	//要區分“文件系統”和“文件系統類型”不一樣!一個文件系統類型下可以包括很多文件系統即很多的super_block
	struct super_operations	*s_op; //超級塊的方法
	struct dquot_operations	*dq_op; .//磁盤限額處理方法
 	struct quotactl_ops	*s_qcop; // 磁盤限額管理方法
	struct export_operations *s_export_op; //網絡文件系統使用的輸出操作
	unsigned long		s_flags; //安裝標誌
	unsigned long		s_magic; //文件系統的魔數
	struct dentry		*s_root; //文件系統根目錄的目錄項對象
	struct rw_semaphore	s_umount; .//卸載所用的信號量
	struct semaphore	s_lock; // 超級快的信號量
	int			s_count; //引用計數器
	int			s_syncing; //對超級塊索引節點進行同步的標誌
	int			s_need_sync_fs; //對超級快已經安裝的文件系統進行同步的標誌
	atomic_t		s_active; //次級引用計數器
	void                    *s_security; //指向超級快安全數據結構的指針
	struct xattr_handler	**s_xattr; //指向超級塊擴展屬性結構的指針

	struct list_head	s_inodes;	/* all inodes */ //所用索引節點的鏈表
	struct list_head	s_dirty;	/* dirty inodes */ //改進型索引節點的鏈表
	struct list_head	s_io;		/* parked for writeback */ //等待被寫入磁盤的索引節點的鏈表
	struct hlist_head	s_anon;		/* anonymous dentries for (nfs) exporting */ //
	struct list_head	s_files;  //文件對象的鏈表

	struct block_device	*s_bdev; //指向塊設備驅動程序描述符的指針
	struct list_head	s_instances; //用於給定文件系統類型的超級快對象鏈表的指針
	struct quota_info	s_dquot;	/* Diskquota specific options */ //磁盤限額描述符

	int			s_frozen; //凍結文件系統時使用的標誌
	wait_queue_head_t	s_wait_unfrozen; //進程掛起的等待隊列,知道文件系統被凍結

	char s_id[32];				/* Informational name */ //包括超級快的塊設備名稱

	void 			*s_fs_info;	/* Filesystem private info *///指向特定系統的塊設備名稱

	/*
	 * The next field is for VFS *only*. No filesystems have any business
	 * even looking at it. You had been warned.
	 */
	struct semaphore s_vfs_rename_sem;	/* Kludge */當vfs通過目錄重命名文件時使用的信號量

	/* Granuality of c/m/atime in ns.
	   Cannot be worse than a second */
	u32		   s_time_gran; //時間戳的粒度
};

所有超級塊都使用雙向鏈表的形式連接起來,鏈表中每一個元素都用super_block變量來表示,而超級塊的s_list字段則存放鏈表相鄰元素的指針,通過自旋鎖保護鏈表免受多處理器系統上的同時訪問。

s_fs_info字段指向屬於具體文件系統的超級塊信息。比如是ext2文件系統的話,這個結構體就指向ext2_sb_info數據結構。爲了效率起見,在操作這些文件系統之前,有s_fs_info所指向的數據被複制到內存,vfs允許這些文件系統直接對內存超級塊中的s_fs_info字段進行操作,無須訪問磁盤。爲了防止vfs超級塊中的數據與磁盤上的數據不同步,有必要引入一個s_dirt標誌,來表示該超級塊是否是髒的,那磁盤數據是否必須要更新。Linux通過在一定週期內將所有髒數據的超級塊寫回磁盤來減少該問題帶來的危害。

每個文件系統都可以定義自己的超級塊操作,當vfs需要調用其中一個操作的時候,他執行sb->s_op->read_inode(inode);

sb存放所涉及的超級塊對象的地址,super_operation表的read_inode字段存放這一函數的地址,因此這一函數被直接調用。

inode節點保存實際數據的信息,稱爲元數據,例如:文件大小,設備標識符號,用戶組標識符,文件模式,擴展屬性,文件讀取或者修改的時間戳,連接數量,指向存儲該內容的磁盤區塊的指針,文件分類等。
一個文件系統處理一個文件所需要的所有文件信息都存放在一個名爲索引節點的數據結構中,文件名可以隨時改,但是文件的索引節點隨着文件的刪除而消失,並且是唯一的。

數據分成:元數據和數據本身。
inode 有兩種:每個i_node節點的大小,一般是128字節或者256字節,inode節點的總數,在格式化時就給定,一般每2KB就設置一個inode。inode號是唯一的,表示不同的文件。當創建一個文件的時候就會分配一個inode。

struct inode數據結構:

struct inode {
	struct hlist_node	i_hash; //指向hash鏈表指針,用於inode的hash表
	struct list_head	i_list; //指向索引節點鏈表指針
	struct list_head	i_sb_list; 
	struct list_head	i_dentry; //指向目錄項鍊表指針
	unsigned long		i_ino;  //索引節點號,每個inode都是唯一的
	atomic_t		i_count; //引用計數
	umode_t			i_mode; //文件權限標識
	unsigned int		i_nlink; //與該節點建立連接的文件數
	uid_t			i_uid; //文件擁有者標識
	gid_t			i_gid; //文件所在組標識
	dev_t			i_rdev; //實際的設備標識
	loff_t			i_size;  //inode所代表的文件大小
	struct timespec		i_atime; //最後一次訪問時間
	struct timespec		i_mtime; //文件最後一次修改時間
	struct timespec		i_ctime; //inode最後一次修改時間
	unsigned int		i_blkbits; //快大小 位爲單位
	unsigned long		i_blksize; //塊大小字節爲單位
	unsigned long		i_version;//版本號
	unsigned long		i_blocks; //文件所佔塊數
	unsigned short          i_bytes; //文件最後一個快的字節數
	unsigned char		i_sock; //
	spinlock_t		i_lock;	/* i_blocks, i_bytes, maybe i_size */
	struct semaphore	i_sem;
	struct rw_semaphore	i_alloc_sem;
	struct inode_operations	*i_op;
	struct file_operations	*i_fop;	/* former ->i_op->default_file_ops */
	struct super_block	*i_sb; //inode索引節點指向超級快的指針
	struct file_lock	*i_flock; //文件鎖鏈表
	struct address_space	*i_mapping; //表示向誰請求頁面
	struct address_space	i_data; //表示inode讀寫頁面
#ifdef CONFIG_QUOTA
	struct dquot		*i_dquot[MAXQUOTAS]; //inode的讀寫限額
#endif
	/* These three should probably be a union */
	struct list_head	i_devices; //設備鏈表
	struct pipe_inode_info	*i_pipe; //指向管道文件
	struct block_device	*i_bdev; //指向塊設備文件
	struct cdev		*i_cdev; //指向字符設備文件
	int			i_cindex; 

	__u32			i_generation;

#ifdef CONFIG_DNOTIFY
	unsigned long		i_dnotify_mask; /* Directory notify events */
	struct dnotify_struct	*i_dnotify; /* for directory notifications */
#endif

	unsigned long		i_state; //索引節點的狀態標識 I_NEW,I_LOCK,I_FREEING
	unsigned long		dirtied_when;	/* jiffies of first dirtying */

	unsigned int		i_flags; //索引節點的安裝標識

	atomic_t		i_writecount; //記錄進程以刻寫模式打開此文件
	void			*i_security;
	union {
		void		*generic_ip;
	} u;
#ifdef __NEED_I_SIZE_ORDERED
	seqcount_t		i_size_seqcount;
#endif
};

每個索引節點都會從磁盤複製索引節點對應的一些數據信息,比如分配文件的磁盤塊數,如果i_state字段值等於i_dirty_*,就是該索引節點是髒的,也就是說,對應磁盤索引節點 必須被更新了,i_dirty標誌可以立即檢查這三個標誌的值,還有其他的標誌值,例如I_LOCK涉及的索引節點處於IO傳送中,I_FREEING索引節點正在被釋放,I_CLEAR索引節點的內容不再有意義,I_NEW索引節點已經被分配但是沒有從磁盤索引節點讀取數據填充。

每個索引節點也包含在每個文件系統的雙向循環鏈表中 ,鏈表的頭存放在超級塊對象的s_inodes字段中,索引節點對象的i_sb_list字段存放了指向鏈表相鄰元素的指針。

同時索引節點也存放在一個稱爲inode_hashtable的散列表中,散列表加快了對索引節點對象的搜索,前提是系統內核要知道索引節點號及文件系統所對應的超級塊對象的地址。

每個索引節點都是通過單個的內核雙向鏈表進行管理:
有效未使用的索引節點鏈表
正在使用的索引節點鏈表
髒索引節點的鏈表
這些索引節點通過適當的索引節點對象的i_list字段連接在一起的。

管理inode的鏈表:

inode_unused:將目前還沒有使用的inode節點連接
inode_in_use :將目前正在使用的inode節點連接起來
super_block中的s_dirty:將所有修改過的inode連接起來,這個字段在super_block中
inode_hashtable:注意爲了加快inode的查找速率,將正在使用的inode和髒inode也會放在inode_hashtable這樣一個hash結構中,但是不同的inode的hash值可能相等,所以將hash值相等的這些inode通過這個i_hash字段連接。

目錄項:目錄也是一種文件,打開目錄就是打開目錄文件。

struct dentry {
	atomic_t d_count;  //引用計數
	unsigned int d_flags;		/* protected by d_lock *///目錄項緩存標識
	spinlock_t d_lock;		/* per dentry lock */自旋鎖
	struct inode *d_inode;		/* Where the name belongs to - NULL is與該目錄項相關聯 的inode
					 * negative */
	/*
	 * The next three fields are touched by __d_lookup.  Place them here
	 * so they all fit in a 16-byte range, with 16-byte alignment.
	 */
	struct dentry *d_parent;	/* parent directory */ 父目錄的目錄項
	struct qstr d_name; ///目錄項名稱

	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 *本目錄的所有孩子目錄鏈表頭*/
	struct list_head d_alias;	/* inode alias list */一個有效的dentry必然與一個inode進行關聯,但是一個inode可以對應多個dentry,因爲一個文件可以被連接到其他文件,所以這個字段連接就屬於自己的inode結構中的i_dentry鏈表中的
	unsigned long d_time;		/* used by d_revalidate *///重新變爲有效的時間
	struct dentry_operations *d_op;//目錄項操作
	struct super_block *d_sb;	/* The root of the dentry tree *///目錄所屬的超級快
	void *d_fsdata;			/* fs-specific data *///文件系統的私有數據
 	struct rcu_head d_rcu; //
	struct dcookie_struct *d_cookie; /* cookie, if any */ 
	struct hlist_node d_hash;	/* lookup hash list */	內核使用hashtable對dentry進行管理,dentey_hashtable是由list_head組成的鏈表,一個dentry創建之後。就通過d_hash連接進入對應的hash值的鏈表中
	int d_mounted; //安裝在該目錄的文件系統的數量,注意文件目錄下可以有不同的文件系統
	unsigned char d_iname[DNAME_INLINE_LEN_MIN];	/* small names *///存放短文件的文件名稱
};

一個有效的dentry結構必定有一個inode結構,這是因爲一個目錄項要麼代表着一個文件,要麼代表着一個目錄,而目錄實際上也是文件。所以,只要dentry結構是有效的,則其指針d_inode必定指向一個inode結構。但是inode卻可以對應多個。

vfs將每個目錄看作若干個子目錄和文件組成的普通文件,一個目錄項被讀入到內存,就會被vfs轉換成一個dentry結構的目錄項對象。目錄項在磁盤上並沒有對應的映象,因此dentry結構中不包含指出該目錄項被修改的標誌,目錄項存放在名爲dentry_cache的slab分配器高速緩存中,目錄項的創建和刪除,kmem_cache_alloc和kmem_cache_free實現。

目錄項可以處於四種狀態:

空閒狀態
處於該狀態的目錄項對象不包含有效信息,還沒有被VFS使用,對應的內存由slab分配器進行分配。
未使用狀態
處於該狀態的目錄項對象當前還沒有被內核使用,該對象的引用計數爲0,但是d_inode字段仍指向關聯的索引節點,該目錄項對象包含有效信息,但爲了在必要時回收內存,它的內容可能會被丟棄。
正在使用狀態
該狀態的目錄項對象正在被內核使用,d_count值爲正數,不能被丟棄。
負狀態
與目錄相關聯的索引節點不復存在,或者因爲相應的磁盤節點已經被刪除,或者因爲目錄對象是通過解析一個不存在文件路徑名創建的,目錄項對象的d_node對象被置爲NULL,但該對象仍然被保存在高速緩存中,以便後續對同一個文件目錄名的查找操作能夠快速完成。

目錄項高速緩存:在一個目錄使用完成後,可能後面還會使用它,所以仍在內存中保存該目錄項信息。

Linux使用目錄項高速緩存,有兩種類型的數據結構組成:
一個處於正在使用,未使用或者負狀態的目錄項對象的集合。
一個散列表,從中能快速獲取與給定的文件名和目錄名對應的目錄項對象,同樣,如果訪問的對象不在高速緩存中,則返回一個空值。

未使用的目錄項都存放在最近最少使用的("least recently used"LRU)雙向鏈表中,該鏈表按照插入的時間排序,最後釋放的對象放在鏈表的首部,最少使用的目錄項對象放在鏈表的尾部。一旦目錄項對象高速緩存的空間開始變小,內核就從鏈表尾部刪除元素,使得最常使用的目錄項得以保存。

進程通過文件描述符操作文件,注意每個文件都有一個32位數字來代表下一個讀寫的字節位置,一般打開文件後,從0開始,Linux中的file結構體來保存打開的文件的位置,所以file稱爲打開的文件描述。file結構形成一個鏈表,稱爲系統打開文件表。這個結構體是針對一個文件設置的。
下面是內核中的數據結構描述:

struct file {
	struct list_head	f_list; //本進程所有打開的文件形成的鏈表
	struct dentry		*f_dentry; //與該文件相關的dentry
	struct vfsmount         *f_vfsmnt; //該文件在文件系統的中的安裝點
	struct file_operations	*f_op; //文件操作,當進程打開文件的時候,這個文件的關聯inode中的i_fop文件操作就會初始化這個f_op字段
	atomic_t		f_count; //引用計數 
	unsigned int 		f_flags; //f_flags打開文件時候指定的標識
	mode_t			f_mode; //文件的訪問模式
	int			f_error; //寫錯誤碼
	loff_t			f_pos; //f_ops  目前文件的相對開頭的偏移
	struct fown_struct	f_owner; //記錄一個進程ID,以及當某些事發送的時候發送給該ID進程的信號
	unsigned int		f_uid, f_gid; //用戶ID
	struct file_ra_state	f_ra; //組ID

	size_t			f_maxcount; //
	unsigned long		f_version; //版本號
	void			*f_security;

	/* 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;
	spinlock_t		f_ep_lock;
#endif /* #ifdef CONFIG_EPOLL */
	struct address_space	*f_mapping;
};

文件對象描述進程如何與一個文件進行交互,文件對象是在文件被打開的時候被創建,由一個file結構組成,文件對象在磁盤上沒有對應的映像,因此file結構中沒有設置髒字段,來表示文件對象是否已經被修改。

存放在文件對象中主要信息是文件指針,及文件中當前的位置,下一個操作將在該位置發生,由於幾個進程可能同時訪問同一文件,因此文件指針必須存放文件對象而不是索引對象。

文件對象通過filp的slab高速緩存分配,filp描述符地址存放在filp_cachep變量中。
在使用文件對象包含在由具體文件系統的超級塊所確立的幾個鏈表中,每個超級塊將文件對象鏈表的頭存放在s_files字段中,因此屬於不同文件系統的文件對象就包含在不同的鏈表中,s_files通過自旋鎖進行保護。免受多處理器干擾。

當VFS代表進程必須打開一個文件的時候,他調用get_empty_filp函數來分配一個新的文件對象,該函數調用kmem_cache_alloc從filp高速緩存中獲取一個空閒的文件對象,然後初始化這個對象的字段。

每個文件系統都有自己的文件操作集合,當VFS將inode節點信息從磁盤裝入內存中時,就會把指向這些文件操作的指針存放在file_opration結構中,而結構的地址存放在該索引節點的i_fop字段中,當進程打開文件時,VFS就用存放該索引節點中的這個地址初始化新文件對象的f_op字段,使得對文件操作的後續調用能使用這些函數,如果需要,VFS隨後也可以通過在f_op字段存放一個新值而修改文件操作的集合。

f_flags,f_mode和f_pos代表的是當前操作這個文件的控制信息,因爲對於一個文件,可以被多個進程同時打開,那麼對於每個進程來說操作這個文件是異步的。

f_count:引用計數,當一個進程關閉已經打開的文件時,只是將對應的f_count減1,當f_count=0放入時候,纔會真的區關閉它,對於dup和fork操作,都會使得f_count增加。

f_op:設計所有的文件的操作的結構體。例如:用戶使用read,最終會調用file_operation中的讀操作,而file_operations結構體是對於不同的文件系統不一定相同,裏面一個重要的操作函數,release函數,當用戶執行close時,其實在內核中執行release函數,這個函數僅僅將f_count減一,直到減爲0,才真正關閉相應的文件。

對於正在使用和未使用的文件對象分別使用一個雙向鏈表進行管理。

用戶打開文件表:

struct files_struct {
        atomic_t count; //引用計數
        spinlock_t file_lock;     /* Protects all the below members.  Nests inside tsk->alloc_lock */自旋鎖
        int max_fds; //當前文件對象的最大數量
        int max_fdset; //文件描述符的最大數
        int next_fd; //已分配的最大的文件描述符
        struct file ** fd;      /* current fd array */ 指向文件指針數組的指針,一般指向最後一個字段fd_dentry,當文件數超過NR_OPEN_DEFAULT時,就會重新分配一個數組,然後指向這個新的數組指針
        fd_set *close_on_exec; //執行exec時,需要關閉的文件描述符
        fd_set *open_fds; //指向打開的文件描述符的指針
        fd_set close_on_exec_init; //執行exec時,需要關閉的文件描述符初始化值
        fd_set open_fds_init; //文件描述符初值的集合
        struct file * fd_array[NR_OPEN_DEFAULT]; //文件對象指針的初始化數組
};

fd的數組長度存放在max_fds字段中,通常fd字段指向files_struct結構的fd_array字段,該字段包含32個文件對象指針,如果進程打開更大多的文件,內核將重新分配一個新的更大的文件指針數組,並肩器地址放在fd中,內核同時更新max_fds字段的值。

當內核開始使用一個文件對象的時候,會通過fget()調用,傳入參數fd,該調用返回的是current->files->fd[fd],我們都知道這是一個struct file類型的文件對象指針類型,指向要操作的文件對象,要是沒找到該對象的話,會返回NULL,找到了的話,fget使得文件對象的引用計數加1。當內核控制路徑完成對文件對象的使用時,調用內核提供的fput()函數,該函數將文件對象的地址作爲參數,並減少文件對象引用計數的值,另外如果這個字段變爲0,該函數就調用文件操作的release方法,減少索引節點的i_write_count的值,將文件對象從超級塊鏈表中移走,釋放文件對象給slab分配器,並且減少相關文件系統描述符的目錄項對象的引用計數值。

對於fd數組來說,數組的索引就是一個文件描述符。

描述進程的一些信息:每個進程都有自己的根目錄和當前的工作目錄,內核使用struct fs_struct來記錄這些信息,進程描述符中的fs字段便是指向該進程的fs_struct結構。

struct fs_struct {
 atomic_t count;
 rwlock_t lock;
 int umask;
 struct dentry * root, * pwd, * altroot;
 struct vfsmount * rootmnt, * pwdmnt, * altrootmnt;
};

其中:
count:共享這個表的進程個數
lock:用於表中字段的讀/寫自旋鎖
umask:當打開文件設置文件權限時所使用的位掩碼
root:根目錄的目錄項
pwd:當前工作目錄的目錄項
altroot:模擬根目錄的目錄項(在80x86結構上始終爲NULL)
rootmnt:根目錄所安裝的文件系統對象
pwdmnt:當前工作目錄所安裝的文件系統對象
altrootmnt:模擬根目錄所安裝的文件系統對象(在80x86結構上始終爲NULL)

open的實現:


asmlinkage long sys_open(const char __user * filename, int flags, int mode)
{
	char * tmp;
	int fd, error;

#if BITS_PER_LONG != 32
	flags |= O_LARGEFILE;
#endif
//將文件名傳送給內核
	tmp = getname(filename);
	//宏函數,獲得錯誤碼
	fd = PTR_ERR(tmp);
	//錯誤碼是無效的
	if (!IS_ERR(tmp)) {
	//在當前進程的fd_array數據中找到一個合適的位置,並返回其索引
		fd = get_unused_fd();
		if (fd >= 0) {
		//執行打開文件的核心操作函數
			struct file *f = filp_open(tmp, flags, mode);
			error = PTR_ERR(f);
			if (IS_ERR(f))
				goto out_error;
				//fd_install函數將該文件對象賦值到fd_array數組的第fd個元素
			fd_install(fd, f);
		}
out:
		putname(tmp);
	}
	return fd;

out_error:
	put_unused_fd(fd);
	fd = error;
	goto out;
}

在這裏插入圖片描述

read函數的實現:

asmlinkage ssize_t sys_read(unsigned int fd, char __user * buf, size_t count)
{
	struct file *file;
	ssize_t ret = -EBADF;
	int fput_needed;

	file = fget_light(fd, &fput_needed);
	if (file) {
		loff_t pos = file_pos_read(file);
		ret = vfs_read(file, buf, count, &pos);
		file_pos_write(file, pos);
		fput_light(file, fput_needed);
	}

	return ret;
}

read函數作用是根據文件描述符讀取指定長度的數據到緩衝區buf中,該系統調用的實現涉及了內核對IO進行處理的各個層次,但是對於VFS層來說實現方法比較清晰。
在read系統調用對應的服務例程中,首先使用fget_light函數根據fd找到fd對應的file對象,再通過file_pos_read函數獲取文件的其實偏移量,即文件對象的f_pos字段的值,接着通過vfs_read函數進行讀操作,通過file_pos_write函數更新文件當前偏移量,通過fput_light函數釋釋放文件對象,最終返回vfs_read函數的返回值,該值則爲實際讀取數據的長度。
read服務例程中,最核心的函數即爲vfs_read,他的主要工作是選擇一個具體的讀操作函數,如果當前文件對象操作函數集中的read鉤子函數被實現(通常在驅動程序中實現),則調用它,否則使用內核默認的讀函數do_sys_read。

ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
{
	ssize_t ret;

	if (!(file->f_mode & FMODE_READ))
		return -EBADF;
	if (!file->f_op || (!file->f_op->read && !file->f_op->aio_read))
		return -EINVAL;
	if (unlikely(!access_ok(VERIFY_WRITE, buf, count)))
		return -EFAULT;

	ret = rw_verify_area(READ, file, pos, count);
	if (!ret) {
		ret = security_file_permission (file, MAY_READ);
		if (!ret) {
			if (file->f_op->read)
				ret = file->f_op->read(file, buf, count, pos);
			else
				ret = do_sync_read(file, buf, count, pos);
			if (ret > 0) {
				dnotify_parent(file->f_dentry, DN_ACCESS);
				current->rchar += ret;
			}
			current->syscr++;
		}
	}

	return ret;
}

事實上,do_sys_read函數在內部調用鉤子函數aiio_read,該鉤子函數一般指向內核實現的通用讀函數generic_file_aio_read。這個通用函數已經不屬於我們本文所述的VFS層實現範疇。

write函數的實現,在vfs文件系統中和read實現類似。

close函數:通過調用flush鉤子函數將頁緩存中的數據寫回磁盤,釋放該文件上的所有鎖,通過fput函數釋放該文件,最後返回0或者一個錯誤碼。

參考文檔:
http://edsionte.com/techblog/archives/tag/vfs

《深入理解Linux內核》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章