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内核》

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值