欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【文件系统】VFS虚拟文件系统

程序员文章站 2022-05-09 19:09:50
...

1.VFS

    Linux支持各种各样的文件系统格式,比如说ext2,ext3,reiserfs,FAT,NTFS,iso9660等,
不同的磁盘分区,光盘或者其他存储设备都有不同的文件系统格式,然后这些文件系统都可以
mount到某个目录下,使我们看到一个统一的目录树,各种文件上的目录和文件我们用ls命令
看起来都是一样的,读写操作起来也是一样的,这个是怎么做到的呢?Linux内核在各种不同
的文件系统格式之上做了一个抽象层,使得文件,目录,读写访问等概念称为抽象层的概念,因此
各种文件系统看起来都是一样的,这个抽象层称为虚拟文件系统(VFS,Vitual Filesystem).

2. 工作原理

应用层函数open,read,write等函数在操作文件的时候不需要关心文件存储在什么系统上,就是通过
VFS来实现的.

【文件系统】VFS虚拟文件系统

3.file结构体

D:\005-代码\001-开源项目源码\004-内核源码\linux-5.8.13\linux-5.8.13\include\Linux\fs.h

struct file {
	union {
		struct llist_node	fu_llist;
		struct rcu_head 	fu_rcuhead;
	} f_u;
	struct path		f_path;
	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;
	enum rw_hint		f_write_hint;
	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;
	const struct cred	*f_cred;
	struct file_ra_state	f_ra;

	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;
	errseq_t		f_wb_err;
	errseq_t		f_sb_err; /* for syncfs */
} __randomize_layout
  __attribute__((aligned(4)));	/* lest something weird decides that 2 is OK */



每个文件描述符都会对应一个file结构体,
file结构体中的重要成员变量:
读写指针位置(描述符会指向它)
flags(状态标识,O_CREATE,O_WRITE,阻塞非阻塞等)
f_op(是一个指针,会指向一组函数,比如说open,close,read,write等,这里的open,close,read,write等函数是驱动层的函数,
不是应用层的,这里的open,close,read,write知道如何去操作硬件)
f_dentry磁盘文件,里面会有Inode_operations这些操作Inode的文件系统驱动,如chmod,link,unlink
等文件系统驱动.
不管是文件描述符还是file结构体还是驱动层的file_operations结构体和文件系统的inode_operation,
这些都是存在3G到4G的内核空间中的,用户是看不见的.
文件引用计数.

问:文件的引用计数是在哪个结构体里面的?
    是struct file还是struct inode




由这些一个一个结构体进行关联,最终形成了虚拟文件系统.
虚拟文件系统不是一个具体的东西,它是一张网,它将最上层的最上层应用层的open函数和最下层的硬件
联系到一起.



比如我们在代码里面写了write(3,"hello",5);当我们调用这个函数的时候,操作系统通过3这个文件描述符
找到这个file结构体,找到这个file机构体以后,file结构体里面记录了对3这个文件描述符的所有驱动层的
操作函数,由于我们调用的是write,那么通过f_op索引到驱动里面的write,驱动里面的write知道如何操作
硬件,不管3是一个磁盘文件还是一个终端文件还是一个串口文件或者网络文件,驱动write都知道怎么去处理.



考虑以下场景:
1.进程A中有两个文件描述符都指向文件file,第一个文件描述符写了“hello”,第二个文件描述符写了“world”,那么这时的文件的内容应该是“helloworld”;
2.进程A和进程B中分别有两个文件指向了文件file,进程A和进程B同时用open打开了一个文件,A写“hello”,B后写“world”,
A和B都维护了各自的结构体,后写的那个覆盖了前面写的那个,最后file文件里面是“world”.
3.进程A开始打开了一个file文件返回描述符3,在文件描述符3里写了“hello”,然后调用open重新打开
  文件file,此时返回文件描述符4,描述符4指向一个新的文件结构体,在文件描述符里写了“world”,
  会将文件描述符3在file文件中写的内容覆盖掉,最后文件里的内容是“world”.

4.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;
	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 timespec64	i_atime;
	struct timespec64	i_mtime;
	struct timespec64	i_ctime;
	spinlock_t		i_lock;	/* i_blocks, i_bytes, maybe i_size */
	unsigned short          i_bytes;
	u8			i_blkbits;
	u8			i_write_hint;
	blkcnt_t		i_blocks;

#ifdef __NEED_I_SIZE_ORDERED
	seqcount_t		i_size_seqcount;
#endif

	/* Misc */
	unsigned long		i_state;
	struct rw_semaphore	i_rwsem;

	unsigned long		dirtied_when;	/* jiffies of first dirtying */
	unsigned long		dirtied_time_when;

	struct hlist_node	i_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;
	struct list_head	i_wb_list;	/* backing dev writeback list */
	union {
		struct hlist_head	i_dentry;
		struct rcu_head		i_rcu;
	};
	atomic64_t		i_version;
	atomic64_t		i_sequence; /* see futex */
	atomic_t		i_count;
	atomic_t		i_dio_count;
	atomic_t		i_writecount;
#if defined(CONFIG_IMA) || defined(CONFIG_FILE_LOCKING)
	atomic_t		i_readcount; /* struct files open RO */
#endif
	union {
		const struct file_operations	*i_fop;	/* former ->i_op->default_file_ops */
		void (*free_inode)(struct inode *);
	};
	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;
		unsigned		i_dir_seq;
	};

	__u32			i_generation;

#ifdef CONFIG_FSNOTIFY
	__u32			i_fsnotify_mask; /* all events this inode cares about */
	struct fsnotify_mark_connector __rcu	*i_fsnotify_marks;
#endif

#ifdef CONFIG_FS_ENCRYPTION
	struct fscrypt_info	*i_crypt_info;
#endif

#ifdef CONFIG_FS_VERITY
	struct fsverity_info	*i_verity_info;
#endif

	void			*i_private; /* fs or device private pointer */
} __randomize_layout;

5.dup和dup2

下面两个函数都可用来复制一个现有的文件描述符。
#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd);
两函数的返回值:若成功,返回新的文件描述符;若出错,返回−1
由dup返回的新文件描述符一定是当前可用文件描述符中的最小数
值。对于 dup2,可以用fd2参数指定新描述符的值。如果fd2已经打开,
则先将其关闭。如若fd等于fd2,则dup2返回fd2,而不关闭它。否则,
fd2的FD_CLOEXEC文件描述符标志就被清除,这样fd2在进程调用exec
时是打开状态。--摘自《APUE》


dup和dup2都可以用来复制一个现存的文件描述符,是两个文件描述符指
向同一个file结构体,如果两个文件描述符指向同一个file结构体,
File Status Flag和读写位置只保存一份在file结构体中,并且file
结构体的引用计数是2.如果两次open同一个文件得到两个文件描述符,则
每个描述符对应一个不同的file机构体,可以用不同的File Status Flag
和读写位置.注意区分两种情况.


如何才能新创建一个file结构体对象呢?
只有open一个文件的时候才能创建一个file结构体对象.


dup和dup2不创建新的file结构体对象.
dup --传入已有的文件描述符,返回新生成的文件描述符,新的文件描述符是文件描述符表中未使用的最小的文件描述符
dup2(dup two)--可以指定新的描述符为fd2,如果newfd是1(在一个程序中假设1就是指向标准输出),第一步会先关闭1,
然后重新打开1将其指向oldfd所指向的那个文件结构体对象.


dup的应用.
比如要给10000个随机数排序,将10000个随机数放在文件中,然后采用输入重定向,读取文件里面的数据进行排序.
如:
./a.out < file

6.不关闭打开的文件和内存泄漏

在一个进程中,如果文件描述符3和文件描述符都指向文件file,在程序结束之前close(3);但是
没有close(4).这时会产生内存泄漏,而且是内核中的内存泄漏.

程序运行时,在进程中文件描述符3和文件描述符4指向同一个File结构体对象,这个结构体对象的引用计数是2,当close(3)的时候,
会将file结构体对象的引用计数减去1,但是只有当file结构体对象的引用计数减成0的时候,内核才会真正的释放这个file结构体对象的空间.

想一想,打开1个文件会在内核中就会有1个file结构体对象,当同时打开10000个文件的时候,内核中就有10000个file结构体对象,会占用内
核中的内存,如果您用完文件没有及时关闭,这个也是一种内存泄漏.

细思极恐:
如果一个项目中有20个程序员,10个程序员写了打开文件的程序之后不知道close,或者引用计数大于1的时候没有对所有描述符进行close,
长此以往,内存不给泄漏完了吗??