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是一样的,所以使用相同的 文件会话。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?