【Linux】Linux文件与进程的关联

作为文件的使用者,进程理所当然地将要使用的文件记录于自己的控制块。另外,由于进程所对应的程序也是一个文件,因此进程控制块还必须记录这个文件的相关信息。由于操作系统要对系统所以进程提供服务,因此操作系统还要维护一个记录所有进程打开文件的总表。

进程与其打开文件的关系

如果说文件管理系统是文件的管理者与提供者,那么进程就是文件系统中文件的使用者。即,文件管理系统与进程之间是服务与客户之间的关系。

文件对象

当进程通过系统调用open()打开一个文件时,该系统调用找到这个文件后,会把文件封装到一个file结构的实例中提供给进程,这个实例称为file对象。

file结构的定义如下:

struct file {
	union {
		struct list_head	fu_list;        //所有打开文件的链表
		struct rcu_head 	fu_rcuhead;
	} f_u;
	struct vfsmount     *f_vfsmnt;            //文件目录的VFS安装点指针
	struct dentry     *f_dentry;            //文件的dentry
#define f_dentry	f_path.dentry
#define f_vfsmnt	f_path.mnt
	const struct file_operations	*f_op;    //指向文件操作函数集的这孩子很
	spinlock_t		f_lock;          /* f_ep_links, f_flags, no IRQ */
	atomic_long_t		f_count;        //记录访问本文件的进程数目的计数器
	unsigned int 		f_flags;        //访问类型
	fmode_t			f_mode;            //访问模式
	loff_t			f_pos;            //文件的当前读写位置
	struct fown_struct	f_owner;
	const struct cred	*f_cred;        //id信息
	struct file_ra_state	f_ra;

	u64			f_version;
#ifdef CONFIG_SECURITY
	void			*f_security;
#endif
	void			*private_data;

#ifdef CONFIG_EPOLL
	struct list_head	f_ep_links;
#endif
	struct address_space	*f_mapping;
#ifdef CONFIG_DEBUG_WRITECOUNT
	unsigned long f_mnt_write_state;
#endif
};

其中,指针f_cred是记录文件所有者ID,文件所有者所在用户组ID的。其cred的结构如下:

struct cred {
	atomic_t	usage;
	uid_t		uid;		/* real UID of the task */
	gid_t		gid;		/* real GID of the task */
	uid_t		suid;		/* saved UID of the task */
	gid_t		sgid;		/* saved GID of the task */
	uid_t		euid;		/* effective UID of the task */
	gid_t		egid;		/* effective GID of the task */
	uid_t		fsuid;		/* UID for VFS ops */
	gid_t		fsgid;		/* GID for VFS ops */
	unsigned	securebits;	/* SUID-less security management */
	kernel_cap_t	cap_inheritable; /* caps our children can inherit */
	kernel_cap_t	cap_permitted;	/* caps we're permitted */
	kernel_cap_t	cap_effective;	/* caps we can actually use */
	kernel_cap_t	cap_bset;	/* capability bounding set */
#ifdef CONFIG_KEYS
	unsigned char	jit_keyring;	/* default keyring to attach requested
					 * keys to */
	struct key	*thread_keyring; /* keyring private to this thread */
	struct key	*request_key_auth; /* assumed request_key authority */
	struct thread_group_cred *tgcred; /* thread-group shared credentials */
#endif
#ifdef CONFIG_SECURITY
	void		*security;	/* subjective LSM security */
#endif
	struct user_struct *user;	/* real user ID subscription */
	struct group_info *group_info;	/* supplementary groups for euid/fsgid */
	struct rcu_head	rcu;		/* RCU deletion hook */
};

这就使得一个文件可能面临三种用户的访问:文件所有者、同组用户、其他用户。内核在处理一个进程或用户访问某一个文件的请求时,要根据进程的uid、fid和访问模式来确定该进程是否具有访问这个文件的权限。对于一个文件的用户来说,有读、写和执行三种文件操作,因此形成了三种权限,这三种权限与三种用户就共有9种组合,即文件的访问权限可以用9个二进制来表示,并将其保存在文件的dentry中。

结构中的f_ops记录了进程对文件读写位置的当前值,它可以通过调用函数llseek()进行移动。

值得注意的是,结构体中的指针f_op指向结构file_operations,该结构中封装了对于文件进行操作函数。该结构在文件include/linux/fs.h的定义如下:

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 *);
	int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
	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 *, struct dentry *, 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 (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
	unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
	int (*check_flags)(int);
	int (*flock) (struct file *, int, struct file_lock *);
	ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
	ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
	int (*setlease)(struct file *, long, struct file_lock **);
};

从结构中可以看到,结构中是一系列函数的指针,这里有熟悉的read()、write()、open()、close()等函数的指针。进程就是通过调用这些函数来访问一个文件的,所以有人说file_operations结构是Linux虚拟文件系统与进程之间的主要接口。

文件描述符

弄清楚了系统管理file对象的方法之后,下面就进一步介绍每个进程对它自己访问的file对象的管理方法。

Linux用一个数组来管理进程打开的文件的file对象,数组中的每一个元素都存放在一个指向进程所打开的文件的file对象。既然用一个数组来存放file对象,那么用数组的下标来访问文件就是一个顺理成章的方法,于是Linux就把数组元素的下标叫做该数组元素所对应的文件的文件描述符,该描述符就是系统对文件的标识,这个数组也叫做文件描述符数组,其示意图如下:

内核通过调用系统调用函数dup()、dup2()和fctl()可以使数组中的多个元素指向同一个文件的file对象。也就是说,在Linux中,同一个文件可以有多个文件描述符。

进程打开文件表

前面看到,文件描述符数组中存放了一个进程所访问的所有文件,把这个文件描述符数组和这个数组在系统中的一些动态信息组合在一起,就会形成一个新的数据结构——进程打开文件表。

进程打开文件表files_struct结构在文件include/linux/fdtable.h中的定义如下:

struct files_struct {
	atomic_t count;            //引用计数
	struct fdtable *fdt;
	struct fdtable fdtab;

	spinlock_t file_lock ____cacheline_aligned_in_smp;
	int next_fd;
	struct embedded_fd_set close_on_exec_init;
	struct embedded_fd_set open_fds_init;
	struct file * fd_array[NR_OPEN_DEFAULT];        //文件描述符表指针
};

显然,这个结构应该属于进程的数据,所以进程控制块用指针files指向它。

struct task_struct{
        ...
        struct files_struct *files;
        ...
}

进程与其打开文件之间的关系如下图所示:

因为进程对于的程序也是一个文件,这个文件的位置在哪呢?

进程在文件系统所在的位置叫做进程与文件系统的关系。Linux用结构fs_struct来描述进程在文件系统中的位置。结构fs_struct在文件include/linux.fs_struct.h中的定义如下:

struct fs_struct {
	int users;
	rwlock_t lock;
	int umask;
	int in_exec; 
	struct vfsmount *rootmnt, *pwdmnt, *altrootmnt;        //与安装有关的指针
	struct dentry *root, *pwd, *altroot;           //与进程位置有关的指针
};

其中,pwd指向进程的当前所在目录;root指向进程的根目录;altroot是进程的替换目录。如下所示:

另外三个指针rootmnt、pwdmnt和altrootmnt则对应地指向上面三个目录的结构vfsmount,该结构存放了目录的一些安装信息。

系统打开和关闭文件表

作为文件对象的提供者,操作系统必须对已经打开和使用过后已被关闭的文件进行记录。内核通过维护两个双向循环链表来管理这些被打开的文件和被关闭的文件:一个专门记录打开文件,另一个专门记录关闭文件。凡是file对象的f_count域不为NULL的文件,都被链入打开系统文件链表;而为NULL的文件,都被链入系统关闭文件链表。

当VFS需要一个file对象时,将调用函数get_empty_file()优先在系统关闭文件链表中摘取,如果链表中file对象的数目已经为限制的底线NR_RESERVED_FILES,则系统就另行分配file对象所需要的内存空间。

总结起来,就是:

猜你喜欢

转载自blog.csdn.net/qq_38410730/article/details/81415419