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

1.VFS

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

2. 工作原理

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

3.file结构体

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


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结构体

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

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,
长此以往,内存不给泄漏完了吗??

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值