1.proc 文件系统简介
proc 文件系统一种伪文件系统,文件内容存储在内存(不占用磁盘空间,不能永久存储)。它用于向用户空间暴露内核数据结构、进程状态及系统配置信息,是系统监控、调试与性能调优的核心工具。
proc文件系统的内核定义如下:
1 | static struct file_system_type proc_fs_type = { |
内核初始化过程中,initramfs init 脚本会挂载 proc 文件系统。proc 文件系统中常见的文件有:
(1)系统信息
- /proc/cpuinfo:CPU 硬件信息。
- /proc/meminfo:内存总大小、空闲、缓存、Swap 等状态。
- /proc/loadavg:系统平均负载。
- /proc/uptime:系统启动总时间、空闲时间。
- /proc/cmdline:内核启动参数。
- /proc/filesystems:内核支持的文件系统列表。
- /proc/stat:系统整体 CPU 使用率、中断、进程切换统计。
- /proc/partitions:块设备与分区信息。
- /proc/mounts:当前已挂载文件系统列表。
(2)进程信息
- /proc/[PID]/cmdline:进程启动命令与参数。
- /proc/[PID]/status:进程状态。
- /proc/[PID]/maps:进程虚拟内存映射。
- /proc/[PID]/fd/:进程打开的文件描述符列表。
- /proc/[PID]/environ:进程环境变量。
- /proc/[PID]/limits:进程资源限制。
- /proc/[PID]/io:进程 I/O 统计。
- /proc/[PID]/sched:进程调度信息与优先级。
(3)网络信息
- /proc/net/dev:网卡流量、错误、丢包统计。
- /proc/net/tcp:TCP 连接表。
- /proc/net/udp:UDP 连接表。
- /proc/net/route:内核路由表。
- /proc/net/arp:ARP 表。
- /proc/net/netstat:网络栈统计。
- /proc/net/sockstat:套接字使用统计。
2.从内核看 proc 文件系统
理解 proc 文件系统的关键在于理解 proc 文件系统内部树,所谓内部树是指独立于 VFS(虚拟文件系统)的文件树,具体情况如图1所示。
图1 proc 文件系统内核实现原理
内核初始化时会调用 start_kernel 函数,该函数会调用 proc_root_init 函数来初始化 proc 内部文件树。proc 内部文件树的节点类型(文件或目录)为 struct proc_dir_entry 结构,其定义如下:
1 | struct proc_dir_entry { |
struct proc_dir_entry(简称 PDE)是 Linux 内核管理 /proc 文件系统所有节点(文件、目录、链接)的数据结构体,每个节点在内核中都对应一个PDE,PDE用于存储节点的名称、权限、所属目录、操作回调、私有数据等关键信息。
如果 PDE 是一个目录,那么 PDE 的 subdir 成员将会生效,subdir 是子节点(文件和目录)的红黑树根,子节点通过 PDE 的 subdir_node 成员插入父节点红黑树,这样就构成了树形结构。
proc 内部文件树的文件节点和 VFS(虚拟文件系统)中的文件是对应关系,proc 的文件由内核创建,内核通过 proc_mkdir 和 proc_create 函数来创建 proc 文件节点。当用户程序需要访问 proc 文件时,内核会基于 proc 文件节点动态生成 struct inode 结构,并将访问 proc 文件的方法(函数表)也赋值给inode。这样用户程序就能够通过 VFS 来访问 proc 文件了。
proc 内部文件树的根节点是 /proc 目录,它是一个特殊的PDE,其定义如下:
1 | struct proc_dir_entry proc_root = { |
proc_root 是 Linux 内核中 /proc 文件系统根节点,所有 /proc 下的文件/目录都是挂载到这个根节点下的子节点,它是整个 proc 内部文件树的 “根”。
3.seq_file 机制
seq_file 是用于简化 /proc 文件的创建和读取操作的机制。它解决了传统 proc 文件处理中的缓冲区管理难题,提供了一种迭代式、分块输出的方式来生成动态内容。
seq_file 的核心数据结构为:
1 | struct seq_file { |
简单来说,seq_file 就是一个内核缓冲区,proc 文件中的数据需要先格式化输出到 seq_file 内核缓冲区,再拷贝至用户缓冲区。格式化输出的过程需要使用特定的方法,这种方法由 struct seq_operations 结构定义,如下:
1 | struct seq_operations { |
seq_operations 的使用方式如图2所示。
图 2 seq_file机制
用户程序(shell)执行 cat /proc/xxx 命令查看 proc 文件时,首先会打开文件,打开文件的过程会创建 file 对象,然后将 file 对象的私有指针指向 proc 文件对应的 seq_file。接着,cat 命令会读取 proc 文件数据,读取的过程需要用到 seq_file 对象中的 op 函数表 (struct struct seq_operations),具体过程如下:
- 步骤1:调用 start 函数开始迭代,获取第一个数据项(整型数、字符串、结构体等),并调用 show 函数格式化输出该数据项至 seq_file 内核缓冲区。
- *步骤2:调用 next 函数获取下一个数据项,并调用 show 函数格式化输出该数据项至 seq_file 内核缓冲区。
- 步骤3:持续执行步骤2,直至所有数据项都格式化输出到 seq_file 内核缓冲区。
- 步骤4:调用 stop 函数停止迭代,清理资源。
- 步骤5::将 seq_file 收集到数据拷贝至用户缓冲区。
4. /proc/partitions 示例/
我们以读取 /proc/partitions 文件为例,来进一步了解 proc 文件的读取过程。/proc/partitions 是 proc 文件系统中专门用于暴露系统块设备(磁盘 / 分区)信息的核心虚拟文件,如图3所示。
图3 读取 /proc/partitions 文件
/proc/partitions 文件的数据项是 struct gendisk 结构,struct gendisk(通用磁盘结构体)是 Linux 内核块设备子系统的核心数据结构,用于抽象和管理系统中所有块设备,定义如下:
1 | struct gendisk |
一台主机通常会有多个块设备,调用 start 函数后,内核会查询块设备列表,获取到第一个块设备。 接着,内核调用 show 函数格式化输出块设备信息,该场景下的 show 函数具体实现如下:
1 | static int show_partition(struct seq_file *seqf, |
show 函数中循环(xa_for_each语言)格式化输出(调用 seq_printf 函数)每个分区的信息至 seq_file 内核缓冲区。一个块设备处理完毕后,调用 next 函数定位到下一个块设备,然后按照相同的方式处理下一个块设备,直至所有块设备的信息都格式化输出到 seq_file 内核缓冲区,最后将 seq_file 内核缓冲区的数据拷贝至用户缓冲区,用户程序就能够查看到内核的块设备分区信息了。
参考:
微信公众号:物联网心球