【Linux】文件系统与文件管理


在 Linux 中,内核为每一个打开的文件提供三种数据结构对其进行维护,它们之间的关系决定了在文件共享方面一个进程对另一个进程可能产生的影响。

  1. 进程级的打开文件描述符表
  2. 系统级的打开文件表
  3. 系统的 inode 表

一. 打开文件描述符表

1、什么是打开文件描述符表?

我们知道,每个进程都有一个描述该进程相关属性的数据结构:进程控制块(PCB),Linux 中的 PCB 叫做 task_struct,它的部分源码如下:
在这里插入图片描述

注意到 task_struct 的成员中有一个类型为 files_struct 的结构体指针变量 files,我们跳转到这个结构体类型的定义:
在这里插入图片描述

所谓打开文件描述符表,实际上就是 files_struct 中的成员 fd_array[NR_OPEN_DEFAULT]。它是一个指针数组,每个元素类型为 file*,即一个打开的文件(file 这一数据结构用来描述一个打开的文件)

fd_array[ ] 的下标有什么意义?

这里的下标编号叫做文件描述符,进程每打开一个文件都会为该文件创建一个 file 类型的结构体,并把该结构体对象的地址填入到 fd_array[ ] 中,填入的那个下标编号是最小并且未被使用的,对应 file_struct 结构体中 next_fd 保存的就是下一个分配的文件描述符,它的值会在调用 open 和 close 时调整,最终使得每次 open 返回的都是当前可用的最小文件描述符。同时还规定每个进程启动的时候,默认会打开三个文件:0是标准输入,1是标准输出,2是标准错误。这意味着如果此时去打开一个新的文件,它的文件描述符会是3,再打开一个文件,它的文件描述符就是4…等以此类推
在这里插入图片描述

PS:这里的标准输入、标准输出和标准错误对应的是键盘、显示器、显示器,而不是C语言里的 stdin、stdout 和 stderr,这三个是C语言专门定义的FILE类型的对象:
在这里插入图片描述

Linux 系统配置下每个进程最大打开的文件描述符个数?

为了控制每个进程消耗的文件资源,内核会对单个进程最大打开文件数做限制,即用户级限制。可以使用 ulimit -n 命令查看单个进程最大打开文件的个数,Windows 中,32 位系统默认值一般是 1024,64 位系统默认值一般是 65535。

我是 SSH 远程登录的云服务器,这里进程的最大文件描述符个数默认设置为 100001。
在这里插入图片描述

当然我们也可以自己去更改进程最大打开文件描述符的个数:

  • 临时更改:使用命令 ulimit -SHn xxxx 来修改,其中xxxx就是要设置的数字。重启或断开 XShell 后,会恢复原来的默认值。
  • 永久更改:vim 编辑 /etc/security/limits.conf 文件,修改其中的 hard nofile xxxx 和 soft nofile xxxx,其中 xxxx 就是要设置的数字。保存后退出。
    在这里插入图片描述

用什么方法查看特定进程的打开文件描述符表?

执行如下程序,程序 “mytest” 启动后在当前目录下打开一个文件 log.txt,然后死循环使进程一直处于运行状态。

// 可执行程序名称:mytest
void test()    
{
       
   int fd = open("log.txt", O_RDWR|O_CREAT, 0666);    
   while(1)    
   {
   }        
}  

在打开另一个 Shell,输入命令:pidof mytest 获取进程 mytest 的 pid 号,然后 ll /proc/pid/fd 查看 “mytest” 进程的文件描述符表的使用情况。
在这里插入图片描述

这里展现出来的 “mytest” 进程打开文件描述符表中每一个表项都是软连接。
/dev/pts 是远程登陆后创建的控制台设备文件所在的目录。因为我是通过 SSH 远程登录的,所以标准输入,标准输出,标准错误对应的文件描述符 0、1、2 指向虚拟终端控制台 /dev/pts/0 。而我们自己打开的文件 log.txt 的绝对路径被被放置在 3 号文件描述符位置上。

2、为什么要有打开文件描述符表?

在 Linux 系统中一切皆可以看成是文件,文件又分为:普通文件、目录文件、链接文件和设备文件。在操作这些所谓的文件的时候,我们每操作一次就找一次名字,这会耗费大量的时间。所以在 Linux 中规定,每一个文件对应一个索引,这样下次要操作文件的时候,我们直接找到索引就可以拿到了。而文件描述符就是内核为了高效管理这些已经被打开的文件所创建的索引,它是一个非负整数,用于指代被打开的文件,所有执行 I/O 操作的系统调用都通过文件描述符来实现,这个我们下面会介绍到。

3、打开文件描述符表的和进程的联系

每启动一个进程,操作系统都会为其创建一个 task_struct 结构体,在 task_struct 的成员中含有一个类型为 files_struct* 的指针变量 files;files_struct 中又含有一个元素类型为 file* 的指针数组 fd_array,它就是打开文件描述符表,里面存储了每个文件描述符作为索引与一个打开文件相对应的关系。

它们之间的关系如下图所示,文件描述符(索引)就是文件描述符表的下标,数组的内容就是指向一个个打开文件的地址。
在这里插入图片描述


二、打开文件表

1、什么是打开文件表?

接着上面的,我们来看看所谓描述打开文件信息的 file 结构体的具体声明:

struct file {
   
	// 记录头结点
	union {
   
		struct list_head	fu_list;
		struct rcu_head 	fu_rcuhead;
	} f_u;
	struct path		f_path;// 文件路径,包括目录项以及i-node
#define f_dentry	f_path.dentry
#define f_vfsmnt	f_path.mnt
	const struct file_operations	*f_op;// 保存关于文件操作的一系列函数指针
	spinlock_t		f_lock;  
#ifdef CONFIG_SMP
	int			f_sb_list_cpu;
#endif
	atomic_long_t		f_count;// 文件打开次数
	unsigned int 		f_flags;// 文件打开时的flag,对应于open函数的flag参数
	fmode_t			f_mode;// 文件打开时的mode,对应于open函数的mode参数
	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
	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;
#endif /* #ifdef CONFIG_EPOLL */
	struct address_space	*f_mapping;
#ifdef CONFIG_DEBUG_WRITECOUNT
	unsigned long f_mnt_write_state;
#endif
};

打开文件表的结构
通过源码发现 file 结构体内有定义一个记录头结点的联合体成员f_u:
在这里插入图片描述

可以推测 file 结构体之间是通过链表组织起来的,每一个 file 结构体叫做一个文件表项,它们组合而成的链表叫做打开文件表,这张表是系统级别的,为所有进程共享,但组成该表的每一个文件是进程级的。
在这里插入图片描述

存放文件操作函数的结构体
file 结构体中,有一个 struct file_operations* 类型的成员 f_op,这个成员中存储了一系列文件读写操作相关的函数指针,这些操作函数是系统级的:

struct file_operations {
   
	struct module *owner;
	loff_t (*llseek) (struct file *, loff_t, int);
	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
	ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
	ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
	int (*readdir) (struct file *, void *, filldir_t);
	unsigned int (*poll) (struct file *, struct poll_table_struct *);
	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
	long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
	int (*mmap) (struct file *, struct vm_area_struct *);
	int (*open) (struct inode *, struct file *);
	int (*flush) (struct file *, fl_owner_t id);
	int (*release) (struct inode *, struct file *);
	int (*fsync) (struct file *, int datasync);
	int (*aio_fsync) (struct kiocb *, int datasync);
	int (*fasync) (int, struct file *, int);
	int (*lock) (struct file *, int, struct file_lock *);
	ssize_t 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值