Салимóненко Дмитрий Александрович
Задание 4: Изучение виртуального адресного пространства
(C, Linux)
СОДЕРЖАНИЕ
Введение
Это задание является продолжением задания 3. Здесь мы продолжим исследование виртуальной оперативной памяти процесса, загруженного в оперативную память в операционной системе LINUX. По сути, нужно будет сделать примерно то же самое, что и в Задании 3, но, немного по-другому, используя несколько иной подход. Добавлены также дополнительные виды переменных, например, переменная окружения. О теории (а именно - о структуре виртуальной оперативной памяти) говорить здесь не будем, так как основы ее изложены в Задании 3.
Рассмотрим код программы на языке С
, демонстрирующей работу с адресами виртуальной оперативной памяти в Linux. Код взят отсюда, немного изменен.
/* memsegments.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct mem
{
char text[140]; // Почему нужно выбрать столь большую размерность массива? Что будет, если выбрать размерность равной, например, 100?
int *p;
} mem;
int cmp_by_address(const void *, const void *);
void print_struct_array(mem *, size_t);
int init_global_var = 10; /* Инициализированная глобальная переменная */
int global_var; /* Неинициализированная глобальная переменная */
static int init_static_var = 20; /* Инициализированная статическая переменная в глобальном списке */
static int static_var; /* Неинициализированная статическая переменная в глобальном списке */
int main(int argc, char **argv, char **envp)
{
system("clear");
static int init_static_local_var = 30; /* Инициализированная статическая локальная переменная */
static int static_local_var; /* Неинициализированная статическая локальная переменная */
int init_local_var = 40; /* Инициализированная локальная переменная */
int local_var; /* Неинициализированная локальная переменная */
int *dynamic_var = (int*)malloc(sizeof(int)); /* Динамическая переменная */
mem structs[] = // Заполняем поля структуры structs
{
{"Инициализированная глобальная переменная", &init_global_var},
{"Неинициализированная глобальная переменная", &global_var},
{"Инициализированная статическая переменная в глобальном списке", &init_static_var},
{"Неинициализированная статическая переменная в глобальном списке", &static_var},
{"Инициализированная статическая локальная переменная", &init_static_local_var},
{"Неинициализированная статическая локальная переменная", &static_local_var },
{"Код функции main", (int*)&main },
{"Код функции cmp_by_address", (int*)&cmp_by_address },
{"Переменная окружения", (int*)&envp[0] },
{"Инициализированная локальная переменная", &init_local_var },
{"Неинициализированная локальная переменная", &local_var },
{"Динамическая переменная", dynamic_var },
// {"Код функции ALLOCA", (int*)&alloca},
{"Код функции FREE", (int*)&free},
{"Код функции SSCANF", (int*)&sscanf},
};
size_t len = sizeof(structs) / sizeof(mem); // Почему производится такое деление?
qsort(structs, len, sizeof(mem), cmp_by_address); // Сортировка на основе функции cmp_by_address
print_struct_array(structs, len);
free(dynamic_var);
return 0;
}
int cmp_by_address(const void *a, const void *b)
{
mem *ma = (mem *)a;
mem *mb = (mem *)b;
if ((unsigned)ma->p > (unsigned)mb->p)
return -1;
else if ((unsigned)ma->p < (unsigned)mb->p)
return 1;
else
return 0;
}
/* Example struct array printing function */
void print_struct_array(mem *array, size_t len)
{
size_t i;
for(i=0; i<len; i++)
printf("%-40s:\t%p\n", array[i].text, array[i].p);
}
На что нужно обратить внимание?
- Как видим, программа использует структуру. В общем-то, в данном случае - совершенно без разницы, какой именно тип данных использовать. Однако, целесообразно ознакомиться, уже на примере структуры, каким образом будут располагаться данные в виртуальной оперативной памяти.
- Обратите внимание на последние элементы структуры
structs
. Та, что закомментирована (выделена оранжевым цветом), пытается задать адрес СИСТЕМНОЙ функцииalloca
(см. Задание 3). Строчка, выделенная зеленым цветом, предназначена для выяснения адреса СИСТЕМНОЙ функцииfree
. Если у оранжевой строчки снять комментирование (т.е. чтобы она стала командой), то компилятор выдаст ошибку, навроде
/tmp/ccee4ATP.o: In function `main':
memsegments.c:(.text+0xb25): undefined reference to `alloca'
collect2: error: ld returned 1 exit status
Можно сделать выводы. Во-первых, виртуальные адреса системных функций, подключенных к конкретной программе, вполне можно получать при помощи вышеуказанного кода - точно также, как и адреса функций, определенных программистом (разработчиком программы) самостоятельно. Однако, такие функции должны быть описаны в заголовочных файлах, подключенных к программе (например, при помощи директивы#include
). Поэтому с функциейfree
проблем нет, после компиляции и запуска программы в консоли будет выведен ее виртуальный адрес, а вот сalloca
ситуация обстоит сложнее: заголовочного файла, в котором она была бы описана, в вышеприведенном программном коде - нет. Поэтому она и не подключается к программе (т.е. она отсутствует в виртуальном адресном пространстве процессаmemsegments.c
и, соответственно, компилятор не знает о ней. Поэтому и невозможно определить ее виртуальный адрес.
Во-вторых, можно получить даже адреса таких системных функций, которые в программе вообще не используются, а лишь подключены к ней (в соответствующих заголовочных файлах). Пример такой функции - этоsscanf
.
Сортировка структуры
Подумайте, для какой цели она производится.
Переменная окружения
Что она собой представляет, для чего используется в Linux? Приведите характерные примеры таких переменных.
Результат работы программы

Примерный результат работы приведенного выше кода представлен на рисунке. Вначале идут переменные, занимающие верхние адреса виртуальной памяти: это переменная окружения, локальные (неинициализированная и инициализированная) переменные. А потом уже, в памяти нижних адресов (условно) располагаются все остальные, используемые данной программой, переменные.
Т.е. как обычно, видим адреса переменных различных типов, расположенные по убыванию. Также видно, что с самых низких адресов располагаются адреса системных команд (процедур), в том числе и тех, которые вообще не используются в программе (в частности, команда sscanf
). Затем идет адрес функции main
, затем - адрес функции, определенной программистом (в частности, cmp_by_address
). Таким образом, чем больше к программе будет подключено заголовочных файлов с описанием системных команд, тем больше будет размер ее адресного пространства и, соответственно, тем большим будет размер сегмента кода, который получится при загрузке такой программы в оперативную память.
Задание для самостоятельной разработки:
- Выполните преобразование формата вывода виртуальных адресов из шестнадцатеричного в десятичное на экран: вначале должны выводиться виртуальные адреса переменных в шестнадцатеричном формате, затем, через пустую строку - адреса тех же переменных, но уже в десятичном формате (в байтах).
- После запуска и окончания работы программы у Вас должен получиться результат, аналогичный приведенному выше. На основании полученных данных, постройте (например, в Word, но, можно вручную, на листе бумаги) аналогичную схему с указанием виртуальных адресов всех переменных. Необходимо указать как начальный, так и конечный адреса каждой переменной, адреса которых содержатся в структуре
structs
. Также необходимо указать начальные адреса функций (free, sscanf, main, cmp_by_address
). При этом схему, как и в Задании 3, необходимо разделить на сегменты (кода, данных, стека и т.д.), т.е. следует указать адреса границ каждого сегмента. - Позапускайте программу несколько раз. Некоторые виртуальные адреса каждый раз меняются. С чем это связано, как это влияет на безопасность работы компьютера?
- Как видим, переменные располагаются в виртуальной оперативной памяти в порядке определенной иерархии, в зависимости от типа: окружения, или статическая, или "обычная", или глобальная. Разберитесь - почему так.
- Все обсуждаемое выше (в том числе и в Задании 3) имеет отношение исключительно к виртуальной оперативной памяти (точнее, к виртуальному адресному пространству, так как никакой виртуальной памяти, соответствующей ему, НЕТ). А как быть насчет физической оперативной памяти? Как Вы думаете, в какие конкретно ее ячейки попадут соответствующие переменные, функции?
С уважением, Салимоненко Д.А.