使用 ssnetstat 等命令查看 TCP 连接信息时,它们可能会从文件 /proc/net/tcp 读取 tcp socket 信息。

1
2
3
4
$ strace netstat -4 -t
...
open("/proc/net/tcp", O_RDONLY)        = 3
...

而使用 file 命令查看 /proc/net/tcp 文件时,提示是 empty:

1
2
$ file /proc/net/tcp
/proc/net/tcp: empty

使用 mount 命令查看一下,发现 /proc 是一个 proc 类型的文件系统:

1
2
$ mount | grep proc
proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)

所以,/proc/net/tcp 并不是一个真正的文件,而是一个虚拟文件,它的内容是内核中的数据结构的映射。

实际上,/proc/net/tcp 是一个 sequence file,它的内容是一系列的 records,每个 record 代表一个 TCP socket。

本文不打算详细介绍 procfs 下 sequence file 的具体实现,而是抓住 sequence file 的核心 seq_file 接口,介绍一下 sequence file 的基本原理。

seq_file 接口

sequence file 参考资料:The seq_file Interface

seq_file 接口定义如下:

1
2
3
4
5
6
7
8
// ${KERNEL}/include/linux/seq_file.h

struct seq_operations {
    void * (*start) (struct seq_file *m, loff_t *pos);
    void (*stop) (struct seq_file *m, void *v);
    void * (*next) (struct seq_file *m, void *v, loff_t *pos);
    int (*show) (struct seq_file *m, void *v);
};

该接口比较简单,它定义了 4 个回调函数:

  1. start: 开始一次会话,并返回一个 iterator。
  2. stop: 结束本次会话。
  3. next: iterator 前进到下一个位置,并返回一个 iterator。
  4. show: 根据 iterator,将一条 record 数据写入到 seq_file

实现了这个接口后,当用户态空间打开一个 sequence file 时,read() 系统调用会调用该接口的回调函数,从而一行一行地将数据写入到用户态空间。

获取 TCP socket 信息

所以,当需要从 /proc/net/tcp 读取 tcp socket 信息时,推荐 line by line 地读取该文件,而不是一次性读取整个文件。

当有大量 tcp socket 时,非常不推荐一次性读取整个文件,也不推荐使用 man 7 inet_diag 的方式获取全部的 tcp socket 信息;即使 inet_diag 方式对代码更加友好。

小结

本质上,sequence file 是内核向用户态空间传递数据的一种方式,类似 netlink,但它的功能简单且单一。

因其简单,内核里不少地方都通过 sequence file 向用户态空间传递数据;全局搜索 struct seq_operations,发现有 400 多个地方使用了 sequence file。

其实,bpf 子系统也基于 sequence file 机制,实现了动态 record 的 sequence files;即 bpf_iter