Почему ps * очень * иногда не может найти правильный процесс?

9

Я столкнулся со странной проблемой, из-за которой ps -o args -p <pid>команда очень редко не может найти нужный процесс, даже если она определенно выполняется на рассматриваемом сервере. Рассматриваемые процессы - это длительные сценарии-оболочки, используемые для запуска некоторых приложений Java.

Кажется, что «в дикой природе» возникновение проблемы всегда происходит рано утром, поэтому есть некоторые свидетельства того, что это связано с загрузкой диска на рассматриваемом сервере, потому что они довольно сильно загружены, но при запуске psin вопрос в узком цикле, я могу в конечном итоге повторить проблему - каждые несколько сотен или около того пробежек я получаю ошибку.

Запустив следующий скрипт bash, мне удалось сгенерировать выходные данные для неудачного и успешного выполнения:

while [ $? == 0 ] ; do strace -o fail.out ps -o args -p <pid> >/dev/null ; done ; strace -o good.out ps -o args -p <pid>

Сравнивая выходные данные fail.outи good.out, я могу видеть, что getdentsсистемный вызов при сбое каким-то образом возвращает намного меньшее число, чем фактическое число процессов в системе (порядка ~ 500 по сравнению с ~ 1100)

grep getdents good.out
  getdents(5, /* 1174 entries */, 32768)  = 32760
  getdents(5, /* 31 entries */, 32768)    = 992
  getdents(5, /* 0 entries */, 32768)     = 0

grep getdents fail.out
  getdents(5, /* 673 entries */, 32768)   = 16728
  getdents(5, /* 0 entries */, 32768)     = 0

... и этот короткий список не включает в себя фактический pid, о котором идет речь, поэтому он не найден.

Вы можете игнорировать этот раздел, ошибки ENOTTY объясняются комментарием dave_thompson ниже и не связаны

Кроме того, при неудачном запуске ENOTTYпоявляются ошибки, которые не появляются при успешном запуске. В начале выхода я вижу

ioctl (1, TIOCGWINSZ, 0x7fffe19db310) = -1 ENOTTY (неподходящий ioctl для устройства) ioctl (1, TCGETS, 0x7fffe19db280) = -1 ENOTTY (неподходящий ioctl для устройства)

И в конце я вижу один

ioctl (1, TCGETS, 0x7fffe19db0d0) = -1 ENOTTY (неподходящий ioctl для устройства)

Сбой ioctlв конце происходит прямо перед psвозвратом, но это происходит после того, как psон уже напечатал пустой набор результатов, поэтому я не уверен, связаны ли они. Я знаю, что они согласованы во всех неудачных выходах strace, которые у меня есть, но не появляются в успешных.

Я абсолютно не знаю, почему getdentsиногда не могу найти полный список процессов, и теперь я дошел до того, что просто собираюсь нанести пластырь на всю вещь, изменив сценарий управления, который проверяет сценарий оболочки вопрос о том, чтобы позвонить во psвторой раз, если первый не помог, но мне было бы интересно узнать, есть ли у кого-нибудь какие-либо идеи о том, что здесь происходит.

В рассматриваемой системе выполняется ядро ​​4.16.13-1.el7.elrepo.x86_64 в CentOS 7 и procps-ng версии 3.3.10-17.el7_5.2.x86_64.

Джеймс
источник
1
К вашему сведению, ioctl имеет отношение к получению настроек терминала (например, первое - найти количество строк и столбцов) - поэтому странно, что они терпят неудачу, но, вероятно, не являются прямой причиной. Это звучит как ошибка ядра ...
Дероберт
2
соответствующее исследование от OpenBSD: https.www.google.com.tedunangst.com/flak/post/…
августа
2
Вы имеете >/dev/nullвызов 'fail' (в цикле), но не вызов 'good', следовательно, ENOTTY на fd 1.
dave_thompson_085
Ох, черт возьми. Спасибо за то, что поймали этого Дейва, это, безусловно, объясняет ENOTTY.
Джеймс
Рад видеть, что я не единственный, у кого есть эта проблема. Способ, которым я могу обойти это, состоит в том, чтобы сделать попытку, которая будет повторяться в случае сбоя команды, но все равно раздражает: /
Джош Коррейя

Ответы:

7

Попробуйте прочитать необходимую информацию непосредственно из /procфайловой системы, а не с помощью такого инструмента, как ps. Вы найдете информацию, которую вы ищете ("args") внутри файла /proc/$pid/cmdline, только разделенные байтами NUL вместо пробелов.

Вы можете использовать эту sedоднострочную строку для получения аргументов процесса $pid:

sed -e 's/\x00\?$/\n/' -e 's/\x00/ /g' "/proc/$pid/cmdline"

Эта команда эквивалентна:

ps -o args= -p "$pid"

(Использование args=in psпропустит заголовок.)

Команда sedсначала ищет последний завершающий байт NUL и заменяет его новой строкой, а после этого заменяет все другие байты NUL (разделяя отдельные аргументы) пробелами, в результате получая тот же формат, который вы видите ps.


Что касается перечисления процессов в системе, psделает это путем перечисления каталогов в /proc, но для этой процедуры есть неотъемлемые условия гонки, поскольку процессы psзапускаются и завершаются во время работы, так что в действительности вы получаете не снимок, а приблизительное значение. В частности, возможно, что psбудут показаны процессы, которые уже завершены к тому времени, когда он показывает свои результаты, или пропущены процессы, которые были запущены во время его работы (но не были возвращены ядром при перечислении содержимого /proc.)

Я всегда предполагал, что если процесс существует перед psзапуском и все еще существует после того, как он завершен, то он не будет пропущен им, я предполагал, что ядро ​​будет гарантировать, что они будут всегда включены, даже если есть много других процессов создается и уничтожается. То, что вы описываете, подразумевает, что это не так. Я по-прежнему скептически отношусь к этому, но, учитывая, что в гонке известны условия гонок ps, я думаю, что по крайней мере правдоподобно, что перечисление идентификаторов PID /procможет пропустить существующий из-за этих гонок.

Это можно было бы проверить, проверив источник ядра Linux, но я этого еще не сделал (пока), поэтому не могу точно сказать, существует ли такое состояние гонки, которое пропустит длительный процесс, так как Вы описываете.


Другая часть - это способ psработы. Даже если вы передаете ему один PID с -pаргументом, он по-прежнему перечисляет все существующие PID, даже если вас интересует только один. В этом случае можно определенно использовать ярлык и пропустить перечисление записей /procи перейти непосредственно к /proc/$pid.

Я не могу сказать, почему это было реализовано таким образом. Возможно, потому что большинство psопций являются «фильтрами» процессов, поэтому реализация -pодного и того же способа была проще , поэтому для быстрого перехода к /proc/$pidбыстрому выбору может потребоваться отдельный путь к коду или дублирование кода ... Другая гипотеза состоит в том, что в некоторых случаях, включая -pдополнительные опции, в конечном итоге требуется листинг, поэтому, возможно, сложно определить, какие именно случаи позволят использовать ярлык, а какие нет.


Это приводит нас к обходному пути, переходя прямо к /proc/$pid, не перечисляя полный набор PID системы, избегая всех известных рас и просто получая необходимую информацию прямо из источника.

Это немного уродливо, но проблема, которую вы описываете, действительно существует, это должен быть надежный способ получить эту информацию.

filbranden
источник
2
Спасибо за этот Filipe, я проголосовал, потому что команда sed полезна (и я изменил наши сценарии, чтобы просто посмотреть в / proc), и потому что я не осознавал, что добавление '=' к ps отбросит заголовок , Я не принял ответ, потому что мне все еще очень любопытно, почему он не видит весь список / proc, и я надеюсь, что кто-то еще знает :)
Джеймс