а код в процессе 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()
), его приоритет и есть то, что понимается под «приоритетом процесса».
Создание нового процесса
Созданию процессов (имеется в виду создание процесса из программного кода) посвящено столько описаний [1-9], что детальное рассмотрение этого вопроса было бы лишь пересказом. Поэтому мы ограничимся только беглым перечислением этих возможностей, тем более что в ходе обсуждения нас главным образом интересуют не сами процессы, а потоки, заключенные в адресных пространствах процессов.
Использование командного интерпретатора
Самый простой способ — запустить из программного кода дочернюю копию командного интерпретатора, которому затем передать команду запуска процесса. Для этого используется вызов:
int system(const char* command);
где command
— текстовая строка, содержащая команду, которую предполагается выполнить ровно в том виде, в котором мы вводим ее командному интерпретатору с консоли.
Функция имеет еще одну специфическую форму вызова, когда в качестве command
задается NULL
. По коду возврата это позволяет выяснить, присутствует ли (и доступен ли) командный интерпретатор в системе (возвращается 0, если интерпретатор доступен).
На время выполнения вызова system()
вызывающий процесс приостанавливается. После завершения порожденного процесса функция возвращает код завершения вновь созданной копии интерпретатора (или -1, если сам интерпретатор не может быть выполнен), то есть младшие 8 бит возвращаемого значения содержат код завершения выполняемого процесса. Возврат вызова system ()
может анализироваться макросом WEXITSTATUS()
, определенным в файле <sys/wait.h>
. Например:
#include <sys/wait.h>
int main(void) {
int rc = system('ls');
if (rc == -1) cout << 'shell could not be run' << endl;
else
cout << 'result of running command is ' << WEXITSTATUS(rc) << endl;
return EXIT_SUCCESS;
}
Эта функция использует вызов spawnlp()
для загрузки новой копии командного интерпретатора, то есть «внутреннее устройство» должно быть в общем виде вам понятно. Особенностью QNX-реализации является то, что spawnlp()
всегда использует вызов /bin/sh
, независимо от конкретного вида интерпретатора, устанавливаемого переменной окружения SHELL (ksh, bash…). Это обеспечивает независимость поведения родительского приложения от конкретных установок системы, в которой это приложение выполняется.
Вызов system()
является не только простым, но и очень наглядным, делающим код легко читаемым. Программисты часто относятся к нему с пренебрежением[10] , отмечая множество его недостатков. Однако в относительно простых случаях это может быть оптимальным решением, а недостатки не так и существенны:
• Используя копию командного интерпретатора, вызов system()
может