

9. Процессы в UNIX
Процессом в терминологии UNIX является просто экземпляр выполняемой программы, соответствующий определению задачи в других средах. Каждый процесс объединяет код программы, значения данных в переменных программы и более экзотические элементы, такие как значения регистров процессора, стек программы и т.д.
Так как процессы соответствуют выполняемым программам, не следует путать их с программами, которые они выполняют. Несколько процессов могут выполнять одну и ту же программу.
Любой процесс UNIX может, в свою очередь, запускать другие процессы. Это придает среде процессов UNIX иерархическую структуру. На вершине дерева процессов находится единственный управляющий процесс, экземпляр очень важной программы init, которая является предком всех системных и пользовательских процессов.
Система UNIX предоставляет программисту набор системных вызовов для создания процессов и управления ими.
Основным примитивом для создания процессов является системный вызов fork. Он является механизмом, который превращает UNIX в многозадачную систему.
Описание.
#include
#include
pid_t fork(void);
В результате успешного вызова fork ядро создает новый процесс, который является почти точной копией вызывающего процесса. Новый процесс выполняет копию той же программы, что и создавший его процесс, при этом все его объекты данных имеют те же самые значения, что и в вызывающем процессе, за одним важным исключением, которое описано ниже.
Созданный процесс называется дочерним процессом (child process), а процесс, осуществивший вызов fork, называется родителем (parent).
После вызова родительский процесс и его вновь созданный потомок выполняются одновременно (параллельно), при этом оба процесса продолжают выполнение с оператора, который следует сразу же за вызовом fork.
Вызов fork не имеет аргументов и возвращает идентификатор процесса pid_t.
Значение, возвращаемое родительскому процессу в переменной pid, называется идентификатором процесса (process-id) дочернего процесса. Это число идентифицирует процесс в системе аналогично идентификатору пользователя. Поскольку все процессы порождаются при помощи вызова fork, то каждый процесс UNIX имеет уникальный идентификатор процесса.
Вызов fork обретает ценность в сочетании с другими средствами UNIX.
Для смены исполняемой программы можно использовать функции семейства exec. Основное отличие между разными функциями в семействе состоит в способе передачи параметров. В конечном итоге все эти функции выполняют один системный Вызов execve.
Описание.
#include
int execl(const char *path, const char *arg0, ...,
const char *argn, (char*)0);
int execlp(const char *file, const char *arg0, ...,
const char *argn, (char*)0);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
Для семейства вызовов execl аргументы должны быть списком, заканчивающимся NULL. Вызову execl нужно передать полный путь к файлу программы. Вызову execlp нужно только имя файла программы.
Семейству вызовов execv нужно передать массив аргументов. Вызову execv нужно передать полный путь к файлу программы. Вызову execvp нужно только имя файла программы.
Все множество системных вызовов ехес выполняет одну и ту же функцию: они преобразуют вызывающий процесс, загружая новую программу в его пространство памяти. Если вызов ехес завершился успешно, то вызывающая программа полностью замещается новой программой, которая запускается с начала. Результат вызова можно рассматривать как запуск нового процесса, который при этом сохраняет идентификатор вызывающего процесса и по умолчанию наследует файловые дескрипторы.
Важно отметить, что вызов ехес не создает новый подпроцесс, который выполняется одновременно с вызывающим, а вместо этого новая программа загружается на место старой. Поэтому, в отличие от вызова fork, успешный вызов ехес не возвращает значения.
Все аргументы функции execl являются указателями строк. Первый из них, аргумент path, задает имя файла, содержащего программу, которая будет запущена на выполнение. Для вызова execl это должен быть полный путь к программе, абсолютный или относительный. Сам файл должен содержать программу или последовательность команд оболочки и быть доступным для выполнения. Система определяет, содержит ли файл программу, просматривая его первые байты (обычно первые два байта). Если они содержат специальное значение, называемое магическим числом (magic number), то система рассматривает файл как программу. Второй аргумент, arg0, является именем программы или команды, из которого исключен путь к ней. Этот аргумент и оставшееся переменное число аргументов (от arg1 до argn) доступны в вызываемой программе, аналогично аргументам командной строки при запуске программы из оболочки. Так как список аргументов имеет произвольную длину, он должен заканчиваться нулевым указателем для обозначения конца списка.
Другие формы вызова ехес упрощают задание списков параметров запуска загружаемой программы. Вызов execv принимает два аргумента: первый (path в описании применения вызова) является строкой, которая содержит полное имя и путь к запускаемой программе. Второй аргумент (argv) является массивом строк. Первый элемент этого массива указывает на имя запускаемой программы (исключая префикс пути). Оставшиеся элементы указывают на все остальные аргументы программы. Так как этот список имеет неопределенную длину, он всегда должен заканчиваться нулевым указателем.
Функции execlp и execvp почти эквивалентны функциям execl и execv. Основное отличие между ними состоит в том, что первый аргумент обоих функций execlp и execvp - просто имя программы, не включающее путь к ней. Путь к файлу находится при помощи поиска в каталогах, заданных в переменной среды PATH.
Любая программа может получить доступ к аргументам активизировавшего ее вызова ехес через параметры, передаваемые функции main. Эти параметры могут быть описаны при определении функции main.
Например:
Системные вызовы fork и ехес, объединенные вместе, представляют мощный инструмент для программиста. Благодаря ветвлению при использовании вызова ехес во вновь созданном дочернем процессе программа может выполнять другую программу в дочернем процессе, не стирая себя из памяти.
Созданный при помощи вызова fork дочерний процесс является почти точной копией родительского. Все переменные в дочернем процессе будут иметь те же самые значения, что и в родительском (единственным исключением является значение, возвращаемое самим вызовом fork). Так как данные в дочернем процессе являются копией данных в родительском процессе и занимают другое абсолютное положение в памяти, важно понимать, что последующие изменения в одном процессе не будут затрагивать переменные в другом.
Аналогично все файлы, открытые в родительском процессе, также будут открытыми и в потомке. При этом дочерний процесс будет иметь свою копию связанных с каждым файлом дескрипторов. Файлы, открытые до вызова fork, остаются тесно связанными в родительском и дочернем процессах. Это обусловлено тем, что указатель чтения/записи для каждого из таких файлов используется совместно родительским и дочерним процессами благодаря тому, что он поддерживается системой и существует не только в самом процессе. Следовательно, если дочерний процесс изменяет положение указателя в файле, то в родительском процессе он также окажется в новом положении.
Дескрипторы открытых файлов обычно сохраняют свое состояние также во время вызова exec. Файлы, открытые в исходной программе, остаются открытыми, когда совершенно новая программа запускается при помощи вызова exec. Указатели чтения/записи на такие файлы остаются неизменными после вызова.
Есть связанный с файловым дескриптором флаг close-on-exec (закрывать при вызове exec), который может быть установлен с помощью универсальной процедуры fсnt1. Если этот флаг установлен (по умолчанию он сброшен), то файл закрывается при вызове любой функции семейства exec.
Вызов exit используется для завершения процесса, хотя это также происходит, когда управление доходит до конца тела функции main или до оператора return в функции main.
Описание.
#include
void exit(int status);
Единственный целочисленный аргумент вызова exit называется статусом завершения (exit status) процесса, младшие восемь бит которого доступны родительскому процессу при условии, если он выполнил системный вызов wait. При этом возвращаемое вызовом exit значение обычно используется для определения успешного или неудачного завершения выполнявшейся процессом задачи. По принятому соглашению, нулевое возвращаемое значение соответствует нормальному завершению, а ненулевое значение говорит о том, что что-то случилось.
Кроме завершения вызывающего его процесса, вызов exit имеет еще несколько последствий: наиболее важным из них является закрытие всех открытых дескрипторов файлов. Если родительский процесс выполнял вызов wait, то его выполнение продолжится. Сочетание вызовов fork и wait наиболее полезно, если дочерний процесс предназначен для выполнения совершенно другой программы при помощи вызова ехес.
Возвращаемое значение wait обычно является идентификатором дочернего процесса, который завершил свою работу. Если вызов wait возвращает значение -1, это может означать, что дочерние процессы не существуют, и в этом случае переменная errno будет содержать код ошибки ECHILD.
Вызов wait принимает один аргумент, status, - указатель на целое число. Если указатель равен NULL, то аргумент просто игнорируется. Если же вызову wait передается допустимый указатель, то после возврата из вызова wait переменная status будет содержать полезную информацию о статусе завершения процесса. Обычно эта информация будет представлять собой код завершения дочернего процесса, переданный при помощи вызова exit.
Значение, возвращаемое родительскому процессу при помощи вызова exit, записывается в старшие восемь бит целочисленной переменной status. Чтобы оно имело смысл, младшие восемь бит должны быть равны нулю.
Системный вызов wait позволяет родительскому процессу ожидать завершения любого дочернего процесса. Если нужна большая определенность, то можно использовать системный вызов waitpid для ожидания завершения определенного дочернего процесса.
Описание.
#include
#include
pid_t waitpid(pid_t pid, int *status, int options);
Первый аргумент pid определяет идентификатор дочернего процесса, завершения которого будет ожидать родительский процесс. Если этот аргумент установлен равным -1, а аргумент options установлен равным 0, то вызов waitpid ведет себя в точности так же, как и вызов wait, поскольку значение -1 соответствует любому дочернему процессу. Если значение pid больше нуля, то родительский процесс будет ждать завершения дочернего процесса с идентификатором процесса равным pid. Во втором аргументе status будет находиться статус дочернего процесса после возврата из вызова waitpid.
Последний аргумент, options, может принимать константные значения, определенные в файле. Наиболее полезное из них - константа WNOHANG. Задание этого значения позволяет вызывать waitpid в цикле без блокирования процесса, контролируя ситуацию, пока дочерний процесс продолжает выполняться. Если установлен флаг WNOHANG, то вызов waitpid будет возвращать 0 в случае, если дочерний процесс еще не завершился.
Иногда могут возникать такие ситуации:
- в момент завершения дочернего процесса родительский процесс не выполняет вызов wait;
- родительский процесс завершается, в то время как один или несколько дочерних процессов продолжают выполняться.
В первом случае завершающийся процесс как бы «теряется» и становится зомби-процессом (zombie). Зомби-процесс занимает ячейку в таблице, поддерживаемой ядром для управления процессами, но не использует других ресурсов ядра. В конце концов, он будет освобожден, если его родительский процесс вспомнит о нем и вызовет wait. Тогда родительский процесс сможет прочитать статус завершения процесса, и ячейка освободится для повторного использования.
Во втором случае родительский процесс завершается нормально.
С каждым процессом UNIX связан набор атрибутов, которые помогают системе управлять выполнением и планированием процессов, обеспечивать защиту файловой системы и так далее. Один из атрибутов - это идентификатор процесса, то есть число, которое однозначно идентифицирует процесс. Другие атрибуты простираются от окружения, которое является набором строк, определяемых программистом и находящихся вне области данных, до действующего идентификатора пользователя, определяющего права доступа процесса к файловой системе.
Система присваивает каждому процессу одно неотрицательное число, которое называется идентификатором процесса. В любой момент времени идентификатор процесса является уникальным, хотя после завершения процесса он может использоваться снова для другого процесса. Некоторые идентификаторы процесса зарезервированы системой для особых процессов. Процесс с идентификатором 0, хотя он и называется планировщиком (scheduler), на самом деле является процессом подкачки памяти (swapper). Процесс с идентификатором 1 - это процесс инициализации, выполняющий программу /etc/init. Этот процесс, явно пли неявно, является предком всех других процессов в системе UNIX.
Программа может получить свой идентификатор процесса при помощи следующего системного вызова.
pid = getpid();
Аналогично вызов getppid возвращает идентификатор родителя вызывающего процесса.
ppid = getppid();
Система UNIX позволяет легко помещать процессы в группы. Группы процессов удобны для работы с набором процессов в целом.
Каждая группа процессов (process group) обозначается идентификатором группы процессов (process group-id), имеющим тип pid_t. Процесс, идентификатор которого совпадает с идентификатором группы процессов, считается лидером (leader) группы процессов, и при его завершении выполняются особые действия. Первоначально процесс наследует идентификатор группы во время вызова fork или exec. Процесс может получить свой идентификатор группы при помощи системного вызова getpgrp.
Описание.
pid_t getpgrp(voud);
Процесс может создать новую группу процессов пли присоединиться к существующей при помощи системного вызова setpgid.
Описание.
int setpgid(pid_t pid, pid_t pgid);
Вызов setpgid устанавливает идентификатор группы процесса с идентификатором pid равным pgid. Если pid равен 0, то используется идентификатор вызывающего процесса. Если значения идентификаторов pid и pgid одинаковы, то процесс становится лидером группы процессов. В случае ошибки возвращается значение -1. Если идентификатор pgid равен нулю, то в качестве идентификатора группы процесса используется идентификатор процесса pid.
Понятие сеанса полезно при работе с фоновыми процессами или процессами-демонами (daemon processes). Процессом-демоном называется просто процесс, не имеющий управляющего терминала. Демон может задать для себя сеанс без управляющего терминала, переместившись в другой сеанс при помощи системного вызова setsid.
Описание.
pid_t setsid(void);
Если вызывающий процесс не является лидером группы процессов, то создается новая группа процессов и новый сеанс, и идентификатор вызывающего процесса станет идентификатором созданного сеанса. Он также не будет иметь управляющего терминала. Процесс-демон теперь будет находиться в странном состоянии, так как он будет единственным процессом в группе процессов, содержащейся в новом сеансе, а его идентификатор процесса pid - также идентификатором группы и сеанса.
Вызов setsid может завершиться неудачей и возвратит значение -1, если вызывающий процесс уже является лидером группы.
С каждым процессом связан корневой каталог, который используется при поиске абсолютного пути. Так же, как и текущий рабочий каталог, корневым каталогом процесса первоначально является корневой каталог его родительского процесса. Для изменения начала иерархии файловой системы для процесса в ОС UNIX существует системный вызов chroot.
Описание
int chroot(const char *path);
Переменная path указывает на путь, обозначающий каталог. В случае успешного вызова chroot путь path становится начальной точкой при поиске в путях, начинающихся с символа /. В случае неудачи вызов chroot не меняет корневой каталог и возвращает значение -1. Для изменения корневого каталога вызывающий процесс должен иметь соответствующие права доступа.
С каждым процессом связаны истинные идентификаторы пользователя и группы. Это всегда идентификатор пользователя и текущий идентификатор группы запустившего процесс пользователя.
Действующие идентификаторы пользователя и группы используются для определения возможности доступа процесса к файлу.
Для получения связанных с процессом идентификаторов пользователя и группы существует несколько системных вызовов.
uid_t getuid(); /*Истинный идентификатор пользователя*/
gid_t getgid(); /*Истинный идентификатор группы*/
uid_t geteuid();/*Действующий идентификатор пользователя*/
gid_t getegid();/*Действующий идентификатор группы*/
Для задания действующих идентификаторов пользователя и группы процесса также существуют системные вызовы.
Описание.
#include
int setuid(uid_t newuid);
int setgid(gid_t newgid);
Процесс, запущенный непривилегированным пользователем, может менять действующие идентификаторы пользователя и группы только на истинные. Обе процедуры возвращают нулевое значение в случае успеха, и -1 в случае неудачи.
Так как процессы соответствуют выполняемым программам, не следует путать их с программами, которые они выполняют. Несколько процессов могут выполнять одну и ту же программу.
Любой процесс UNIX может, в свою очередь, запускать другие процессы. Это придает среде процессов UNIX иерархическую структуру. На вершине дерева процессов находится единственный управляющий процесс, экземпляр очень важной программы init, которая является предком всех системных и пользовательских процессов.
Система UNIX предоставляет программисту набор системных вызовов для создания процессов и управления ими.
Основным примитивом для создания процессов является системный вызов fork. Он является механизмом, который превращает UNIX в многозадачную систему.
Описание.
#include
#include
pid_t fork(void);
В результате успешного вызова fork ядро создает новый процесс, который является почти точной копией вызывающего процесса. Новый процесс выполняет копию той же программы, что и создавший его процесс, при этом все его объекты данных имеют те же самые значения, что и в вызывающем процессе, за одним важным исключением, которое описано ниже.
Созданный процесс называется дочерним процессом (child process), а процесс, осуществивший вызов fork, называется родителем (parent).
После вызова родительский процесс и его вновь созданный потомок выполняются одновременно (параллельно), при этом оба процесса продолжают выполнение с оператора, который следует сразу же за вызовом fork.
Вызов fork не имеет аргументов и возвращает идентификатор процесса pid_t.
Значение, возвращаемое родительскому процессу в переменной pid, называется идентификатором процесса (process-id) дочернего процесса. Это число идентифицирует процесс в системе аналогично идентификатору пользователя. Поскольку все процессы порождаются при помощи вызова fork, то каждый процесс UNIX имеет уникальный идентификатор процесса.
Вызов fork обретает ценность в сочетании с другими средствами UNIX.
Для смены исполняемой программы можно использовать функции семейства exec. Основное отличие между разными функциями в семействе состоит в способе передачи параметров. В конечном итоге все эти функции выполняют один системный Вызов execve.
Описание.
#include
int execl(const char *path, const char *arg0, ...,
const char *argn, (char*)0);
int execlp(const char *file, const char *arg0, ...,
const char *argn, (char*)0);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
Для семейства вызовов execl аргументы должны быть списком, заканчивающимся NULL. Вызову execl нужно передать полный путь к файлу программы. Вызову execlp нужно только имя файла программы.
Семейству вызовов execv нужно передать массив аргументов. Вызову execv нужно передать полный путь к файлу программы. Вызову execvp нужно только имя файла программы.
Все множество системных вызовов ехес выполняет одну и ту же функцию: они преобразуют вызывающий процесс, загружая новую программу в его пространство памяти. Если вызов ехес завершился успешно, то вызывающая программа полностью замещается новой программой, которая запускается с начала. Результат вызова можно рассматривать как запуск нового процесса, который при этом сохраняет идентификатор вызывающего процесса и по умолчанию наследует файловые дескрипторы.
Важно отметить, что вызов ехес не создает новый подпроцесс, который выполняется одновременно с вызывающим, а вместо этого новая программа загружается на место старой. Поэтому, в отличие от вызова fork, успешный вызов ехес не возвращает значения.
Все аргументы функции execl являются указателями строк. Первый из них, аргумент path, задает имя файла, содержащего программу, которая будет запущена на выполнение. Для вызова execl это должен быть полный путь к программе, абсолютный или относительный. Сам файл должен содержать программу или последовательность команд оболочки и быть доступным для выполнения. Система определяет, содержит ли файл программу, просматривая его первые байты (обычно первые два байта). Если они содержат специальное значение, называемое магическим числом (magic number), то система рассматривает файл как программу. Второй аргумент, arg0, является именем программы или команды, из которого исключен путь к ней. Этот аргумент и оставшееся переменное число аргументов (от arg1 до argn) доступны в вызываемой программе, аналогично аргументам командной строки при запуске программы из оболочки. Так как список аргументов имеет произвольную длину, он должен заканчиваться нулевым указателем для обозначения конца списка.
Другие формы вызова ехес упрощают задание списков параметров запуска загружаемой программы. Вызов execv принимает два аргумента: первый (path в описании применения вызова) является строкой, которая содержит полное имя и путь к запускаемой программе. Второй аргумент (argv) является массивом строк. Первый элемент этого массива указывает на имя запускаемой программы (исключая префикс пути). Оставшиеся элементы указывают на все остальные аргументы программы. Так как этот список имеет неопределенную длину, он всегда должен заканчиваться нулевым указателем.
Функции execlp и execvp почти эквивалентны функциям execl и execv. Основное отличие между ними состоит в том, что первый аргумент обоих функций execlp и execvp - просто имя программы, не включающее путь к ней. Путь к файлу находится при помощи поиска в каталогах, заданных в переменной среды PATH.
Любая программа может получить доступ к аргументам активизировавшего ее вызова ехес через параметры, передаваемые функции main. Эти параметры могут быть описаны при определении функции main.
Например:
Системные вызовы fork и ехес, объединенные вместе, представляют мощный инструмент для программиста. Благодаря ветвлению при использовании вызова ехес во вновь созданном дочернем процессе программа может выполнять другую программу в дочернем процессе, не стирая себя из памяти.
Созданный при помощи вызова fork дочерний процесс является почти точной копией родительского. Все переменные в дочернем процессе будут иметь те же самые значения, что и в родительском (единственным исключением является значение, возвращаемое самим вызовом fork). Так как данные в дочернем процессе являются копией данных в родительском процессе и занимают другое абсолютное положение в памяти, важно понимать, что последующие изменения в одном процессе не будут затрагивать переменные в другом.
Аналогично все файлы, открытые в родительском процессе, также будут открытыми и в потомке. При этом дочерний процесс будет иметь свою копию связанных с каждым файлом дескрипторов. Файлы, открытые до вызова fork, остаются тесно связанными в родительском и дочернем процессах. Это обусловлено тем, что указатель чтения/записи для каждого из таких файлов используется совместно родительским и дочерним процессами благодаря тому, что он поддерживается системой и существует не только в самом процессе. Следовательно, если дочерний процесс изменяет положение указателя в файле, то в родительском процессе он также окажется в новом положении.
Дескрипторы открытых файлов обычно сохраняют свое состояние также во время вызова exec. Файлы, открытые в исходной программе, остаются открытыми, когда совершенно новая программа запускается при помощи вызова exec. Указатели чтения/записи на такие файлы остаются неизменными после вызова.
Есть связанный с файловым дескриптором флаг close-on-exec (закрывать при вызове exec), который может быть установлен с помощью универсальной процедуры fсnt1. Если этот флаг установлен (по умолчанию он сброшен), то файл закрывается при вызове любой функции семейства exec.
Вызов exit используется для завершения процесса, хотя это также происходит, когда управление доходит до конца тела функции main или до оператора return в функции main.
Описание.
#include
void exit(int status);
Единственный целочисленный аргумент вызова exit называется статусом завершения (exit status) процесса, младшие восемь бит которого доступны родительскому процессу при условии, если он выполнил системный вызов wait. При этом возвращаемое вызовом exit значение обычно используется для определения успешного или неудачного завершения выполнявшейся процессом задачи. По принятому соглашению, нулевое возвращаемое значение соответствует нормальному завершению, а ненулевое значение говорит о том, что что-то случилось.
Кроме завершения вызывающего его процесса, вызов exit имеет еще несколько последствий: наиболее важным из них является закрытие всех открытых дескрипторов файлов. Если родительский процесс выполнял вызов wait, то его выполнение продолжится. Сочетание вызовов fork и wait наиболее полезно, если дочерний процесс предназначен для выполнения совершенно другой программы при помощи вызова ехес.
Возвращаемое значение wait обычно является идентификатором дочернего процесса, который завершил свою работу. Если вызов wait возвращает значение -1, это может означать, что дочерние процессы не существуют, и в этом случае переменная errno будет содержать код ошибки ECHILD.
Вызов wait принимает один аргумент, status, - указатель на целое число. Если указатель равен NULL, то аргумент просто игнорируется. Если же вызову wait передается допустимый указатель, то после возврата из вызова wait переменная status будет содержать полезную информацию о статусе завершения процесса. Обычно эта информация будет представлять собой код завершения дочернего процесса, переданный при помощи вызова exit.
Значение, возвращаемое родительскому процессу при помощи вызова exit, записывается в старшие восемь бит целочисленной переменной status. Чтобы оно имело смысл, младшие восемь бит должны быть равны нулю.
Системный вызов wait позволяет родительскому процессу ожидать завершения любого дочернего процесса. Если нужна большая определенность, то можно использовать системный вызов waitpid для ожидания завершения определенного дочернего процесса.
Описание.
#include
#include
pid_t waitpid(pid_t pid, int *status, int options);
Первый аргумент pid определяет идентификатор дочернего процесса, завершения которого будет ожидать родительский процесс. Если этот аргумент установлен равным -1, а аргумент options установлен равным 0, то вызов waitpid ведет себя в точности так же, как и вызов wait, поскольку значение -1 соответствует любому дочернему процессу. Если значение pid больше нуля, то родительский процесс будет ждать завершения дочернего процесса с идентификатором процесса равным pid. Во втором аргументе status будет находиться статус дочернего процесса после возврата из вызова waitpid.
Последний аргумент, options, может принимать константные значения, определенные в файле
Иногда могут возникать такие ситуации:
- в момент завершения дочернего процесса родительский процесс не выполняет вызов wait;
- родительский процесс завершается, в то время как один или несколько дочерних процессов продолжают выполняться.
В первом случае завершающийся процесс как бы «теряется» и становится зомби-процессом (zombie). Зомби-процесс занимает ячейку в таблице, поддерживаемой ядром для управления процессами, но не использует других ресурсов ядра. В конце концов, он будет освобожден, если его родительский процесс вспомнит о нем и вызовет wait. Тогда родительский процесс сможет прочитать статус завершения процесса, и ячейка освободится для повторного использования.
Во втором случае родительский процесс завершается нормально.
Атрибуты процессов
С каждым процессом UNIX связан набор атрибутов, которые помогают системе управлять выполнением и планированием процессов, обеспечивать защиту файловой системы и так далее. Один из атрибутов - это идентификатор процесса, то есть число, которое однозначно идентифицирует процесс. Другие атрибуты простираются от окружения, которое является набором строк, определяемых программистом и находящихся вне области данных, до действующего идентификатора пользователя, определяющего права доступа процесса к файловой системе.
Система присваивает каждому процессу одно неотрицательное число, которое называется идентификатором процесса. В любой момент времени идентификатор процесса является уникальным, хотя после завершения процесса он может использоваться снова для другого процесса. Некоторые идентификаторы процесса зарезервированы системой для особых процессов. Процесс с идентификатором 0, хотя он и называется планировщиком (scheduler), на самом деле является процессом подкачки памяти (swapper). Процесс с идентификатором 1 - это процесс инициализации, выполняющий программу /etc/init. Этот процесс, явно пли неявно, является предком всех других процессов в системе UNIX.
Программа может получить свой идентификатор процесса при помощи следующего системного вызова.
pid = getpid();
Аналогично вызов getppid возвращает идентификатор родителя вызывающего процесса.
ppid = getppid();
Система UNIX позволяет легко помещать процессы в группы. Группы процессов удобны для работы с набором процессов в целом.
Каждая группа процессов (process group) обозначается идентификатором группы процессов (process group-id), имеющим тип pid_t. Процесс, идентификатор которого совпадает с идентификатором группы процессов, считается лидером (leader) группы процессов, и при его завершении выполняются особые действия. Первоначально процесс наследует идентификатор группы во время вызова fork или exec. Процесс может получить свой идентификатор группы при помощи системного вызова getpgrp.
Описание.
pid_t getpgrp(voud);
Процесс может создать новую группу процессов пли присоединиться к существующей при помощи системного вызова setpgid.
Описание.
int setpgid(pid_t pid, pid_t pgid);
Вызов setpgid устанавливает идентификатор группы процесса с идентификатором pid равным pgid. Если pid равен 0, то используется идентификатор вызывающего процесса. Если значения идентификаторов pid и pgid одинаковы, то процесс становится лидером группы процессов. В случае ошибки возвращается значение -1. Если идентификатор pgid равен нулю, то в качестве идентификатора группы процесса используется идентификатор процесса pid.
Понятие сеанса полезно при работе с фоновыми процессами или процессами-демонами (daemon processes). Процессом-демоном называется просто процесс, не имеющий управляющего терминала. Демон может задать для себя сеанс без управляющего терминала, переместившись в другой сеанс при помощи системного вызова setsid.
Описание.
pid_t setsid(void);
Если вызывающий процесс не является лидером группы процессов, то создается новая группа процессов и новый сеанс, и идентификатор вызывающего процесса станет идентификатором созданного сеанса. Он также не будет иметь управляющего терминала. Процесс-демон теперь будет находиться в странном состоянии, так как он будет единственным процессом в группе процессов, содержащейся в новом сеансе, а его идентификатор процесса pid - также идентификатором группы и сеанса.
Вызов setsid может завершиться неудачей и возвратит значение -1, если вызывающий процесс уже является лидером группы.
С каждым процессом связан корневой каталог, который используется при поиске абсолютного пути. Так же, как и текущий рабочий каталог, корневым каталогом процесса первоначально является корневой каталог его родительского процесса. Для изменения начала иерархии файловой системы для процесса в ОС UNIX существует системный вызов chroot.
Описание
int chroot(const char *path);
Переменная path указывает на путь, обозначающий каталог. В случае успешного вызова chroot путь path становится начальной точкой при поиске в путях, начинающихся с символа /. В случае неудачи вызов chroot не меняет корневой каталог и возвращает значение -1. Для изменения корневого каталога вызывающий процесс должен иметь соответствующие права доступа.
С каждым процессом связаны истинные идентификаторы пользователя и группы. Это всегда идентификатор пользователя и текущий идентификатор группы запустившего процесс пользователя.
Действующие идентификаторы пользователя и группы используются для определения возможности доступа процесса к файлу.
Для получения связанных с процессом идентификаторов пользователя и группы существует несколько системных вызовов.
uid_t getuid(); /*Истинный идентификатор пользователя*/
gid_t getgid(); /*Истинный идентификатор группы*/
uid_t geteuid();/*Действующий идентификатор пользователя*/
gid_t getegid();/*Действующий идентификатор группы*/
Для задания действующих идентификаторов пользователя и группы процесса также существуют системные вызовы.
Описание.
#include
int setuid(uid_t newuid);
int setgid(gid_t newgid);
Процесс, запущенный непривилегированным пользователем, может менять действующие идентификаторы пользователя и группы только на истинные. Обе процедуры возвращают нулевое значение в случае успеха, и -1 в случае неудачи.