前言
在 linux 中常见的文件系统 有很多, 如下
基于磁盘的文件系统, ext2, ext3, ext4, xfs, btrfs, jfs, ntfs
内存文件系统, procfs, sysfs, tmpfs, squashfs, debugfs
闪存文件系统, ubifs, jffs2, yaffs
文件系统这一套体系在 linux 有一层 vfs 抽象, 用户程序不用关心 底层文件系统的具体实现, 用户只用操作 open/read/write/ioctl/close 的相关 系统调用, 这一层系统调用 会操作 vfs 来处理响应的业务
vfs 会有上面各种文件系统对应的 读写 相关服务, 进而 将操作下沉到 具体的文件系统
我们这里 来看一下 proc 文件系统, 这是一个 基于 内核内存的文件系统, 读写的都是 内核的相关信息项
如何分配 inode ?
inode 的创建是 懒加载 的, 也就是 第一次访问的时候 vfs 在 i_ops 中预留了 lookup, 基于这个函数 来实现的懒加载, 如果没有找到, 则新建给定的文件对应的 inode, 以及初始化 i_ops, f_ops 的相关操作
这里传入的 ptr, 是当前访问的 进程文件 的一个配置信息, 如 stat, cmdline 对应于不同的配置, 对应于 pid_entry
各个 pid_entry 的配置如下, 比如 cmdline, 定义了文件标志, 用户/组/其他用户的读写执行权限, 具体当前文件的 读写 相关 api 的具体实现
比如说 /proc/$pid/cmdline 文件的相关 api 如下
我们来看一下 inode 的相关处理, inode 的分配是来自于 proc 对应的 fs 的 super_block
这里初始化了 读写权限, inode, m/i/c/time, i_op, f_op, pid, uid, gid 等等相关属性
其中 f_op 在上面 cmdline 的例子中就对应于 proc_pid_cmdline_ops, 然后实际操作的时候 vfs 会将操作委托给 这个函数
新增了 inode 之后, d_add 会将 inode 注册到 dcache.dentry_hashtable 中
/proc/$pid 文件夹中的各个文件什么时候创建的?
是在 你访问的时候创建的, 比如你 直接读取 "/proc/$pid/cmdline" 或者 "ls /proc/$pid" 的时候创建的
如下为 ls "/proc/321" 的时候, 会遍历 ents 来创建对应的文件, 这的 ents 对应于上面的 tgid_base_stuff 的列表, 对应于 进程需要创建的 pid 相关的所有文件的信息
如何分配 存储的空间?
大多数的 proc 中的文件是不单独占用存储空间
是通过对应的 读写函数 直接读取业务数据结构的相关信息 直接返回
如何 读写数据?
根据文件路径 查询到文件对应的 inode, 封装 file, file->f_op 为 inode->f_op
对应于 /proc/$pid/cmdline 读取函数为 proc_pid_cmdline_ops, 如下图 即为完整的调用栈
申请一个临时物理页, 然后读取给定的进程的 mm 的 arg_start + offset 的数据, 读取到 argv_end
读取出来的内容其实就是 cmdline 的内容, 即所有的参数
其中 argv[0] 为当前执行程序, argv[1], argv[2], ..., argv[n] 为用户传入的程序参数
这里读取到了 21 个字符 "./Test05SocketServer"
然后将数据从 物理页 拷贝到 用户空间的 buffer, 得到 "./Test05SocketServer"
用户程序这边 cat 输出结果为
再比如 /proc/$pid/oom_score, 的读取
使用的另外一种方式来读取文件的数据, 输出数据到 seq_file, 这个是 封装 file 的时候, linux 构造的一个容器
输出如下
再比如 /proc/$pid/status, 的读取
输出如下
根据 path 获取到 inode 的过程
上面提到了 新建的 proc 的 inode 放在了 dcache.dentry_hashtable
那么 linux 又是怎么讲 path 和 dcache.dentry_hashtable 关联起来的呢?
在常见的 ext4 文件系统中, 文件夹的 inode 信息是放在了 inode 的文件内容中的, 那么 proc 文件系统是怎么做的呢 ?
path_init 的时候初始化 nd 中为 根文件系统的 inode, dentry, 然后 link_path_walk 一个文件夹一个文件夹的向后遍历, 调整 nd, do_last 处理目标文件/文件夹
所以这里 "/proc/310/cmdline" 会 调用一次 path_init, 两次 link_path_walk, 第一次 do_last
我们这里 着重关注 link_path_walk
第一次 link_path_walk 的处理, __d_lookup_rcu 的时候, 在 rootfs "/" 下面的 rootfs "/proc"
注意这里的 __d_lookup_rcu 查找的是 dcache.dentry_hashtable
follow_mount_rcu 之后, 查询 "/proc" 是否有挂载点, 如果有 更新到对应的挂载点上面去
nd 中的 dentry 信息, 由 rootfs "/proc" 更新为了 proc "/", 这里 mount 信息是记录在 namespace.mount_hashtable 中, 在 mount 的时候会记录相关信息
第二次 link_path_walk 的处理, 是在 proc "/" 下面查找 "310", 这里找到了这个 proc "310" 的 dentry
注意这里的 __d_lookup_rcu 查找的是 dcache.dentry_hashtable, 因为在创建 inode 节点的时候, 向 dcache.dentry_hashtable 中注册了 dentry 的信息, 因此这里能够查到
proc "/310" 上面没有其他的挂载点, 因此获取到的数据 还是原有的 proc "310" 对应的 dentry, inode
do_last 中 lookup_fast 从 dcache.dentry_hashtable 中在 proc "310" 下面查找 "cmdline"
能够找到 proc "/310/cmdline"
do_dentry_open 中根据上下文的相关信息, 填充 file 对象
然后最外层 sys_open 中建立 fd -> file 的映射
这里放进去的就是 read/write 中根据 fd 拿到的 file
file.f_ops 为对应的 inode.f_ops
完