Выбрать главу

Листинг 6.1. Размещение переменных программы в сегментах памяти процесса

proc/mem_segments.c

#include <stdio.h>

#include <stdlib.h>

char globBuf[65536]; /* Сегмент неинициализированных данных */

int primes[] = { 2, 3, 5, 7 }; /* Сегмент инициализированных данных */

static int

square(int x) /* Размещается в фрейме для square() */

{

int result; /* Размещается в фрейме для square() */

result = x * x;

return result; /* Возвращаемое значение передается через регистр */

}

static void

doCalc(int val) /* Размещается в фрейме для doCalc() */

{

printf("The square of %d is %d\n", val, square(val));

if (val < 1000) {

int t; /* Размещается в фрейме для doCalc() */

t = val * val * val;

printf("The cube of %d is %d\n", val, t);

}

}

int

main(int argc, char *argv[]) /* Размещается в фрейме для main() */

{

static int key = 9973; /* Сегмент инициализированных данных */

static char mbuf[10240000]; /* Сегмент неинициализированных данных */

char *p; /* Размещается в фрейме для main() */

p = malloc(1024); /* Указывает на память в сегменте кучи */

doCalc(key);

exit(EXIT_SUCCESS);

}

proc/mem_segments.c

Двоичный интерфейс приложений — Application Binary Interface (ABI) представляет собой набор правил, регулирующих порядок обмена информацией между двоичной исполняемой программой в ходе ее выполнения и каким-либо сервисом (например, ядром или библиотекой). Помимо всего прочего, ABI определяет, какие регистры и места в стеке используются для обмена этой информацией и какой смысл придается обмениваемым значениям. Программа, единожды скомпилированная в соответствии с требованием некоторого ABI, должна запускаться в любой системе, предоставляющей точно такой же ABI. Это отличается от стандартизированного API (например, SUSv3), гарантирующего портируемость только для приложений, скомпилированных из исходного кода.

Хотя это и не описано в SUSv3, среда программы на языке C во многих реализациях UNIX (включая Linux) предоставляет три глобальных идентификатора: etext, edata и end. Они могут использоваться из программы для получения адресов следующего байта соответственно за концом текста программы, за концом сегмента инициализированных данных и за концом сегмента неинициализированных данных. Чтобы воспользоваться этими идентификаторами, их нужно явным образом объявить:

extern char etext, edata, end;

/* К примеру, &etext сообщает адрес первого байта после окончания

текста программы/начала инициализированных данных */

На рис. 6.1 показано расположение различных сегментов памяти в архитектуре x86-32. Пространство с пометкой argv, охватывающее верхнюю часть этой схемы, содержит аргументы командной строки программы (которые в C доступны через аргумент argv функции main()) и список переменных среды процесса (который вскоре будет рассмотрен). Шестнадцатеричные адреса, приведенные в схеме, могут варьироваться в зависимости от конфигурации ядра и ключей компоновки программы. Области, закрашенные серым цветом, представляют собой недопустимые диапазоны в виртуальном адресном пространстве процесса, то есть области, для которых не созданы таблицы страниц (см. далее раздел, посвященный управлению виртуальной памятью).

Рис. 6.1. Типичная структура памяти процесса в Linux/x86-32

6.4. Управление виртуальной памятью

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

Следуя в ногу с большинством современных ядер, Linux использует подход, известный как управление виртуальной памятью. Цель этой технологии заключается в создании условий для эффективного использования как центрального процессора, так и оперативной (физической) памяти путем применения свойства локальности ссылок, присущего многим программам. Локальность в большинстве программ проявляется в двух видах.