У каждого процесса есть свой уникальный идентификатор, и он содержит запись идентификатора своего родительского процесса.
Виртуальная память процесса логически разделена на несколько сегментов: текстовый, сегмент данных (инициализированных и неинициализированных), стека и кучи.
Стек состоит из последовательности фреймов, при этом новый фрейм добавляется при вызове функции и удаляется при возвращении из этой функции. Каждый фрейм содержит локальные переменные, аргументы функций и информацию, связанную с вызовом для отдельно взятого вызова функции.
Аргументы командной строки, предоставляемые при запуске программы, становятся доступны через аргументы argc и argv функции main(). По соглашению в argv[0] содержится имя, использованное для вызова программы.
Каждый процесс получает копию списка переменных среды своего родительского процесса, представляющего собой набор из пар «имя-значение». Доступ процесса к переменным в его списке переменных среды и возможность их изменения предоставляется через глобальную переменную environ и посредством различных библиотечных функций.
Функции setjmp() и longjmp() предлагают способ выполнения нелокальных переходов из одной функции в другую (с раскруткой стека). Чтобы избежать проблем с оптимизацией в ходе компиляции, при использовании этих функций может понадобиться объявлять переменные с модификатором volatile. Нелокальные переходы могут отрицательно сказаться на читаемости программы и затруднить ее сопровождение, поэтому по возможности их нужно избегать.
Дополнительная информация
Подробное описание системы управления виртуальной памятью можно найти в изданиях [Tanenbaum, 2007] и [Vahalia, 1996]. Алгоритмы управления памятью, используемые в ядре Linux, и соответствующий им код подробно рассмотрены в книге [Gorman, 2004].
6.1. Скомпилируйте программу из листинга 6.1 (mem_segments.c) и выведите на экран ее размер, воспользовавшись командой ls — l. Хотя программа содержит массив (mbuf), размер которого приблизительно составляет 10 Мбайт, размер исполняемого файла существенно меньше. Почему?
6.2. Напишите программу, чтобы посмотреть, что случится, если попытаться осуществить переход с помощью функции longjmp() в функцию, возвращение из которой уже произошло.
6.3. Реализуйте функции setenv() и unsetenv(), используя функции getenv(), putenv() и там, где это необходимо, код, который изменяет массив environ напрямую. Ваша версия функции unsetenv() должна проверять наличие нескольких определений переменной среды и удалять все определения (точно так же, как это делает glibc-версия функции unsetenv()).
7. Выделение памяти
Почти все системные программы должны обладать возможностью выделения дополнительной памяти для динамических структур данных. Например, такая память нужна для работы связанных списков и двоичных деревьев, чей размер зависит от информации, доступной только в ходе выполнения программы. В этой главе рассматриваются функции, используемые для выделения памяти в куче или стеке.
Процесс может выделить память, увеличив размер кучи (сегмента непрерывной виртуальной памяти переменного размера), который начинается сразу же после сегмента неинициализированных данных процесса и увеличивается/уменьшается по мере выделения/высвобождения памяти (см. рис. 6.1). Текущее ограничение кучи называется крайней точкой программы (program break).
Для выделения памяти в программах на языке C обычно используется семейство функций malloc, которое мы вскоре рассмотрим. Но сначала разберем функции brk() и sbrk(), на применении которых основана работа функций malloc.
7.1.1. Установка крайней точки программы: brk() и sbrk()
Изменение размеров кучи (то есть выделение и высвобождение памяти) сводится лишь к тому, чтобы всего лишь объяснить ядру, где располагается крайняя точка программы (program break). Изначально крайняя точка программы находится непосредственно сразу же за окончанием сегмента неинициализированных данных (то есть там же, где на рис. 6.1 стоит метка &end). После того как эта точка будет сдвинута вверх, программа сможет получать доступ к любому адресу во вновь выделенной области, но страницы физической памяти пока выделяться не будут. Ядро автоматически выделит новые физические страницы при первой же попытке процесса обратиться к адресам этих страниц.