Процессы в Linux
Данной теме посвящено много статей, но в Сети мало сугубо практических статей. О какой именно практике идет речь, вы узнаете прочитав эту статью. Правда, одной только практикой нам не обойтись — вдруг вы не читали всю эту серую массу теории, которую можно найти в Сети.
- программа на стадии выполнения
- «объект», которому выделено процессорное время
- асинхронная работа
Выполнение — это активное состояние, во время которого процесс обладает всеми необходимыми ему ресурсами. В этом состоянии процесс непосредственно выполняется процессором.
Ожидание — это пассивное состояние, во время которого процесс заблокирован, он не может быть выполнен, потому что ожидает какое-то событие, например, ввода данных или освобождения нужного ему устройства.
Готовность — это тоже пассивное состояние, процесс тоже заблокирован, но в отличие от состояния ожидания, он заблокирован не по внутренним причинам (ведь ожидание ввода данных — это внутренняя, «личная» проблема процесса — он может ведь и не ожидать ввода данных и свободно выполняться — никто ему не мешает), а по внешним, независящим от процесса, причинам. Когда процесс может перейти в состояние готовности? Предположим, что наш процесс выполнялся до ввода данных. До этого момента он был в состоянии выполнения, потом перешел в состояние ожидания — ему нужно подождать, пока мы введем нужную для работы процесса информацию. Затем процесс хотел уже перейти в состояние выполнения, так как все необходимые ему данные уже введены, но не тут-то было: так как он не единственный процесс в системе, пока он был в состоянии ожидания, его «место под солнцем» занято — процессор выполняет другой процесс. Тогда нашему процессу ничего не остается как перейти в состояние готовности: ждать ему нечего, а выполняться он тоже не может.
Из состояния готовности процесс может перейти только в состояние выполнения. В состоянии выполнения может находится только один процесс на один процессор. Если у вас n-процессорная машина, у вас одновременно в состоянии выполнения могут быть n процессов.
Из состояния выполнения процесс может перейти либо в состояние ожидания или состояние готовности. Почему процесс может оказаться в состоянии ожидания, мы уже знаем — ему просто нужны дополнительные данные или он ожидает освобождения какого-нибудь ресурса, например, устройства или файла. В состояние готовности процесс может перейти, если во время его выполнения, квант времени выполнения «вышел». Другими словами, в операционной системе есть специальная программа — планировщик, которая следит за тем, чтобы все процессы выполнялись отведенное им время. Например, у нас есть три процесса. Один из них находится в состоянии выполнения. Два других — в состоянии готовности. Планировщик следит за временем выполнения первого процесса, если «время вышло», планировщик переводит процесс 1 в состояние готовности, а процесс 2 — в состояние выполнения. Затем, когда, время отведенное, на выполнение процесса 2, закончится, процесс 2 перейдет в состояние готовности, а процесс 3 — в состояние выполнения.
Диаграмма модели трех состояний представлена на рисунке 1.
Рисунок 1. Модель трех состояний
Более сложная модель — это модель, состоящая из пяти состояний. В этой модели появилось два дополнительных состояния: рождение процесса и смерть процесса. Рождение процесса — это пассивное состояние, когда самого процесса еще нет, но уже готова структура для появления процесса. Как говорится в афоризме: «Мало найти хорошее место, надо его еще застолбить», так вот во время рождения как раз и происходит «застолбление» этого места. Смерть процесса — самого процесса уже нет, но может случиться, что его «место», то есть структура, осталась в списке процессов. Такие процессы называются зобми и о них мы еще поговорим в этой статье.
Диаграмма модели пяти состояний представлена на рисунке 2.
Рисунок 2. Модель пяти состояний
- Создание процесса — это переход из состояния рождения в состояние готовности
- Уничтожение процесса — это переход из состояния выполнения в состояние смерти
- Восстановление процесса — переход из состояния готовности в состояние выполнения
- Изменение приоритета процесса — переход из выполнения в готовность
- Блокирование процесса — переход в состояние ожидания из состояния выполнения
- Пробуждение процесса — переход из состояния ожидания в состояние готовности
- Запуск процесса (или его выбор) — переход из состояния готовности в состояние выполнения
- Присвоить процессу имя
- Добавить информацию о процессе в список процессов
- Определить приоритет процесса
- Сформировать блок управления процессом
- Предоставить процессу нужные ему ресурсы
Подробнее о списке процессов, приоритете и обо всем остальном мы еще поговорим, а сейчас нужно сказать пару слов об иерархии процессов. Процесс не может взяться из ниоткуда: его обязательно должен запустить какой-то процесс. Процесс, запущенный другим процессом, называется дочерним (child) процессом или потомком. Процесс, который запустил процесс называется родительским (parent), родителем или просто — предком. У каждого процесса есть два атрибута — PID (Process ID) — идентификатор процесса и PPID (Parent Process ID) — идентификатор родительского процесса.
Процессы создают иерархию в виде дерева. Самым «главным» предком, то есть процессом, стоящим на вершине этого дерева, является процесс init (PID=1).
На мой взгляд, приведенной теории вполне достаточно, чтобы перейти к практике, а именно — «пощупать» все состояния процессов. Конечно, мы не рассмотрели системные вызовы fork(), exec(), exit(), kill() и многие другие, но в Сети предостаточно информации об этом. Тем более, что про эти вызовы вы можете прочитать в справочной системе Linux, введя команду man fork. Правда, там написано на всеми любимом English, так что за переводом (если он вам нужен) все-таки придется обратиться за помощью к WWW.
Для наблюдения за процессами мы будем использовать программу top.
15:03:11 up 58 min, 4 users, load average: 0,02, 0,01, 0,00 52 processes: 51 sleeping, 1 running, 0 zombie, 0 stopped CPU states: 0,8% user, 0,6% system, 0,0% nice, 0,0% iowait, 98,3% idle Mem: 127560k av, 124696k used, 2864k free, 0k shrd, 660k buff 13460k active, 17580k inactive Swap: 152576k av, 8952k used, 143624k free 28892k cached PID USER PRI NI SIZE RSS SHARE STAT %CPU %MEM TIME COMMAND 3097 den 15 0 1128 1128 832 R 2,8 0,8 0:00 top 1 root 8 0 120 84 60 S 0,0 0,0 0:04 init 2 root 12 0 0 0 0 SW 0,0 0,0 0:00 keventd 3 root 19 19 0 0 0 SWN 0,0 0,0 0:00 ksoftirqd_CPU0 .
Полный вывод программы я по понятным причинам урезал. Рассмотрим по порядку весь вывод программы. В первой строке программа сообщает текущее время, время работы системы ( 58 min), количество зарегистрированных (login) пользователей (4 users), общая средняя загрузка системы (load average).
Примечание. Общей средней загрузкой системы называется среднее число процессов, находящихся в состоянии выполнения (R) или в состоянии ожидания (D). Общая средняя загрузка измеряется каждые 1, 5 и 15 минут.
Во второй строке вывода программы top сообщается, что в списке процессов находятся 52 процесса, из них 51 спит (состояние готовности или ожидания), 1 выполняется (у меня только 1 процессор), 0 процессов зомби и 0 остановленных процессов.
В третьей-пятой строках приводится информация о загрузке процессора, использования памяти и файла подкачки. Нас данная информация не очень интересует, поэтому переходим сразу к таблице процессов.
В таблице отображается различная информация о процессе. Нас сейчас интересуют колонки PID (идентификатор процесса), USER (пользователь, запустивший процесс), STAT (состояние процесса) и COMMAND (команда, которая была введена для запуска процесса).
- R — процесс выполняется или готов к выполнению (состояние готовности)
- D — процесс в «беспробудном сне» — ожидает дискового ввода/вывода
- T — процесс остановлен (stopped) или трассируется отладчиком
- S — процесс в состоянии ожидания (sleeping)
- Z — процесс-зобми
- < — процесс с отрицательным значением nice
- N — процесс с положительным значением nice (о команде nice мы поговорим позже)
Давайте просмотрим, когда же процесс находится в каждом состоянии. Создайте файл process — это обыкновенный bash-сценарий
#!/bin/bash x=1 while [ $x -lt 10 ] do x=2 done
Сделайте этот файл исполнимым chmod +x ./process и запустите его ./process. Теперь перейдите на другую консоль (ALT + Fn) и введите команду ps -a | grep process. Вы увидите следующий вывод команды ps:
Данный вывод означает, что нашему процессу присвоен идентификатор процесса 4035. Теперь введите команду top -p 4035
15:30:15 up 1:25, 6 users, load average: 0,44, 0,19, 0,07 1 processes: 0 sleeping, 1 running, 0 zombie, 0 stopped CPU states: 2,3% user, 0,6% system, 0,0% nice, 0,0% iowait, 96,8% idle Mem: 127560k av, 124496k used, 3064k free, 0k shrd, 1208k buff 15200k active, 16400k inactive Swap: 152576k av, 15676k used, 136900k free 27548k cached PID USER PRI NI SIZE RSS SHARE STAT %CPU %MEM TIME COMMAND 4035 den 15 0 1320 1320 988 R 99,9 1,0 0:31 process
Обратите внимание на колонку состояния нашего процесса. Она содержит значение R, которое означает, что в данный момент выполняется процесс с номером 4035.
Теперь приостановим наш процесс — состояние T. Перейдите на консоль, на которой запущен ./process и нажмите Ctrl + Z. Вы увидите сообщение Stopped.
PID USER PRI NI SIZE RSS SHARE STAT %CPU %MEM TIME COMMAND 4035 den 9 0 1320 1320 988 T 0,0 1,0 0:51 process
Теперь попробуем «усыпить» наш процесс. Для этого нужно сначала «убить» его: kill 4035. Затем добавить перед циклом while в сценарии ./process строку sleep 10m, которая означает, что процесс будет спать 10 минут. После этого опять запустите команду ps -a | grep process, чтобы узнать PID процесса, а затем — команду top -p PID. Вы увидите в колонке состояния букву S, что означает, что процесс находится в состоянии ожидания или готовности — попросту говоря «спит».
Мы вплотную подошли к самому интересному — созданию процесса-зомби. Во многих статьях, посвященных процессам, пишется «зомби = не жив, не мертв». А что это означает на самом деле? При завершении процесса должна удаляться его структура из списка процессов. Иногда процесс уже завершился, но его имя еще не удалено из списка процессов. В этом случае процесс становится зомби — его уже нет, но мы его видим в таблице команды top. Такое может произойти, если процесс-потомок (дочерний процесс) завершился раньше, чем этого ожидал процесс-родитель. Сейчас мы напишем программу, порождающую зомби, который будет существовать 8 секунд. Процесс-родитель будет ожидать завершения процесса-потомка через 10 секунд, а процесс-потомок завершить через 2 секунды.
#include <unistd.h> #include <signal.h> #include <stdlib.h> #include <sys/wait.h> #include <stdio.h> int main() < int pid; int status, died; pid=fork(); switch(pid) < case -1: printf("can't fork\n"); exit(-1); case 0 : printf(" I'm the child of PID %d\n", getppid()); printf(" My PID is %d\n", getpid()); // Ждем 2 секунды и завершаемся, следующую строку я закомментировал // чтобы зомби "прожил" на 2 секунды больше // sleep(2); exit(0); default: printf("I'm the parent.\n"); printf(" My PID is %d\n", getpid()); // Ждем завершения дочернего процесса через 10 секунд, а потом убиваем его sleep(10); if (pid & 1) kill(pid,SIGKILL); died= wait(&status); >>
Для компиляции данной программы нам нужен компилятор gcc:
Для тех, у кого не установлен компилятор, скомпилированная программа доступна отсюда.
После того, как программа будет откомпилирована, запустите ее: ./zombie. Программа выведет следующую информацию:
I'm the parent My PID is 1147 I'm the child of PID 1147 My PID is 1148
Запомните последний номер и быстро переключайтесь на другую консоль. Затем введите команду top -p 1148
16:04:22 up 2 min, 3 users, load average: 0,10, 0,10, 0,04 1 processes: 0 sleeping, 0 running, 1 zombie, 0 stopped CPU states: 4,5% user, 7,6% system, 0,0% nice, 0,0% iowait, 87,8% idle Mem: 127560k av, 76992k used, 50568k free, 0k shrd, 3872k buff 24280k active, 19328k inactive Swap: 152576k av, 0k used, 152576k free 39704k cached PID USER PRI NI SIZE RSS SHARE STAT %CPU %MEM TIME COMMAND 1148 den 17 0 0 0 0 Z 0,0 0,0 0:00 zombie <defunct>
Мы видим, что в списке процессов появился 1 зомби (STAT=Z), который проживет аж 10 секунд.
Мы уже рассмотрели все возможные состояния процессов. Осталось только рассмотреть команду для повышения приоритета процесса — это команда nice. Повысить приоритет команды может только пользователь root, указав соответствующий коэффициент понижения. Для увеличения приоритета нужно указать отрицательный коэффициент, например, nice -5 process