Linux——应用到内核——1.文件

1. 什么是文件

  • Linux管理的一切对象都可以视为文件,所以是否是文件和该对象的固有特性无关,由Linux决定,当Linux将其视为文件管理,则称其为文件。
  • Linux对被视为文件的对象,利用VFS管理,对外提供统一访问接口,用户通过文件描述符访问对象。

2. 内核对文件的管理

2.1 文件会话管理

task_struct->files,中,定义如下

struct files_struct {
	// 当前 struct files_struct 的引用计数
	atomic_t count;

	// 文件描述符表
	// 为什么会有两个struct fdtable呢?
	// 这是linux的优化技巧,开始使用 fdtab,fdtab为普通变量,容量为默认大小,
	// 当需求大于默认大小时,使用fdt指针动态分配。
	struct fdtable __rcu *fdt;
	struct fdtable fdtab;

	// 使用 ____cacheline_aligned_in_smp 保证 file_lock 是以 cache line 对齐的,避免 false sharing
	spinlock_t file_lock ____cacheline_aligned_in_smp;	

	// 用于查找下一个空闲的fd
	int next_fd;

	// 保存执行exec时,需要关闭的文件描述符位图
	struct embedded_fd_set close_on_exec_init;

	// 文件会话数组
	struct file __rcu *fd_array[NR_OPEN_DEFAULT];
};

struct files_struct 有两个和 文件描述符相关的结构, fdtab, fd_array,他们之间的关系是什么呢?

init进程是第一个进程,他的 struct files_struct 是静态初始化的,如下

struct files_struct init_files {
	.count = ATOMIC_INIT(1),
	.fdt = &init_files.fdtab,
	.fdtab = {
		.max_fds = NR_OPEN_DEFAULT,
		.fd = &init_files.fd_array[0],
		.close_on_exec = (fd_set *)&init_files.close_on_exec_init,
		.open_fds = (fd_set *)&init_files.open_fds_init,
	},
	.file_lock = __SPIN_LOCK_UNLOCKED(init_task.file_lock),
}

明显 fdtab 是 文件描述符表 的元信息,而 fd_array 是文件描述符表的表中数据和空闲资源。

当进程fork后,子进程初始化时,会设置fdtab(fork后,子进程的fdtab描述父进程的fd_array),让其描述自己的 fd_array。

newf = kmem_cache_alloc(files_cachep, GFP_KERNEL);
if (!newf)
	goto out;
atomic_set(&newf->count, 1);
spin_lock_init(&newf->file_lock);
newf->next_fd = 0;
new_fdt = &newf->fdtab;
new_fdt->max_fds = NR_OPEN_DEFAULT;
new_fdt->close_on_exec = (fd_set *)&newf->close_on_exec_init;
new_fdt->open_fds = (fd_set *)&newf->open_fds_init;
new_fdt->fd = &newf->fd_array[0];
new_fdt->next = NULL;

这里也可以看出fork后每个进程有自己的 files_struct。

2.2 open

open->do_sys_open

long do_sys_open(int dfd, const char __user *filename, int flags, int mode)
{
	struct open_flags op;

	// 检测flags
	int lookup = build_open_flags(flags, mode, &op);
	// 将用户空间的filename,复制到内核空间
	char *tmp = getname(filename);
	int fd = PTR_ERR(tmp);
	if (!IS_ERR(tmp)) {
		// 分配文件描述符
		fd = get_unused_fd_flags(flags);
		if (fd > 0) {
			// 分配 struct file
			struct file *f = do_filp_open(dfd, tmp, &op, lookup);
			if (IS_ERR(f)) {
				put_unused_fd(fd);
				fd = PTR_ERR(f);
			}
			else {
				fsnotify_open(f);
				// 将struct file,和 文件描述符 关联
				fd_install(fd, f);
			}
		}
		putname(tmp);
	}
	return fd;
}

可见open操作,是 分配文件描述符 和 文件会话

2.3 close

close 是 释放fd 和 struct file

2.3 自定义 file_operations

static const struct file_operations socket_file_ops = {
	.owner = THIS_MODULE,
	.open = sock_no_open,
	.release = sock_close,
	...
};

如上 为 socket 的 ops
alloc_file用于申请file.
socket_alloc_file -> alloc_file

file = alloc_file(&path, FMODE_READ | FMODE_WRITE, &socket_file_ops);

alloc_file

struct file *alloc_file(struct path *path, fmode_t mode, const struct file_operations *fop)
{
	struct file *file;
	file = get_empty_filp();
	if (!file)
		return NULL;
	file->f_path = *path;
	file->f_mapping = path->dentry->d_inode->i_mapping;
	file->f_mode = mode;
	file->f_op = fop;
	...
}

上面示例说明,Linux可以将非文件对象当文件构造(就是构造一个 struct file),
如此用户可以使用统一接口访问该资源,但接口实现可能不全。
且Linux使用文件方式统一管理资源。

2.4 dup

SYSCALL_DEFINE1(dup, unsigend int, fildes)
{
	int ret = -EBADF;
	struct file *file = fget_raw(fildes);
	if (file) {
		ret = get_unused_fd();
		if (ret >= 0 ) {
			fd_install(ret, file);
		}
		else
			fput(file);
	}
	return ret;
}

可见 复制文件描述符 就是新分配个fd,指向同一个 struct file。

2.5 文件元信息

vfs_stat -> vfs_fstatat -> vfs_getattr

int vfs_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *stat)
{
	struct inode *inode = dentry->d_inode;
	int retval;
	// 对获取inode 进行安全性检测
	retval = security_inode_getattr(mnt, dentry);
	if (retval)
		return retval;
	// 如果该inode 定义了自定义获得元数据的操作,则使用自定义方法
	if (inode->i_op->getattr)
		return inode->i_op->getattr(mnt, dentry, stat);
	// 通用方法获得元数据
	generic_fillattr(inode, stat);
	return 0;
}

可见inode维护文件元信息。

2.6 write

write容易让人疑惑的是多线程写,加上 追加标志 即可,此时偏移量在写时计算。

2.7 fork 和 文件

fork时会复制 struct file_struct

static int copy_files(unsigned long clone_flags, struct task_struct *tsk)
{
	struct file_struct *oldf, *newf;
	int error = 0;
	oldf = current->files;
	if (!oldf)
		goto out;
	// 创建线程和vfork都不需要复制文件描述符表,增加引用计数即可
	if (clone_flags & CLONE_FILES) {
		atomic_inc(&oldf->count);
		goto out;
	}
	// fork 时,复制父进程的文件描述符表
	newf = dup_fd(oldf, &error);
	if (!newf)
		goto out;
	tsk->files = newf;
	error = 0;
out:
	return error;
}

dup_fd 为子进程 复制一个 struct file_struct,并初始化他。
如此 父子进程的 struct file_struct->fd_array是一样的,所以使用相同的 文件会话。

posted on 2022-07-28 17:15  开心种树  阅读(105)  评论(0编辑  收藏  举报