Linux procFS 和 SysFS

文件系统与调试基础

File System in Linux

  • 提供非易失性数据的存储空间。

  • 一种特定的数据存储格式,包含两部分:数据本身(Data)和元数据(Meta-data)。

  • 每种文件系统类型使用自身的元数据结构来定义数据的存储和访问方式。

  • 支持所有文件操作。

  • 一些最常用的文件系统包括:EXT4、EXT3、FAT-32、NTFS。

      graph LR
      subgraph UserSpace[用户空间]
          App[Application]
      end
    
      subgraph KernelSpace[内核空间]
          SysCall[System Call]
          VFS[Virtual File System]
          EXT3[EXT3]
          EXT4[EXT4]
          FAT32[FAT-32]
          NTFS[NTFS]
      end
    
      subgraph HardwareLayer[硬件层]
          Hardware[Hardware]
      end
    
      App --> SysCall
      SysCall --> VFS
      VFS --> EXT3
      VFS --> EXT4
      VFS --> FAT32
      VFS --> NTFS
      EXT3 --> Hardware
      EXT4 --> Hardware
      FAT32 --> Hardware
      NTFS --> Hardware
    
      style UserSpace fill:#e1f5fe,stroke:#01579b
      style KernelSpace fill:#fff3e0,stroke:#e65100
      style HardwareLayer fill:#e8f5e9,stroke:#1b5e20
    

Disadvantage of printk as debugging tool

  • 修改源代码可能并非易事,也无法快速完成。
  • 不必要的 printk 可能会影响整体性能。
  • 你需要维护两个版本的软件;一个包含调试信息,另一个不包含。
  • 有时,printk 会掩盖实际的问题。
  • 在大多数情况下,获取信息的最佳方式是查询系统。

procFS 概述

What is proc

  • 通过查询从内核及内核模块获取信息的技术之一。
  • /proc 文件系统是一个特殊的、由软件创建的文件系统。
  • 提供了一个用于在用户空间和内核空间之间进行通信的接口
  • 它包含关于当前正在运行的进程的有用信息,例如
    • /proc/modules – 提供模块列表
    • /proc/meminfo – 统计内存使用情况

procFS vs other File Systems

  • proc 是虚拟文件系统或伪文件系统。
  • 数据不存储在非易失性存储器中。
  • 没有实际的文件
  • proc 没有像标准文件系统那样的数据存储格式
  • 文件系统独立于操作系统。然而,/proc 并非在所有操作系统中都可用。

Importance of procFS

  • Maintains information about the currently running system

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    charles@ubuntu24:~$ cat /proc/meminfo
    MemTotal:        1995676 kB
    MemFree:          698620 kB
    MemAvailable:    1642032 kB
    Buffers:           59008 kB
    Cached:           993020 kB
    SwapCached:            0 kB
    Active:           139892 kB
    Inactive:         939312 kB
    
  • ​ Useful tool to monitor the system

    1
    2
    3
    4
    5
    
    charles@ubuntu24:~$  cat /proc/net/route
    Iface   Destination     Gateway         Flags   RefCnt  Use     Metric  Mask            MTU     Window  IRTT                                              
    enp0s3  00000000        0202000A        0003    0       0       100     00000000        0       0       0  enp0s3  0002000A        00000000        0001    0       0       100     00FFFFFF        0       0       0 
    charles@ubuntu24:~$  cat /proc/net/nf_conntrack
    tcp 6 431999 ESTABLISHED src=192.168.1.10 dst=93.184.216.34 sport=54321 dport=80 src=93.184.216.34 dst=192.168.1.10 sport=80 dport=54321 [ASSURED] mark=0 use=1
    
  • It is used to configure and debug the system. /proc/sysdoes that

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    charles@ubuntu24:~$ cat /proc/sys/kernel/printk
    4       4       1       7
    charles@ubuntu24:~$ echo 7 > /proc/sys/kernel/printk
    charles@ubuntu24:~$ cat /proc/sys/kernel/printk
    7       4       1       7
    charles@ubuntu24:~$ cat /proc/sys/net/ipv4/ip_forward
    0
    charles@ubuntu24:~$ echo 1 > /proc/sys/net/ipv4/ip_forward
    charles@ubuntu24:~$ cat /proc/sys/net/ipv4/ip_forward
    1
    charles@ubuntu24:~$ ls -l /proc/80094/fd
    total 0
    lrwx------ 1 root root 64 Jul  9 14:32 0 -> /dev/null
    lrwx------ 1 root root 64 Jul  9 14:32 1 -> /dev/null
    lr-x------ 1 root root 64 Jul  9 14:32 10 -> 'pipe:[85474]'
    lr-x------ 1 root root 64 Jul  9 14:32 12 -> 'pipe:[85475]'
    lrwx------ 1 root root 64 Jul  9 14:32 2 -> /dev/null
    lrwx------ 1 root root 64 Jul  9 14:32 3 -> 'socket:[84325]'
    lrwx------ 1 root root 64 Jul  9 14:32 4 -> 'socket:[84293]'
    lrwx------ 1 root root 64 Jul  9 14:32 5 -> 'socket:[85467]'
    lrwx------ 1 root root 64 Jul  9 14:32 6 -> 'socket:[85442]'
    l-wx------ 1 root root 64 Jul  9 14:32 8 -> 'pipe:[85473]'
    l-wx------ 1 root root 64 Jul  9 14:32 9 -> /run/systemd/sessions/8.ref
    

Creating a proc file and interfacing with user space

  • Creating entry in /proc
    Entry can be created using proc_create()
    ent = proc_create("dummydev", 0660, NULL, &myops);

  • Removing entry from /proc
    Entry can be removed using proc_remove() proc_remove(ent);

  • Handlers of proc file
    Two handlers; read and write.

    1
    2
    3
    4
    5
    6
    
    static struct file_operations myops =
    {
          .owner = THIS_MODULE,
        .read = dummy_read,
        .write = dummy_write,
    }
    
  • Communicating with driver using proc
    can use shell or program

1
2
3
4
cat /proc/dummydev

irq = 20
mode = 1    
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
void main(void)
{
    char buf[100];
    int fd = open("/proc/dummydev", O_RDWR);
    read(fd, buf, 100);
    puts(buf);

    lseek(fd, 0 , SEEK_SET);
    write(fd, "33 4", 5);

    lseek(fd, 0 , SEEK_SET);
    read(fd, buf, 100);
    puts(buf);
}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <linux/uaccess.h>
#include <linux/seq_file.h>

#define PROC_DIR_NAME "proc_demo"
#define PROC_FILE_NAME "status"
#define BUF_SIZE 1024

static char kernel_buffer[BUF_SIZE];

// 回调函数:读取文件内容
static int proc_show(struct seq_file *m, void *v)
{
    seq_printf(m, "Message from kernel: %s\n", kernel_buffer);
    return 0;
}

// 回调函数:打开文件
static int proc_open(struct inode *inode, struct file *file)
{
    return single_open(file, proc_show, NULL);
}

// 回调函数:向文件写入数据
static ssize_t proc_write(struct file *file, const char __user *buf, 
                          size_t count, loff_t *ppos)
{
    size_t len = (count >= BUF_SIZE) ? BUF_SIZE - 1 : count;

    if (copy_from_user(kernel_buffer, buf, len))
        return -EFAULT;

    kernel_buffer[len] = '\0';
    printk(KERN_INFO "proc_demo: Received from user space: %s\n", kernel_buffer);

    return len;
}

// 文件操作结构体
static const struct proc_ops proc_fops = {
    .proc_open    = proc_open,
    .proc_read    = seq_read,
    .proc_write   = proc_write,
    .proc_lseek   = seq_lseek,
    .proc_release = single_release,
};

static int __init proc_demo_init(void)
{
    struct proc_dir_entry *dir, *file;

    // 1. 创建 /proc/proc_demo 目录
    dir = proc_mkdir(PROC_DIR_NAME, NULL);
    if (!dir)
        return -ENOMEM;

    // 2. 在目录下创建 /proc/proc_demo/status 文件
    file = proc_create(PROC_FILE_NAME, 0666, dir, &proc_fops);
    if (!file) {
        remove_proc_entry(PROC_DIR_NAME, NULL); // 清理已创建的目录
        return -ENOMEM;
    }

    // 初始化默认消息
    strcpy(kernel_buffer, "Hello from /proc/proc_demo/status!");

    printk(KERN_INFO "proc_demo: Directory /proc/%s created successfully\n", PROC_DIR_NAME);
    return 0;
}

static void __exit proc_demo_exit(void)
{
    // 3. 安全地删除整个目录及其下的所有子文件
    remove_proc_subtree(PROC_DIR_NAME, NULL);
    printk(KERN_INFO "proc_demo: /proc/%s removed\n", PROC_DIR_NAME);
}

module_init(proc_demo_init);
module_exit(proc_demo_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Test Author");
MODULE_DESCRIPTION("A /proc directory and file demo module");
1
2
3
4
5
6
7
obj-m += proc_demo.o

all:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
1
2
3
4
5
6
7
8
charles@ubuntu24:~/test-proc$ sudo insmod  proc_demo.ko
charles@ubuntu24:~/test-proc$ ls /proc/proc_demo
status
charles@ubuntu24:~/test-proc$ echo 1111 > /proc/proc_demo/status
charles@ubuntu24:~/test-proc$ dmesg -c
[176365.569965] proc_demo: Received from user space: 1111
charles@ubuntu24:~/test-proc$ cat /proc/proc_demo/status
Message from kernel: 1111

SysFS 概述

What is sysfs

sysfs 是一个挂载在 /sys 下的内存文件系统(RAM-based),它把内核内部的对象模型(设备、驱动、总线、内核子系统等)以“目录 + 文件”的形式导出到用户空间。相比 procFS,它更加结构化、规范化。

它的核心价值在于:

  • 一个属性一个文件:每个文件通常只表示一个值(one value per file),便于 shell、脚本读写;
  • 树形结构反映内核对象关系:目录层级对应内核对象(kobject)的父子/从属关系;
  • 替代 /proc 的滥用:早期很多设备信息塞进 /proc,sysfs 提供了结构化、规范化的替代方案。

什么是内存文件系统(RAM-based)

“内存文件系统”指的是:文件系统的数据结构和内容完全存在于内存(RAM)中,不落盘到任何块设备(硬盘、SSD、U 盘)上

核心特征:

  1. 无后端存储介质:普通文件系统(EXT4、NTFS、FAT32)的数据最终写到磁盘等非易失存储上,掉电后仍在;内存文件系统只活在 RAM 里,掉电/重启即消失
  2. 内容通常是“动态生成”的,而非“存储”的:对于 sysfs / procFS 这类特殊内存文件系统,文件里的内容甚至不是提前存好的字节,而是你 cat 的那一刻由内核回调函数临时生成的。例如 cat /sys/kernel/sysfs_demo/foo 会触发内核调用 foo_show()sprintf(buf, "%d\n", foo),把当前内核变量 foo 的值现算现返回。
  3. 没有真正的磁盘布局/元数据格式:procFS/sysfs “没有实际的文件”,也“没有像标准文件系统那样的数据存储格式”,只是借用了 VFS 的“文件/目录”接口外壳,底层数据来自内核对象(sysfs 用 kobject + kernfs)。

内存文件系统其实有两种典型形态:

类型 例子 内容来源 用途
数据型内存 FS tmpfsramfs 真的把你写入的文件字节存在 RAM 里 当作高速临时磁盘用(如 /tmp/dev/shm
接口型内存 FS(伪文件系统) procfssysfsdebugfs 内容由内核动态生成,是内核状态的“视图” 用户空间 ↔ 内核空间通信

sysfs 属于第二类:它不是用来存文件的,而是把内核对象模型(设备、驱动、总线等)以目录+文件的形式“投影”出来,给用户空间一个读写内核状态的规范接口。

一句话总结:内存文件系统 = 只存在于 RAM、掉电即失的文件系统;而 sysfs 更进一步,连内容都不真正“存储”,而是内核在你访问的瞬间实时生成的一层结构化视图。

sysfs vs procFS

维度 procFS (/proc) sysfs (/sys)
定位 进程信息 + 早期的杂项内核接口 内核对象模型(设备/驱动/总线)的结构化视图
组织方式 较随意,一个文件可含多行多字段 规范:一个文件一个值
底层实现 proc_fs / seq_file kobject + kernfs
常见用途 状态查询、/proc/sys 调参 设备属性导出、驱动调参与状态

常见的 /sys 顶层目录:

目录 含义
/sys/kernel 内核自身的可调参数、子系统入口
/sys/devices 系统中所有设备的全局设备树
/sys/class 按“功能类别”组织的设备视图(如 netblock
/sys/bus 按总线组织(如 pciusbplatform
/sys/module 已加载模块及其参数

sysfs 核心原理:kobject + attribute + kernfs

sysfs 本身几乎不存放数据,它是内核对象模型的“视图层”。要理解它,需要抓住三个关键抽象。

kobject —— 目录

kobject 是 sysfs 目录的载体。每一个出现在 /sys 里的目录,背后基本都对应一个 kobject(源码:include/linux/kobject.h)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
struct kobject {
    const char        *name;
    struct list_head    entry;
    struct kobject        *parent;   /* 决定目录层级 */
    struct kset        *kset;
    struct kobj_type    *ktype;    /* 决定属性如何读写 */
    struct kernfs_node    *sd;       /* sysfs directory entry,指向 kernfs 节点 */
    struct kref        kref;      /* 引用计数,控制生命周期 */
    ...
};

关键点:

  • parent 决定该目录挂在谁下面;
  • sd(sysfs directory)指向底层的 kernfs_node,这是真正在虚拟文件系统里存在的节点;
  • kref 是引用计数,kobject_get/put 增减引用,归零时调用 ktype->release 释放。

attribute —— 文件

目录里的“文件”由 struct attribute 描述,它只声明文件名和权限,本身不含读写逻辑(源码:include/linux/sysfs.h)。

1
2
3
4
5
struct attribute {
    const char        *name;   /* 文件名 */
    umode_t            mode;    /* 权限,如 0664 */
    ...
};

sysfs_ops / kobj_type —— 读写逻辑的分发

真正的读写行为绑定在 kobj_type 上。它把 attribute 与一组 sysfs_ops 关联起来(源码:include/linux/kobject.h)。

1
2
3
4
5
6
7
struct kobj_type {
    void (*release)(struct kobject *kobj);      /* 引用计数归零时释放 */
    const struct sysfs_ops *sysfs_ops;          /* show/store 分发入口 */
    struct attribute **default_attrs;
    const struct attribute_group **default_groups;
    ...
};

当用户态 cat 一个 sysfs 文件时,VFS → kernfs → sysfs_ops->show(),再由 show 找到对应的 attribute 并调用具体处理函数;echo > file 则走 store()

kobj_attribute —— 最常用的“文件 + 处理函数”打包

对于挂在 /sys/kernel 这类简单场景,内核提供了现成的 struct kobj_attribute,把 attribute 和 show/store 直接绑在一起(源码:include/linux/kobject.h)。

1
2
3
4
5
6
7
struct kobj_attribute {
    struct attribute attr;
    ssize_t (*show)(struct kobject *kobj, struct kobj_attribute *attr,
            char *buf);
    ssize_t (*store)(struct kobject *kobj, struct kobj_attribute *attr,
             const char *buf, size_t count);
};

配合 __ATTR 宏可以一行完成定义(源码:include/linux/sysfs.h)。

1
2
3
4
5
6
#define __ATTR(_name, _mode, _show, _store) {                \
    .attr = {.name = __stringify(_name),                \
         .mode = VERIFY_OCTAL_PERMISSIONS(_mode) },        \
    .show    = _show,                        \
    .store    = _store,                        \
}

注意 VERIFY_OCTAL_PERMISSIONSsysfs 属性不允许 world-writable,否则编译期就会报错。

一张图理清调用链

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
用户态:   cat /sys/kernel/xxx/foo
           VFS read()
          kernfs 文件操作
   kobj_type->sysfs_ops->show()
     kobj_attribute->show()   ← 你写的函数
        sprintf(buf, "%d\n", foo)

写方向(echo 5 > foo)完全对称,走 store()

常用 API 速查

API 作用
kobject_create_and_add(name, parent) 创建一个 kobject 目录并加入 sysfs
sysfs_create_file(kobj, attr) 在目录下创建单个文件
sysfs_create_group(kobj, group) 一次性创建一组文件
sysfs_remove_group(kobj, group) 移除一组文件
kobject_put(kobj) 减少引用,归零后销毁目录

attribute_group 用于批量管理属性,若指定 .name 会额外生成一层子目录(源码:include/linux/sysfs.h)。

Creating a sysfs file and interfacing with user space

Linux 源码本身就自带一个非常好的示例:samples/kobject/kobject-example.c。它会在 /sys/kernel/kobject_example/ 下创建 foobazbar 三个可读写文件。

下面基于它给出一个精简、可独立编译的版本,并附带构建/运行步骤。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include <linux/string.h>

/* 被 sysfs 文件读写的内核变量 */
static int foo;

/* cat 文件时被调用 */
static ssize_t foo_show(struct kobject *kobj, struct kobj_attribute *attr,
            char *buf)
{
    return sprintf(buf, "%d\n", foo);
}

/* echo > 文件时被调用 */
static ssize_t foo_store(struct kobject *kobj, struct kobj_attribute *attr,
             const char *buf, size_t count)
{
    int ret = kstrtoint(buf, 10, &foo);

    if (ret < 0)
        return ret;
    return count;   /* 必须返回已消费的字节数 */
}

/* 定义属性:0664 -> 允许 owner/group 写,但不允许 world-writable,
“world-writable” 在 Linux 权限语境下,特指文件对“其他用户(Others)”拥有写权限。
6 (owner) = 读(4) + 写(2) = 可读可写
6 (group) = 读(4) + 写(2) = 可读可写
4 (others) = 只读(4) */
static struct kobj_attribute foo_attribute =
    __ATTR(foo, 0664, foo_show, foo_store);

static struct attribute *attrs[] = {
    &foo_attribute.attr,
    NULL,    /* 必须以 NULL 结尾 */
};

static struct attribute_group attr_group = {
    .attrs = attrs,
};

static struct kobject *demo_kobj;

static int __init sysfs_demo_init(void)
{
    int ret;

    /* 在 /sys/kernel/ 下创建 sysfs_demo 目录 */
    demo_kobj = kobject_create_and_add("sysfs_demo", kernel_kobj);
    if (!demo_kobj)
        return -ENOMEM;

    /* 在该目录下创建属性文件 */
    ret = sysfs_create_group(demo_kobj, &attr_group);
    if (ret)
        kobject_put(demo_kobj);

    pr_info("sysfs_demo: loaded, see /sys/kernel/sysfs_demo/\n");
    return ret;
}

static void __exit sysfs_demo_exit(void)
{
    kobject_put(demo_kobj);   /* 引用归零后自动删除目录及文件 */
    pr_info("sysfs_demo: unloaded\n");
}

module_init(sysfs_demo_init);
module_exit(sysfs_demo_exit);

MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("your name");
MODULE_DESCRIPTION("A minimal sysfs demo module");
1
2
3
4
5
6
7
8
9
obj-m += sysfs_demo.o

KDIR ?= /lib/modules/$(shell uname -r)/build

all:
    $(MAKE) -C $(KDIR) M=$(PWD) modules

clean:
    $(MAKE) -C $(KDIR) M=$(PWD) clean
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 1. 编译(需要先安装对应内核头文件/源码)
make

# 2. 加载模块
sudo insmod sysfs_demo.ko
dmesg | tail            # 应看到 "sysfs_demo: loaded ..."

# 3. 查看目录与文件
ls -l /sys/kernel/sysfs_demo/
# -rw-rw-r-- 1 root root 4096 ... foo

# 4. 读值(默认 0)
cat /sys/kernel/sysfs_demo/foo
# 0

# 5. 写值
echo 42 | sudo tee /sys/kernel/sysfs_demo/foo
cat /sys/kernel/sysfs_demo/foo
# 42

# 6. 卸载
sudo rmmod sysfs_demo
dmesg | tail            # 应看到 "sysfs_demo: unloaded"

编写 sysfs 属性的注意事项

  1. 一个文件一个值:不要在一个文件里塞多个字段,保持简单可解析。
  2. show 的 buf 大小是一个 PAGE_SIZE:写入内容不要超过一页(通常 4KB),用 sysfs_emit()(新内核)或 sprintf 时注意边界。
  3. store 返回值语义:返回“已消费字节数”(通常是 count),或返回负的错误码;返回 0 会导致用户态无限重试。
  4. 权限不能 world-writable__ATTR 里的 mode 若包含 0002 会被 VERIFY_OCTAL_PERMISSIONS 拒绝编译。
  5. 生命周期用引用计数管理:目录的销毁靠 kobject_put(),不要手动去删底层文件。
  6. 动态命名对象用 kobject_init_and_add + ueventkobject_create_and_add 适合名字固定的简单目录,不会发送 uevent。

小结

  • sysfs = 内核对象模型(kobject)在用户空间的结构化视图
  • 目录 = kobject,文件 = attribute,读写逻辑 = sysfs_ops/kobj_attribute
  • 底层由 kernfs 提供虚拟文件系统能力,kobject 通过 sd 挂接;
  • kobject_create_and_add + sysfs_create_group 即可几十行代码导出一个可读写接口,非常适合驱动调参与状态导出。

想进一步深入,可阅读本仓库文档 Documentation/filesystems/sysfs.txt 以及 samples/kobject/ 下的两个官方示例。

Licensed under CC BY-NC-SA 4.0
使用 Hugo 构建
主题 StackJimmy 设计