fasionchan

读万卷书,行万里路,品万味肴,撸万行码。

Procfs伪文件系统原理

| Comments

Linux系统/proc目录下,有一些特殊的目录和文件,用来展示或者设置内核数据。例如,/proc/meminfo展示系统内存信息:

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
$ cat /proc/meminfo
MemTotal:         506160 kB
MemFree:           73528 kB
MemAvailable:     335160 kB
Buffers:           54756 kB
Cached:           162888 kB
SwapCached:            0 kB
Active:           247648 kB
Inactive:          96840 kB
Active(anon):     127044 kB
Inactive(anon):     4332 kB
Active(file):     120604 kB
Inactive(file):    92508 kB
Unevictable:           0 kB
Mlocked:               0 kB
SwapTotal:             0 kB
SwapFree:              0 kB
Dirty:               104 kB
Writeback:             0 kB
AnonPages:        126824 kB
Mapped:            14412 kB
Shmem:              4532 kB
Slab:              70608 kB
SReclaimable:      59128 kB
SUnreclaim:        11480 kB
KernelStack:        2928 kB
PageTables:         3028 kB
NFS_Unstable:          0 kB
Bounce:                0 kB
WritebackTmp:          0 kB
CommitLimit:      253080 kB
Committed_AS:     530132 kB
VmallocTotal:   34359738367 kB
VmallocUsed:        5536 kB
VmallocChunk:   34359731707 kB
HardwareCorrupted:     0 kB
AnonHugePages:         0 kB
HugePages_Total:       0
HugePages_Free:        0
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB
DirectMap4k:       69568 kB
DirectMap2M:      454656 kB

这些数据随着系统的变化动态调整,感觉好神奇!

还有一些文件,直接跟一些内核变量映射。除了可以读出数据,还可以更新数据呢。例如,/proc/sys/net/ipv4/ip_forward用来控制IP包转发,设置为1则是是启用转发:

1
2
3
4
5
$ cat /proc/sys/net/ipv4/ip_forward
0
echo "1" > /proc/sys/net/ipv4/ip_forward
$ cat /proc/sys/net/ipv4/ip_forward
1

那么,这些文件本质是什么呢?是真正的文件吗?

来龙去脉

系统用户和程序(进程)经常需要某些内核信息,该怎么获得呢?

/proc/sys/net/ipv4/ip_forward为例,内核里有一个变量用来控制协议栈是否转发IP包。系统用户要控制转发是否开启,就需要去设置这个内核变量。怎么设置呢?要知道用户空间可是没有办法直接访问内核空间的。

可以考虑实现一个新的系统调用——int set_ip_forward(int value)。然而,天呀有好多这样或那样的场景,这样系统调用表是要爆炸的!而且这种方式用起来也麻烦,需要实现一个专用的命令,调用这个系统调用完成设置。

有没有更通用的方式呢?其实,让这个系统调用更加通用化,也是一种思路——int set_kernel_value(char *path, void *value)。这样一个系统调用就搞定了一些列设置要求,但是还是没有解决调用麻烦的问题。

如果,可以将内核数据伪装成一个文件,用read系统调用获取;用write系统调用设置不就完美了吗?这样,有现成的命令可以直接使用,比如用cat来获取,用echo来设置!

那么内核有办法做到吗?

办法肯定是有的。最直观的想法是,在内核处理read的代码进行控制,让read直接从内核数据中拷贝,而不是磁盘。当然了,内核黑客们不会写这么恶心的代码,而是抽象了一层——VFS

VFS

Linux支持各种各样的文件系统,光ext系列就有:ext2ext3ext4。内核如何实现不同文件系统差异性的呢?

答案是,内核抽象了VFS接口。跟面向对象编程中的接口有点类似,具体的文件系统按照各自的方式分别实现(多态)。

archi_VFS-eng.gif

如上图所示,不同的文件系统以各自的方式实现处理接口,如readwrite等等。这些处理接口被封装成结构一致的结构体注册。这样,同样的系统调用,在不同的文件系统下,由不同的函数处理。

因此,完全有可能实现一个假的文件系统,就叫procfs,读写分别映射为变量的读出和写入。

流程

最后,图解说明访问/proc下文件的整个流程:

procfs

  1. 用户进程发起一个系统调用read
  2. 内核VFS根据文件描述符找出操作描述体;
  3. 内核从描述体取出read处理函数指针;
  4. 内核执行该处理函数;
  5. 处理函数读出内核变量并拷贝到用户态空间;

Comments