Данные — статические переменные, используемые программой.
Динамическая память (куча) — область, из которой программа может динамически выделять дополнительную память.
Стек — часть памяти, которая может расширяться и сжиматься по мере вызова функций и возвращения из них и которая используется для выделения хранилища под локальные переменные и информацию о взаимосвязанности вызовов функций.
Создание процесса и выполнение программы
Процесс может создать новый процесс с помощью системного вызова fork(). Процесс, вызывающий fork(), известен как родительский процесс, а новый процесс называется дочерним процессом. Ядро создает дочерний процесс путем изготовления дубликата родительского процесса. Дочерний процесс наследует копии родительских сегментов данных, стека и кучи, которые затем могут изменяться независимо от своих родительских копий. (Текст программы размещается в области памяти с пометкой «только для чтения» и совместно используется двумя процессами.)
Дочерний процесс запускается либо для выполнения другого набора функций в том же самом коде, что и у родительского процесса, либо зачастую для использования системного вызова execve() с целью загрузки и выполнения совершенно новой программы. Вызов execve() удаляет существующие сегменты текста, данных, стека и кучи, заменяя их новыми сегментами, основываясь на коде новой программы.
У вызова execve() есть ряд надстроек в виде родственных функций библиотеки языка C с несколько отличающимся интерфейсом, но сходной функциональностью. У всех этих функций имена начинаются со строки exec. (В тех случаях, когда разница между ними неважна, мы будем для общей ссылки на эти функции использовать обозначение exec(). И все же следует иметь в виду, что на самом деле функции по имени exec() не существует.)
В основном глагол «выполнять» (exec) будет употребляться для описания операций, выполняемых execve() и ее библиотечными функциями-надстройками.
Идентификатор процесса и идентификатор родительского процесса
У каждого процесса есть уникальный целочисленный идентификатор процесса (PID). У каждого процесса также есть атрибут идентификатора родительского процесса (PPID), идентифицирующий процесс, запросивший у ядра создание данного процесса.
Завершение процесса и код завершения
Процесс может быть завершен двумя способами: запросом своего собственного завершения с использованием системного вызова _exit() (или родственной ему библиотечной функции exit()) или путем его уничтожения извне с помощью сигнала. В любом случае процесс выдает код завершения, небольшое неотрицательное целое число, которое может быть проверено родительским процессом с использованием системного вызова wait(). В случае вызова _exit() процесс явным образом указывает свой собственный код завершения. Если процесс уничтожается сигналом, код завершения устанавливается по типу сигнала, уничтожившего процесс. (Иногда мы будем называть аргумент, передаваемый _exit(), кодом выхода процесса, чтобы отличить его от кода завершения, который является либо значением, переданным _exit(), либо указателем на сигнал, уничтоживший процесс.)
По соглашению, код завершения 0 служит признаком успешного завершения процесса, а ненулевое значение служит признаком возникновения какой-то ошибки. Большинство оболочек позволяют получить код завершения последней выполненной программы с помощью переменной оболочки по имени $?.
Принадлежащие процессу идентификаторы пользователя и группы (учетные данные)
У каждого процесса имеется несколько связанных с ним идентификаторов пользователей (UID) и групп (GID). К ним относятся следующие.
• Реальный идентификатор пользователя и реальный идентификатор группы. Они идентифицируют пользователя и группу, которым принадлежит процесс. Новый процесс наследует эти идентификаторы (ID) от своего родительского процесса. Оболочка входа в систему получает свой реальный UID и реальный GID от соответствующих полей в системном файле паролей.