что такое дочерний процесс
Изучаем процессы в Linux
В этой статье я хотел бы рассказать о том, какой жизненный путь проходят процессы в семействе ОС Linux. В теории и на примерах я рассмотрю как процессы рождаются и умирают, немного расскажу о механике системных вызовов и сигналов.
Данная статья в большей мере рассчитана на новичков в системном программировании и тех, кто просто хочет узнать немного больше о том, как работают процессы в Linux.
Всё написанное ниже справедливо к Debian Linux с ядром 4.15.0.
Содержание
Введение
Системное программное обеспечение взаимодействует с ядром системы посредством специальных функций — системных вызовов. В редких случаях существует альтернативный API, например, procfs или sysfs, выполненные в виде виртуальных файловых систем.
Атрибуты процесса
Процесс в ядре представляется просто как структура с множеством полей (определение структуры можно прочитать здесь).
Но так как статья посвящена системному программированию, а не разработке ядра, то несколько абстрагируемся и просто акцентируем внимание на важных для нас полях процесса:
Жизненный цикл процесса
Рождение процесса
Состояние «готов»
Сразу после выполнения fork(2) переходит в состояние «готов».
Фактически, процесс стоит в очереди и ждёт, когда планировщик (scheduler) в ядре даст процессу выполняться на процессоре.
Состояние «выполняется»
Перерождение в другую программу
В некоторых программах реализована логика, в которой родительский процесс создает дочерний для решения какой-либо задачи. Ребёнок в данном случае решает какую-то конкретную проблему, а родитель лишь делегирует своим детям задачи. Например, веб-сервер при входящем подключении создаёт ребёнка и передаёт обработку подключения ему.
Однако, если нужно запустить другую программу, то необходимо прибегнуть к системному вызову execve(2) :
или библиотечным вызовам execl(3), execlp(3), execle(3), execv(3), execvp(3), execvpe(3) :
Как не путаться во всех этих вызовах и выбирать нужный? Достаточно постичь логику именования:
Семейство вызовов exec* позволяет запускать скрипты с правами на исполнение и начинающиеся с последовательности шебанг (#!).
Есть соглашение, которое подразумевает, что argv[0] совпадает с нулевым аргументов для функций семейства exec*. Однако, это можно нарушить.
Любопытный читатель может заметить, что в сигнатуре функции int main(int argc, char* argv[]) есть число — количество аргументов, но в семействе функций exec* ничего такого не передаётся. Почему? Потому что при запуске программы управление передаётся не сразу в main. Перед этим выполняются некоторые действия, определённые glibc, в том числе подсчёт argc.
Состояние «ожидает»
Некоторые системные вызовы могут выполняться долго, например, ввод-вывод. В таких случаях процесс переходит в состояние «ожидает». Как только системный вызов будет выполнен, ядро переведёт процесс в состояние «готов».
В Linux так же существует состояние «ожидает», в котором процесс не реагирует на сигналы прерывания. В этом состоянии процесс становится «неубиваемым», а все пришедшие сигналы встают в очередь до тех пор, пока процесс не выйдет из этого состояния.
Ядро само выбирает, в какое из состояний перевести процесс. Чаще всего в состояние «ожидает (без прерываний)» попадают процессы, которые запрашивают ввод-вывод. Особенно заметно это при использовании удалённого диска (NFS) с не очень быстрым интернетом.
Состояние «остановлен»
В любой момент можно приостановить выполнение процесса, отправив ему сигнал SIGSTOP. Процесс перейдёт в состояние «остановлен» и будет находиться там до тех пор, пока ему не придёт сигнал продолжать работу (SIGCONT) или умереть (SIGKILL). Остальные сигналы будут поставлены в очередь.
Завершение процесса
Состояние «зомби»
Сразу после того, как процесс завершился (неважно, корректно или нет), ядро записывает информацию о том, как завершился процесс и переводит его в состояние «зомби». Иными словами, зомби — это завершившийся процесс, но память о нём всё ещё хранится в ядре.
Более того, это второе состояние, в котором процесс может смело игнорировать сигнал SIGKILL, ведь что мертво не может умереть ещё раз.
Забытье
Код возврата и причина завершения процесса всё ещё хранится в ядре и её нужно оттуда забрать. Для этого можно воспользоваться соответствующими системными вызовами:
Передача argv[0] как NULL приводит к падению.
После того, как родитель забрал информацию о смерти ребёнка, ядро стирает всю информацию о ребёнке, чтобы на его место вскоре пришёл другой процесс.
Благодарности
Спасибо Саше «Al» за редактуру и помощь в оформлении;
Спасибо Саше «Reisse» за понятные ответы на сложные вопросы.
Они стойко перенесли напавшее на меня вдохновение и напавший на них шквал моих вопросов.
Что такое дочерний процесс
Процессы организованы иерархически. Каждый процесс имеет родительский процесс. Процессы, созданные данным родителем называются дочерними процессами. Дочерний наследует многие из атрибутов родительского процесса.
Эта глава описывает, как программа может создавать, завершать, и управлять дочерними процессами. Фактически, имеются три различных операции: создание нового дочернего процесса, назначение новому процессу выполнить программу, и координирование завершения дочернего процесса.
Функция системы обеспечивает простой механизм для выполнения другой программы; он делает все три шага автоматически. Если Вы нуждаетесь в большом количестве контроля, Вы можете использовать примитивные функции, чтобы делать каждый шаг индивидуально.
Простой способ выполнять другую программу состоит в том, чтобы использовать функцию system. Эта функция делает всю работу выполнения подпрограммы, но она не дает Вам контроля над подробностями: Вы должны ждать, пока подпрограмма не завершится прежде, чем Вы сможете делать что-нибудь еще.
Функция system объявлена в заглавном файле » stdlib.h «.
Примечание Переносимости: Некоторые реализации C могут не иметь понятие командного процессора, который может выполнять другие программы. Вы можете определить, существует ли командный процессор, выполняя system (NULL); если возвращаемое значение отлично от нуля, командный процессор доступен.
Popen и pclose функции (см. Раздел 10.2 [Трубопровод на Подпроцесса]) близко связаны функцией system. Они позволяют родительскому процессу связываться со стандартным вводом и выводом выполняемой команды.
Этот раздел дает краткий обзор действий и шагов по созданию процесса и выполнения им другой программы.
Каждый процесс именован ID процесса. Уникальный ID процесса дан каждому процессу при создании.
Раздвоенный дочерний процесс продолжает выполнять ту же самую программу как родительский процесс, в точке возвращения fork. Вы можете использовать возвращаемое значение от fork, чтобы отличить, выполняется ли программа в родительском процессе или в дочернем.
Наличие нескольких процессов выполняющх ту же самую программу не очень полезно. Но дочерний может выполнять другую программу, используя одну из запускающих функций; см. Раздел 23.5 [Выполнение Файла]. Программа, которую процесс выполняет, называется образом процесса. Начало выполнения новой программы заставляет процесс забыть все относительно предыдущего образа процесса; когда программа выходит, процесс тоже выходит, вместо того, чтобы возвратиться к предыдущему образу процесса.
Pid_t тип данных для ID процесса. Вы можете получить ID процесса, вызывая getpid. Функция getppid возвращает ID родителя текущего процесса (это также известно как ID родительского процесса). Ваша программа должна включить заглавные файлы » unistd.h » и » sys/types.h » чтобы использовать эти функции.
Если операция является успешной, то и родительский и дочерний процессы видят что fork возвращается, но с различными значениями: она возвращает значение 0 в дочернем процессе и ID порожденного процесса (ребенка) в родительском процессе.
не имеется достаточных ресурсов системы чтобы создать другой процесс, или пользователь уже имеет слишком много процессов. ENOMEM
В то время как fork делает полную копию адресного пространства вызывающего процесса и позволяет, и родителю и дочернему выполняться независимо, vfork не делает эту копию.
Взамен, дочерний процесс, созданный с vfork совместно использует адресное пространство родителя, пока он не вызывает одну из функций exec. Тем временем, родительский процесс приостанавливает свое выполнение.
Вы должны быть очень осторожны, чтобы не позволить дочернему процессу, созданному с vfork изменять любые глобальные данные или даже локальные переменные, общедоступнные с родителем. Кроме того, дочерний процесс не может возвращаться из (или делать длинный переход) функции, которая вызвала vfork! Это спутало бы информацию управления родительского процесса. Если Вы сомневаетесь, используйте fork.
Некоторые операционные системы не выполняют vfork. Библиотека GNU C разрешает Вам использовать vfork на всех системах, но фактически выполняет fork, если vfork не доступна. Если Вы соблюдаете соответствующие предосторожности при использовании vfork, ваша программа будет работать, даже если система использует fork взамен.
Этот раздел описывает совокупность exec функций, для выполнения файла как образа процесса. Вы можете использовать эти функции, чтобы заставить дочерний процесс выполнить новую программу после того, как он был раздвоен.
Эти функции отличаются тем, как Вы определяете аргументы, но они все делают ту же самую вещь. Они объявлены в заглавном файле » unistd.h «.
Среда для нового образа процесса берется из переменной environ текущего образа процесса; см. Раздел 22.2 [Переменные среды], для уточнения инфрмации относительно сред.
Эта функция полезна для выполняющихся утилит системы, потому что она ищет их в местах, которые пользователь выбрал. Оболочки используют ее, чтобы выполнить команды написанные пользователем.
Размер списка параметров и списка среды, вместе не должен быть больше чем ARG_MAX байт. См. Раздел 27.1 [Общие Ограничения]. В системе GNU, размер (который сравнивается c ARG_MAX) включает, для каждой строки, число символов в строке, плюс размер char*, плюс один, округленный вверх после умножения на размер char*. Другие системы могут иметь несколько отличные правила для подсчета.
заданный файл не может быть выполнен, потому что он не находится в правильном формате.
Выполнение заданного файла требует большего количества памяти чем было доступно. Если выполнение нового файла преуспевает, это модифицирует поле времени доступа файла, как будто файл был прочитан. См. Раздел 9.8.9 [Времена Файла].
Новый образ процесса не имеет никаких потоков за исключением тех, что он создает заново.
Каждый из потоков в предыдущем образе процесса имеет описатель внутри него, и эти описатели остаются после exec (если они не имеют FD_CLOEXEC). Новый образ процесса может повторно соединять их с новыми потоками, используя fdopen (см. Раздел 8.4 [Описатели и Потоки]).
Функции, описанные в этом разделе используются, чтобы ждать завершения или останова дочернего процесса и определять его состояние. Эти функции объявлены в заглавном файле » sys/wait.h «.
Если информация состояния дочернего процесса доступна немедленно, эта функция возвращается немедленно без ожидания. Если доступна информация состояния больше чем одного готового продолжиться дочернего процесса, один из них будет выбран беспорядочно, и его состояние возвращено немедленно.
Чтобы получить состояние других готовых продолжиться дочерних процессов, Вы должны вызвать waitpid снова.
Информация состояния дочернего процесса сохранена в объекте, на который указывает status_ptr, если status_ptr не пустой указатель.
Функция была прервана получением сигнала. См. Раздел 21.5 [Прерванные Примитивы]. ECHILD
Не имеется никаких дочерних процессов, или заданный pid не дочерний для вызывающего процесса. EINVAL
Недопустимое значение аргумента options. Эти символические константы определены как значения для pid аргумента waitpid функции.
Эти символические константы определены как флаги для аргумента options функции waitpid.
Вы можете сделать OR флагов вместе, чтобы получить значение, и использовать его как аргумент.
Библиотека GNU также обеспечивает эти средства для совместимости с UNIX BSD. BSD использует тип данных union, чтобы представить значения состояния, а не int. Два представления фактически взаимозаменяемы; они описывают те же самые битовые шаблоны. Библиотека GNU C определяет макрокоманды типа WEXITSTATUS так, чтобы они работали на любом виде объекта, и функция wait определена, чтобы принять любой тип указателя как аргумент status_ptr.
Эти функции объявлены в » sys/wait.h «.
Вместо того, чтобы обращаться к этим элементам непосредственно, Вы должны использовать эквивалентные макрокоманды.
Не забудьте, что первый аргумент argv, представляет имя выполняемой программы. Именно поэтому, в обращении к execl, SHELL обеспечена один раз, чтобы назвать выполняемую программу, и второй раз, чтобы обеспечить значение для argv [0].
Вызовите _exit, чтобы выполнить это. Причина для использования _exit вместо exit состоит в том, чтобы избежать flush полностью буферизированных потоков типа stdout. Буфера этих потоков возможно содержат данные, которые были скопированы из родительского процесса функцией fork, эти данные будут выводиться в конечном счете родительским процессом. Вызов exit в дочернем вывел бы данные дважды. См. Раздел 22.3.5 [Внутренняя организация Окончания].
Наследование
Дочерний процесс может наследовать несколько свойств и ресурсов от своего родительского процесса. Кроме того, дочерний процесс может не допустить наследования свойств родительского процесса. Может быть унаследовано следующее:
Дочерний процесс не наследует следующие:
Наследование дескрипторов
Дочерний процесс может наследовать некоторые его дескрипторы родителя, но не наследовать другие. Чтобы вызвать наследование маркера, необходимо выполнить два действия:
Унаследованный обработчик ссылается на тот же объект в дочернем процессе, что и в родительском процессе. Он также имеет то же значение и привилегии доступа. Таким образом, когда один процесс изменяет состояние объекта, изменение влияет на оба процесса. Чтобы использовать маркер, дочерний процесс должен получить значение Handle и «знает» объект, к которому он относится. Как правило, родительский процесс передает эти сведения дочернему процессу через ее командную строку, блок среды или некоторую форму межпроцессного взаимодействия.
Используйте функцию сесандлеинформатион для управления наследованием существующего маркера.
Наследование переменных среды
По умолчанию дочерний процесс наследует переменные среды родительского процесса. Однако CreateProcess позволяет родительскому процессу указывать другой блок переменных среды. Дополнительные сведения см. в разделе переменные среды.
Наследование текущего каталога
Процесcы в операционной системе Linux (основные понятия)
Основными активными сущностями в системе Linux являются процессы. Каждый процесс выполняет одну программу и изначально получает один поток управления. Иначе говоря, у процесса есть один счетчик команд, который отслеживает следующую исполняемую команду. Linux позволяет процессу создавать дополнительные потоки (после того, как он начинает выполнение).
Linux представляет собой многозадачную систему, так что несколько независимых процессов могут работать одновременно. Более того, у каждого пользователя может быть одновременно несколько активных процессов, так что в большой системе могут одновременно работать cотни и даже тысячи процессов. Фактически на большинстве однопользовательских рабочих станций (даже когда пользователь куда-либо отлучился) работают десятки фоновых процессов, называемых демонами (daemons). Они запускаются при загрузке системы из сценария оболочки.
Типичным демоном является cron. Он просыпается раз в минуту, проверяя, не нужно ли ему что-то сделать. Если у него есть работа, то он ее выполняет, а затем отправляется спать дальше (до следующей проверки).
Этот демон позволяет планировать в системе Linux активность на минуты, часы, дни и даже месяцы вперед. Например, представьте, что пользователю назначено явиться во военкомат в 3 часа дня в следующий вторник. Он может создать запись в базе данных демона cron, чтобы тот просигналил ему, скажем, в 14:30. Когда наступает назначенный день и время, демон cron видит, что у него есть работа, и запускает в назначенное время программу звукового сигнала (в виде нового процесса).
Демон cron также используется для периодического запуска задач, например ежедневного резервного копирования диска в 4 часа ночи или напоминания забывчивым пользователям каждый год за неделю до 31 декабря купить подарки для празднования нового года. Другие демоны управляют входящей и исходящей электронной почтой, очередями принтера, проверяют, достаточно ли еще осталось свободных страниц памяти и т.д. Демоны реализуются в системе Linux довольно просто, так как каждый из них представляет собой отдельный процесс, независимый от всех остальных процессов.
Процессы создаются в операционной системе Linux очень просто. Системный вызов fork создает точную копию исходного процесса, называемого родительским процессом (parent process). Новый процесс называется дочерним процессом (child process). У родительского и у дочернего процессов есть свои собственные (приватные) образы памяти. Если родительский процесс впоследствии изменяет какие-либо свои переменные, то эти изменения остаются невидимыми для дочернего процесса (и наоборот).
Открытые файлы используются родительским и дочерним процессами совместно. Это значит, что если какой-либо файл был открыт в родительском процессе до выполнения системного вызова fork, то он останется открытым в обоих процессах и в дальнейшем. Изменения, произведенные с этим файлом любым из процессов, будут видны другому. Такое поведение является единственно разумным, так как эти изменения будут видны также и любому другому процессу, который тоже откроет этот файл.
Тот факт, что образы памяти, переменные, регистры и все остальное и у родительского процесса, и у дочернего идентичны, приводит к небольшому затруднению: как процессам узнать, который из них должен исполнять родительский код, а который дочерний? Секрет в том, что системный вызов fork возвращает дочернему процессу число 0, а родительскому — отличный от нуля PID (Process IDentifier — идентификатор процесса) дочернего процесса. Оба процесса обычно проверяют возвращаемое значение и действуют соответственно:
pid = fork( ); /* если fork завершился успешно, pid > 0 в родительском процессе */
if (pid 0) <
/* здесь располагается родительский код */
> else <
/* здесь располагается дочерний код */
>
Если дочерний процесс желает узнать свой PID, то он может воспользоваться системным вызовом getpid. Идентификаторы процессов используются различным образом. Например, когда дочерний процесс завершается, его родитель получает PID только что завершившегося дочернего процесса. Это может быть важно, так как у родительского процесса может быть много дочерних процессов. Поскольку у дочерних процессов также могут быть дочерние процессы, то исходный процесс может создать целое дерево детей, внуков, правнуков и более дальних потомков.
В системе Linux процессы могут общаться друг с другом с помощью некой формы передачи сообщений. Можно создать канал между двумя процессами, в который один процесс сможет писать поток байтов, а другой процесс сможет его читать. Эти каналы иногда называют трубами (pipes). Синхронизация процессов достигается путем блокирования процесса при попытке прочитать данные из пустого канала. Когда данные появляются в канале, процесс разблокируется.
При помощи каналов организуются конвейеры оболочки. Когда оболочка видит строку вроде
sort
Процессы
Процессы – действующее начало. В общем случае с процессом связаны код и данные в виртуальной оперативной памяти, отображение виртуальной памяти на физическую, состояние процессора (регистры, текущая исполняемая инструкция и т.п.). Кроме того в Unix с процессом связана информация о приоритете (в том числе понижающий коэффициент nice ), информация об открытых файлах и обработчиках сигналов. Программа, выполняемая внутри процесса, может меняться в течение его существования.
Создание процессов fork()
После создания, дочерний процесс может загрузить в свою память новую программу (код и данные) из исполняемого файла вызовом execve(const char *filename, char *const argv [], char *const envp[]);
Процесс init
В момент загрузки ядра создаётся особый процесс с PID=1, который должен существовать до перезагрузки ОС. Все остальные процессы в системе являются его дочерними процессами (или дочерними от дочерних и т.д.). Обычно, в первом процессе исполняется программа init поэтому в дальнейшем я буду называть его «процесс init«.
В современных дистрибутивах классическая программа init заменена на systemd, но сущности процесса с PID=1 это не меняет.
Для того, чтобы выполнить эти два пункта через загрузчик в начального init два параметра:
Если второй параметр опущен то ищется имя зашитое в начальный init по умолчанию.
Если вы загрузите вместо init /bin/bash, как в моём примере, то сможете завершить первый и единственный процесс командой exit и пронаблюдать сообщение:
Этот пример так же показывает, как получить права администратора при физическом доступе к компьютеру.
Каждый процесс имеет уникальный на данный момент времени идентификатор PID. Поменять PID процесса невозможно.
Максимальное значение PID в Linux равняется PID_MAX-1. Текущее значение PID_MAX можно посмотреть командой:
По умолчанию это 2^16 (32768) однако в 64-разрядных Linux его можно увеличить до 2^22 (4194304):
UID и GID
С процессом связано понятие «владельца» и «группы», определяющие права доступа процесса к другим процессам и файлам в файловой системе. «Владелец» и «группа», это числовые идентификатор UID и GID, являющийся атрибутами процесса. В отличие от файла, процесс может принадлежать нескольким группам одновременно. Пользователь в диалоговом сеансе имеет право на доступ к своим файлам поскольку диалоговая программа (shell), которую он использует, выполняется в процессе с тем же UIDом, что и UID, указанный в атрибутах файлов.
Процесс может поменять своего владельца и группу в двух случаях:
Жизненный цикл процесса
Создание процесса
Запуск программы
В оперативной памяти процесса находятся код и данные, загруженные из файла. При запуске программы из командной строки, обычно создается новый процесс и в его память загружается файл с программой. Загрузка файла делается вызовом одной из функций семейства exec (см. man 3 exec ). Функции отличаются способом передачи параметров, а также тем, используется ли переменная окружения PATH для поиска исполняемого файла. Например execl в качестве первого параметра принимает имя исполняемого файла, вторым и последующими – строки аргументы, передаваемые в argv[], и, наконец, последний параметр должен быть NULL, он дает процедуре возможность определить, что параметров больше нет.
Пример exec с двумя ошибками:
Ошибка 2: Поскольку код из файла /bin/ls будет загружен в текущий процесс, то старый код и данные, в том числе printf(«Программа ls запущена успешно\n»), будет затерты. Первый printf не сработает никогда.
Завершение процесса
_exit() может быть вызван несколькими путями.
Удаление завершенного процесса из таблицы процессов
Вызов wait(&status); эквивалентен waitpid(-1, &status, 0);
Статус завершения проверяется макросами:
Основы планирования процессов
Для обеспечения многозадачности каждый пользовательский процесс периодически прерывается, его контекст сохраняется, а управление передаётся другому процессу. Прерывание выполнения процесса может происходить по таймеру или во время обработки системного вызова. В зависимости от обстоятельств прерванный процесс ставится в очередь процессов на исполнение, в список процессов ожидающих ресурсы (например, ожидание пользовательского ввода или завершения вывода на физический носитель) или в список остановленных процессов.
Прерывания по таймеру происходят в соответствии с квантом времени, выделенному процессу. В Linux квант времени по умолчанию (DEF_TIMESLICE) равен 0,1 секунды, но может быть пересчитан планировщиком процессов ( sheduler ).
После завершения процесса вызовом _exit() или по сигналу все его ресурсы (память, открытые файлы) освобождаются, но запись в таблице процессов остаётся и занимает PID. Такой процесс называется «зомби» и должен быть явно очищен из таблицы процессов вызовом wait() в родительском процессе. Если родительский процесс завершился раньше дочерних, то всем его дочерним процессам приписывается значение PPID (parent pid) равное 1, возлагая обязательства по очистке от них таблицы процессов на особый процесс init с PID=1.
На диаграмме показаны различные состояния процесса
В Linux команда ps использует следующие обозначения состояния процесса:
Планировщик процессов
Задачей планировщика процессов процессов является извлечение процессов, готовых на выполнение, в соответствии с некоторыми правилами. Планировщик старается распределить процессорные ресурсы так, чтобы ни один из процессов не простаивал длительное время, и чтобы процессы, считающиеся приоритетными, получали процессорное время в первую очередь. В многопроцессорных системах желательно, чтобы в последовательных квантах времени процесс запускался на одном и том же процессоре, чтобы максимально использовать процессорный кэш. При этом сам планировщик должен выполнять выбор как можно быстрее.
Простейшая реализация очереди в виде FIFO очень быстра, но не поддерживает приоритеты и многопроцессорность. В Linux 2.6 воспользовались простотой FIFO, добавив к ней несколько усовершенствований:
Процесс Idle
Если нет процессов готовых для выполнения, то планировщик вызывает нить (процесс) Idle. В Linux 2.2 однопроцессорная кроссплатформенная версия Idle выглядела так:
В аппаратно-зависимую реализацию idle() может быть вынесено управление энергосбережением.
В ранних версиях Linux процесс Idle имел PID=0, но, вообще говоря, Idle как самостоятельный процесс не существует.
Вычисление средней загрузки
Эффективные права процесса
euid равный нулю используется для обозначения привилегированного процесса, имеющего особые права на доступ к ФС и другим процессам, а так же на доступ к административным функциям ядра, таким как монтирование диска или использование портов TCP с номерами меньше 1024. Процесс с euid=0 всегда имеет право на чтение и запись файлов и каталогов. Право на выполнение файлов предоставляется привилегированному процессу только в том случае, когда у файла выставлен хотя бы один атрибут права на исполнение.
(re)uid/(re)gid, а также вспомогательные группы, наследуются от родительского процесса при вызове fork(). При вызове exec() ruid/rgid сохраняются, а euid/egid могут быть изменены если у исполняемого файла выставлен флаг смены владельца. Для скриптов флаг смены владельца игнорируется т.к. фактически запускается интерпретатор, а скрипт передаётся ему в качестве параметра. В момент входа пользователя в систему программа login считывает из файлов /etc/passwd и /etc/group необходимые величины и устанавливает их перед загрузкой командного интерпретатора.
Для инициализации вспомогательных групп в Linux можно воспользоваться функцией int initgroups(const char *user, gid_t group); эта функция разбирает файл /etc/group, а за тем обращается к системному вызову int setgroups(size_t size, const gid_t *list);.
В Linux, HP-UX и некоторых других ОС дополнительно поддерживаются атрибут сохраненных прав процесса suid/sgid (не путать с одноименными атрибутами файла). Соответственно есть функция для установки всех трёх атрибутов setresuid(rid,eid,sid);
Если euid=0 или ruid=0 то ruid и euid могут меняться произвольно. Т.е. можно сделать euid<>0 или ruid<>0, а затем вернуться в состояние euid=ruid=0. Если оба атрибута не равны нулю, то возможно лишь изменение euid в ruid (отказ от дополнительных прав). Программа su получает euid=0 благодаря соответствующему атрибуту файла и использует возможности привилегированного процесса для запуска программ от имени произвольного пользователя (в том числе root). Веб-сервер apache, наоборот, стартует с ruid=euid=0, но затем отбирает у себя лишние права меняя ruid и euid на непривилегированные значения.