приглашение */
if (pw == 0)
retry();
/* В противном случае установим идентификаторы процесса
равными значениям, полученным из файла паролей и запустим
командный интерпретатор */
else {
setuid(pw->pw_uid);
setgid(pw->pw_gid);
execve(pw->pw_shell, arg, envir);
}
...
Вызов execve(2) запускает на выполнение программу, указанную в первом аргументе. Мы рассмотрим эту функцию в разделе "Создание и управление процессами" далее в этой главе.
При обсуждении формата исполняемых файлов и образа программы в памяти мы отметили, что сегменты данных и стека могут изменять свои размеры. Если для стека операцию выделения памяти операционная система производит автоматически, то приложение имеет возможность управлять ростом сегмента данных, выделяя дополнительную память из хипа (heap — куча). Рассмотрим этот программный интерфейс.
Память, которая используется сегментами данных и стека, может быть выделена несколькими различными способами как во время создания процесса, так и динамически во время его выполнения. Существует четыре способа выделения памяти:
1. Переменная объявлена как глобальная, и ей присвоено начальное значение в исходном тексте программы, например:
char ptype = "Unknown file type";
Строка ptype
размещается в сегменте инициализированных данных исполняемого файла, и для нее выделяется соответствующая память при создании процесса.
2. Значение глобальной переменной неизвестно на этапе компиляции, например:
char ptype[32];
В этом случае место в исполняемом файле для ptype
не резервируется, но при создании процесса для данной переменной выделяется необходимое количество памяти, заполненной нулями, в сегменте BSS.
3. Переменные автоматического класса хранения, используемые в функциях программы, используют стек. Память для них выделяется при вызове функции и освобождается при возврате. Например:
func1() {
int a;
char *b;
static int с = 4;
...
}
В данном примере переменные а
и b
размещаются в сегменте стека. Переменная с размешается в сегменте инициализированных данных и загружается из исполняемого файла либо во время создания процесса, либо в процессе загрузки страниц по требованию. Более подробно страничный механизм описан в главе 3.
4. Выделение памяти явно запрашивается некоторыми системными вызовами или библиотечными функциями. Например, функция malloc(3C) запрашивает выделение дополнительной памяти, которая в дальнейшем используется для динамического размещения данных. Функция ctime(3C) , предоставляющая системное время в удобном формате, также требует выделения памяти для размещения строки, содержащей значения текущего времени, указатель на которую возвращается программе.
Напомним, что дополнительная память выделяется из хипа (heap) — области виртуальной памяти, расположенной рядом с сегментом данных, размер которой меняется для удовлетворения запросов на размещение. Следующий за сегментом данных адрес называется разделительным или брейк-адресом (break address). Изменение размера сегмента данных по существу заключается в изменении брейк-адреса. Для изменения его значения UNIX предоставляет процессу два системных вызова — brk(2) и sbrk(2) .
#include
int brk(void *endds);
void *sbrk(int incr);
Системный вызов brk(2) позволяет установить значение брейк-адреса равным endds
и, в зависимости от его значения, выделяет или освобождает память (рис. 2.11). Функция sbrk(2) изменяет значение брейк-адреса на величину incr
. Если значение incr
больше 0, происходит выделение памяти, в противном случае, память освобождается. [23] Заметим, что в некоторых системах дополнительная память выделяется (или освобождается) в порциях, кратных размеру страницы. Например, выделение всего 100 байтов на самом деле приведет к выделению 4096 байтов, если размер страницы равен 4K.
Рис 2.11. Динамическое выделение памяти с помощью brk(2)
Существуют четыре стандартные библиотечные функции, предназначенные для динамического выделения/освобождения памяти.
#include
void *malloc(size_t size);
void *calloc(size_t nelem, size_t elsize);
void *realloc(void *ptr, size_t size);
Читать дальше