Управление памятью в системах windows динамическая память

Виртуальная память

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

Виртуальная адресация

Каждому процессу в Windows предоставляется свое собственное виртуальное адресное пространство. Это означает, что каждый процесс видит свою собственную непрерывную область адресов, начиная с нуля. Этот механизм позволяет изолировать процессы друг от друга, так что один процесс не может напрямую обратиться к памяти другого процесса.

Физическая память и страничный файл

Виртуальная память Windows состоит из физической оперативной памяти (RAM) и страничного файла на диске. Если физическая память заполняется, то часть данных может быть перемещена в страничный файл, освобождая место для новых данных. Этот процесс называется “подкачкой” (paging).

Страницы памяти

Виртуальная память разбивается на небольшие блоки, называемые страницами памяти. Размер страницы обычно составляет 4 КБ. Windows использует систему управления таблицами страниц (Page Table) для отображения виртуальных адресов на физические адреса или на адреса в страничном файле.

Отображение виртуальной памяти

Когда процесс обращается к виртуальной памяти, операционная система Windows преобразует виртуальный адрес в соответствующий физический адрес. Если требуемая страница находится в физической памяти, это происходит незаметно. Если страница находится в страничном файле, она должна быть загружена в физическую память перед доступом к ней.

Защита памяти

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

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

Операционная система Windows автоматически управляет виртуальной памятью, включая подкачку данных между физической памятью и страничным файлом. Программисты обычно не заботятся о деталях управления виртуальной памятью, но могут использовать API для запроса дополнительной памяти (например, функции VirtualAlloc) и управления защитой памяти (например, функции VirtualProtect).

Управление динамической памятью

Управление памятью в Windows может быть выполнено с использованием различных функций и API операционной системы. Давайте рассмотрим несколько примеров кода на языке C/C++ для выделения и освобождения памяти в Windows.

Выделение памяти с использованием malloc и free (C/C++)

#include <stdio.h>
#include <stdlib.h>

int main() {
    // Выделение памяти под массив целых чисел
    int *arr = (int*)malloc(5 * sizeof(int));

    if (arr == NULL) {
        printf("Не удалось выделить память\n");
        return 1;
    }

    // Использование выделенной памяти
    for (int i = 0; i < 5; i++) {
        arr[i] = i * 10;
    }

    // Освобождение памяти после использования
    free(arr);

    return 0;
}

В этом примере мы используем функции malloc для выделения памяти под массив целых чисел и free для освобождения этой памяти после ее использования.

Выделение памяти с использованием функции VirtualAlloc (WinAPI)

#include <Windows.h>
#include <stdio.h>

int main() {
    // Выделение 1 мегабайта (1048576 байт) виртуальной памяти
    LPVOID mem = VirtualAlloc(NULL, 1048576, MEM_COMMIT, PAGE_READWRITE);

    if (mem == NULL) {
        printf("Не удалось выделить виртуальную память\n");
        return 1;
    }

    // Использование выделенной виртуальной памяти

    // Освобождение виртуальной памяти
    VirtualFree(mem, 0, MEM_RELEASE);

    return 0;
}

Здесь мы используем функцию VirtualAlloc из библиотеки WinAPI для выделения виртуальной памяти. После использования памяти мы освобождаем ее с помощью функции VirtualFree.

Выделение и освобождение памяти с использованием C++ операторов new и delete

#include <iostream>
#include <windows.h>

int main() {
    SetConsoleOutputCP(1251);
    // Выделение памяти под одно целое число
    int *num = new int;

    // Использование выделенной памяти
    *num = 42;
    std::cout << "Значение: " << *num << std::endl;

    // Освобождение памяти
    delete num;

    return 0;
}

Стек и куча

Стек и куча — это две основные области памяти, используемые в программах для хранения данных и управления памятью. Они имеют разные характеристики и предназначены для разных целей. Давайте рассмотрим их более подробно:

Стек (Stack)

  • Характеристики:
    • Ограниченный по размеру.
    • Доступ к данным выполняется в порядке “первым вошел, последним вышел” (LIFO — Last-In, First-Out).
    • Часто фиксированный размер стека определяется на этапе компиляции.
  • Использование:
    • Хранит локальные переменные функций и адреса возврата после вызова функций.
    • Используется для управления вызовами функций (стек вызовов).
  • Жизненный цикл данных:
    • Данные, хранящиеся в стеке, автоматически удаляются при завершении функции, в которой они определены.
    • Ограниченное время жизни.
  • Примеры языков:
    • Стек используется в C, C++, Java (для вызовов методов), Python (для вызовов функций).

Куча (Heap)

  • Характеристики:
    • Динамически расширяемая область памяти.
    • Доступ к данным происходит в произвольном порядке.
    • Размер кучи ограничен объемом доступной физической и виртуальной памяти.
  • Использование:
    • Хранит данные, которые могут иметь долгий или неопределенный срок жизни, такие как объекты, созданные динамически.
  • Жизненный цикл данных:
    • Данные, хранящиеся в куче, существуют до тех пор, пока на них есть указатели, и могут быть освобождены вручную (например, с помощью free в C/C++ или сборщика мусора в других языках).
  • Примеры языков:
    • Куча используется в C, C++, C#, Java (для объектов, созданных с помощью new), Python (с использованием модуля gc для сборки мусора).

Сравнение стека и кучи

  • Стек обычно быстрее доступен для чтения и записи, чем куча.

  • Куча предоставляет более гибкое управление памятью, но требует явного освобождения ресурсов.

  • Стек обеспечивает управление временем жизни данных автоматически, в то время как в куче это делается вручную.

  • Использование стека ограничено, поэтому он лучше подходит для хранения данных с известным временем жизни, в то время как куча подходит для данных с неопределенным или долгим временем жизни.

  • Оба механизма имеют свои применения и зависят от конкретных требований программы.

Функции для работы со стеком

Windows предоставляет набор функций и API для работы со стеком приложения. Эти функции позволяют программам управлять стеком вызовов функций, а также получать информацию о текущем состоянии стека. Вот некоторые из наиболее часто используемых функций Windows для работы со стеком:

GetCurrentThreadStackLimits (Windows 8.1 и более поздние версии)

Эта функция позволяет получить информацию о границах стека текущего потока. Она возвращает указатель на начало и конец стека текущего потока. Это может быть полезно, например, для отслеживания использования стека и предотвращения переполнения стека.

Пример использования:

void GetStackLimits() {
    ULONG_PTR lowLimit, highLimit;
    GetCurrentThreadStackLimits(&lowLimit, &highLimit);
    printf("Low Limit: 0x%llx\n", lowLimit);
    printf("High Limit: 0x%llx\n", highLimit);
}

RtlCaptureContext (Windows XP и более поздние версии)

Эта функция захватывает текущий контекст выполнения, включая информацию о регистрах и указателях стека. Это может быть полезно при анализе стека или сохранении контекста выполнения для последующего использования.

Пример использования:

CONTEXT context;
RtlCaptureContext(&context);
// Теперь у вас есть информация о контексте выполнения текущего потока

VirtualQuery (Windows XP и более поздние версии)

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

Пример использования:

MEMORY_BASIC_INFORMATION mbi;
VirtualQuery(&someAddress, &mbi, sizeof(mbi));
// Теперь вы можете получить информацию о найденной памяти, включая стек

SetThreadStackGuarantee (Windows 8 и более поздние версии)

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

Пример использования:

DWORD stackSize = 0x10000; // 64 КБ
SetThreadStackGuarantee(&stackSize);

StackWalk64 (DbgHelp API)

Эта функция из библиотеки DbgHelp API позволяет выполнять обход стека вызовов функций для получения информации о вызовах и адресах функций. Она полезна при создании отладочных и профилирующих инструментов.

Пример использования:

STACKFRAME64 stackFrame;
// Настройка параметров и выполнение обхода стека

Функции для работы с кучей

WinAPI предоставляет ряд функций для работы с кучей (памятью, выделяемой в куче). Основные функции включают в себя HeapCreate, HeapAlloc, HeapFree, HeapReAlloc и HeapDestroy. Давайте рассмотрим эти функции более подробно:

HeapCreate

  • Создает новую кучу.

  • Синтаксис: HANDLE HeapCreate(DWORD flOptions, SIZE_T dwInitialSize, SIZE_T dwMaximumSize);

  • Пример:

    HANDLE hHeap = HeapCreate(0, 0, 0);

HeapAlloc

  • Выделяет блок памяти из кучи.

  • Синтаксис: LPVOID HeapAlloc(HANDLE hHeap, DWORD dwFlags, SIZE_T dwBytes);

  • Пример:

    int* pData = (int*)HeapAlloc(hHeap, 0, sizeof(int) * 10);

HeapFree

  • Освобождает блок памяти, выделенный ранее с помощью HeapAlloc.

  • Синтаксис: BOOL HeapFree(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem);

  • Пример:

    HeapFree(hHeap, 0, pData);

HeapReAlloc

  • Изменяет размер выделенного блока памяти в куче.

  • Синтаксис: LPVOID HeapReAlloc(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem, SIZE_T dwBytes);

  • Пример:

    pData = (int*)HeapReAlloc(hHeap, 0, pData, sizeof(int) * 20);

HeapDestroy

  • Уничтожает кучу и освобождает все связанные с ней ресурсы.

  • Синтаксис: BOOL HeapDestroy(HANDLE hHeap);

  • Пример:

HeapSize

  • Возвращает размер выделенного блока памяти в куче.

  • Синтаксис: SIZE_T HeapSize(HANDLE hHeap, DWORD dwFlags, LPCVOID lpMem);

  • Пример:

    SIZE_T size = HeapSize(hHeap, 0, pData);

HeapValidate

  • Проверяет целостность кучи и выделенных блоков.

  • Синтаксис: BOOL HeapValidate(HANDLE hHeap, DWORD dwFlags, LPCVOID lpMem);

  • Пример:

    if (HeapValidate(hHeap, 0, pData)) {
        printf("Куча валидна.\n");
    } else {
        printf("Куча повреждена.\n");
    }

Пример 1: Создание кучи и выделение памяти

#include <Windows.h>
#include <stdio.h>

int main() {
    SetConsoleOutputCP(1251);
    // Создание кучи
    HANDLE hHeap = HeapCreate(0, 0, 0);

    if (hHeap == NULL) {
        printf("Не удалось создать кучу\n");
        return 1;
    }

    // Выделение памяти из кучи
    int *data = (int*)HeapAlloc(hHeap, 0, sizeof(int) * 5);

    if (data == NULL) {
        printf("Не удалось выделить память из кучи\n");
        HeapDestroy(hHeap);
        return 1;
    }

    // Использование выделенной памяти
    for (int i = 0; i < 5; i++) {
        data[i] = i * 10;
    }

    // Освобождение памяти
    HeapFree(hHeap, 0, data);

    // Уничтожение кучи
    HeapDestroy(hHeap);

    return 0;
}

В этом примере мы создаем кучу с помощью HeapCreate, выделяем память из кучи с помощью HeapAlloc, используем эту память и освобождаем ее с помощью HeapFree, а затем уничтожаем кучу с помощью HeapDestroy.

Пример 2: Выделение строки в куче

#include <Windows.h>
#include <stdio.h>

int main() {
    SetConsoleOutputCP(1251);
    // Создание кучи
    HANDLE hHeap = HeapCreate(0, 0, 0);

    if (hHeap == NULL) {
        printf("Не удалось создать кучу\n");
        return 1;
    }

    // Выделение строки в куче
    char *str = (char*)HeapAlloc(hHeap, 0, 256);

    if (str == NULL) {
        printf("Не удалось выделить память для строки\n");
        HeapDestroy(hHeap);
        return 1;
    }

    // Копирование строки в выделенную память
    strcpy_s(str, 256, "Пример строки в куче");

    // Использование строки

    // Освобождение памяти
    HeapFree(hHeap, 0, str);

    // Уничтожение кучи
    HeapDestroy(hHeap);

    return 0;
}

В этом примере мы выделяем память для строки в куче, копируем строку в эту память, используем ее и освобождаем память.

Отображение файлов на адресное пространство

File mapping (сопоставление файла) в WinAPI — это механизм, который позволяет отображать содержимое файла в виртуальную память процесса. Это может быть полезно для обмена данными между процессами, создания разделяемой памяти или для улучшения производительности при доступе к большим файлам. Давайте рассмотрим основы использования file mapping в WinAPI:

Создание файла для сопоставления

Сначала необходимо создать или открыть файл, который вы хотите сопоставить. Это можно сделать с помощью функций, таких как CreateFile или OpenFile. Например:

HANDLE hFile = CreateFile(
    L"C:\\example.txt",           // Имя файла
    GENERIC_READ | GENERIC_WRITE, // Режим доступа
    0,                            // Атрибуты файла
    NULL,                         // Дескриптор безопасности
    OPEN_ALWAYS,                  // Действие при открытии (создать, если не существует)
    FILE_ATTRIBUTE_NORMAL,        // Атрибуты файла
    NULL                          // Шаблон для атрибутов
);

Создание отображения файла в памяти

Затем создайте отображение файла в виртуальную память с помощью функции CreateFileMapping. Это создает объект отображения файла, который может быть использован для доступа к содержимому файла:

HANDLE hMapFile = CreateFileMapping(
    hFile,                   // Дескриптор файла
    NULL,                    // Атрибуты безопасности (можно использовать NULL)
    PAGE_READWRITE,          // Режим доступа к файлу в отображении
    0,                       // Размер отображения файла (0 - весь файл)
    0,                       // Высший значащий байт размера файла
    NULL                     // Имя отображения файла (можно использовать NULL)
);

Отображение файла в виртуальную память

Завершите процесс сопоставления файла, отображая его в виртуальную память с помощью функции MapViewOfFile:

LPVOID pData = MapViewOfFile(
    hMapFile,              // Дескриптор отображения файла
    FILE_MAP_ALL_ACCESS,   // Режим доступа к отображению
    0,                     // Смещение в файле
    0,                     // Начальный байт отображения
    0                      // Размер отображения (0 - весь файл)
);

Использование данных

Теперь pData указывает на начало отображения файла в виртуальной памяти. Вы можете работать с данными, как с обычной памятью.

Освобождение ресурсов

После завершения работы с данными не забудьте освободить ресурсы:

UnmapViewOfFile(pData);  // Освобождение отображения файла
CloseHandle(hFile);      // Закрытие дескриптора файла
CloseHandle(hMapFile);   // Закрытие дескриптора отображения файла

Наверх

1.

Лекция №3
Управление памятью в ОС Windows

2.

Основные понятия
• Физическая память представляет собой упорядоченное множество
ячеек и все они пронумерованы, то есть с каждой из них можно
обратиться, указав ее порядковый номер (адрес). Количество ячеек
физической памяти ограничено и фиксировано
• Виртуальная память создает иллюзию того, что каждый процесс
имеет доступ к 4Гб непрерывного адресного пространства
• Виртуальное адресное пространство процесса является набором
адресов, доступным всем потокам этого процесса
• ОС распределяет адресное пространство физической и виртуальной
памяти страницами (pages) – блоками по 4Кб

3.

Механизмы управления памятью решают две главные задачи:
• Трансляция, или проецирование, виртуального адресного пространства
процесса на физическую память. Это позволяет ссылаться на
корректные адреса физической памяти, когда нити, выполняемые в
контексте процесса, читают и записывают его в виртуальном адресном
пространстве
• Подкачка части содержимого памяти на диск, когда нити или
системный код пытаются задействовать больший объем физической
памяти, чем тот, который имеется в наличии, и загрузка страниц
обратно в физическую память по мере необходимости
Физическое подмножество виртуального адресного пространства
процесса называется рабочим набором (working set)

4.

Организация виртуальной памяти
Процесс 1
Процесс 2
Физическая память
Файл подкачки
• В период выполнения диспетчер памяти (в
Ntoskrnl.exe), транслирует, или проецирует
(maps) виртуальные адреса на физические, по
которым хранятся данные
• Подкачка данных на диск освобождает
физическую память для других процессов или
самой ОС
• Диспетчер памяти опирается на аппаратную
поддержку механизма подкачки
• В процессе работы система виртуальной
памяти использует один или несколько файлов
подкачки, расположенных на жестком диске
(pagefile.sys)

5.

Страницы виртуальной памяти имеют три состояния:
Процесс 1
Процесс 2
Физическая память
Файл подкачки
• Большинство страниц пусто, поскольку
процесс их не использует, они никуда не
отображаются
• Используемые страницы отображаются с
помощью невидимого для процесса
указателя в область физической
оперативной памяти (ОЗУ)
• Некоторые страницы, к которым не было
обращений в течение определенного
времени, отображаются с помощью
невидимого для процесса указателя в 4Кб
раздел файла подкачки (pagefile.sys)

6.

Файл подкачки
pagefile.sys может быть создан на каждом логическом диске системы, находится
в корне дерева каталогов и является скрытым и системным файлом

7.

Процесс управления местоположением страниц – в ОЗУ или в
страничном файле называется подкачкой страниц по запросу
Процесс 1
Процесс 2
Физическая память
Файл подкачки
• Приложение делает попытку сохранить данные в памяти
• Диспетчер виртуальной памяти перехватывает запрос и определяет,
сколько страниц необходимо для его выполнения. После этого он
отображает неиспользуемую физическую память на нужные незанятые
области виртуального пространства процесса. При этом диспетчер
виртуальной памяти скрывает от приложения (процесса) способ
организации физической памяти. Когда приложение обратится к
конкретному виртуальному адресу, он будет транслирован в
уникальный физический адрес, не конфликтующий с другими
процессами
• Если в системе не хватает физической памяти, диспетчер виртуальной
памяти выполняет поиск страниц ОЗУ, не использовавшихся в течение
определенного времени, и копирует эти страницы в страничный файл
(pagefile.sys), находящийся на жестком диске. Освободившаяся область
ОЗУ отображается на виртуальное адресное пространство
запросившего память процесса

8.

Структура адресного пространства пользовательского процесса
Системная память
Системный кэш
Пул неподкачиваемой памяти
Пул подкачиваемой памяти
4 Гб
Ядро, HAL, драйверы
2 Гб
• По умолчанию каждый пользовательский процесс в Windows 32-bit
получает закрытое адресное пространство размером до 2 Гб, а
остальные 2 Гб занимает ОС
• Код ОС отображается в верхние 2Гб виртуального адресного
пространства процесса
• При инициализации системы диспетчер памяти создает два типа
динамических пулов памяти, используемых компонентами режима
ядра для выделения системной памяти:
Код приложения
Глобальные переменные
Стеки нитей
Код DLL
• Пул неподкачиваемой памяти (nonpaged pool). Состоит из
диапазонов системных виртуальных адресов, которые всегда
присутствуют в физической памяти и доступны в любой момент.
• Пул подкачиваемой памяти (paged pool). Регион виртуальной памяти
в системном пространстве, содержимое которого система может
выгружать в страничный файл и загружать из него
64 Кб
0

9.

Разделение памяти процессами. Разделяемая память
Процесс 1
Физическая память
Процесс 2
• Разделяемой (shared memory) называется
память, видимая более, чем одному процессу
или присутствующая в виртуальном
пространстве более, чем одного процесса.
• Например, если два процесса используют одну и
ту же DLL, имеет смысл загрузить ее код в
физическую память один раз и сделать ее
доступной всем процессам, в виртуальной
памяти которых присутствует эта DLL.
• Каждый процесс поддерживает закрытые
области памяти для хранения собственных
данных, но программный код и страницы
немодифицируемых данных в принципе можно
использовать совместно с другими процессами.

10.

Режим ядра и пользовательский режим
• Windows и Linux используют два режима доступа к процессору:
пользовательский (user mode – кольцо 3)
ядра (kernel mode — кольцо 0)
• Код приложений работает в пользовательском режиме, тогда как код ОС (например, системные
сервисы и драйверы устройств) – в режиме ядра (режим супервизора)
• В режиме ядра предоставляется доступ ко всей системной памяти и разрешается выполнять
любые машинные команды процессора
• Хотя каждый Win32-процесс имеет свою (закрытую) память, код ОС и драйверы устройств,
работающие в режиме ядра, делят единое виртуальное адресное пространство
• Каждая страница в виртуальной памяти помечается тэгом, определяющим, в каком режиме
Кольца привилегий
архитектуры x86 в
защищённом режиме
должен работать процессор для чтения и/или записи данной страницы
Страницы в системном пространстве доступны лишь в режиме ядра, а все страницы в
пользовательском адресном пространстве – в пользовательском режиме. Страницы только для
чтения (например, содержащие лишь исполняемый код) ни в каком режиме для записи
недоступны

11.

Режим ядра и пользовательский режим
• Windows не предусматривает никакой защиты системной памяти от компонентов, работающих в режиме ядра. Иначе говоря,
код ОС и драйверов устройств в режиме ядра получает полный доступ к системной памяти и может обходить средства
защиты Windows для обращения к любым объектам
• Надо быть осторожным при загрузке драйвера устройства от стороннего поставщика: перейдя в режим ядра, он получит
полный доступ ко всем данным ОС. Такая уязвимость стала одной из причин, по которой в Windows 2000 был введен
механизм проверки цифровых подписей драйверов, предупреждающий пользователя о попытке установки
неавторизированного (неподписанного) драйвера
• Режим гипервизора (Hypervisor mode), который называют Ring -1, реализуется с целью поддержки технологий виртуализации
на уровне аппаратного обеспечения. Это позволяет достигнуть одновременного выполнения нескольких операционных
систем на одном процессоре без существенных потерь производительности. При выполнении привилегированных операций
операционными системами в режиме супервизора управление передается специальной программе — гипервизору.
• Гипервизор осуществляет арбитраж использования имеющихся аппаратных ресурсов несколькими операционными
системами аналогично тому как сами операционные системы осуществляют распределение ресурсов между несколькими
задачами. По сути, гипервизор обычно является небольшим ядром, которое управляет распределением ресурсов между
несколькими операционными системами и работает уровнем ниже, чем сами операционные системы.

Last Updated :
01 Feb, 2022

The memory management in the operating system is to control or maintain the main memory and transfer processes from the primary memory to disk during execution. Memory management keeps track of all memory locations, whether the process uses them or not. Determines how much memory should be allocated to each process. Specifies how much memory each process should be given. It decides which processes will be remembered and when. It tracks when memory is released or when it is shared and changes the status accordingly.

Windows Memory Management

Microsoft Windows has its own virtual address space for each 32-bit process, allowing up to 4 gigabytes of memory to be viewed. Each process has 8-terabyte address space on 64-bit Windows. All threads have access to the visible address space of the process. Threads, on the other hand, do not have access to the memory of another process, which protects one process from being damaged by another.

Architecture for 32-bit Windows: The automatic configuration of the 32-bit Windows Operating System (OS) allocates 4 GB (232) of accessible memory space to the kernel and user programs equally. With 4 GB physical memory available, the kernel will receive 2 GB and the app memory will receive 2 GB. Kernel-mode address space is shared by all processes, but application mode access space is provided for each user process.

Architecture for 64-bit Windows: The automatic configuration of the 64-bit Windows Operating System (OS) allocates up to 16 TB (254) of accessible memory space to the kernel and user programs equally. As 16 TB real memory is available, the kernel will have 8 TB of virtual address (VA) space and user application memory will have 8 TB of VA space. Visible address space in the kernel is allocated for all processes. Each 64-bit functionality gets its place, but each 32-bit system works on a 2 GB (Windows) virtual machine.

Virtual Address Space

The process’ visible address space is the range of memory addresses that you can use. The address area of ​​each process is private, and can only be accessed through other processes if it is shared.

A virtual address does not reflect the actual location of an object in memory; instead, the system stores a table for each process, which is an internal data structure that converts visible addresses into local addresses. The program converts the virtual address into a local address every time the chain refers to it.

The virtual address area of Windows is divided into two parts: one for process use and the other for system usage.

Virtual Memory Functions

A process can alter or determine the state of pages in its virtual address space using virtual memory functions.

The width of the visible address space is reserved for the process. Although saving address space does not provide material storage, it prevents scope from using other sharing processes. It does not affect other active address spaces for other processes. Page storage reduces unnecessary use of virtual storage while allowing the process of setting aside part of its address space for the flexible data structure. As required, the procedure can provide a physical repository for this area.

Provide a set of cached pages in the address of the process so that only a shared process can access real storage (either RAM or disk).

For the most dedicated pages, specify read/write, read-only, or no access. This differs from the general distribution procedures, which often provide read/write access to the pages.

Release a set of saved pages, making the visible address set accessible for the following call process sharing actions.

We can withdraw a group of committed pages, freeing up portable storage that can be assigned to any process in the future.

To prevent the program from changing pages in the file, lock one or more memory pages bound to the virtual memory (RAM). Find information about a set of pages in a call process or the address space of a specific process. It may change the access protection of a set of pages bound to the physical address of the call process.

Heap Functions

The system provides a default heap for each process. Private heaps can help applications that make frequent allocations from the heap perform better. A private heap is a block of one or more pages in the caller process’s address space. After constructing the private heap, the process manages the memory in it via operations like HeapAlloc and HeapFree.

File Mapping

The association of file content with a piece of visible address space in the process is known as a file map. To track this relationship, the system creates a file map maker (also known as a category object). File view is the physical address area are used for the file content access process. The process may use both input and outgoing sequences (I/O) thanks to the file map. It also allows the process to work effectively with large data files, such as websites, without requiring the entire file to be mapped to memory. Files with a memory map can be used with many processes to exchange data.

Управление памятью в операционных системах

Введение

Управление памятью – это критически важная и при этом довольно сложная задача для операционной системы. Это дает возможность запускать несколько процессов одновременно без сбоев. 

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

В этой статье мы рассмотрим ключевые концепции управления памятью в операционных системах. 

Что такое управление памятью?

Основные компоненты управления памятью – это процессор и блок памяти. Эффективность системы зависит от того, как эти два компонента взаимодействуют друг с другом.

Эффективность управления памятью зависит от двух факторов:

1. Организация блока памяти. Блок памяти состоит из нескольких типов памяти. Иерархия и организация памяти компьютера влияют на скорость доступа к данным и размер хранилища. Более быстрые и меньшие кэши хранятся ближе к процессору, а более крупная и медленная память – дальше.

2. Доступ к памяти. Процессор постоянно обращается к данным, которые хранятся в памяти. Эффективный доступ к памяти влияет на то, насколько быстро процессор выполняет задачи и становится доступным для новых. Доступ к памяти предусматривает работу с адресами и определение правил доступа на разных уровнях памяти.

Управление памятью позволяет найти компромисс между скоростью, вместимостью и энергопотреблением компьютера. Основная память обеспечивает быстрый доступ, но не постоянное хранение. А вот вспомогательная память обеспечивает постоянное хранение, но медленный доступ.

Для чего нужно управление памятью?

Основная память – это неотъемлемая часть операционной системы. С ее помощью процессор может получать доступ к данным, которые необходимы для запуска процессов. Однако многочисленные операции чтения-записи замедляют работу системы.

В связи с этим, чтобы оптимизировать использование процессора и повысить скорость компьютера, в памяти размещаются несколько процессов сразу. Управление памятью необходимо для того, чтобы наиболее эффективно разделить память между процессами.

Таким образом, управление памятью влияет на следующие факторы:

  • Использование ресурсов. Управление памятью – это ключевой аспект распределения ресурсов компьютера. Центральный компонент – оперативная память, и процессы используют ее для работы. Операционная система сама решает, как разделить память между процессами. Правильное распределение гарантирует, что каждый процесс получит необходимое количество памяти для одновременного выполнения.
  • Оптимизация производительности. Те или иные механизмы управления памятью оказывают значительное влияние на скорость и стабильность системы. Все эти механизмы направлены на то, чтобы сократить количество операций получения доступа к памяти, которые дают большую нагрузку на процессор.
  • Безопасность. Управление памятью обеспечивает безопасность данных и процессов. Изоляция памяти позволяет сделать так, чтобы процессы использовали только ту память, которую им предоставили. Кроме того, управление памятью позволяет реализовать механизм прав доступа, который поможет предотвратить вход в закрытые области памяти. 

Для отслеживания выделенной процессам памяти операционные системы используют адреса памяти.

Адреса памяти

Адреса памяти крайне важны для управления памятью в операционных системах. Адрес памяти – это уникальный идентификатор конкретной области или ячейки памяти. С помощью адресов можно легко находить и получать доступ к информации, которая хранится в памяти.

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

Ниже описаны два типа адресов основной памяти. Каждый из них играет свою роль в управлении памятью и служит определенной цели.

Физические адреса

Физический адрес – это числовой идентификатор, который указывает на ячейку физической памяти. Этот адрес представляет собой фактическое расположение данных в аппаратном обеспечении и играет важнейшую роль в низкоуровневом управлении памятью.

Аппаратные компоненты, такие как процессор и контроллер памяти, используют именно физические адреса. Эти адреса уникальны и указывают на конкретную ячейку, за счет чего аппаратное обеспечение может быстро находить любые данные. Для пользовательских программ физические адреса непригодны. 

Виртуальные адреса

Виртуальный адрес – это адрес, сгенерированный программой. Он представляет собой абстракцию физической памяти. Все процессы используют адресное пространство виртуальной памяти в качестве выделенной памяти.

Виртуальные адреса не соответствуют никаким ячейкам физической памяти. Программы читают и создают виртуальные адреса, не подозревая о существовании физического адресного пространства. Блок оперативной памяти (MMU — Main Memory Unit) отвечает за сопоставление виртуальных адресов с физическими, чтобы обеспечить правильный доступ к памяти.

Для более эффективного использования памяти виртуальное адресное пространство разделено на сегменты и страницы. 

Статическая и динамическая загрузка

Статическая и динамическая загрузка – это два способа выделения памяти под исполняемые программы. Они различаются количеством используемой памяти и ресурсов. Выбор зависит от количества доступной памяти и ресурсов, а также необходимой производительности.

  • Статическая загрузка распределяет память и адреса при запуске программы. Однако, когда программа заранее загружается в память вместе со всеми необходимыми ресурсами, мы получаем предсказуемое, но крайне неэффективное использование ресурсов. Системные утилиты и приложения используют статическую загрузку с целью упростить распространение программ. Исполняемые файлы требуют компиляции и, как правило, представляют собой довольно большие файлы. Как правило, статическую загрузку используют операционные системы реального времени, загрузчики и устаревшие системы.
  • Динамическая загрузка распределяет память и адреса непосредственно во время выполнения программы, и сама программа запрашивает ресурсы по мере необходимости. Динамическая загрузка позволяет снизить объем потребляемой памяти и обеспечивает многозадачную среду. Исполняемые файлы меньше по размеру, но добавляют дополнительную сложность за счет утечек памяти, потребления ресурсов и ошибок в процессе выполнения. Современные операционные системы (Linux, macOS, Windows), мобильные операционные системы (Android, iOS) и веб-браузеры используют именно динамическую загрузку.

Статическое и динамическое связывание

Статическое и динамическое связывание — это два способа работы с библиотеками и зависимостями программ, аналогичные статической и динамической загрузке:

  • Статическое связывание выделяет память для библиотек и зависимостей при запуске программы и до компиляции. Программы являются полностью готовыми и не требуют внешних библиотек в процессе компиляции.
  • Динамическое связывание выделяет память для библиотек и зависимостей после запуска программы и по мере необходимости. После компиляции программы находятся в поиске необходимых внешних библиотек.

Как правило, статическая загрузка и связывание объединяются в единый подход управления памятью, при котором все ресурсы программы определяются заранее. Аналогичным образом динамическая загрузка и связывание создают свою стратегию, в которой программы распределяют и ищут ресурсы по мере необходимости.

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

Подкачка 

Подкачка (англ. swapping) – это механизм управления памятью, который операционные системы используют для того, чтобы освобождать место в оперативной памяти. Механизм позволяет перемещать неактивные процессы или данные между оперативной памятью и вспомогательным хранилищем, например, жестким или SSD-диском.

Для разрешения вопроса, связанного с ограниченным размером оперативной памяти, процесс подкачки прибегает к использованию виртуальной памяти. За счет этого, подкачка считается крайне важным методом управления памятью в операционных системах. Этот метод использует область вспомогательной памяти и создает там swap-память, то есть дополнительный раздел или файл. 

Swap-пространство позволяет превысить объем оперативной памяти за счет разделения данных на блоки фиксированного размера (страницы). Механизм подкачки отслеживает, какие страницы находятся в оперативной памяти, а какие откачиваются из-за ошибок страниц. 

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

Фрагментация

Фрагментация – это то, что мы получаем при попытке разделить память на разделы. Операционная система занимает только часть основной памяти. Оставшаяся основная память предназначена для процессов и делится на более мелкие разделы. Такое разделение не подразумевает использование виртуальной памяти.

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

  • Внутренняя. Если оставшаяся память разделена на одинаковые по размеру разделы, то программы, размер которых превышает размер раздела, требуют перекрытия, а программы меньшего размера занимают больше места, чем им положено. И все это нераспределенное пространство создает внутреннюю фрагментацию.
  • Внешняя. Динамическое разделение оставшейся памяти позволяет создавать разделы с изменяемым размером. Процесс получает ровно столько памяти, сколько ему необходимо. Когда процесс завершается, пространство освобождается. Через какое-то время начинают появляться неиспользуемые интервалы памяти, что приводит к внешней фрагментации.

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

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

Методы управления памятью

Разные методы управления памяти решают разные проблемы, возникающие вследствие неправильной организации памяти. Одна из основных целей этих методов – оптимизировать потребление ресурсов в системе.

Существует два основных подхода к распределению и управлению памятью: управление непрерывной памятью и управление несмежной памятью. Каждый из этих подходов имеет свои преимущества, и выбор зависит от требований системы и аппаратной архитектуры.

Схемы управления непрерывной памятью

Схемы управления непрерывной памятью выделяют процессам блоки непрерывной памяти. Адреса памяти и процессы имеют линейную зависимость, что упрощает реализацию этой системы.

Существуют различные способы реализации схем управления непрерывной памятью. Ниже мы представили краткое описание известных схем.

Выделение общей непрерывной памяти

Персональное выделение непрерывной памяти – это один из первых методов управления памятью. При такой схеме оперативная память делится на два раздела:

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

Такая схема была реализована в более старых операционных системах, таких как MS-DOS. Система довольно простая, и отслеживать необходимо всего два раздела. Но такой простой подход не обеспечивает изоляцию процессов и приводит к тому, что память тратиться впустую.

Современные операционные системы не используют такой метод управления памятью. Но эта схема заложила фундамент для развития других методов управления памятью.

Распределение с фиксированными разделами

Распределение с фиксированными разделами – это схема управления памятью, которая подразумевает деление оперативной памяти на разделы одинакового размера. Размер раздела определяется заранее, поэтому необходимо заранее знать, сколько памяти требуется процессам.

Операционная система отслеживает и выделяет процессам разделы. Каждый процесс получает выделенный ему раздел, который обеспечивает изоляцию памяти и безопасность процесса.

Однако использование разделов фиксированного размера приводит к фрагментации. Если процессы оказываются меньше раздела, то возникает внутренняя фрагментация. А внешняя фрагментация возникает со временем, в результате чего становится сложнее выделять память под более крупные процессы.

Алгоритм двойников

Алгоритм двойников – это схема динамического управления памятью. Здесь оперативная память делится на блоки переменного размера. Зачастую размеры представляются степенями двойки (2 Кб, 4 Кб, 8 Кб, 16 Кб и т.д.).

Когда процесс запрашивает память, ОС начинает искать наиболее подходящий блок наименьшего размера, чтобы выделить его под этот процесс. Если блоков поменьше нет, тогда большие блоки делятся пополам. Когда память освобождается, ОС проверяет, свободны ли соседние блоки (блоки-двойники), и объединяет их в более крупные. 

Для отслеживания состояния блоков памяти и поиска свободных алгоритм двойников использует двоичное дерево. Такая схема поддерживает баланс между фрагментацией и эффективным распределением памяти. Самые примечательные области применения – это память ядра Linux и встроенные системы.

Схемы управления несмежной памятью

Схемы управления несмежной памятью позволяют распределять процессы по всей памяти. Адреса памяти и процессы имеют нелинейную зависимость, и процессы могут получить память где угодно.

Эти схемы направлены на решение проблем фрагментации, но они довольно сложны с точки зрения реализации. Большая часть современных операционных систем используют именно управление несмежной памятью.

Ниже мы привели описание самых важных механизмов управления несмежной памятью.

Страничная организация памяти

Страничная организация памяти – это подход к управлению оперативной и виртуальной памятью. При таком подходе память делится на блоки одинакового размера:

  • Страницы. Блоки виртуальной памяти с логическими адресами.
  • Страничные кадры. Блоки оперативной памяти с физическими адресами.

Этот механизм к каждому процессу применяет таблицу страниц, где отслеживает процесс сопоставления адресов страниц и кадров. А вот операционная система по мере необходимости перемещает данные между оперативной и вспомогательной памятью, используя подкачку.

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

Сегментация 

Сегментация — это схема управления памятью, при которой память делится на логические сегменты. Каждый сегмент соответствует определенной области, где в рамках процесса выполняются различные функции и задачи.

В отличие от страниц, сегменты имеют разные размеры. Каждый сегмент имеет уникальный идентификатор, который называется дескриптором сегмента. Операционная система хранит таблицу сегментов, содержащую дескриптор, смещение и базовый адрес. И чтобы вычислить физический адрес, процессор объединяет смещение и базовый адрес.

Сегментация — это динамический, безопасный и логический подход к управлению памятью. Однако этот механизм довольно сложен и подходит не для всех случаев.

Заключение

Прочитав данное руководство, вы ознакомились со всеми подробностями управления памятью в операционных системах.

Для сегментированных
моделей предусмотрено динамическое
выделение памяти из ближнего (near) или
дальнего (far) пулов. Функции для
динамического распределения или
перераспределения памяти определены
в стандарте ANSI и включены практически
во все библиотеки систем программирования
на С. Прототипы функций для распределения
памяти описаны в заголовочном файле
<alloc.Н> (еще и в <malloc.Н>,
<memory.Н>
и т.п.).

Чтобы выделить
блок памяти, используют функцию:

void *malloc(int size);

Параметром этой
функции является запрашиваемое количество
байт основной памяти. Эта функция
возвращает адрес на начало выделенной
динамической области.

Например:

int
*p
= (int*)
malloc
(1024);

— запpашивается
область в 1К для хранения целых данных.
float *p = (float*) malloc (1024);

— запpашивается
область в 1К для хранения данных
вещественного типа.

void
*calloc(int items, int size_item);

— позволяет выделить
заданное количество объектов указанного
типа.

Передаются
количество объектов и протяженность
одного объекта.

При выделении
памяти все пространство обнуляется.

Например:

int *p =
(int*) calloc(256, sizeof(int));

— выделение памяти
под 256 объектов длиной sizeof(int).

void
*realloc (void *addr, int size); — перераспределение
памяти.
Первый
параметр содержит адрес существующей
области, второй параметр — новый размер
этой области. Если изменение размера
может быть выполнено, функция возвращает
указатель на новую область.

Все эти функции в
случае аварийного завершения возвращают
нулевой указатель, т.е. NULL.

Для освобождения
памяти используется функция

void free (void *adr);

которой передается
адрес освобождаемой области.

Набор функций с
префиксом far позволяют работать с дальним
пулом. Например, farmalloc.

2.5.3.Динамическое распределение памяти в windows nt

При программировании
под MS Windows 3.х можно создать динамически
распределенные области в локальном и
глобальном пулах. Локальный пул выделяется
каждому процессу, действующему в системе,
а глобальный пул является общим для
всех процессов. Выделение памяти из
локального пула — LocalAlloc, выделение памяти
из глобального пула — GlobalAlloc. Данные
функции возвращают идентификатор
выделенной области, т.е. некоторый номер,
под которым эта область зарегистрирована
в ОС. Чтобы получить зарегистрированную
область в пользование, выполняют функцию
GlobalLock, которая возвращает указатель.
Снимается фиксация с помощью функции
GlobalUnLock. GlobalFree — освободить ранее
зарезервированную память.

В ОС WINDOWS 95 и
WINDOWS NT нет разделения
на локальные и глобальные пулы, поскольку
адресные пространства всех приложений
разделены. Каждое приложение работает
в своем виртуальном адресном пространстве.
Приложению по умолчанию выделяется
один стандартный пул равный 1М и при
необходимости приложение может создавать
произвольное количество динамических
пулов.

Ранее рассмотренные
функции типа malloc, GlobalAlloc и т.п. позволяют
выделять память из стандартного пула.

В программном
интерфейсе Win32 API имеется несколько
функций для работы с пулами. Всем
функциям, используемым для распределения
памяти, передается идентификатор пула,
который имеет тип HANDLE. Для получения
идентификатора стандартного пула
существует функция: GetProcessНeap (void); которая
возвращает данный идентификатор.

Для создания
динамического пула применяется функция:
HANDLE
НeapСreate
(DWORD
flOptions,

DWORD
dwInitialSize,

DWORD
dwMaximumSize);

Тип
DWORD соответствует
unsigned long.

Первый параметр
— флаги. Допустимы следующие сочетания:
HEAP_GENERATE_EXEPTIONS — генерировать исключение
при аварийном завершении; HEAP_NO_SERIALIZE —
не выполнять блокировку одновременного
обращения к пулу нескольких задач одного
процесса. Сочетание этих флагов может
быть обеспечено операцией логического
сложения (дизъюнкцией).

Второй параметр
— начальная длина пула;

Третий параметр
— максимальная протяженность пула (если
неизвестна, то = 0).

Функция разрушения
динамического пула:

BOOL НeapDestroy (НANDLE
ННeap);

Ей передается
идентификатор пула, а возвращается
логическое значение.

Получение блока
памяти в пуле:

LPVOID
НeapAlloc
(НANDLE
ННeap,

DWORD
dwFlags,

DWORD
dwBytes);

1 параметр —
идентификатор пула;

2 параметр —
рассмотренные ранее флаги и HEAP_ZERO_MEMORY,
который позволяет обнулить выделенное
пространство;

3 параметр —
протяженность блока памяти.

Функция возвращает
указатель на выделенную область памяти
(LPVOID — дальний указатель на тип void: void
far*).

Изменение
протяженности ранее выделенного
пространства:

LPVOID
НeapRealloc (НANDLE ННeap,

DWORD
dwFlags,

LPVOID
lpMem,

DWORD dwBytes);

2 параметр
— флаги.
Используются
все предыдущие + HEAP_REALLOC_IN_PLACE_ONLY
(не изменять начальный адрес области);

3 параметр — адрес
исходного блока;

4 параметр — новая
протяженность.

Если нет ошибок,
возвращается указатель на область
нового размера.

Чтобы определить
размер пула используется следующая
функция:

DWORD
НeapSize (НANDLE ННeap,

DWORD
dwFlags,

LPVOID
lpMem);

значения параметров
флагов могут быть «0» или
HEAP_NO_SERIALIZE.

Освобождение
выделенного блока:

BOOL
НeapFree(НANDLE
ННeap,

DWORD
dwFlags,

LPVOID
lpMem);

значение флагов
те же.

Понравилась статья? Поделить с друзьями:
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
  • Windows 2003 server настройка active directory windows server
  • Где сохранения скайрим на windows 10
  • Prestigio 133s установка windows на ssd
  • Как посмотреть активность пользователя в windows 10
  • Российская операционная система вместо windows какая лучше