技术

并发的成本 基础设施优化 hashicorp raft源码学习 docker 架构 mosn细节 与微服务框架整合 Java动态代理 编程范式 并发通信模型 《网络是怎样连接的》笔记 go细节 codereview mat使用 jvm 线程实现 go打包机制 go interface及反射 如何学习Kubernetes 《编译原理之美》笔记——后端部分 《编译原理之美》笔记——前端部分 Pilot MCP协议分析 go gc 内存管理玩法汇总 软件机制 istio流量管理 Pilot源码分析 golang io 学习Spring mosn源码浅析 MOSN简介 《datacenter as a computer》笔记 学习JVM Tomcat源码分析 Linux可观测性 MVCC 学习存储 学计算 Gotty源码分析 kubernetes operator kaggle泰坦尼克问题实践 kubernetes自动扩容缩容 神经网络模型优化 直觉上理解机器学习 knative入门 如何学习机器学习 神经网络系列笔记 TIDB源码分析 《阿里巴巴云原生实践15讲》笔记 Alibaba Java诊断工具Arthas TIDB存储——TIKV 《Apache Kafka源码分析》——简介 netty中的线程池 guava cache 源码分析 Springboot 启动过程分析 Spring 创建Bean的年代变迁 Linux内存管理 自定义CNI IPAM 扩展Kubernetes 副本一致性 spring redis 源码分析 kafka实践 spring kafka 源码分析 Linux进程调度 让kafka支持优先级队列 Codis源码分析 Redis源码分析 C语言学习 《趣谈Linux操作系统》笔记 docker和k8s安全机制 jvm crash分析 Prometheus 学习 Kubernetes监控 Kubernetes 控制器模型 容器日志采集 容器狂占cpu怎么办? 容器狂打日志怎么办? Kubernetes资源调度——scheduler 时序性数据库介绍及对比 influxdb入门 maven的基本概念 《Apache Kafka源码分析》——server Kubernetes objects之编排对象 源码分析体会 自动化mock AIOps说的啥 《数据结构与算法之美》——算法新解 Kubernetes源码分析——controller mananger Kubernetes源码分析——apiserver Kubernetes源码分析——kubelet Kubernetes介绍 ansible学习 Kubernetes源码分析——从kubectl开始 jib源码分析之Step实现 kubernetes实践 jib源码分析之细节 线程排队 跨主机容器通信 jib源码分析及应用 为容器选择一个合适的entrypoint kubernetes yaml配置 marathon-client 源码分析 《持续交付36讲》笔记 mybatis学习 程序猿应该知道的 无锁数据结构和算法 CNI 为什么很多业务程序猿觉得数据结构和算法没用? 串一串一致性协议 当我在说PaaS时,我在说什么 《数据结构与算法之美》——数据结构笔记 swagger PouchContainer技术分享体会 harbor学习 用groovy 来动态化你的代码 《深入剖析kubernetes》笔记 精简代码的利器——lombok 学习 编程语言的动态性 rxjava3——背压 rxjava2——线程切换 spring cloud 初识 《深入拆解java 虚拟机》笔记 《how tomcat works》笔记 hystrix 学习 rxjava1——概念 Redis 学习 TIDB 学习 分布式计算系统的那些套路 Storm 学习 AQS1——论文学习 Unsafe Spark Stream 学习 linux vfs轮廓 mysql 批量操作优化 《自己动手写docker》笔记 java8 实践 中本聪比特币白皮书 细读 区块链泛谈 比特币 大杂烩 总纲——如何学习分布式系统 hbase 泛谈 forkjoin 泛谈 看不见摸不着的cdn是啥 《jdk8 in action》笔记 程序猿视角看网络 bgp初识 mesos 的一些tips mesos 集成 calico calico学习 AQS2——粗略的代码分析 我们能用反射做什么 web 跨域问题 《clean code》笔记 硬件对软件设计的影响 《Elasticsearch权威指南》笔记 mockito简介及源码分析 2017软件开发小结—— 从做功能到做系统 《Apache Kafka源码分析》——clients dns隐藏的一个坑 《mysql技术内幕》笔记2 《mysql技术内幕》笔记1 log4j学习 为什么netty比较难懂? 回溯法 apollo client源码分析及看待面向对象设计 学习并发 从一个marathon的问题开始的 docker 环境(主要运行java项目)常见问题 Scala的一些梗 OpenTSDB 入门 spring事务小结 事务一致性 javascript应用在哪里 《netty in action》读书笔记 netty对http2协议的解析 ssl证书是什么东西 http那些事 苹果APNs推送框架pushy apple 推送那些事儿 编写java框架的几大利器 java内存模型 java exception Linux IO学习 network channel network byte buffer 测试环境docker化实践 netty(七)netty在框架中的使用套路 Nginx简单使用 《Linux内核设计的艺术》小结 Go并发机制及语言层工具 mesos深入 Macvlan Linux网络源代码学习——数据包的发送与接收 《docker源码分析》小结 docker中涉及到的一些linux知识 hystrix学习 Linux网络源代码学习——整体介绍 zookeeper三重奏 数据库的一些知识 Spark 泛谈 链式处理的那些套路 netty(六)netty回顾 Thrift基本原理与实践(二) Thrift基本原理与实践(一) 回调 异步执行抽象——Executor与Future Docker0.1.0源码分析 java gc Jedis源码分析 Redis概述 机器学习泛谈 Linux网络命令操作 JTA与TCC 换个角度看待设计模式 Scala初识 向Hadoop学习NIO的使用 以新的角度看数据结构 并发控制相关的硬件与内核支持 systemd 简介 那些有用的sql语句 异构数据库表在线同步 quartz 源码分析 基于docker搭建测试环境(二) spring aop 实现原理简述 自己动手写spring(八) 支持AOP 自己动手写spring(七) 类结构设计调整 分析log日志 自己动手写spring(六) 支持FactoryBean 自己动手写spring(九) 总结 自己动手写spring(五) bean的生命周期管理 自己动手写spring(四) 整合xml与注解方式 自己动手写spring(三) 支持注解方式 自己动手写spring(二) 创建一个bean工厂 自己动手写spring(一) 使用digester varnish 简单使用 关于docker image的那点事儿 基于docker搭建测试环境 分布式配置系统 JVM内存与执行 git spring rmi和thrift maven/ant/gradle使用 再看tcp mesos简介 缓存系统 java nio的多线程扩展 《Concurrency Models》笔记 回头看Spring IOC IntelliJ IDEA使用 Java泛型 vagrant 使用 Go常用的一些库 Python初学 Goroutine 调度模型 虚拟网络 《程序员的自我修养》小结 VPN(Virtual Private Network) Kubernetes存储 Kubernetes 其它特性 访问Kubernetes上的Service Kubernetes副本管理 Kubernetes pod 组件 使用etcd + confd + nginx做动态负载均衡 如何通过fleet unit files 来构建灵活的服务 CoreOS 安装 CoreOS 使用 Go学习 JVM类加载 硬币和扑克牌问题 LRU实现 virtualbox 使用 ThreadLocal小结 docker快速入门

标签


linux vfs轮廓

2018年05月19日

简介

vfs 数据结构 / 两个关系

从文件 I/O 看 Linux 的虚拟文件系统

进程与超级块、文件、索引结点、目录项的关系

linux系统的进程结构体有以下几个字段

struct task_struct {
    ...
    struct m_inode * pwd;
    struct m_inode * root;
    struct m_inode * executable;				//进程对应可执行文件的i节点
    ...
    /* Filesystem information: */
    struct fs_struct                *fs;
    /* Open file information: */
    struct files_struct             *files;
    ...
}

struct files_struct {//打开的文件集
        atomic_t count;              /*结构的使用计数*/
        ……
        int max_fds;                 /*文件对象数的上限*/
        int max_fdset;               /*文件描述符的上限*/
        int next_fd;                 /*下一个文件描述符*/
        struct file ** fd;           /*全部文件对象数组*/
        ……
};

struct fs_struct {//建立进程与文件系统的关系
        atomic_t count;              /*结构的使用计数*/
        rwlock_t lock;               /*保护该结构体的锁*/
        int umask;                  /*默认的文件访问权限*/
        struct dentry * root;        /*根目录的目录项对象*/
        struct dentry * pwd;         /*当前工作目录的目录项对象*/
        struct dentry * altroot;    /*可供选择的根目录的目录项对象*/
        struct vfsmount * rootmnt;   /*根目录的安装点对象*/
        struct vfsmount * pwdmnt;    /*pwd的安装点对象*/
        struct vfsmount * altrootmnt;/*可供选择的根目录的安装点对象*/
};

超级块、安装点和具体的文件系统的关系

linux 和 windows 一个很大区别就是,window 有c盘、d盘等,而linux 则都是从根目录开始,好像只有一个磁盘一样。为了实现这个效果,linux 就要引入vfsmount 等概念。

  1. 被Linux支持的文件系统,都有且仅有一个file_system_type结构
  2. 每安装一个文件系统,就对应有一个超级块和安装点

文件访问

查找时,在遍历路径的过程中,会逐层地将各个路径组成部分解析成目录项对象,如果此目录项对象在目录项缓存中,则直接从缓存中获得;如果该目录项在缓存中不存在,则进行一次实际的读盘操作,从磁盘中读取该目录项所对应的索引节点。得到索引节点后,则建立索引节点与该目录项的联系。如此循环,直到最终找到目标文件对应的目录项,也就找到了索引节点,这样就建立了文件对象与实际的物理文件的关联。

文件对象所对应的文件操作函数 列表是通过索引结点的域i_fop得到的

mount 过程

linux文件系统之mount流程分析

一个磁盘如何被使用?

  1. insmod xx.ko 加载块设备驱动
  2. mknod /dev/xx type major minor 创建设备文件,实质将文件操作与设备驱动程序关联,对于字符设备,操作/dev/xx便是读写字符设备了,对于块设备,会复杂一点。
  3. 例如,mount -t ext3 /dev/sdb /mnt/alan/dev/sdb块设备被mount到/mnt/alan目录。

那么mount 如何实现这个神奇的效果呢?mount系统调用 入口

SYSCALL_DEFINE5(mount, char __user *, dev_name, char __user *, dir_name, char __user *, type, unsigned long, flags, void __user *, data){
    ......
    ret = do_mount(kernel_dev, dir_name, kernel_type, flags, options);
    ......
}

接下里的调用链:do_mount->do_new_mount

static int do_new_mount(struct path *path, const char *fstype, int flags,
            int mnt_flags, const char *name, void *data)
{
    ...
    mnt = vfs_kern_mount(type, flags, name, data);
    ...
    err = do_add_mount(real_mount(mnt), path, mnt_flags);
    ...
}

do_new_mount()函数主要分成两大部分:

  1. 建立vfsmount对象和superblock对象,必要时从设备上获取文件系统元数据;
  2. 将vfsmount对象加入到mount树和Hash Table中,并且将原来的dentry对象无效掉。

/dev/sdb被mount之后,用户想要访问该设备上的一个文件ab.c,假设该文件的地址为:/mnt/alan/ab.c

  1. 在打开该文件的时候,首先需要进行path解析。
  2. 在解析到/mnt/alan的时候,得到/mnt/alan的dentry目录项,并且发现该目录项已经被标识为DCACHE_MOUNTED。
  3. 之后,会采用/mnt/alan计算HASH值去检索VFSMOUNT Hash Table,得到对应的vfsmount对象。
  4. 然后采用vfsmount指向的mnt_root目录项替代/mnt/alan原来的dentry,从而实现了dentry和inode的重定向。
  5. 在新的dentry的基础上,解析程序继续执行,最终得到表示ab.c文件的inode对象。

总结一下就是:Mount Point DefinitionThe mount point becomes the root directory of the newly added filesystem, and that filesystem becomes accessible from that directory.

vfs_kern_mount

struct vfsmount *
vfs_kern_mount(struct file_system_type *type, int flags, const char *name, void *data){
    ......
    mnt = alloc_vfsmnt(name);
    ......
    // 从文件系统中读取超级块
    root = mount_fs(type, flags, name, data);
    ......
    mnt->mnt.mnt_root = root;
    mnt->mnt.mnt_sb = root->d_sb;
    mnt->mnt_mountpoint = mnt->mnt.mnt_root;
    mnt->mnt_parent = mnt;
    list_add_tail(&mnt->mnt_instance, &root->d_sb->s_mounts);
    return &mnt->mnt;
}

struct dentry * mount_fs(struct file_system_type *type, int flags, const char *name, void *data)
{
    struct dentry *root;
    struct super_block *sb;
    char *secdata = NULL;
    int error = -ENOMEM;
    ...
    root = type->mount(type, flags, name, data);
    ...
    sb = root->d_sb;
    ...	
}
  1. alloc_vfsmnt,vfs_kern_mount 先是创建 struct mount 结构,内部包含一个vfsmount 结构
  2. mount_fs,mount_fs()函数中会调用特定文件系统的mount方法,对于 /dev/sdb设备上的ext3文件系统,ext3_mount–> mount_bdev,Mount_bdev()函数主要完成superblock对象的内存初始化,并且加入到全局superblock链表中。
  3. Vfsmount中的mnt_root指向superblock对象的s_root根目录项。

相关的数据结构

struct mount {
    struct hlist_node mnt_hash;
    struct mount *mnt_parent;   // 装载点所在的父文件系统
    struct dentry *mnt_mountpoint; // 装载点在父文件系统中的dentry
    struct vfsmount mnt;
    union {
        struct rcu_head mnt_rcu;
        struct llist_node mnt_llist;
    };
    struct list_head mnt_mounts;	/* list of children, anchored here */
    struct list_head mnt_child;	/* and going through their mnt_child */
    struct list_head mnt_instance;	/* mount instance on sb->s_mounts */
    const char *mnt_devname;	/* Name of device e.g. /dev/dsk/hda1 */
    struct list_head mnt_list;
    ......
} __randomize_layout;


struct vfsmount {
    struct dentry *mnt_root; // 当前文件系统根目录的dentry
    struct super_block *mnt_sb;	// 指向超级块的指针
    int mnt_flags;
} __randomize_layout;

do_add_mount

do_add_mount–> graft_tree–> attach_recursive_mnt

将创建的vfsmount对象加入到mount树和VFSMOUNT Hash Table中,并且将老的dentry目录项标识成DCACHE_MOUNTED,一旦dentry被标识成DCACHE_MOUNTED,也就意味着在访问路径上对其进行了屏蔽。

挂载方式

如果将mount的过程理解为:inode被替代的过程。除了将设备mount到rootfs上,根据被替代方式的不同,mount的花样可多了。

bind mount

mount –bind olddir newdir

ln分为软链接和硬链接

硬链接只能连文件 你删除它源文件就被删了

软链接相当于windows的快捷方式,文件和目录都可以连,但是你删出它只是删除的这个快捷方式

在操作系统中ln -s和mount -bind也许使用起来没多少差别,但是ftp目录里貌似不能放软连接。把一个文件夹mount到另一个地方,等于在把它挂载到那个地方当成一个磁盘,进到两个文件夹中操作 都是等价的,删除一个文件也会在两边同时删除。

union mount

参见Union mount,In computer operating systems, union mounting is a way of combining multiple directories into one that appears to contain their combined contents.

union mount的使用场景——读写只读文件系统,As an example application of union mounting, consider the need to update the information contained on a CD-ROM or DVD. While a CD-ROM is not writable, one can overlay the CD’s mount point with a writable directory in a union mount. Then, updating files in the union directory will cause them to end up in the writable directory, giving the illusion that the CD-ROM’s contents have been updated.

Union FileSystem的核心逻辑是Union Mount,它支持把一个目录A和另一个目录B union,提供一个虚拟的C目录(目录union的语义)。对于特定的权限设置策略,如果设置A目录可写,B目录只读。用户对目录C的读取就是A加上B的内容,而对C目录里文件写入和改写则会保存在目录A上。这样,就有了A在B上层的感觉。 DOCKER基础技术:AUFS

Docker存储驱动简介

rootfs

rootfs是基于内存的文件系统,所有操作都在内存中完成;也没有实际的存储设备,所以不需要设备驱动程序的参与。基于以上原因,Linux在启动阶段使用rootfs文件系统。

参见https://www.kernel.org/doc/Documentation/filesystems/ramfs-rootfs-initramfs.txt

What is initramfs?

All 2.6 Linux kernels contain a gzipped “cpio” format archive, which is extracted into rootfs when the kernel boots up. After extracting, the kernel checks to see if rootfs contains a file “init”, and if so it executes it as PID

  1. If found, this init process is responsible for bringing the system the rest of the way up, including locating and mounting the real root device (if any). If rootfs does not contain an init program after the embedded cpio archive is extracted into it, the kernel will fall through to the older code to locate and mount a root partition, then exec some variant of /sbin/init out of that.

所以一个linux的启动过程经历了rootfs ==> 挂载initramfs ==> 挂载磁盘上的真正的fs

存储之道 - 51CTO技术博客 中的《一个IO的传奇一生》