Открыть сервис

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

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

История

Ранние системы и ручное управление

В первых компьютерах (например, ENIAC, 1945 год) управление памятью отсутствовало как таковое. Программы загружались в память напрямую, и программист был обязан вручную следить за тем, чтобы код и данные не перекрывались. С появлением пакетных систем (1950-е годы) возникла необходимость в мультипрограммировании — одновременном выполнении нескольких задач. Для этого требовалось разделять память между программами, что привело к созданию первых простых схем управления, таких как статическое распределение с фиксированными разделами.

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

Ключевой прорыв произошёл в 1960-х годах с разработкой концепции виртуальной памяти. В 1961 году в Манчестерском университете на компьютере Atlas была впервые реализована страничная организация памяти, позволяющая программам использовать адресное пространство, превышающее объём физической памяти. В 1970-х годах эта технология стала стандартом для большинства операционных систем, включая UNIX и ранние версии Windows. В 1978 году компания Intel представила процессор 8086, который использовал сегментную модель адресации, что также повлияло на развитие методов управления.

Современные подходы

С 1990-х годов, с распространением многозадачных и многопользовательских систем (Linux, Windows NT, macOS), управление памятью стало включать сложные алгоритмы, такие как замещение страниц (LRU, FIFO), кэширование, поддержка неоднородного доступа к памяти (NUMA) и виртуализация. В 2000-х годах, с развитием облачных вычислений и контейнеризации (Docker, Kubernetes), появились задачи эффективного распределения памяти между изолированными средами.

Основные функции управления памятью

Выделение и освобождение

ОС отслеживает, какие области физической памяти свободны, а какие заняты. При запуске процесса или запросе на выделение динамической памяти (например, через malloc в C или new в C++) система находит подходящий блок и резервирует его. После завершения работы процесса или освобождения памяти (через free или delete) блок возвращается в пул свободных ресурсов. Для этого используются такие структуры данных, как битовые карты, списки свободных блоков (связанные списки) или алгоритмы "buddy system" (система близнецов).

Трансляция адресов

Каждый процесс работает в своём виртуальном адресном пространстве, которое может быть значительно больше физической памяти. ОС с помощью блока управления памятью (MMU) преобразует виртуальные адреса в физические. Этот процесс может быть основан на страничной (paging) или сегментной (segmentation) организации, либо на их комбинации.

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

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

Подкачка (swapping) и замещение страниц

Когда физической памяти не хватает для всех активных процессов, ОС может временно выгрузить часть данных (страницы или сегменты) на диск (в область подкачки). При обращении к выгруженным данным возникает страничное прерывание (page fault), и система загружает нужную страницу обратно в ОЗУ, возможно, вытесняя другую. Алгоритмы замещения страниц (например, LRU — Least Recently Used, Clock, Working Set) определяют, какую страницу следует выгрузить.

Модели организации памяти

Фиксированные разделы

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

Динамические разделы

Разделы создаются по мере необходимости, их размер соответствует запросу процесса. Уменьшает внутреннюю фрагментацию, но приводит к внешней фрагментации — появлению множества мелких свободных областей, которые невозможно использовать для больших запросов. Для борьбы с внешней фрагментацией применяется уплотнение (compaction) — перемещение занятых блоков.

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

Физическая память делится на блоки одинакового размера — кадры (frames) (обычно 4 КБ в x86), а виртуальное адресное пространство — на страницы (pages) того же размера. Таблица страниц (page table) хранит соответствие между виртуальными и физическими страницами. Это позволяет эффективно использовать память, устраняет внешнюю фрагментацию и упрощает подкачку. Недостаток — накладные расходы на хранение таблиц страниц (для больших адресных пространств используются многоуровневые таблицы, например, в x86-64 — 4 уровня).

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

Память делится на логические сегменты разного размера (код, данные, стек). Каждый сегмент имеет свой базовый адрес и длину. Сегментная модель удобна для защиты и разделения прав доступа, но страдает от внешней фрагментации. В современных системах (например, x86-64) сегментация используется в основном для совместимости, а основой является страничная организация.

Комбинированные модели

Например, в архитектуре x86-64 применяется страничная организация с поддержкой сегментных регистров для управления привилегиями. В операционной системе Windows используется модель "сегментно-страничная" с виртуальным адресным пространством 48 бит.

Алгоритмы распределения памяти

Алгоритмы выделения блоков

При работе с динамической памятью (куча, heap) используются следующие стратегии:

  • First-fit — выбирается первый свободный блок, размер которого достаточен для запроса.
  • Best-fit — выбирается наименьший подходящий блок (минимизирует внешнюю фрагментацию, но медленнее).
  • Worst-fit — выбирается наибольший блок (оставляет большие свободные области, но может привести к быстрому исчерпанию крупных блоков).
  • Buddy system — память делится на блоки размером 2^n. При запросе блок делится пополам до получения нужного размера; при освобождении соседние блоки (близнецы) объединяются.

Алгоритмы замещения страниц

  • FIFO (First-In, First-Out) — выгружается самая старая страница. Прост, но может выгружать часто используемые страницы (аномалия Белади).
  • LRU (Least Recently Used) — выгружается страница, к которой дольше всего не было обращений. Обеспечивает хорошую производительность, но требует дорогостоящего аппаратного или программного отслеживания.
  • Clock (или Second Chance) — приближение LRU, использующее бит доступа. Страницы просматриваются циклически; если бит установлен, он сбрасывается, и страница получает "второй шанс".
  • Working Set — основан на концепции рабочего набора (набор страниц, активно используемых процессом). Выгружаются страницы, не входящие в рабочий набор.
  • NRU (Not Recently Used) — классифицирует страницы по битам доступа и модификации, выгружает страницы из наименее приоритетной группы.

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

Linux

Использует страничную организацию с многоуровневыми таблицами страниц. Поддерживает виртуальную память, подкачку, анонимные и файловые отображения (mmap). Алгоритм замещения — приближение LRU (двухсписковый алгоритм: активный и неактивный списки). Для управления кучей применяется "buddy system" для страниц и slab-аллокатор для малых объектов (например, для структур ядра). В версиях ядра 5.x и 6.x активно развивается поддержка страниц большого размера (Huge Pages) и прозрачных огромных страниц (THP).

Windows

Использует страничную организацию (размер страницы 4 КБ, также поддерживаются 2 МБ и 1 ГБ). Виртуальное адресное пространство 64-битных процессов — до 128 ТБ (пользовательская часть) и 128 ТБ (системная). Управление кучей реализовано через диспетчер кучи (Heap Manager), который использует алгоритмы First-fit и Best-fit. Подкачка осуществляется с помощью диспетчера виртуальной памяти (VMM), алгоритм замещения — приближение LRU с использованием рабочего набора. В Windows 10/11 появилась поддержка сжатия памяти (Memory Compression) — неактивные страницы сжимаются и хранятся в ОЗУ вместо выгрузки на диск.

macOS

Основана на ядре XNU (гибридное ядро, включающее компоненты Mach и FreeBSD). Использует страничную организацию (4 КБ). Виртуальная память управляется через диспетчер памяти Mach (Mach VM). Алгоритм замещения — адаптивный, основанный на рабочем наборе и частоте страничных прерываний. macOS активно использует сжатие памяти (с 2013 года, OS X Mavericks) и технологию "NVRAM" для хранения критических данных.

Управление памятью в языках программирования

Ручное управление

В языках C и C++ программист явно выделяет и освобождает память с помощью функций malloc, calloc, realloc, free (C) или операторов new и delete (C++). Это даёт полный контроль, но требует высокой дисциплины и приводит к ошибкам: утечки памяти (memory leaks), двойное освобождение (double free), использование после освобождения (use-after-free). Для борьбы с этими проблемами используются статические анализаторы, санитайзеры (AddressSanitizer, Valgrind) и умные указатели (smart pointers) в C++ (например, std::unique_ptr, std::shared_ptr).

Автоматическое управление (сборка мусора)

В языках Java, C#, Python, JavaScript, Go и других управление памятью осуществляется средой выполнения (JVM, CLR, интерпретатором). Сборщик мусора (Garbage Collector, GC) автоматически отслеживает, какие объекты больше не используются, и освобождает их память. Основные алгоритмы:

  • Reference counting (подсчёт ссылок) — каждый объект хранит счётчик ссылок; при обнулении память освобождается. Используется в Python, PHP, Objective-C. Недостаток — неспособность обрабатывать циклические ссылки.
  • Mark-and-sweep (пометить и очистить) — GC проходит по графу объектов от корней (глобальные переменные, стек) и помечает достижимые объекты, затем удаляет непомеченные. Используется в Java, C#.
  • Copying collection (копирующая сборка) — объекты копируются из одного полупространства в другое, оставляя свободное место. Применяется в некоторых реализациях JVM.
  • Generational collection (поколенческая сборка) — объекты делятся на поколения (молодое, старое); молодые объекты проверяются чаще, так как большинство объектов умирают быстро. Стандарт для современных GC (Java G1, .NET GC).

Управление памятью в Rust

Язык Rust использует уникальную модель владения (ownership) и заимствования (borrowing), которая обеспечивает безопасность памяти без сборщика мусора. Каждый объект имеет ровно одного владельца; при выходе владельца из области видимости память автоматически освобождается. Заимствования (ссылки) проверяются на этапе компиляции с помощью механизма времени жизни (lifetimes), что исключает гонки данных и висячие указатели. Это позволяет достичь производительности, сравнимой с C/C++, при полной безопасности памяти.

Проблемы и вызовы

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

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

Утечки памяти

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

Производительность

Частые страничные прерывания, интенсивная работа сборщика мусора или неэффективные алгоритмы замещения могут существенно замедлить систему. Современные ОС и среды выполнения используют адаптивные алгоритмы, кэширование и предвыборку (prefetching) для минимизации задержек.

Безопасность

Уязвимости, связанные с памятью (переполнение буфера, use-after-free, двойное освобождение), являются основой для многих эксплойтов. Для защиты применяются аппаратные средства (NX-бит, ASLR, SMEP, SMAP) и программные методы (санитайзеры, безопасные языки).

Источники

  • Таненбаум Э., Бос Х. "Современные операционные системы" (4-е издание), 2015.
  • Сильбершац А., Гэлвин П., Гэгн Г. "Операционные системы: внутренняя структура и принципы проектирования" (9-е издание), 2018.
  • Документация ядра Linux: "Memory Management" (kernel.org).
  • Документация Microsoft: "Memory Management" (Windows Driver Kit).
  • Бэкон Д. Ф., Ченг П., Раджан В. Т. "A Unified Theory of Garbage Collection", 2004.
  • Клэбхолц Д. "Rust in Action", 2021.

BFOmetr — база данных и аналитика по компаниям России.

На главную BFOmetr →