Разделяемость библиотек между процессами обеспечивается тем, что их код и статические данные отображаются на один и тот же участок физической оперативной памяти.
Таблица процессов
С точки зрения ядра процесс представляет собой запись в таблице процессов. Эта запись содержит данные, существующие в течение всего времени жизни процесса, и сведения о его состоянии. Размер таблицы процессов позволяет запускать несколько сотен процессов одновременно. Другая важная информация о процессе — например, таблица всех открытых процессом файлов — хранится в его адресном пространстве. Запись в таблице процессов и пространство процесса вместе составляют контекст, или окружение, процесса. В него входят:
♦ PID— идентификатор процесса. Он принудительно назначается планировщиком при запуске процесса.
♦ PPID— идентификатор родительского процесса (о порождении процессов — дальше в этом же параграфе).
♦ TTY— имя управляющего терминала (терминал, с которого запущен процесс).
♦ WD— текущий каталог процесса, от которого отсчитываются относительные пути.
♦ RID, RGID— реальные ID и групповой ID пользователя, запустившего процесс.
♦ EUID, EGID— эффективные ID и GID: см. п.2.1.4.8.
♦ NICE— показатель уступчивости. Процессы выполняются в режиме разделения времени, то есть время центрального процессора делится между готовыми к выполнению процессами с учетом их приоритета. Чем выше показатель уступчивости, тем ниже приоритет.
♦ Переменные окружения.
Системные вызовы fork() и exec() или как размножаются процессы
Каждый процесс порождается другим процессом, использующим для этого системный вызов fork() . Таким образом, структура процессов, подобно файловой системе, древовидна. Корнем этого дерева служит init — процесс инициализации системы. Он запускается ядром первым, получает идентификатор 1 и порождает еще несколько процессов (сколько и каких, можно узнать из его конфигурационного файла /etc/inittab
), которые, в свою очередь, при участии пользователя порождают другие процессы.
В результате системного вызова fork() родительский процесс полностью копирует свое окружение, включая адресное пространство, в дочерний, так что в момент рождения дочерний процесс отличается только своим ID. Потом дочерний процесс с помощью вызова exec() загружает в свое адресное пространство какой-нибудь исполняемый файл и начинает исполнять содержащуюся в нем программу.
Может случиться и так, что процесс выполняет вызов exec() без fork() : тогда не возникает нового процесса, но в старом начинает выполняться другая программа. Например, программа loginвыполняется с привилегиями суперпользователя, поскольку ей нужен доступ к файлу паролей. Проверив пароль, она устанавливает себе права зарегистрировавшегося пользователя и выполняет вызов exec() , замещая свой код кодом командной оболочки. После этого из командной оболочки изменить свои привилегии обратно на root нельзя, потому что кода программы loginв текущем процессе уже нет.

Рис. 3.3. Как размножаются процессы
Каждый процесс, завершившись, возвращает родительскому процессу какое-то значение, называемое кодом завершения или кодом возврата. По соглашению разработчиков, нулевой код возврата означает успешное завершение, а ненулевые — разнообразные ошибки. Процесс-родитель может приостановить свое выполнение до завершения потомка и выполнить разные действия в зависимости от возвращенного дочерним процессом значения, а может и не делать этого.
Снимок протекающих в системе процессов — команда ps
Моментальный снимок протекающих в системе процессов можно посмотреть с помощью команды ps( process status ). Без аргументов она покажет список процессов, связанных с текущей консолью (или виртуальным терминалом). Список возможных ключей команды можно, как обычно, получить по команде ps --help
. Вот некоторые полезные из них:
♦ -p <���список_PID>: только процессы с указанными ID;
♦ -u <���список_USERID>: только запущенные указанными пользователями;
Читать дальше
Конец ознакомительного отрывка
Купить книгу