Салимóненко Дмитрий Александрович

Операционные системы

Задание 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);
}

На что нужно обратить внимание?

  1. Как видим, программа использует структуру. В общем-то, в данном случае - совершенно без разницы, какой именно тип данных использовать. Однако, целесообразно ознакомиться, уже на примере структуры, каким образом будут располагаться данные в виртуальной оперативной памяти.
  2. Обратите внимание на последние элементы структуры 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). Таким образом, чем больше к программе будет подключено заголовочных файлов с описанием системных команд, тем больше будет размер ее адресного пространства и, соответственно, тем большим будет размер сегмента кода, который получится при загрузке такой программы в оперативную память.

Задание для самостоятельной разработки:

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

С уважением, Салимоненко Д.А.