Анализ зависшего процесса

Решил в кратце описать о зависшем процессе - как определить, что он зависший и при возможности найти причину. Нахождение причины самое сложное, однозначного совета нет и чтобы найти виновника нужно применять серъезные утилиты, работа с которыми требует определенных знаний и навыков.
Цель чисто ознакомительная - ознакомить с основными моментами и утилитами. Все это есть в интернете, но разбросано по разным статьям. Просто приходилось несколько раз этим заниматься, а потому имелись некоторые наброски, с которыми и решил поделиться.
Повторюсь, это небольшая часть, чтобы не теряться и иметь хоть какой то план действий для проведения анализа, что при желании всегда можно расширить, благо интернет пока работает.

Процесс - это объект, который состоит из адресного пространства памяти и набора структур данных. Процессы являются частью операционной системы.
Грубо говоря, процесс это запущенная программа или служба … но следует отметить, что работающая программа содержит один или более процессов.
Также следует отметить, что наряду с процессами есть понятие задача, которую выполняет командный процессор. Задача - это рабочая единица командного процессора. Задачи находятся на более высоком уровне, чем процессы, операционная система ничего о них не знает.

Обычно, если процесс в течение некоторого времени не отвечает на запросы, то принято это событие считать зависанием процесса.
Хотя, если быть точным, для процесса не существует понятия зависания, он не может зависнуть в понимаемом нами смысле. Вот что об этом пишут умные дяди, привожу без перевода, (буду приводить такие цитаты и в дальнейшем - отношение свое к этому высказывать не буду, пусть каждый решает сам)
Unfortunately there is no hung state for a process. Now hung can be deadlock. This is block state. The threads in the process are blocked. The other things could be live lock where the process is running but doing the same thing again and again. This process is in running state. So as you can see there is no definite hung state. As suggested you can use the top command to see if the process is using 100% CPU or lot of memory.
Но мы также будем употреблять термин зависание процесса в его обычном понимании.
Как правило к зависанию процесса приводят следующие основные причины
- нехватка оперативной памяти
- использует 100% CPU
- уход в бесконечный цикл
- блокировки
- можно отнести и дисковые операции ввода/вывода
Команда, позволяющая определить зависание процесса отсутствует. А потому чтобы выяснить, что процесс завис, необходимо задействовать целый комплекс утилит, при условии, конечно, что есть доступ к терминал-эмулятору или консоли. И конечно в результе анализа необходимо будет установить - завис процесс или не завис и установить причину этого зависания.
Привожу опять цитату умных дядей
Если процесс ничем не заблокирован (например, читает всё ему необходимое из кэша), он должен использовать до 100% CPU. Если же он в блокировке из-за IO или проблем с сетью, то нагрузка на процессор должна быть ниже, либо вообще отсутствовать.
1. А потому начинать лучше с самого простого - узнать потребление памяти, нагрузку cpu.
Для этого можно использовать простую утилиту ps
- сортировка по памяти (1-ая команда) или сортировка по нагрузке cpu (2-ая команда)
ps -eo pid,pcpu,pmem,comm | sort -r -k3 | head -5
ps -eo pid,pcpu,pmem,comm | sort -r -k2 | head -5
PS - вместо head -5 можно применить grep <PID>
Но если причина в нехватке памяти, то будут тормоза как при открытии терминала, так и ввода команды, так и при ожидании ответа при выполнении команды. В этом случае рекомендую использовать прямую связь с ядром (использовать волшебные кнопки)
ALT+SysRq+f - вызов oom_kill, чтобы убить самый жрущий процесс (действует не сразу, придется подождать несколько минут)
PS 1 - не забывем, что значение /proc/sys/kernel/sysrq должно быть равно 1.
PS 2 - рекомендую читать логи (обычно запускаю journalctl -f) - oom_kill работает хитро, если он считает, что памяти достаточно, то ни одно приложение убито не будет, просто выдаст сообщение. Привожу пример запуска в нормальном состоянии
arch kernel: sysrq: Manual OOM execution
arch kernel: Purging GPU memory, 40534 pages freed, 2198 pages still pinned.
На любителя можно использовать top, htop, atop, vmstat и др., которые позволяют узнать нагрузки памяти и процессора (некоторые позволяют узать и нагрузку на дисковые операции ввода/вывода).

2. Имеются простые способы узнать/оценить, находится процесс в зависшем состоянии или нет. Правда в поиске виновника это нам не поможет, но хотя бы прояснит немного ситуацию.
2.1 Посмотреть параметр state процесса
можно добавить это в команду ps -eo pid,state,pcpu,pmem,comm | grep <PID>
или сразу обратится к первоисточнику, из которого ps черпает эту информацию
cat /proc/<PID>/status | grep State
2.2 Есть еще два интересных параметр voluntary_ctxt_switches и nonvoluntary_ctxt_switches (показывают сколько раз процессор получал кванты CPU и отдавал назад)
cat /proc/<PID>/status | grep voluntary
Если с течением времени эти параметры не меняются, то это говорит о том, приложение зависло.
2.3 Можно узнать место в коде ядра, где зависло - узнать имя функции, которая привела процесс к состоянию спячки/ожидания.
Для этого можно использовать ту же утилиту ps, но добавив поле wchan
ps -eo pid,pcpu,pmem,comm,status,wchan | grep <PID>
или посмотреть параметр stack
cat /proc/<PID>/stack
Верхняя функция (верхушка стэка) и покажет эту функцию. Что означает эта функция, можно нагуглить.
Есть и другие ядерные вещи, но, имхо, толку от них обычным юзерам мало, нам нужно узнать хотя бы причину, намек, виновника блокировки, если это блокировка.
А потому здесь нужны более серъезные утилиты.
PS 1 - уточнение для утилиты ps в части wchan - это адрес события, которого ожидает процесс. У активного процесса эта колонка пустая.
PS 2 - можно для нахождения значений wchan использовать и такую штуку
cat /proc/<PID>/stat | ./procstat
Например, для активного нормального процесса palemoon
cat /proc/`pidof palemoon`/stat | ./procstat | grep wchan
               wchan: 0
где procstat - небольшая програмка для распарсивания вывода cat /proc/<PID>/stat (имеется исходник procstat.c, который нужно скомпилить).

3. Посмотрели и убедились, что приложение зависло, далее нужно использовать другие утилиты.
Привожу опять цитату умных дядей
Есть весьма важное различие между процессом, который полностью завис, не имея вообще шанса получить квант процессора, и процессом, который постоянно просыпается из состояния ожидания и затем сразу снова засыпает (например, некая операция опроса (poll), которая постоянно завершается по таймауту, а затем процесс по ходу выполнения вызывает её снова и засыпает).
Явный признак такого состояния - 0% CPU, что означает, что процесс находится в каком-то блокирующем системном вызове, который приводит к тому, что ядро усыпляет процесс.
3.1 В этом случае рекомендуют утилиту strace (приаттачится к процессу)
или strace -c -p <PID>  … или strace -i -p <PID>  … или strace -p <PID> … и др.
вообщем на любителя и описывать нюансы работы утилиты strace не буду (всего не опишешь).
Только отмечу один момент - блокировки - в конце вывода будет строка типа - flock(3, LOCK_EX, что говорит о том, что файл, имеющий дескриптор 3, заблокирован.
Нужно узнать что это за файл - ls /proc/<PID>/fd , получим что то типа /tmp/file.lock
и далее находим процесс, удерживающий этот файл - lsof | grep /tmp/file.lock
3.2 Можно использовать и утилиту gdb, но для получения информативности необходимо чтобы пакет исследуемого приложения был собран с отладочной информацией. Хотя если анализировать только стэк, то можно и без отладочной информации - как правило, если трассировка стека не меняется с течением времени (достаточно несколько проверок с периодичностью в несколько минут), то приложение висит. Также на вершине стэка увидим функцию, которая к этому привела.
А вот чтобы выполнить отладку зависшего процесса, необходимо, во 1-ых, чтобы пакет был собран с отладочной информацией и, во 2-ых, иметь навык использования отладки с помощью gdb.

4. И напоследок - иногда наблюдается не зависание, как таковое, а небольшая задержка при выполнении/старте приложения (может доходить до нескольких секунд). В этом случае может помочь комбайн sysdig - описывать не буду, а даю ссылку на один из топиков, в котором был найден виновник, с использованием sysdig (там же можно найти и установку и ссылку на описание).

EDIT 1 - и, конечно, у кого появится что то новенькое, уточняющее неплохо выкладывать здесь же - пусть все будет в одном месте.

..................................................................................
Дополнение/уточнение от 05.06.2019

В части зависания можно придерживаться следующих правил (но не забывать, что бывают и исключения)
Приложение скорее всего заблокировано и ничего не делает если cpu и mem не загружены.
Приложение находится в бесконечном цикле - cpu под 100% и возможно загружена память.
Приложению не хватает памяти - сильное торможение, долгий ответ на запросы, загружена память.

ОЖИДАНИЕ - пассивное состояние процесса, процесс заблокирован (скорее всего на каком-то системном вызове), точнее, процесс не может выполняться, так как ожидает некоторого события, вероятнее всего завершения операции ввода-вывода и менее вероятно получения сообщения от другого процесса или освобождения какого-либо необходимого ему ресурса.
И если процесс висит долго, то, как правило, убить процесс не удается, так как теряется связь ядра с процессом и не срабатывают даже волшебные клавиши. Но пробовать их всеравно нужно, чтобы уточнить наличие/отсутствие связи с ядром.

Дополнение в части определения ядерного кода/функции/ или как говорят системного вызова на котором заввисли.
Вместо cat /proc/<PID>/stack можно также использовать и cat /proc/<PID>/syscall , вывод которого покажет нам номер системного вызова - будет что то типа такого, где 7 (1-ая цифра) это номер системного вызова
7 0x7f209cbe5a40 0x4 0xffffffffffffffff 0x7f2090f56de0 0x0 0x7f2092db3e60 0x7ffe35959f10 0x7f20ada8697b
и далее узнаем название системного вызова по его номеру
grep 7 /usr/include/asm/unistd_64.h
#define __NR_poll 7

В части определения зависшего дескриптора (при использование strace) вместо ls /proc/<PID>/fd можно использовать readlink /proc/<PID>/fd/3

И еще про волшебные кнопки - комбинация Alt+SysRq+t - выведет всю информацию о запущенных процессах и плюс стэк - будет слишком большой вывод, но возможно и пригодится (лучше использовать в паре с journalctl -f)
PS - 't' - show-task-states. Will dump a list of current tasks and their information to your console.
.......................................................................................................................................................................
Уточнение от 06.12.2020

Иногда проще получить информацию о зависании процесса, используя комбинации клавищ Alt+SysRq+KEY, где KEY=f|m|t|w
f - вызов oom_kill (убъет жрущий процесс, потребуется несколько минут)
m - вся инфа о памяти
t - вся инфа о запущенных процессах
w -  список всех заблокированных, ждущих окончания ввода-вывода задач
Информация с большой вероятностью запишется в journal, где ее можно проанализировать при следующей загрузке.
Но чтобы это задействовать, нужно сначала (заранее) установить значение kernel.sysrq=1, для чего прописать в файле /etc/sysctl.d/99-sysctl.conf строчку
kernel.sysrq=1
чтобы изменения вступили в силу в текущей загрузке, необходимо выполнить команду: sudo sysctl -p /etc/sysctl.d/99-sysctl.conf
Примерный анализ логов после применения комбинаций Alt+SysRq+KEY описал в этом топике
Ошибки не исчезают с опытом - они просто умнеют
Спасибо.
Полезная статья, всегда при проблемных ситуациях возникает главный вопрос <<С чего начать!>>
И Ваши статьи большое подспорье в этом.
Сделал небольшое дополнение/уточнение
Ошибки не исчезают с опытом - они просто умнеют
Сделал небольшое уточнение в части комбинаций клавиш с SysRq
Ошибки не исчезают с опытом - они просто умнеют
 
Зарегистрироваться или войдите чтобы оставить сообщение.