Linux文件系统
用户层面
在linux系统中,一切皆文件,除了通常所说的狭义的文件(文本文件和二进制文件)以外,目录、设备、套接字和管道等都是文件
->在存储设备上组织文件的方法,包括数据结构和访问方法
->按照某种文件系统类型格式化的一块存储介质。就是我们常说的在某个目录下载或卸载文件系统
->内核中负责管理和储存文件的模块,即文件系统模块
用户空间层面
- 一个存储设备上的文件系统,只有挂载到内存中目录树的某个目录下,进程才能访问这个系统,系统调用mount命令 mount -t fstype device dir,把文件系统挂载到某个目录下
- 系统调用umount用来卸载某个目录下挂载的文件系统,可以执行命令 mount dir,来卸载文件系统
- 使用open打开文件
- 使用close关闭文件
- 使用read读文件
- 使用lseek设置文件偏移
硬件层面
外部存储设备分为块设备、闪存和NVDIMM
- 机械硬盘
- 闪存类块设备
内核空间层面
内核支持多种文件系统类型,为了对用户程序提供统一的文件操作接口,为了使不同的文件系统实现能够共存,内核实现了一个抽象层,称为虚拟文件系统(Virtual File System,VFS),文件系统分为4种:
- 块设备文件系统,存储设备是机械硬盘和固态硬盘等块设备,通常块设备文件系统EXT和btrfs
- 闪存文件系统,存储设备是NAND闪存和NOR闪存,常用JFFS2、UBIFS
- 内存文件系统,文件在内存种,断电以后文件丢失,常用内存文件系统tmpfs
- 伪文件系统,是假的文件系统。如:sockfs、proc、sysfs、hugetlbfs、cgroup
虚拟文件系统的数据结构
- 超级块
- 挂载描述符
- 文件系统类型
- 索引节点
- 目录项
挂载文件系统
命令 mount -t fstype [-o options] device dir 执行流程如下
- 调用user_path,根据目录名称找到挂载描述符和dentry实例
- 调用get_fs_type,根据文件系统类型的名称查找file_system_type实例
- 调用alloc_vfsmnt,分配挂载描述符
- 调用文件系统类型的挂载方法,读取并且解析超级快
- 把挂载描述符添加到超级块的挂载实例链表中
- 把挂载描述符加入散列表
- 把挂载描述符加入父亲的孩子链表
挂载根文件系统
注册rootfs文件系统
init/do_mounts.c
struct file_system_type rootfs_fs_type = {
.name = "rootfs",
.init_fs_context = rootfs_init_fs_context,
.kill_sb = kill_litter_super,
};
static void __init init_mount_tree(void)
{
struct vfsmount *mnt;
struct mount *m;
struct mnt_namespace *ns;
struct path root;
/* 挂接rootfs文件系统 */
mnt = vfs_kern_mount(&rootfs_fs_type, 0, "rootfs", NULL);
if (IS_ERR(mnt))
panic("Can't create rootfs");
ns = alloc_mnt_ns(&init_user_ns, false); /* 创建第一个挂载命名空间 */
if (IS_ERR(ns))
panic("Can't allocate initial namespace");
m = real_mount(mnt);
m->mnt_ns = ns;
ns->root = m;
ns->mounts = 1;
list_add(&m->mnt_list, &ns->list);
init_task.nsproxy->mnt_ns = ns; /* 设置0号线程的挂载命名空间 */
get_mnt_ns(ns);
root.mnt = mnt;
root.dentry = mnt->mnt_root;
mnt->mnt_flags |= MNT_LOCKED;
set_fs_pwd(current->fs, &root); /* 把0号线程的当前工作目录设置为rootfs文件系统的根目录 */
set_fs_root(current->fs, &root); /* 把0号线程的根目录设置为rootfs文件系统的根目录 */
}
打开文件
SYSCALL_DEFINE3
->do_sys_open
->do_sys_openat2
static long do_sys_openat2(int dfd, const char __user *filename,
struct open_how *how)
{
struct open_flags op;
int fd = build_open_flags(how, &op); /* 把标志位分类为打开标志位、访问模式、意图和查找标志位,保存到结构体open_flags中 */
struct filename *tmp;
if (fd)
return fd;
tmp = getname(filename); /* 把文件路径从用户空间的缓冲区复制都内核空间的缓冲区 */
if (IS_ERR(tmp))
return PTR_ERR(tmp);
fd = get_unused_fd_flags(how->flags); /* 分配文件描述符 */
if (fd >= 0) {
struct file *f = do_filp_open(dfd, tmp, &op); /* 解析文件路径并得到文件的索引节点,创建文件的一个打开实例,把打开实例关联都索引节点 */
if (IS_ERR(f)) {
put_unused_fd(fd);
fd = PTR_ERR(f);
} else {
fsnotify_open(f); /* 通告打开文件事件,进程可以使用inotify监视文件系统的事件 */
fd_install(fd, f); /* 把打开文件的打开实例添加到进程的打开文件表中 */
}
}
putname(tmp);
return fd;
}
分配文件描述符
static int alloc_fd(unsigned start, unsigned end, unsigned flags)
{
struct files_struct *files = current->files;
unsigned int fd;
int error;
struct fdtable *fdt;
spin_lock(&files->file_lock);
repeat:
fdt = files_fdtable(files);
fd = start;
if (fd < files->next_fd) /* 开始尝试分配文件描述符 */
fd = files->next_fd;
if (fd < fdt->max_fds) /* 如果fd小于打开文件描述符表的大小,那么在打开文件描述符位图中查找一个空闲的文件描述符 */
fd = find_next_fd(fdt, fd);
/*
* N.B. For clone tasks sharing a files structure, this test
* will limit the total number of files that can be opened.
*/
error = -EMFILE; /* 如果进程打开的文件数量达到限制,那么返回-EMFILE */
if (fd >= end)
goto out;
error = expand_files(files, fd); /* 如果当前的打开的文件表已经分配完文件描述符,那么扩大打开文件表 */
if (error < 0)
goto out;
/*
* If we needed to expand the fs array we
* might have blocked - try again.
*//* 如果打开的文件表被扩大了,那么重新尝试分配文件描述符 */
if (error)
goto repeat;
if (start <= files->next_fd)
files->next_fd = fd + 1; /* 记录下次分配文件描述开始尝试的位置 */
__set_open_fd(fd, fdt); /* 在文件描述符位图中记录fd已被分配 */
if (flags & O_CLOEXEC)
__set_close_on_exec(fd, fdt);
else
__clear_close_on_exec(fd, fdt);
error = fd;
#if 1
/* Sanity check */
if (rcu_access_pointer(fdt->fd[fd]) != NULL) {
printk(KERN_WARNING "alloc_fd: slot %d not NULL!\n", fd);
rcu_assign_pointer(fdt->fd[fd], NULL);
}
#endif
out:
spin_unlock(&files->file_lock);
return error;
}
解析文件路径
struct file *do_filp_open(int dfd, struct filename *pathname, const struct open_flags *op)
{
struct nameidata nd;
int flags = op->lookup_flags;
struct file *filp;
set_nameidata(&nd, dfd, pathname, NULL);
filp = path_openat(&nd, op, flags | LOOKUP_RCU);
if (unlikely(filp == ERR_PTR(-ECHILD)))
filp = path_openat(&nd, op, flags);
if (unlikely(filp == ERR_PTR(-ESTALE)))
filp = path_openat(&nd, op, flags | LOOKUP_REVAL);
restore_nameidata();
return filp;
}
系统调用读/写文件
以ext4文件系统为例
ex4_file_read_iter
ext4_dio_write_iter