【问题标题】:Is it safe to parse a /proc/ file?解析 /proc/ 文件是否安全?
【发布时间】:2011-08-08 11:30:08
【问题描述】:

我想解析/proc/net/tcp/,但它安全吗?

我应该如何打开和读取来自/proc/ 的文件,而不用担心其他进程(或操作系统本身)会同时更改它?

【问题讨论】:

  • +1。这是一个该死的好问题。我只希望我能得到答案,但我期待着找到答案,因为我以前做过很多类似的事情。
  • 我很确定只是阅读它会给你一个连接列表,加上拥有每个连接的UID,就像你打开时一样我>它。但是,我找不到该文档,因此现在将其作为评论。
  • 简单的答案显然是肯定的,因为它不是一个文件——阅读它应该总是安全的。以后阅读的答案可能不一致,但它会是安全的。
  • 这就是你应该使用 sysctl 的原因。 (它的系统调用也更少)
  • @GoodPerson - 例如,这个sysctl 如何帮助我解析/proc/net/tcp/ 文件?

标签: c++ c linux unix procfs


【解决方案1】:

/proc 是一个虚拟文件系统:事实上,它只是提供了一个方便的内核内部视图。阅读它绝对是安全的(这就是它在这里的原因),但从长远来看它是有风险的,因为这些虚拟文件的内部可能会随着更新版本的内核而发展。

编辑

更多信息请参见proc documentation in Linux kernel doc,第 1.4 章网络 我无法找到信息是否随着时间的推移而演变。我以为它在打开时就被冻结了,但无法给出明确的答案。

EDIT2

根据Sco doc(不是 linux,但我很确定 *nix 的所有风格都是这样)

虽然进程状态和 因此 /proc 的内容 文件可以从即时更改为 瞬间,单次读取(2)/proc 文件保证返回一个 状态的“理智”表示,即 是,读取将是原子的 进程状态的快照。 此类保证不适用于 应用于 /proc 的连续读取 正在运行的进程的文件。在 另外,原子性是具体的 不保证适用于任何 I/O as(地址空间)文件;这 任何进程地址的内容 空间可能同时被修改 由该过程的 LWP 或任何其他 系统中的进程。

【讨论】:

  • “我认为”?最好有一个明确的答案:)
  • 鉴于 /proc 在内核中的实现,这也适用于 linux。如果您在单个读取调用中读取一个 procfs 文件,它是一致的 - 当然假设您读取的 proc 文件已在内核端正确实现。
  • 我不认为你能想出比 SCO 更糟糕的信息来源,并试图将proc 视为它在不同内核之间具有相似的行为(甚至假设它存在 - - 在 Unix 系统中不必这样做)会给你带来伤害。
  • @Nicholas :好吧,在内核文档中找不到明确的答案,如果您知道,请随时指出。
  • 有趣的是 SCO 文档这么说。不幸的是,在 Linux 中并非总是如此,尤其是对于 OP 的主要关注点 /proc/net/tcp 而言并非如此。相反,只有输出中的每一行都是原子的。详情见我的回答。
【解决方案2】:

虽然/proc 中的文件在用户空间中显示为常规文件,但它们并不是真正的文件,而是支持来自用户空间的标准文件操作的实体(openreadclose)。 请注意,这与磁盘上有一个由内核更改的普通文件完全不同。

内核所做的只是使用类似sprintf 的函数将其内部状态打印到自己的内存中,并且每当您发出read(2) 系统调用时,该内存都会复制到用户空间中。

内核以与常规文件完全不同的方式处理这些调用,这可能意味着您将读取的数据的整个快照可能在您open(2)它时准备好,而内核确保并发调用是一致的和原子的。我没有在任何地方读到过,但否则真的没有意义。

我的建议是看看你的特定 Unix 风格的 proc 文件的实现。这实际上是一个不受标准约束的实现问题(输出的格式和内容也是如此)。

最简单的例子是在 Linux 中实现uptime proc 文件。注意整个缓冲区是如何在提供给single_open 的回调函数中生成的。

【讨论】:

  • @Ignacio:我只是将 OP 指向这个方向,因为我留下的印象是他认为proc 文件是由内核打开用于写入的普通文件。
  • 你建议看具体文件的执行情况就好了。不幸的是,对于许多文件,特别是对于 OP 所关注的/proc/net/tcp,所有文件都在open() 进行快照的猜测是错误的。如果你考虑提供这些语义的成本,这是有道理的——你必须做一些事情,比如锁定记录所有 TCP 连接的内部数据结构,这在繁忙的系统上是一场灾难,即使你只持有它很长时间足以扫描并将数据格式化到缓冲区。有关实际情况的详细信息,请参阅我的回答。
【解决方案3】:

Linux 内核中的 procfs API 提供了一个接口来确保读取返回一致的数据。阅读 __proc_file_read 中的 cmets。大注释块中的第 1 项)解释了这个界面。

话虽如此,要正确使用该接口以确保其返回的数据一致,这当然取决于特定 proc 文件的实现。所以,回答你的问题:不,内核不保证 proc 文件在读取期间的一致性,但它为这些文件的实现提供了提供一致性的方法。

【讨论】:

  • 不幸的是,/proc 中的许多文件实际上并不提供一致性。详情见我的回答。
  • 另外,__proc_file_read() 已被弃用,取而代之的是 seq_file。请参阅长块评论上方的相当愤怒的评论(由 Linus 撰写)。
【解决方案4】:

没有未知的错误,/proc 中没有会导致读取损坏数据或新旧数据混合的竞争条件。从这个意义上说,它是安全的。但是,仍然存在竞争条件,即您从/proc 读取的大部分数据一旦生成就可能已经过时,甚至在您开始读取/处理它时更是如此。例如,进程可能随时死亡,并且可以为新进程分配相同的 pid;您可以在没有竞争条件的情况下使用的唯一进程 ID 是您自己的子进程。网络信息(开放端口等)和/proc 中的大部分信息也是如此。我认为依赖/proc 中的任何数据都是准确的,除了关于您自己的进程和可能的子进程的数据之外,这是一种不好且危险的做法。当然,将来自/proc 的其他信息提供给用户/管理员以获取信息/日志记录/等可能仍然有用。目的。

【讨论】:

  • 我这样做是为了获取和使用一些信息用于我自己的进程(对于我的PID,使用getpid())。所以,它必须是安全的。
  • 是的,我认为这完全安全。
  • 我不同意子进程会比任何其他进程表现得更好。就/proc 接口而言,它们都有相同的弱点和优势。无论如何,OP 询问的是设备驱动程序相关信息,而不是进程。
  • 如果 pid N 是您的子进程,您可以确保 pid N 仍然引用同一个(可能已终止的)进程,直到您在其上调用 wait-family 函数。这样可以确保没有比赛。
  • -1 的泛滥没有解释是怎么回事?
【解决方案5】:

我手头有 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
...

在函数raw_sock_seq_show() 中,它是procfs 处理函数层次结构的一部分。在对/proc/net/tcp 文件发出read() 请求之前不会生成文本,这是一种合理的机制,因为procfs 读取肯定比更新信息少得多。

某些驱动程序(例如我的)使用单个 sprintf() 实现 proc_read 函数。核心驱动程序实现中的额外复杂性是处理可能非常长的输出,这些输出在单次读取期间可能不适合中间的内核空间缓冲区。

我使用一个使用 64K 读取缓冲区的程序对此进行了测试,但它在我的系统中产生了一个 3072 字节的内核空间缓冲区,以便 proc_read 返回数据。需要使用前进指针进行多次调用才能获得超过返回的文本。当需要多个 i/o 时,我不知道使返回的数据保持一致的正确方法是什么。当然/proc/net/tcp 中的每个条目都是自洽的。并排的线条有可能是不同时间的快照。

【讨论】:

  • 真的很抱歉,我没听懂。所以,你的意思是,如果我使用ifstream,它会不安全,但如果我使用read,它会安全吗?还是ifstream 内部使用read?你有什么建议?
  • @Kiril:很抱歉造成混乱。这是对/proc/net/tcp 的数据如何格式化的解释,并且完全独立于任何人的阅读方式。
  • 是的!你的猜测是正确的,不同的行(/proc/net/tcp)不是来自同一个快照。请参阅我的答案以获得一些解释。
【解决方案6】:

当您从 /proc 文件中读取时,内核正在调用一个已预先注册为该 proc 文件的“读取”函数的函数。请参阅 fs/proc/generic.c 中的 __proc_file_read 函数。

因此,proc 读取的安全性仅与内核为满足读取请求而调用的函数一样安全。如果该函数正确地锁定了它接触到的所有数据并在缓冲区中返回给您,那么使用该函数读取是完全安全的。由于诸如用于满足对 /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() 和 tcp6_seq_show() 在 net/ipv6/tcp_ipv6.c。

【讨论】:

    【解决方案7】:

    一般来说,不会。(所以这里的大多数答案都是错误的。)它可能是安全的,这取决于你想要什么属性。但是,如果您对/proc 中文件的一致性假设过多,那么很容易导致代码中出现错误。例如,请参阅this bug which came from assuming that /proc/mounts was a consistent snapshot

    例如:

    • /proc/uptime完全原子的,正如另一个答案中提到的那样——但仅从 Linux 2.6.30 开始,不到两岁。因此,在此之前,即使是这个微小的、琐碎的文件也会受到竞争条件的影响,并且仍然存在于大多数企业内核中。当前源请参见fs/proc/uptime.c,或the commit that made it atomic。在 2.6.30 之前的内核上,你可以 open 文件,read 一点点,然后如果你稍后再回来read,你得到的文件将与第一个文件不一致。 (我只是演示了这一点——你自己试试看。)

    • /proc/mounts在单个 read 系统调用中的原子。因此,如果您同时 read 整个文件,您会得到一个系统上安装点的单个一致快照。但是,如果您使用多个read 系统调用——并且如果文件很大,这正是使用普通 I/O 库并且不特别注意这个问题时会发生的情况——您将受制于竞争条件。您不仅不会获得一致的快照,而且在您开始之前存在并且从未停止存在的挂载点可能会在您看到的内容中丢失。要查看它对于read() 的原子性,请查看m_start() in fs/namespace.c 并查看它获取一个保护挂载点列表的信号量,它一直保留到m_stop(),当read() 完成时调用它。要查看可能出现的问题,请参阅this bug from last year(我在上面链接的同一个),在其他高质量软件中轻松阅读/proc/mounts

    • /proc/net/tcp,这是您实际询问的那个,甚至比这更不一致。它仅在表格的每一行中是原子的。要查看这一点,请查看同一文件下方的listening_get_next() in net/ipv4/tcp_ipv4.cestablished_get_next(),并依次查看它们在每个条目上取出的锁。我没有方便的重现代码来证明行与行之间缺乏一致性,但是那里没有锁(或其他任何东西)可以使其保持一致。如果您考虑一下,这是有道理的 - 网络通常是系统中非常繁忙的部分,因此在此诊断工具中呈现一致的视图是不值得的。

    在每一行中保持/proc/net/tcp 原子的另一部分是seq_read() 中的缓冲,您可以阅读in fs/seq_file.c。这可确保一旦您 read() 成为某一行的一部分,整行的文本将保存在缓冲区中,以便下一个 read() 将在开始新行之前获得该行的其余部分。 /proc/mounts 使用相同的机制来保持每一行原子,即使您执行多个 read() 调用,这也是新内核中的 /proc/uptime 用来保持原子的机制。该机制缓冲整个文件,因为内核对内存使用很谨慎。

    /proc 中的大多数文件将至少与/proc/net/tcp 一样一致,每一行都是它们提供的任何信息中一个条目的一致图片,因为它们中的大多数使用相同的seq_file 抽象。不过,正如/proc/uptime 示例所示,直到 2009 年,一些文件仍在迁移以使用 seq_file;我敢打赌,仍然有一些使用较旧的机制,甚至没有那种程度的原子性。这些警告很少被记录在案。对于给定的文件,您唯一的保证就是读取源代码。

    对于/proc/net/tcp,你可以毫无顾忌地阅读并解析每一行。但是,如果您尝试一次从多行中得出任何结论——请注意,其他进程和内核会在您阅读它时对其进行更改,并且您可能正在创建一个错误。

    【讨论】:

    • readdir 原子性怎么样?喜欢阅读 /proc/self/fd 吗?安全吗?
    • 并不是说它回答了问题,而是添加了关于如何检查正常运行时间的信息,您可以将 clock_gettime(2)CLOCK_MONOTONIC 一起使用(尽管可能有是我在这里不知道的技术性问题,但我个人只在启动时才看到它)。对于 Linux,您还可以选择 sysinfo(2)
    • /proc/mounts 不能通过一次巨大的读取持续读取,因为内部 seq_file.c 缓冲区默认为一页大小,一旦填满,读取结束。因此,除了一些非常不寻常的情况(第一行超过 4096 字节)外,可以一致读取的最大 /proc/mounts 块是 4096 字节的全部内容。
    • linux.die.net/man/2/openat 会解决竞态问题吗?
    • strace -e verbose=all cat /proc/net/tcp
    猜你喜欢
    • 2014-08-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-10
    • 1970-01-01
    相关资源
    最近更新 更多