Что такое процесс, как он рождается, живёт и завершается — и как администратору их найти и остановить
ps и в PowerShellПрограмма — файл на диске: notepad.exe, /usr/bin/ls. Она ничего не делает, пока её не запустили. Процесс — запущенная программа вместе со всем, что ОС ей выделила: память, открытые файлы, права, время процессора. Одну программу можно запустить несколько раз — получится несколько независимых процессов.
О каждом процессе ядро хранит запись — блок управления процессом (PCB, process control block). В Windows это структура EPROCESS, в Linux — task_struct. В ней:
| Что | Зачем |
|---|---|
| Идентификатор (PID) и родитель (PPID) | Найти процесс, построить дерево |
| Состояние | Выполняется, готов, ждёт |
| Регистры процессора и счётчик команд | Продолжить с того же места после паузы |
| Описание памяти | Какие страницы принадлежат процессу (раздел 07) |
| Открытые файлы и устройства | Дескрипторы, с которыми работают read и write |
| Владелец и права | Что процессу разрешено (раздел 10) |
| Приоритет и учёт времени | Для планировщика (раздел 05) |
Часть PCB Linux показывает в файле /proc/PID/status. Запустим процесс sleep в фоне (символ &) и посмотрим на него:
$ sleep 300 &
[1] 2684809
$ ps -o pid,ppid,stat,etime,comm -p 2684809
PID PPID STAT ELAPSED COMMAND
2684809 2684790 S 00:00 sleep
$ grep -E '^(Name|State|PPid|Threads)' /proc/2684809/status
Name: sleep
State: S (sleeping)
PPid: 2684790
Threads: 1
Процесс получил номер, у него есть родитель — оболочка, из которой его запустили, — состояние «спит» и один поток. Сколько всего процессов и потоков в системе:
$ ps -e --no-headers | wc -l
137
$ ps -eL --no-headers | wc -l
395
PS> (Get-Process).Count
147
PS> (Get-Process | ForEach-Object Threads).Count
1441
Потоков в несколько раз больше, чем процессов: почти каждый процесс многопоточный.
Ядер у процессора единицы, а процессов — сотни. Поэтому в каждый момент выполняются немногие, а остальные чего-то ждут. Классическая модель — пять состояний:
| Состояние | Что значит | Куда переходит |
|---|---|---|
| Новый | Процесс создаётся: ядро заводит PCB, выделяет память | → Готов |
| Готов | Может работать, ждёт свободное ядро | → Выполняется, когда планировщик его выбрал |
| Выполняется | Его команды исполняет процессор | → Готов (вытеснен, кончился квант); → Ожидает (запросил ввод-вывод); → Завершён |
| Ожидает (блокирован) | Ждёт события: данных с диска, нажатия клавиши, сетевого пакета, таймера | → Готов, когда событие произошло |
| Завершён | Отработал; ядро освобождает ресурсы | — |
Из «Ожидает» процесс не может сразу перейти в «Выполняется»: сначала событие делает его готовым, а потом планировщик решает, когда дать ему процессор.
В Linux состояние — первая буква столбца STAT в ps: R — выполняется или готов, S — ожидает (прерываемый сон), D — ожидает ввода-вывода, прервать нельзя, T — остановлен, Z — зомби, I — простаивающий поток ядра. Посчитаем:
$ ps -eo stat --no-headers | cut -c1 | sort | uniq -c
46 I
1 R
92 S
В Windows планируются не процессы, а потоки, и состояние у каждого потока своё: Running — выполняется, Ready — готов, Standby — выбран следующим на ядро, Wait — ожидает, Initialized — создаётся, Terminated — завершён:
PS> Get-Process | ForEach-Object Threads | Group-Object ThreadState | Select-Object Count, Name
Count Name
----- ----
1446 Wait
3 Running
2 Standby
2 Initialized
В обеих системах картина одна: выполняется единица процентов, остальные ждут. Это нормально — большую часть времени программы ждут пользователя, диск или сеть. Поэтому процессор свободного компьютера загружен на 0–5 %.
D в Linux — признак проблемы с диском или сетевой папкой: процессы застряли в ожидании ввода-вывода, и даже kill -9 их не завершит, пока устройство не ответит.Новый процесс всегда создаёт другой процесс — родитель. Проводник создаёт процесс Блокнота, когда вы щёлкаете по ярлыку; оболочка — процесс ls, когда вы вводите команду.
CreateProcess — ядро создаёт пустой процесс и загружает в него указанную программу.fork создаёт копию родителя — тот же код, та же память, — а exec заменяет программу в этой копии на новую. В современном Linux fork выполняется вызовом clone.Посмотрим, как bash выполняет команду ls:
$ strace -f -o trace.txt -e trace=clone,execve,wait4,exit_group bash -c 'ls > /dev/null; echo готово'
готово
$ cat trace.txt
2684828 execve("/usr/bin/bash", ["bash", "-c", "ls > /dev/null; echo \320\263\320\276\321\202\320\276\320\262\320"...], 0x7ffcf94f6ab8 /* 18 vars */) = 0
2684828 clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fbcfc5a7e50) = 2684829
2684828 wait4(-1, <unfinished ...>
2684829 execve("/usr/bin/ls", ["ls"], 0x558e1e2eee70 /* 18 vars */) = 0
2684829 exit_group(0) = ?
2684829 +++ exited with 0 +++
2684828 <... wait4 resumed>[{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 2684829
2684828 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=2684829, si_uid=1000, si_status=0, si_utime=0, si_stime=1 /* 0.01 s */} ---
2684828 wait4(-1, 0x7ffc1db98ad0, WNOHANG, NULL) = -1 ECHILD (Нет дочерних процессов)
2684828 exit_group(0) = ?
2684828 +++ exited with 0 +++
Номера в начале строк — PID процесса, сделавшего вызов. bash (номер в первых строках) сделал clone и получил потомка; потомок выполнил execve("/usr/bin/ls") и стал программой ls; родитель тем временем ждал в wait4. Потомок завершился (exit_group(0)), ядро послало родителю сигнал SIGCHLD, и wait4 вернул код завершения 0 — «успешно». Затем bash сам вывел «готово» и завершился.
Зачем два шага? Между fork и exec потомок может настроить себе окружение: перенаправить вывод в файл, сменить каталог, понизить права. Так оболочка выполняет ls > файл.
Зомби. Завершившийся процесс не исчезает сразу: ядро хранит его код завершения, пока родитель не заберёт его вызовом wait. Процесс в таком состоянии называют зомби (Z). Покажем это программой на Python: потомок завершается сразу, а родитель забирает его код только через секунду:
$ cat zombie.py
import os, time
pid = os.fork()
if pid == 0:
print("потомок", os.getpid(), "завершается")
os._exit(0)
time.sleep(1)
os.system(f"ps -o pid,ppid,stat,comm -p {pid}")
os.waitpid(pid, 0)
print("родитель забрал код возврата потомка")
os.system(f"ps -o pid,ppid,stat,comm -p {pid}")
$ python3 zombie.py
PID PPID STAT COMMAND
2684854 2684853 Z python3
PID PPID STAT COMMAND
родитель забрал код возврата потомка
Пока родитель не вызвал waitpid, потомок был зомби; после — исчез совсем. Зомби не занимает памяти и процессора, только строку в таблице процессов. Но если программа с ошибкой порождает зомби тысячами, таблица заканчивается и новые процессы не создаются. Лечат это перезапуском родителя: его потомков-сирот «усыновляет» процесс номер 1, а он забирает коды сразу.
У каждого процесса есть родитель, поэтому процессы образуют дерево. В Linux его рисует pstree. Ключ -s показывает цепочку предков — от кого произошла оболочка, в которой мы работаем:
$ pstree -A -s 2684790
systemd---sshd---sshd-session---sshd-session---bash---bash-+-bash---pstree
|-sed
`-sleep
$ pstree -A -p 2535644
postgres(2535644)-+-postgres(2535645)
|-postgres(2535646)
|-postgres(2535648)
|-postgres(2535649)
`-postgres(2535650)
Цепочка читается слева направо: systemd (PID 1) запустил службу SSH, она — сеанс подключения, сеанс — оболочку bash, а оболочка — pstree. Сервер СУБД PostgreSQL — главный процесс и его рабочие процессы-потомки.
В Windows родитель хранится в свойстве ParentProcessId. Функция ниже поднимается от процесса к родителям, пока они есть:
PS> function Parents($id) { while ($id) { $q = Get-CimInstance Win32_Process -Filter "ProcessId=$id"; if (-not $q) { break }; "{0,6} {1}" -f $q.ProcessId, $q.Name; $id = $q.ParentProcessId } }
PS> Parents 968
968 svchost.exe
836 services.exe
700 wininit.exe
Это та цепочка загрузки из раздела 02: wininit.exe запустил диспетчер служб services.exe, а он — svchost.exe, в котором работают службы. Выше wininit подняться нельзя: его создал smss.exe, который завершился после загрузки.
Разница с Linux: в Windows связь «родитель — потомок» почти ничего не значит после запуска. Завершение родителя не влияет на потомков, а номер умершего родителя может достаться новому процессу. Поэтому ParentProcessId — подсказка, а не гарантия.
Поток — последовательность команд, которую планировщик ставит на ядро процессора. В каждом процессе есть хотя бы один поток; потоки одного процесса делят его память и открытые файлы, а свои у каждого — только регистры и стек.
| Процесс | Поток | |
|---|---|---|
| Память | Своя, другие процессы её не видят | Общая с потоками своего процесса |
| Создание и переключение | Дороже: новое адресное пространство | Дешевле |
| Ошибка | Падает только этот процесс | Падает весь процесс со всеми потоками |
| Обмен данными | Через ОС: файлы, каналы, сокеты | Напрямую через общую память — быстро, но нужна синхронизация (раздел 06) |
Потоки нужны, чтобы делать несколько дел одновременно: браузер загружает страницу в одном потоке и отвечает на щелчки в другом, сервер обслуживает каждого клиента своим потоком, а на многоядерном процессоре потоки одного процесса работают параллельно.
Даже у консольной программы ping в Windows несколько потоков — служебные потоки создаёт сама система. Почти все они ждут:
PS> $p = Start-Process ping -ArgumentList "-n 60 127.0.0.1" -WindowStyle Hidden -PassThru
PS> $p.Id
3932
PS> Get-Process -Id 3932 | Select-Object Id, ProcessName, StartTime, @{n='Потоков'; e={$_.Threads.Count}}
Id ProcessName StartTime Потоков
-- ----------- --------- -------
3932 PING 31.10.2026 10:21:35 5
PS> Get-CimInstance Win32_Process -Filter 'ProcessId=3932' | Select-Object ProcessId, ParentProcessId, CommandLine
ProcessId ParentProcessId CommandLine
--------- --------------- -----------
3932 1168 "C:\WINDOWS\system32\PING.EXE" -n 60 127.0.0.1
PS> (Get-Process -Id 3932).Threads | Select-Object Id, ThreadState, WaitReason
Id ThreadState WaitReason
-- ----------- ----------
5312 Wait ExecutionDelay
8124 Wait EventPairLow
5272 Wait EventPairLow
9384 Wait EventPairLow
9872 Wait EventPairLow
В Linux поток — тоже задача ядра со своим номером; потоки процесса перечислены в /proc/PID/task, а их число — столбец NLWP:
$ ps -o pid,nlwp,comm -C dockerd,containerd,cron
PID NLWP COMMAND
579 1 cron
1556917 12 containerd
1601490 10 dockerd
$ ls /proc/$(pgrep -o dockerd)/task | head -4
1601490
1601491
1601493
1601494
Номер первого потока совпадает с PID процесса — это его главный поток.
Диспетчер задач (Ctrl+Shift+Esc): вкладка «Процессы» группирует по программам, вкладка «Подробности» показывает каждый процесс с PID, состоянием и пользователем. Щелчок правой кнопкой по заголовку таблицы → «Выбрать столбцы» добавляет «Потоки», «Дескрипторы», «Командная строка». Более подробный инструмент — Монитор ресурсов (resmon): какие файлы и сетевые соединения у процесса.
В PowerShell — Get-Process. Шесть процессов, занимающих больше всего памяти:
PS> Get-Process | Sort-Object WorkingSet64 -Descending | Select-Object -First 6 ProcessName, Id, @{n="Память, МБ"; e={[math]::Round($_.WorkingSet64/1MB)}}, @{n="Потоков"; e={$_.Threads.Count}}
ProcessName Id Память, МБ Потоков
----------- -- ---------- -------
explorer 3196 266 65
MsMpEng 3144 198 33
powershell 8784 162 11
StartMenuExperienceHost 2464 146 16
msedge 2908 124 69
msedgewebview2 1068 121 52
В Windows много процессов svchost.exe — в каждом работает одна или несколько служб. Какие именно, показывает tasklist /svc; без него не понять, какую службу «держит» нагруженный svchost:
PS> tasklist /fi "imagename eq svchost.exe" /svc | Select-Object -First 8
Имя образа PID Службы
========================= ======== ============================================
svchost.exe 968 BrokerInfrastructure, DcomLaunch, PlugPlay,
Power, SystemEventsBroker
svchost.exe 564 RpcEptMapper, RpcSs
svchost.exe 880 LSM
svchost.exe 1152 BDESVC
В Linux то же показывает top: в шапке — время работы, нагрузка, число процессов по состояниям, память; ниже — процессы, отсортированные по загрузке процессора. В режиме диалога top обновляется каждые 3 секунды; q — выход, M — сортировка по памяти, k — завершить процесс.
$ top -b -n 1 | head -12
top - 10:21:38 up 66 days, 44 min, 19 users, load average: 0,00, 0,02, 0,00
Tasks: 137 total, 1 running, 136 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0,0 us, 0,0 sy, 0,0 ni,100,0 id, 0,0 wa, 0,0 hi, 0,0 si, 0,0 st
MiB Mem : 3921,8 total, 561,4 free, 691,6 used, 2995,5 buff/cache
MiB Swap: 1722,0 total, 868,8 free, 853,2 used. 3230,2 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 24504 9432 5948 S 0,0 0,2 4:38.58 systemd
2 root 20 0 0 0 0 S 0,0 0,0 0:02.79 kthreadd
3 root 20 0 0 0 0 S 0,0 0,0 0:00.00 pool_wo+
4 root 0 -20 0 0 0 I 0,0 0,0 0:00.00 kworker+
5 root 0 -20 0 0 0 I 0,0 0,0 0:00.00 kworker+
| Задача | Windows | Linux |
|---|---|---|
| Список процессов | Get-Process, tasklist | ps -ef, ps aux |
| Нагрузка в реальном времени | Диспетчер задач, resmon | top, htop |
| Найти по имени | Get-Process -Name explorer | pgrep -a sshd |
| Родитель и дерево | Get-CimInstance Win32_Process | pstree, ps -o ppid |
| Командная строка процесса | столбец CommandLine | cat /proc/PID/cmdline, ps -o args |
Процесс завершают двумя способами: вежливо — просят завершиться, и программа успевает сохранить данные, — и принудительно — ядро уничтожает процесс сразу.
В Windows taskkill /pid N без ключей посылает окнам программы сообщение «закрыться» — как щелчок по крестику. У ping, запущенного без окна, закрывать нечего, поэтому вежливо не получится; ключ /f завершает принудительно:
PS> taskkill /pid 3932
Ошибка: Не удается завершить процесс с идентификатором 3932.
Причина: Данный процесс может быть прерван только насильственно ( с параметром /f ).
PS> taskkill /pid 3932 /f
Успешно: Процесс, с идентификатором 3932, успешно завершен.
PS> Get-Process -Id 3932
Get-Process : Не удается найти процесс с идентификатором процесса 3932.
В PowerShell то же делает Stop-Process -Id N; он сразу завершает принудительно и для чужих процессов спрашивает подтверждения.
В Linux процессу посылают сигнал — короткое уведомление от ядра. Программа может перехватить сигнал и обработать его по-своему, кроме SIGKILL и SIGSTOP:
| Сигнал | Номер | Что делает | Как послать |
|---|---|---|---|
SIGTERM | 15 | Вежливо просит завершиться — по умолчанию у kill | kill PID |
SIGINT | 2 | Прерывание с клавиатуры | Ctrl+C |
SIGKILL | 9 | Уничтожить немедленно, перехватить нельзя | kill -9 PID |
SIGSTOP / SIGCONT | 19 / 18 | Приостановить и продолжить | kill -STOP, Ctrl+Z (близкий SIGTSTP) |
SIGHUP | 1 | Терминал закрыт; службы по нему перечитывают настройки | kill -HUP PID |
$ kill -STOP 2684809; ps -o pid,stat,comm -p 2684809
PID STAT COMMAND
2684809 T sleep
$ kill -CONT 2684809; ps -o pid,stat,comm -p 2684809
PID STAT COMMAND
2684809 S sleep
$ kill 2684809; sleep 0.2; ps -o pid,stat,comm -p 2684809
PID STAT COMMAND
$ kill -l | head -4
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
После SIGSTOP состояние стало T, после SIGCONT — снова S, после SIGTERM процесса больше нет: ps вывел только заголовок.
taskkill /f, kill -9) не даёт программе сохранить данные и удалить временные файлы — база данных после этого может восстанавливаться долго. Сначала вежливо, принудительно — только если процесс не отвечает. Системные процессы Windows (csrss.exe, wininit.exe) завершать нельзя: система аварийно остановится с синим экраном.CreateProcess, Linux — fork и exec; зомби — завершённый процесс, код которого не забрал родительtaskkill, kill), принудительно (/f, -9) — только зависшие процессы