Операционная система UNIX - страница 69

стр.

. Правда, с помощью функций malloc(3C), calloc(3C) или realloc(3C) можно снова выделить и использовать эту память и снова освободить ее, но она не передается обратно ядру, а остается в пуле malloc(3C).

Для иллюстрации этого положения приведем небольшую программу, выделяющую и освобождающую память с помощью функций malloc(3C) и free(3C), соответственно. Контроль действительного значения брейк-адреса осуществляется с помощью системного вызова sbrk(2):

>#include

>#include


>main() {

> char *obrk;

> char *nbrk;

> char *naddr;

> /* Определим текущий брейк-адрес */

> obrk = sbrk(0);

> printf("Текущий брейк-адрес= 0x%x\n", obrk);

> /* Выделим 64 байта из хипа */

> naddr = malloc(64);

> /* Определим новый брейк-адрес */

> nbrk = sbrk(0);

> printf("Новый адрес области malloc= 0x%x,"

>  " брейк-адрес= 0х%x (увеличение на %d байтов)\n",

>  naddr, nbrk, nbrk — obrk);

> /* "Освободим" выделенную память и проверим, что произошло

>    на самом деле */

> free(naddr);

> printf("free(0x%x)\n", naddr);

> obrk = sbrk(0);

> printf("Новый брейк-адрес= 0x%x (увеличение на %d байтов)\n",

>  obrk, obrk — nbrk);

>}

Откомпилируем и запустим программу:

>$ a.out

>Текущий брейк-адрес= 0x20ac0

>malloc(64)

>Новый адрес области malloc = 0x20ac8, брейк-адрес = 0x22ac0

>(увеличение на 8192 байтов)

>free(0x20ac8)

>Новый брейк-адрес = 0x22ac0 (увеличение на 0 байтов)

>$

Как видно из вывода программы, несмотря на освобождение памяти функцией free(3C), значение брейк-адреса не изменилось. Также можно заметить, что функция malloc(3C) выделяет больше памяти, чем требуется. Дополнительная память выделяется для необходимого выравнивания и для хранения внутренних данных malloc(3C), таких как размер области, указатель на следующую область и т.п.

Создание и управление процессами

Работая в командной строке shell вы, возможно, не задумывались, каким образом запускаются программы. На самом деле каждый раз порождается новый процесс, а затем загружается программа. В UNIX эти два этапа четко разделены. Соответственно система предоставляет два различных системных вызова: один для создания процесса, а другой для запуска новой программы.

Новый процесс порождается с помощью системного вызова fork(2):

>#include

>#include


>pid_t fork(void);

Порожденный, или дочерний процесс, хотя это кажется странным, является точной копией процесса, выполнившего этот вызов, или родительского процесса. В частности, дочерний процесс наследует такие атрибуты родителя, как:

□ идентификаторы пользователя и группы,

□ переменные окружения,

□ диспозицию сигналов и их обработчики,

□ ограничения, накладываемые на процесс,

□ текущий и корневой каталог,

□ маску создания файлов,

□ все файловые дескрипторы, включая файловые указатели,

□ управляющий терминал.

Более того, виртуальная память дочернего процесса не отличается от образа родительского: такие же сегменты кода, данных, стека, разделяемой памяти и т.д. После возврата из вызова fork(2), который происходит и в родительский и в дочерний процессы, оба начинают выполнять одну и ту же инструкцию.

Легче перечислить немногочисленные различия между этими процессами, а именно:

□ дочернему процессу присваивается уникальный идентификатор PID.

□ идентификаторы родительского процесса PPID у этих процессов различны,

□ дочерний процесс свободен от сигналов, ожидающих доставки,

□ значение, возвращаемое системным вызовом fork(2) различно для родителя и потомка.

Последнее замечание требует объяснения. Как уже говорилось, возврат из функции fork(2) происходит как в родительский, так и в дочерний процесс. При этом возвращаемое родителю значение равно PID дочернего процесса, а дочерний, в свою очередь, получает значение, равное 0. Если fork(2) возвращает -1, то это свидетельствует об ошибке (естественно, в этом случае возврат происходит только в процесс, выполнивший системный вызов).

В возвращаемом fork(2) значении заложен большой смысл, поскольку оно позволяет определить, кто является родителем, а кто — потомком, и соответственно разделить функциональность. Поясним это на примере:

>main() {

> int pid;

> pid = fork();

> if (pid == -1) {