我想解析/proc/net/tcp/
,但它安全吗?
我应该如何打开和读取文件/proc/
而不害怕其他进程(或操作系统本身)会同时更改它?
一般来说,没有。(所以这里的大多数答案都是错误的。)它可能是安全的,这取决于你想要什么属性。但是,如果您对/proc
. 例如,请参阅假设这/proc/mounts
是一个一致的快照这个错误。
例如:
/proc/uptime
完全是原子的,正如有人在另一个答案中提到的那样——但仅从不到两年的Linux 2.6.30 开始。因此,在此之前,即使是这个微小的、琐碎的文件也会受到竞争条件的影响,并且仍然存在于大多数企业内核中。查看fs/proc/uptime.c
当前来源,或使其成为 atomic 的提交。在 2.6.30 之前的内核上,你可以open
文件,read
一点点,然后如果你以后再回来read
,你得到的部分将与第一个部分不一致。(我只是演示了这一点——你自己试试看吧。)
/proc/mounts
在单个read
系统调用中是原子的。因此,如果您同时查看read
整个文件,您将获得系统上挂载点的单个一致快照。然而,如果你使用多个read
系统调用——如果文件很大,这正是你使用普通 I/O 库并且不特别注意这个问题时会发生的情况——你将受到竞争健康)状况。您不仅不会获得一致的快照,而且在您开始之前存在并且从未停止存在的挂载点可能会在您看到的内容中丢失。要查看它对 one 的原子性read()
,请查看m_start()
infs/namespace.c
并看到它获取了一个信号量,该信号量保护了挂载点列表,它一直保存到m_stop()
,当read()
已经完成了。要查看可能出现的问题,请查看去年的这个错误(我在上面链接的同一个)在其他高质量的软件中愉快地阅读/proc/mounts
。
/proc/net/tcp
,这是您实际询问的问题,甚至比这更不一致。它仅在 table 的每一行中是原子的。要查看这一点,请查看同一个文件listening_get_next()
中net/ipv4/tcp_ipv4.c
的内部和established_get_next()
下方,然后依次查看它们在每个条目上取出的锁。我没有方便的重现代码来证明行与行之间缺乏一致性,但是那里没有锁(或其他任何东西)可以使其保持一致。如果您考虑一下,这是有道理的——网络通常是系统中超级繁忙的部分,因此在此诊断工具中呈现一致的视图是不值得的。
在每一行中保持/proc/net/tcp
原子性的另一部分是缓冲输入seq_read()
,您可以读入fs/seq_file.c
。这可确保一旦您read()
成为某一行的一部分,整行的文本将保存在缓冲区中,以便下一read()
行在开始新行之前获得该行的其余部分。/proc/mounts
即使您进行多次read()
调用,也使用相同的机制来保持每一行的原子性,这也是/proc/uptime
在较新的内核中用来保持原子性的机制。该机制不会缓冲整个文件,因为内核对内存使用很谨慎。
Most files in /proc
will be at least as consistent as /proc/net/tcp
, with each row a consistent picture of one entry in whatever information they're providing, because most of them use the same seq_file
abstraction. As the /proc/uptime
example illustrates, though, some files were still being migrated to use seq_file
as recently as 2009; I bet there are still some that use older mechanisms and don't have even that level of atomicity. These caveats are rarely documented. For a given file, your only guarantee is to read the source.
In the case of /proc/net/tcp
, you can read it and parse each line without fear. But if you try to draw any conclusions from multiple lines at once -- beware, other processes and the kernel are changing it while you read it, and you are probably creating a bug.
尽管其中的文件/proc
在用户空间中显示为常规文件,但它们并不是真正的文件,而是支持来自用户空间的标准文件操作的实体(open
, read
, close
)。请注意,这与在磁盘上有一个由内核更改的普通文件完全不同。
内核所做的只是使用类似 - 的函数将其内部状态打印到自己的内存中sprintf
,并且每当您发出read(2)
系统调用时,该内存都会复制到用户空间中。
内核以与常规文件完全不同的方式处理这些调用,这可能意味着您将读取的数据的整个快照可以在您读取时准备好open(2)
,而内核确保并发调用是一致的和原子的。我没有在任何地方读到过,但否则真的没有意义。
我的建议是看看你的特定 Unix 风格的 proc 文件的实现。这实际上是一个不受标准约束的实现问题(输出的格式和内容也是如此)。
最简单的例子是uptime
proc 文件在 Linux 中的实现。注意整个缓冲区是如何在提供给single_open
.
/proc 是一个虚拟文件系统:事实上,它只是提供了一个方便的内核内部视图。阅读它绝对是安全的(这就是它在这里的原因),但从长远来看它是有风险的,因为这些虚拟文件的内部可能会随着更新版本的内核而发展。
编辑
更多信息可在Linux 内核文档中的 proc 文档中获得,第 1.4 章网络我找不到信息如何随着时间的推移而演变。我以为它在打开时被冻结了,但无法给出明确的答案。
编辑2
根据Sco doc(不是 linux,但我很确定 *nix 的所有风格都是这样)
尽管进程状态和 /proc 文件的内容可以随时更改,但 /proc 文件的单次读取 (2) 可以保证返回“正常”的状态表示,也就是说,读取将是进程状态的原子快照。没有这样的保证适用于应用于正在运行的进程的 /proc 文件的连续读取。此外,对于应用于 as(地址空间)文件的任何 I/O,原子性都无法保证;任何进程的地址空间的内容都可能被该进程的 LWP 或系统中的任何其他进程同时修改。
Linux 内核中的 procfs API 提供了一个接口来确保读取返回一致的数据。阅读 中的评论__proc_file_read
。大注释块中的第 1 项)解释了这个界面。
话虽如此,要正确使用该接口以确保其返回的数据一致,这当然取决于特定 proc 文件的实现。所以,回答你的问题:不,内核不保证读取过程中 proc 文件的一致性,但它为这些文件的实现提供了提供一致性的方法。
我有 Linux 2.6.27.8 的源代码,因为我目前正在嵌入式 ARM 目标上进行驱动程序开发。
例如,第 934 行的文件 ...linux-2.6.27.8-lpc32xx/net/ipv4/raw.c
包含
seq_printf(seq, "%4d: %08X:%04X %08X:%04X"
" %02X %08X:%08X %02X:%08lX %08X %5d %8d %lu %d %p %d\n",
i, src, srcp, dest, destp, sp->sk_state,
atomic_read(&sp->sk_wmem_alloc),
atomic_read(&sp->sk_rmem_alloc),
0, 0L, 0, sock_i_uid(sp), 0, sock_i_ino(sp),
atomic_read(&sp->sk_refcnt), sp, atomic_read(&sp->sk_drops));
哪个输出
[wally@zenetfedora ~]$ cat /proc/net/tcp
sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode
0: 017AA8C0:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 15160 1 f552de00 299
1: 00000000:C775 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 13237 1 f552ca00 299
...
in function raw_sock_seq_show()
,它是procfs处理函数层次结构的一部分。read()
在对文件发出请求之前不会生成文本,这是/proc/net/tcp
一种合理的机制,因为procfs读取肯定比更新信息少得多。
一些驱动程序(例如我的)使用单个sprintf()
. 核心驱动程序实现中的额外复杂性是处理潜在的非常长的输出,这些输出在单次读取期间可能不适合中间的内核空间缓冲区。
我使用一个使用 64K 读取缓冲区的程序对其进行了测试,但它在我的系统中导致 proc_read 返回数据的内核空间缓冲区为 3072 字节。需要使用前进指针进行多次调用才能获得超过返回的文本。当需要多个 i/o 时,我不知道使返回的数据保持一致的正确方法是什么。当然,每个条目/proc/net/tcp
都是自洽的。并排的线条有可能是不同时间的快照。
缺少未知错误,没有竞争条件/proc
会导致读取损坏的数据或新旧数据混合。从这个意义上说,它是安全的。但是,仍然存在竞争条件,即您从中读取的大部分数据/proc
一旦生成就可能已经过时,甚至在您开始读取/处理它时更是如此。例如,进程可能随时死亡,并且可以为新进程分配相同的 pid;您可以在没有竞争条件的情况下使用的唯一进程 ID 是您自己的子进程。网络信息(开放端口等)和/proc
. 我认为依赖于/proc
准确,但有关您自己的进程和可能的子进程的数据除外。当然,/proc
向用户/管理员提供其他信息以获取信息/日志记录/等可能仍然有用。目的。
当您从 /proc 文件中读取时,内核正在调用一个已预先注册为该 proc 文件的“读取”函数的函数。请参阅__proc_file_read
fs/proc/generic.c 中的函数。
因此,proc read 的安全性仅与内核为满足读取请求而调用的函数一样安全。如果该函数正确地锁定了它接触到的所有数据并在缓冲区中返回给您,那么使用该函数读取是完全安全的。由于诸如用于满足对 /proc/net/tcp 的读取请求的 proc 文件已经存在了一段时间并且经过了严格的审查,因此它们与您所要求的一样安全。事实上,许多常见的 Linux 实用程序依赖于从 proc 文件系统读取并以不同的方式格式化输出。(在我的脑海中,我认为'ps'和'netstat'会这样做)。
与往常一样,您不必相信我的话。你可以看看源头来平息你的恐惧。proc_net_tcp.txt 中的以下文档告诉您 /proc/net/tcp 的“读取”函数在哪里运行,因此您可以查看从该 proc 文件读取时运行的实际代码并自己验证没有锁定危险。
本文档描述了接口 /proc/net/tcp 和 /proc/net/tcp6。
请注意,这些接口已被弃用,取而代之的是 tcp_diag。这些 /proc 接口提供有关当前活动 TCP 连接的信息,并分别由 net/ipv4/tcp_ipv4.c 中的 tcp4_seq_show() 和 net/ipv6/tcp_ipv6.c 中的 tcp6_seq_show() 实现。