c++ - Безопасно ли разбирать файл / proc / file?


Я хочу разбирать /proc/net/tcp/ , но безопасно ли это?

Как мне открывать и читать файлы из /proc/ и не бояться, что какой-то другой процесс (или сама ОС) будет менять его в одно и то же время?




Answers


В общем, нет. (Таким образом, большинство ответов здесь неверны.) Это может быть безопасно, в зависимости от того, какое свойство вы хотите. Но легко получить ошибки в коде, если вы слишком много думаете о согласованности файла в /proc . Например, см. Эту ошибку, исходящую из предположения, что /proc/mounts является последовательным моментальным снимком .

Например:

  • /proc/uptime полностью атомно , как кто-то упомянул в другом ответе, - но только с Linux 2.6.30 , которому меньше двух лет. Таким образом, даже этот крошечный, тривиальный файл до тех пор находился под условием гонки и все еще находится в большинстве корпоративных ядер. См. fs/proc/uptime.c для текущего источника или фиксации, сделавшей его атомарным . На ядре до 2.6.30 вы можете open файл, read его немного, а затем, если вы позже вернетесь и read снова, часть, которую вы получите, будет несовместима с первой частью. (Я просто продемонстрировал это - попробуйте сами для удовольствия.)

  • /proc/mounts является атомарным в рамках единого системного вызова. Поэтому, если вы read весь файл сразу, вы получаете единый согласованный снимок точек монтирования в системе. Однако, если вы используете несколько системных вызовов для read - и если файл большой, это именно то, что произойдет, если вы используете обычные библиотеки ввода-вывода и не обращаете особого внимания на эту проблему - вы будете подвержены гонки состояние. Мало того, что вы не получите согласованный снимок, но точки монтирования, которые присутствовали до того, как вы начали, и никогда не переставали присутствовать, могут пропадать без вести в том, что вы видите. Чтобы увидеть, что он является атомарным для одного read() , посмотрите на m_start() в fs/namespace.c и посмотрите, как он захватывает семафор, который защищает список точек монтирования, который он сохраняет до m_stop() , который вызывается при read() . Чтобы узнать, что может пойти не так, см. Эту ошибку с прошлого года (то же самое, что я указал выше), в противном случае высококачественное программное обеспечение, которое blithely читает /proc/mounts .

  • /proc/net/tcp , о котором вы действительно спрашиваете, даже менее согласован. Он является атомарным только в каждой строке таблицы . Чтобы увидеть это, посмотрите на net/ipv4/tcp_ipv4.c listening_get_next() в net/ipv4/tcp_ipv4.c и net/ipv4/tcp_ipv4.c established_get_next() чуть ниже в том же файле и посмотрите блокировки, которые они net/ipv4/tcp_ipv4.c на каждую запись по очереди. У меня нет перепрограммируемого кода, чтобы продемонстрировать отсутствие согласованности от строки к строке, но там нет блокировок (или чего-то еще), которые бы сделали его согласованным. Что имеет смысл, если вы думаете об этом - сетевое взаимодействие часто является суперзанятой частью системы, поэтому не стоит накладных представлять постоянный вид в этом диагностическом инструменте.

Другая часть, которая хранит /proc/net/tcp atomic внутри каждой строки, является буферизацией в seq_read() , которую вы можете прочитать в fs/seq_file.c . Это гарантирует, что после того, как вы read() часть одной строки, текст всей строки будет храниться в буфере, чтобы следующий read() оставил остальную часть этой строки перед тем, как начать новую. Тот же механизм используется в /proc/mounts чтобы сохранить каждую атомную строку, даже если вы выполняете несколько вызовов read() , а также механизм, который /proc/uptime в более новых ядрах использует, чтобы оставаться атомарным. Этот механизм не блокирует весь файл, потому что ядро ​​осторожно относится к использованию памяти.

Большинство файлов в /proc будут по меньшей мере такими же согласованными, как /proc/net/tcp , причем каждая строка представляет собой согласованное изображение одной записи в любой информации, которую они предоставляют, потому что большинство из них использует одну и seq_file же абстракцию seq_file . Как показывает пример /proc/uptime , некоторые файлы все еще переносятся для использования seq_file еще в 2009 году; Бьюсь об заклад, есть еще некоторые, которые используют более старые механизмы и не имеют даже такого уровня атомарности. Эти оговорки редко документируются. Для данного файла ваша единственная гарантия - прочитать источник.

В случае /proc/net/tcp вы можете прочитать его и проанализировать каждую строку без страха. Но если вы попытаетесь сделать какие-либо выводы из нескольких строк сразу - будьте осторожны, другие процессы и ядро ​​меняют его во время чтения, и вы, вероятно, создаете ошибку.




Хотя файлы в /proc отображаются как обычные файлы в пользовательском пространстве, они не являются файлами, а объектами, которые поддерживают стандартные операции с файлами из пользовательского пространства ( open , read , close ). Обратите внимание, что это совсем не так, как обычный файл на диске, который изменяется ядром.

Все ядро ​​выполняет печать своего внутреннего состояния в собственную память с помощью функции sprintf like, и эта память копируется в пользовательское пространство всякий раз, когда вы выдаете системный вызов read(2) .

Ядро обрабатывает эти вызовы совершенно по-другому, чем обычные файлы, что может означать, что весь моментальный снимок данных, которые вы будете читать, может быть готов в то время, когда вы open(2) , а ядро ​​удостоверяется, что одновременные вызовы последовательный и атомный. Я не читал этого нигде, но на самом деле не имеет смысла быть иначе.

Мой совет - взглянуть на реализацию файла proc в вашем конкретном вкусе Unix. Это действительно проблема реализации (как и формат и содержание вывода), которые не регулируются стандартом.

Простейшим примером может быть реализация файла proc uptime в Linux. Обратите внимание на то, как весь буфер создается в функции обратного вызова, single_open в single_open .




/ proc - это виртуальная файловая система: на самом деле она просто дает удобный обзор внутренних элементов ядра. Это безопасно читать (вот почему это здесь), но в долгосрочной перспективе это рискованно, так как внутренние эти виртуальные файлы могут развиваться с более новой версией ядра.

РЕДАКТИРОВАТЬ

Дополнительная информация доступна в документации по процессам в документе ядра Linux , глава 1.4. Сеть. Невозможно найти информацию о том, как информация эволюционирует с течением времени. Я думал, что он заморожен на открытом воздухе, но не может дать определенного ответа.

EDIT2

Согласно Sco doc (не linux, но я уверен, что все вкусы * nix ведут себя так)

Несмотря на то, что состояние процесса и, следовательно, содержимое файлов / proc может меняться от момента к мгновенному, одно чтение (2) файла / proc гарантированно возвращает «состояние» представления состояния, то есть чтение будет атомный снимок состояния процесса. Никакая такая гарантия не применяется к последовательным чтениям, применяемым к файлу / proc для выполняемого процесса. Кроме того, атомарность специально не гарантируется для любого ввода-вывода, применяемого к файлу as (address-space); содержимое адресного пространства любого процесса может быть одновременно изменено LWP этого процесса или любым другим процессом в системе.




API-интерфейс procfs в ядре Linux обеспечивает интерфейс, обеспечивающий считывание возвращаемых согласованных данных. Прочитайте комментарии в __proc_file_read . Пункт 1) в блоке больших комментариев объясняет этот интерфейс.

При этом, конечно, для реализации конкретного файла proc необходимо правильно использовать этот интерфейс, чтобы убедиться, что его возвращаемые данные согласованы. Итак, чтобы ответить на ваш вопрос: нет, ядро ​​не гарантирует согласованности файлов proc во время чтения, но предоставляет средства для реализации этих файлов для обеспечения согласованности.




У меня есть источник для Linux 2.6.27.8, так как я сейчас занимаюсь разработкой драйверов на встроенной цели ARM.

Файл ... linux-2.6.27.8-lpc32xx/net/ipv4/raw.c в строке 934 содержит, например

    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 . Текст не генерируется до тех пор, пока запрос read() будет сделан из файла /proc/net/tcp , разумный механизм, поскольку считывания procfs , безусловно, гораздо реже, чем обновление информации.

Некоторые драйверы (например, мои) реализуют функцию proc_read с помощью одного sprintf() . Дополнительным осложнением в реализации основных драйверов является обработка потенциально очень длинного вывода, который может не соответствовать промежуточному буферу пространства ядра во время одного чтения.

Я протестировал это с помощью программы, использующей 64-килобайтный буфер чтения, но это привело к тому, что в моей системе был загружен буфер пространства ядра 3072 байта для proc_read для возврата данных. Для получения большего количества возвращаемого текста требуется несколько вызовов с продвигающимися указателями. Я не знаю, какой правильный способ сделать возвращенные данные согласованными, когда требуется более одного ввода-вывода. Конечно, каждая запись в /proc/net/tcp является самосогласованной. Существует некоторая вероятность того, что линии бок о бок являются моментальными снимками в разное время.




За исключением неизвестных ошибок, в /proc нет условий гонки, которые приведут к чтению поврежденных данных или сочетанию старых и новых данных. В этом смысле это безопасно. Однако по-прежнему существует условие гонки, что большая часть данных, которые вы читаете из /proc , потенциально устарела, как только она сгенерирована, и даже более того, к тому времени, когда вы ее прочитаете / обработаете. Например, процессы могут умереть в любое время, и новому процессу может быть назначен один и тот же pid; единственные идентификаторы процесса, которые вы можете использовать без условий гонки, - это ваши собственные дочерние процессы ». То же самое касается сетевой информации (открытые порты и т. Д.) И действительно большая часть информации в /proc . Я считаю, что плохая и опасная практика полагается на то, что любые данные в /proc точны, за исключением данных о вашем собственном процессе и, возможно, его дочерних процессах. Конечно, может быть полезно представить другую информацию из /proc пользователю / администратору для информативного / ведения журнала / etc. цели.




Когда вы читаете файл / proc, ядро ​​вызывает функцию, которая была заранее зарегистрирована, чтобы быть функцией чтения для этого файла proc. См. __proc_file_read в __proc_file_read fs / proc / generic.c.

Поэтому безопасность прочтения считывается только так же безопасно, как функция, которую ядро ​​вызывает для удовлетворения запроса на чтение. Если эта функция должным образом блокирует все данные, которые она затрагивает и возвращает вам в буфер, то ее можно безопасно читать с использованием этой функции. Поскольку файлы proc, подобные тем, которые используются для удовлетворения запросов на чтение / proc / net / tcp, уже давно существуют и прошли тщательный обзор, они настолько безопасны, насколько вы могли бы попросить. На самом деле многие распространенные утилиты Linux полагаются на чтение из файловой системы proc и форматирование вывода по-другому. (Сверху моей головы я думаю, что «ps» и «netstat» делают это).

Как всегда, вам не нужно об этом говорить; вы можете посмотреть на источник, чтобы успокоить свои страхи. Следующая документация из proc_net_tcp.txt сообщает вам, где «читать» функции для / proc / net / tcp live, поэтому вы можете посмотреть фактический код, который запускается при чтении из этого файла proc и убедиться, что нет блокировки.

В этом документе описаны интерфейсы / proc / net / tcp и / proc / net / tcp6.
Обратите внимание, что эти интерфейсы устарели в пользу tcp_diag. Эти интерфейсы / proc предоставляют информацию о текущих активных TCP-соединениях и реализуются tcp4_seq_show () в net / ipv4 / tcp_ipv4.c и tcp6_seq_show () в net / ipv6 / tcp_ipv6.c соответственно.