Поделиться в Facebook Поделиться ВКонтакте Поделиться в LinkedIn Опубликовать в Twitter

Менеджер процессов

Эта глава охватывает следующие темы:

Введение

Обязанности Менеджера процессов

Менеджер процессов тесно взаимодействует с Микроядром, чтобы обеспечить услуги, составляющие сущность операционной системы. Хотя он и является единственным процессом, который использует то же адресное пространство, что и Микроядро, Менеджер процессов выполняется как истинный процесс. И он, как и все остальные процессы, подвергается диспетчеризации со стороны Ядра и использует предоставляемые Микроядром примитивы передачи сообщений для связи с другими процессами в системе.

Менеджер процессов отвечает за создание новых процессов в системе и за управление основными ресурсами, связанными с процессом. Все эти услуги предоставляются посредством сообщений. Так, например, если процесс хочет породить новый процесс, он делает это, посылая сообщение с указанием атрибутов создаваемого процесса. Обратите внимание, что т.к. сообщения передаются по сети, вы можете легко создать процесс на другом узле сети, послав сообщение Менеджеру процессов на этом узле.

Примитивы создания процессов

QNX поддерживают три примитива создания процесса:

  • fork();
  • exec();
  • spawn().

Примитивы fork() и exec() определены стандартом POSIX, а примитив spawn() реализован только в QNX.

Примитив fork()

Примитив fork() создает новый процесс, который является точной копией вызвавшего его процесса. Новый процесс использует тот же самый код, что и породивший его процесс, и наследует копию всех данных родительского процесса.

Примитив exec()

Примитив exec() заменяет вызвавший процесс новым. После успешного вызова exec() возврата не происходит, т.к. образ вызывающего процесса заменяется образом нового процесса. Обычной практикой в POSIX-системах для создания нового процесса - без удаления вызывающего процесса - является сначала вызов fork(), а затем вызов exec() из порожденного процесса.

Примитив spawn()

Примитив spawn() создает новый процесс как потомок вызывающего процесса. С его помощью можно избежать вызовов fork() и exec(), используя более быстрый и эффективный способ создания новых процессов. В отличие от fork() и exec(), которые по своей природе выполняются на том же самом узле, что и вызывающий процесс, примитив spawn() может создавать процессы на любом узле сети.

Наследование

Когда с помощью одного из трех рассмотренных выше примитивов задается новый процесс, он наследует многое из своего окружения от родителя. Это сведено в следующую таблицу:

Наследуемый параметрfork()exec()spawn()
Идентификатор процессанетданет
Открытые файлыдапо выбору*по выбору
Блокировка файловнетданет
Ожидающие сигналынетданет
Маска сигналовдапо выборупо выбору
Игнорируемые сигналыдапо выборупо выбору
Обработчики сигналовданетнет
Переменные окружениядапо выборупо выбору
Идентификатор сеансададапо выбору
Группа процессададапо выбору
Реальные UID, GIDдадада
Эффективные UID, GIDдапо выборупо выбору
Текущий рабочий каталогдапо выборупо выбору
Маска создания файловдадада
Приоритетдапо выборупо выбору
Алгоритм диспетчеризациидапо выборупо выбору
Виртуальные каналынетнетнет
Символьные именанетнетнет
Таймеры реального временинетнетнет

*по выбору: вызывающий процесс может по необходимости выбрать - да или нет.

Жизненный цикл процесса

Процесс проходит через четыре стадии:

  1. Создание
  2. Загрузка
  3. Выполнение
  4. Завершение

Создание

Создание нового процесса состоит из присвоения новому процессу идентификатора (ID) процесса и подготовки информации, которая определяет окружение нового процесса. Большая часть этой информации наследуется от родительского процесса (смотри раздел "Наследование").

Загрузка

Загрузка образа процесса производится нитью загрузчика. Код загрузчика находится в Менеджере процессов, но нить выполняется под ID нового процесса. Это позволяет Менеджеру процессов обрабатывать и другие запросы во время загрузки программы.

Выполнение

После того, как код программы загружен, процесс готов к выполнению; он начинает конкурировать с остальными процессами за ресурсы ЦП. Заметьте, что все процессы выполняются параллельно со своими родителями. Кроме того, смерть родительского процесса не означает автоматическую смерть его дочерних процессов.

Завершение

Процесс завершается одним из двух способов:

  • процесс получает сигнал, который вызывает завершение процесса;
  • процесс вызывает функцию Си exit(), либо в явном виде, либо в качестве действия по умолчанию при возврате из функции main().

Завершение включает две стадии:

  1. Выполняется нить завершения в Менеджере процессов. Этот "заслуживающий доверия" код расположен в Менеджере процессов, но нить выполняется с ID завершающегося процесса. Эта нить закрывает все открытые файловые дескрипторы и освобождает следующие ресурсы:
    • все виртуальные каналы, принадлежащие процессу;
    • всю память, выделенную процессу;
    • все символьные имена;
    • любые старшие номера устройств (только менеджеры ввода/вывода);
    • любые обработчики прерывания;
    • любые прокси;
    • любые таймеры.
  2. После того, как нить завершения выполнена, извещение о завершении процесса посылается родительскому процессу (эта фаза выполняется внутри Менеджера процессов).

Если родительский процесс не вызвал wait() или waitpid(), то дочерний процесс становится "зомби" и не будет завершен, пока родительский процесс не вызовет wait() или не завершит выполнение. (Если вы не хотите, чтобы процесс ждал смерти дочерних процессов, вы должны либо установить _SPAWN_NOZOMBIE флаг функциями qnx_spawn() или qnx_spawn_options(), либо установить действие SIG_IGN для сигнала SIGCHLD посредством функции signal(). Таким образом, дочерние процессы не будут становиться зомби после смерти.)

Родительский процесс может ждать смерти дочернего процесса, запущенного на удаленном узле. Если родитель процесса-зомби умирает, то зомби освобождается.

Если запущена утилита dumper и процесс завершается в результате получения сигнала, то генерируется дамп памяти. Вы можете затем исследовать его с помощью отладчика.

Состояния процесса

Процесс всегда находится в одном из следующих состояний:

  1. READY (готов) - процесс способен использовать ЦП (т.е. он не ждет наступления какого-либо события).
  2. BLOCKED (блокирован) - процесс в одном из следующих блокированных состояний:
    • SEND-блокирован;
    • RECEIVE-блокирован;
    • REPLY-блокирован;
    • SIGNAL-блокирован;
    • SEMAPHORE-блокирован.
  3. HELD (приостановлен) - процесс получил сигнал SIGSTOP. В этом состоянии процесс не имеет права использовать ЦП; выйти из состояния HELD процесс может только в результате получения сигнала SIGCONT, или завершив свою работу после получения другого сигнала.
  4. WAIT (блокирован) - процесс выполнил вызов функции wait() или waitpid() для ожидания сообщения о завершении выполнения дочернего процесса.
  5. DEAD (мертв) - процесс завершил выполнение, но не может послать сообщения об этом родительскому процессу, т.к. родительский процесс не вызвал wait() или waitpid(). Когда процесс находится в состоянии DEAD, ранее занимаемая им память уже освобождена. Процесс в состоянии DEAD также называют зомби.

Note: Для получения более полной информации о блокированном состоянии обратитесь к главе "Микроядро".


Возможные состояния процесса в QNX

Возможные состояния процесса в QNX

Показаны следующие переходы:

  1. Процесс посылает сообщение
  2. Процесс-адресат получает сообщение
  3. Процесс-адресат отвечает на сообщение
  4. Процесс ожидает сообщения
  5. Процесс получает сообщение
  6. Сигнал разблокирует процесс
  7. Сигнал пытается разблокировать процесс; адресат ранее запросил извещение о сигнале путем посылки сообщения
  8. Процесс-адресат получает сообщение о сигнале
  9. Процесс ожидает смерти дочернего процесса
  10. Дочерний процесс умирает, либо сигнал разблокирует процесс
  11. Процесс получает SIGSTOP
  12. Процесс получает SIGCONT
  13. Процесс умирает
  14. Родительский процесс вызывает функцию wait() или waitpid(), завершает ее выполнение, либо ранее уже завершил выполнение
  15. Процесс вызывает функцию semwait() для семафора с неположительным значением
  16. Другой процесс вызывает функцию sempost(), или приходит немаскированный сигнал

Определение состояний процесса

Чтобы определить состояние конкретного процесса из командного интерпретатора (Shell), используйте утилиты ps и sin (внутри приложений используйте функцию qnx_psinfo()).

Чтобы определить состояние операционной системы в целом из командного интерпретатора (Shell), используйте утилиту sin (внутри приложений используйте функцию qnx_osinfo()).

Утилита ps определена стандартом POSIX; ее использование в командных файлах может быть переносимым в другие системы. Утилита sin, напротив, уникальна для QNX; она дает вам полезную информацию, специфическую для QNX, которую вы не можете получить, используя утилиту ps.

Символьные имена процессов

QNX стимулирует разработку приложений, которые разбиты на несколько взаимодействующих процессов. Такое приложение, которое существует как группа взаимодействующих процессов, имеет большие возможности распараллеливания и может быть распределено по сети для лучшей производительности.

Однако разделение приложений на взаимодействующие процессы требует ряда специальных соображений. Чтобы взаимодействующие процессы могли установить связь между собой, они должны иметь возможность определить идентификаторы друг друга. Например, допустим, что имеется сервер базы данных, который может обслуживать произвольное количество клиентов. Клиенты могут запускаться и прекращать работу в любое время, однако сервер всегда остается доступным. Как процессы-клиенты узнают идентификатор процесса-сервера базы данных с тем, чтобы послать ему сообщение?

В QNX процессы могут присваивать себе символьные имена. В контексте одного узла процесса могут зарегистрировать эти имена у Менеджера процессов на том узле, на котором они выполняются. Остальные процессы могут затем запросить у Менеджера процессов идентификатор процесса, ассоциированный с определенным именем.

Ситуация становится более сложной, если мы рассмотрим сеть, в которой сервер обслуживает клиентов, находящихся на различных узлах. Поэтому в QNX предусмотрена поддержка как глобальных, так и локальных имен. Глобальные имена определены для всей сети, в то время как локальные имена определены только для того узла, на котором они зарегистрированы. Глобальные имена начинаются с косой черты (/). Например:

qnx/fsysлокальное имя
company/xyzлокальное имя
/company/xyzлокальное имя

Note: Мы рекомендуем вам использовать название вашей компании в качестве префикса для всех регистрируемых имен, чтобы избежать конфликтов имен между программами разных производителей.

Чтобы сделать возможным использование глобальных имен, хотя бы на одном узле сети должен быть запущен процесс, известный как определитель имен (утилита nameloc). Этот процесс ведет учет всех зарегистрированных глобальных имен.

В каждый момент времени в сети могут быть запущены до десяти определителей имен. Каждый хранит идентичные копии всех активных глобальных имен. Такая избыточность гарантирует нормальное функционирование сети, даже если один или несколько узлов, обеспечивающих определение имен, выйдут из строя одновременно.

Чтобы зарегистрировать имя, процесс сервер использует функцию Си qnx_name_attach(). Чтобы определить процесс по имени, процесс клиент использует функцию Си qnx_name_locate().

Таймеры

Исчисление времени

В QNX исчисление времени основывается на системном таймере, поддерживаемом операционной системой. Таймер содержит текущее значение Координированного Всемирного Времени (UTC) относительно 0 часов, 0 минут, 0 секунд, 1 января 1970 года. Чтобы определить местное время, функции исчисления времени используют переменную окружения TZ (которая описывается в книге "Руководство пользователя").

Простые средства отсчета времени

Командные файлы и процессы могут делать паузу на определенное количество секунд, используя простые средства отсчета времени. В командных файлах для этой цели используется утилита sleep; в процессах используется функция Си sleep(). Также может использоваться функция delay(), которой в качестве аргумента передается длина интервала времени в миллисекундах.

Более сложное средство отсчета времени

Кроме того, процесс может создавать таймеры, устанавливать их на определенный интервал времени и удалять таймер. Эти средства отсчета времени основываются на спецификации POSIX Std 1003.4/Draft 9.

Создание таймеров

Процесс может создать один или больше таймеров. Эти таймеры могут быть любых типов и в любом сочетании. С учетом конфигурируемого ограничения общего числа таймеров, поддерживаемых операционной системой (смотри Proc в "Описание утилит"). Для создания таймера используйте функцию timer_create().

Установка таймеров

При установке таймеров вы можете использовать один из двух типов интервалов времени:

  • абсолютный - время относительно 0 часов, 0 минут, 0 секунд, 1 января 1970 года;
  • относительный - время относительно текущего показания часов.

Вы также можете создать таймер, периодически срабатывающий с заданным интервалом времени. Например, вы установили таймер таким образом, чтобы он сработал завтра в 9 часов утра. Вы можете задать, чтобы после этого он срабатывал каждые 5 минут.

Вы также можете установить временной интервал для существующего таймера. Результат будет зависеть от типа интервала времени:

  • для абсолютного таймера новый интервал заменяет текущий интервал времени;
  • для относительного таймера новый интервал добавляется к любому остающемуся интервалу времени.

Чтобы установить таймер:Используйте функцию:
Абсолютный или относительный интервалtimer_settime()

Удаление таймеров

Чтобы удалить таймер, используйте функцию Си timer_delete().

Установка разрешения таймера

Вы можете установить разрешение для таймера с помощью утилиты ticksize или функции qnx_timerperiod(). Вы можете настраивать разрешение от 500 микросекунд до 50 миллисекунд.

Чтение таймера

Чтобы узнать оставшийся от таймера интервал или проверить, не был ли таймер удален, используйте функцию Си timer_gettime().

Обработчики прерываний

Обработчики прерываний обслуживают систему аппаратных прерываний компьютера - они реагируют на аппаратные прерывания и осуществляют передачу данных между компьютером и внешними устройствами на низком уровне.

Обработчики прерываний физически являются частью стандартного процесса QNX (например, драйвер), но они всегда выполняются асинхронно по отношению к процессу, в который они включены.

Обработчик прерываний:

  • получает управление посредством дальнего вызова, а не непосредственно от прерывания (это может быть написано на Си, а не на ассемблере);
  • выполняется в контексте процесс, в который он включен, таким образом имеет доступ ко всем глобальным переменам этого процесса;
  • выполняется при разрешенных прерываниях, но вытесняется только в случае, если происходит прерывание с более высоким приоритетом;
  • не должен непосредственно управлять контроллером прерывания 8259 (об этом заботится операционная система);
  • должен быть как можно короче.

Несколько процессов могут обрабатывать одно и то же прерывание (если это поддерживается аппаратно). Когда происходит физическое прерывание, все обработчики прерываний будут по очереди получать управление. Не должно делаться никаких предположений относительно порядка, в котором будут вызываться обработчики прерываний, разделяющие одно и то же прерывание.

Если вы хотите:Используйте функцию:
Установить обработчик прерыванияqnx_hint_attach()
Удалить обработчик прерыванияqnx_hint_detach()

Обработчики прерывания таймера

Вы можете установить обработчик прерывания непосредственно для системного таймера таким образом, что обработчик будет вызываться по каждому прерыванию таймера. Чтобы установить период, вы можете использовать утилиту ticksize.

Вы можете также установить обработчик прерывания для отмасштабированного прерывания таймера, которое будет происходить каждые 50 миллисекунд, независимо от tick size. Эти таймеры предлагают низкоуровневую альтернативу POSIX 1003.4 таймерам.

<< Микроядро| Оглавление |Пространство имен ввода/вывода >>




Задать вопрос on-line Обсудить на форуме Написать электронное письмо