waitpid(pid, NULL, WEXITED);
exit(EXIT_SUCCESS);
}
}
if (pid == -1) {
cout << "exit with process number: << n << " - " << flush;
perror(NULL);
}
}
Этот достаточно непривычный по внешнему виду код дает нам следующий результат:
# pn
exit with process number: 1743 - Not enough memory
Системному сообщению о недостатке памяти достаточно трудно верить: чуть меньше 4 Кбайт программного кода в своих 1743 «реинкарнациях» требуют не более 6,6 Мбайт для своего размещения при свободных более 230 Мбайт в системе, в которой мы испытывали это приложение. Оставим это на совести создателей ОС QNX.
В продолжение нашей основной темы любопытно рассмотреть результаты вывода команды
pidin
, а именно последнюю ее строку с информацией о последнем запущенном в системе процессе:
• до запуска обсуждаемого приложения:
4/366186 1 /photon/bin/phcalc 10r REPLY 241691
• и после его завершения:
54652947 1 bin/pidin 10r REPLY 1
Легко видеть, что разница PID, равная 54652947 – 47366186 = 7286761, никак не является числом активированных на этом временном промежутке процессов, которое равно 1743. Поэтому к численным значениям PID нужно относиться с заметной осторожностью: это не просто инкрементированное значение числа запущенных процессов, схема формирования PID заметно сложнее.
В любом случае мы можем принять, что в ОС QNX Neutrino 6.2.1, как и в других «канонических» UNIX, количество процессов (если, конечно, эта ОС не дает нам более вразумительных оценок) ограничено цифрой 4095. Видно, что общее количество независимых потоков исполнения в системе может достигать совершенно ошеломляющей цифры. Но как бы много потоков мы ни создавали, им все равно придется конкурировать за доступ к самому главному ресурсу — процессору. В настоящее время реализованные в QNX дисциплины диспетчеризации работают над суммарным полем всех потоков в системе (рис. 2.1): если в системе выполняется Nпроцессов и i-й процесс реализует M i потоков, то в очередях диспетчеризации одновременно задействовано
управляемых объектов (потоков).
Рис. 2.1. Диспетчеризация процессов
На рис. 2.1 изображены два процесса, выполняющиеся под управлением системы. Каждый процесс создал внутри себя различное количество потоков равного приоритета. Обратите внимание, что фактическая диспетчеризация производится не между процессами, а между потоками процессов, даже если иногда для простоты говорят «диспетчеризация процессов». Потоки объединены в циклическую очередь диспетчеризации, и пунктирная линия показывает порядок, в котором (в направлении стрелки) они будут поочередно получать квант времени.
Если ни один из потоков не будет выполнять блокирующих операций (
read()
,
delay()
,
accept()
,
MsgSend()
и множество других), что реально встречается крайне редко, то показанный порядок «следования» потоков при диспетчеризации будет сохраняться неограниченно долго. Как только поток выполнит блокирующий вызов, он будет удален из очереди готовых к выполнению потоков, а после завершения вызова возвращен в очередь, причем (что характерно!) в голову очереди. После этого топология «петли» (порядок чередования), показанной на рисунке пунктиром, может произвольным образом измениться.
Из рисунка хорошо видно, что при диспетчеризации «в рамках системы» (об этом мы будем говорить позже) два запущенных процесса будут выполняться в неравных условиях: на каждый полный цикл диспетчеризации программный код, выполняющийся в рамках процесса А, будет получать 1 квант времени, а код в процессе B — 3 кванта.
Примечание
Стандарт POSIX, определяя названную стратегию диспетчеризации константой
PTHREAD_SCOPE_SYSTEM
, предусматривает и другую стратегию, обозначаемую константой
PTHREAD_SCOPE_PROCESS
, когда потоки конкурируют за процессорный ресурс в пределах процесса, к которому они принадлежат (в Sun Solaris первой стратегии соответствуют «bound thread», а второй — «unbound thread»). Реализация стратегии
PTHREAD_SCOPE_PROCESS
связана с серьезными трудностями. Насколько нам известно, в настоящее время из числа широко распространенных ОС она реализована только в Sun Solaris. В QNX для совместимости с POSIX даже присутствуют системные вызовы относительно стратегии диспетчеризации:
int pthread_attr_setscope(pthread_attr_t* attr, int scope);
int pthread_attr_getscope(const pthread_attr_t* attr, int* scope);
но в качестве параметра scope они допускают... только значение
PTHREAD_SCOPE_SYSTEM
и на поведение потоков никакого влияния не оказывают.
PID (Process ID) — идентификатор процесса, присваиваемый процессу при его создании, например вызовом
fork()
. PID позволяет системе однозначно идентифицировать каждый процесс. При создании нового процесса ему присваивается первый свободный (то есть не ассоциированный ни с каким процессом) идентификатор. Присвоение происходит по возрастающей: идентификатор нового процесса больше идентификатора процесса, созданного перед ним. Когда последовательность идентификаторов достигает максимального значения (4095), следующий процесс получает минимальный свободный (за счет завершившихся процессов) PID, и весь цикл повторяется снова. Значения PID нумеруются, начиная с 0. Процесс, загружавший ОС, является родительским для всех процессов в системе и его PID = 0.
Из других важных атрибутов процесса отметим [9]:
• PPID (Parent Process ID) — PID процесса, породившего данный процесс. Таким образом, все процессы в системе включены в единую древовидную иерархию.
• TTY — терминальная линия: терминал или псевдотерминал, ассоциированный с процессом. Если процесс становится процессом-демоном, то он отсоединяется от своей терминальной линии и не имеет ассоциированной терминальной линии. (Запуск процесса как фонового — знак «&» в конце командной строки — не является достаточным основанием для отсоединения процесса от терминальной линии.)
• RID и EUID — реальный и эффективный идентификаторы пользователя. Эффективный идентификатор служит для определения прав доступа процесса к системным ресурсам (в первую очередь к файловым системам). Обычно RID и EUID совпадают, но установка флага SUID для исполняемого файла процесса позволяет расширить полномочия процесса.
• RGID и EGID — реальный и эффективный идентификаторы группы пользователей. Как и в случае идентификаторов пользователя, EGID не совпадает с RGID, если установлен флаг SGID для исполняемого файла процесса.
Часто в качестве атрибутов процесса называют и приоритет выполнения. Однако приоритет является атрибутом не процесса (процесс — это статическая субстанция, контейнер), а потока, но если поток единственный (главный, порожденный функцией
main()
), его приоритет и есть то, что понимается под «приоритетом процесса».