Безопасно ли анализировать файл / proc /?

152

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

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

Кирилл Киров
источник
29
+1. Это чертовски хороший вопрос. Жаль только, что у меня не было ответа, но я с нетерпением жду, когда узнаю об этом, потому что я уже делал подобные вещи немного раньше.
paxdiablo
1
Я уверен, что просто прочитав его, вы получите список соединений, а также UID, который владеет каждым из них, как они были, когда вы открывали его. Однако я не могу найти это документированным, поэтому пока делаю это комментарием.
Тим Пост
3
Простой ответ, очевидно, да, так как это не файл - чтение его всегда должно быть безопасным. Ответы могут быть непоследовательными в следующий раз, когда вы их прочитаете, но это будет безопасно.
Рори Олсоп
Вот почему вы должны использовать вместо sysctl. (также меньше системных вызовов)
Good Person
@ GoodPerson - как это может sysctlпомочь мне /proc/net/tcp/, например, разобрать файл?
Кирилл Киров

Ответы:

111

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

Например:

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

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

  • /proc/net/tcpо котором вы на самом деле спрашиваете, даже менее последовательный, чем этот. Это атомарно только в каждом ряду таблицы . Чтобы убедиться в этом, посмотрите на 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в более новых ядрах, чтобы оставаться атомарным. Этот механизм не буферизирует весь файл, потому что ядро ​​осторожно относится к использованию памяти.

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

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

Грег Прайс
источник
1
как насчет атомности readdir? нравится читать / proc / self / fd? это безопасно?
пара гнезд
Не то, что он отвечает на вопрос , но добавить о том , как проверить безотказную работу вы можете использовать clock_gettime(2)с CLOCK_MONOTONIC(хотя , возможно , есть формальность я не знаю здесь , но лично я только видел его , так как время загрузки). Для Linux у вас также есть возможность sysinfo(2).
Прифтан
44

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

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

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

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

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

Благовест Буюклиев
источник
3
@Ignacio: Я просто указываю ОП в этом направлении, потому что у меня сложилось впечатление, что он думает, что procфайлы - это обычные файлы, открытые для записи ядром.
Благовест Буюклиев
4
Ваш совет посмотреть на реализацию конкретного файла хорош. К сожалению, предположение о том, что это все снимок, open()неверно для многих файлов, и в частности для того /proc/net/tcp, что касается OP. Это имеет смысл, если вы задумываетесь о стоимости предоставления этой семантики - вам нужно что-то сделать, например, заблокировать внутренние структуры данных, которые записывают все эти соединения TCP, что в загруженной системе является катастрофой, даже если вы удерживаете ее долго достаточно для сканирования и форматирования данных в буфер. Смотрите мой ответ для деталей о том, что на самом деле происходит.
Грег Прайс
16

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

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

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

EDIT2

Согласно Sco Doc (не Linux, но я уверен, что все разновидности * nix ведут себя так)

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

Брюс
источник
3
"Думаю" ? Было бы неплохо получить окончательный ответ :)
static_rtti
Учитывая реализацию / proc в ядре, это также верно для linux. Если вы читаете файл procfs за один вызов read, это согласуется - конечно, при условии, что прочитанный вами файл proc был правильно реализован на стороне ядра.
Эрик
8
Я не думаю, что вы могли бы придумать худший возможный источник информации, чем SCO, и пытаться рассматривать его так, procкак если бы он имел схожее поведение между различными ядрами (или даже предполагать, что он существует - это не обязательно в Unix-системе). ) принесет вам мир боли.
Николас Найт
1
@Nicholas: ну, я не смог найти какой-то окончательный ответ в документации по ядру, не стесняйся указывать его, если знаешь.
Брюс
2
Интересно, что в документах ШОС так говорят. К сожалению, это не всегда верно в Linux, и, в частности, это не так /proc/net/tcp, что является главной заботой ОП. Скорее, только каждая отдельная строка в выводе является атомарной. Смотрите мой ответ для деталей.
Грег Прайс
14

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

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

работа
источник
4
К сожалению, многие файлы /procна самом деле не обеспечивают согласованность. Смотрите мой ответ для деталей.
Грег Прайс
3
Также __proc_file_read()не рекомендуется в пользу seq_file. Посмотрите довольно раздраженный комментарий (от Линуса) чуть выше длинного блочного комментария.
Грег Прайс
6

У меня под рукой есть исходник для 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(). Дополнительной сложностью в реализации драйверов ядра является обработка потенциально очень длинных выходных данных, которые могут не помещаться в промежуточный буфер пространства ядра во время одного чтения.

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

wallyk
источник
Очень жаль, я не получил это много. Итак, вы имеете в виду, что если я использую ifstream, это будет небезопасно, но если я буду использовать readэто будет безопасно? Или ifstreamиспользует внутренне read? А что ты предлагаешь?
Кирилл Киров
@ Кирилл: Извините за путаницу. Это объяснение того, как данные /proc/net/tcpформатируются и полностью не зависит от того, как их читают.
Wallyk
1
Ага! И ваше предположение верно, что разные строки (в /proc/net/tcp) не приходят из одного и того же снимка. Смотрите мой ответ для некоторых объяснений.
Грег Прайс
3

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

R .. GitHub ОСТАНОВИТЬ, ПОМОГАЯ ЛЕД
источник
Я делаю это, чтобы получить и использовать некоторую информацию для моего собственного процесса (для моего PID, используя getpid()). Так что это должно быть безопасно.
Кирилл Киров
1
Да, я бы посчитал это совершенно безопасным.
R .. GitHub ОСТАНОВИТЬ, ПОМОГАЯ ЛЬДУ
Я не согласен с тем, что дочерние процессы ведут себя лучше, чем любой другой процесс. Что касается /procинтерфейса, все они имеют одинаковые недостатки и сильные стороны. В любом случае, OP запрашивает информацию о драйвере устройства, а не о процессах.
Wallyk
1
Если pid N- ваш дочерний процесс, вы можете убедиться, что pid по- Nпрежнему ссылается на тот же (возможно, завершенный) процесс, пока вы не waitвызовете для него функцию -family. Это гарантирует, что нет гонок.
R .. GitHub ОСТАНОВИТЬ ЛЬДА
Что с потоком -1 и без объяснений?
R .. GitHub ОСТАНОВИТЬ ЛЬДА
2

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

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

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

вереск
источник