Лекция 4. Работа с памятью
Архитектура: фон Неймана vs Гарвардская
- 2 потока: поток данных и поток команд-вычислений.
- В архитектуре фон Неймана они идут через одну шину памяти.
- В Гарвардской — данные и команды разделены.
- 2 блока: блок процессора и блоки, отвечающие за память:
- SSD (сотни ГБ) и HDD (ГБ–ТБ) — жёсткие диски.
- Оперативная память (~16 ГБ).
- Кэш процессора (3 уровня: L1 — ~24 КБ, L2 — ~112 КБ, L3 — ~4 МБ) — сверхоперативная память, используемая микропроцессором для уменьшения среднего времени доступа к компьютерной памяти.
“Процесс”
- Под каждый процесс отводится своя память.
- Своё адресное пространство.
- Свои потоки для каждого процесса.
- ОС для каждого процесса создаёт иллюзию того, что он может пользоваться всей виртуальной памятью, хотя на самом деле она разделена между процессами.
- ОС хранит взаимно-однозначное соответствие между виртуальной памятью и физической памятью с помощью Page Table (выдаёт страницы памяти, забирает, отдаёт дальше).
- Для каждого процесса создаётся своя виртуальная таблица — одним и тем же виртуальным адресам в разных процессах могут соответствовать разные места физической памяти.
- ОС может swap-ать часть информации на жёсткий диск, если она используется редко.
Сегменты памяти
- Сегмент ОС (виртуальная таблица) — доступа у пользователя нет.
- 3 сегмента фиксированного размера:
- Text — исполняемый код.
- Data — инициализированные глобальные/статические переменные.
- BSS — неинициализированные глобальные/статические переменные.
- 3 сегмента, меняющих размер:
- Stack — стек вызовов.
- Heap — куча.
- Memory mapping segment — для маппинга файлов.
Process id: 18636
Data segment: 00007ff6cc079000
BSS segment: 00007ff6cc078000
Text segment: 00007ff6cc079004
Code segment: 00007ff6cc071430
Stack segment: 0000007 8c0bff884
Первые сегменты расположены близко друг к другу, а Stack — где-то далеко (растёт в обратном направлении).
Stack (стек)
- Чтение и запись.
- Локальные переменные функций.
- Почему размер стека такой небольшой? — Чтобы обеспечить возможность быть достаточно близким к кэшу процессора, но при этом достаточно большим для типичных задач.
- Растёт в сторону уменьшения адресов.
- Управляется автоматически (LIFO).
Heap (куча)
- В отличие от стека, позволяет создавать динамические структуры большого размера.
- Управление жизнью объектов в куче — «ручное».
Семейство C-функций:
malloc/free— оперируют байтами.mallocне гарантирует выделение памяти (может вернутьNULL).- Не забывать выставлять указатель в
NULLпосле освобождения (избегаем dangling pointer). free(NULL)ничего не делает (безопасно).
calloc/realloc— оперируют не байтами, а массивами:callocдополнительно зануляет память.reallocизменяет размер ранее выделенного блока.
- Если не освобождать память — объём используемой памяти будет раздуваться (memory leak).
Операторы C++:
new/delete:- При нехватке памяти выкинет исключение (
std::bad_alloc), в отличие от C-функций. - Занимается инициализацией и вызовом конструктора классов (в отличие от C-функций).
- Для массивов используется
new[]/delete[].
- При нехватке памяти выкинет исключение (
Segmentation fault
Возникает при:
- Обращении к несуществующему адресу.
- Обращении к сегменту, прав для которого нет.
Если объявить массив
charчерез[](char s[] = "abc") — ему можно менять элементы. А если через*(char* s = "abc") — это указатель на глобальный строковый литерал, и его уже нельзя менять. - Попытке поменять данные в read-only сегменте.
- Обращении по нулевому указателю.
- Обращении по указателю на удалённый объект (dangling pointer).
- Переполнении стека (stack overflow).
- Переполнении буфера (buffer overflow).
Memory mapping segment
- Для физических файлов, которые мы можем загружать на компьютер (через
mmap). - Используется также для подгрузки разделяемых библиотек.
Text
- Тексты, строчки (строковые литералы), исполняемый код.
- Не доступен для записи (read-only).
Data
- Статические переменные и константы (инициализированные).
- В случае констант — только для чтения.
BSS
- Глобальные/статические неинициализированные переменные.
- Запись и чтение.
- Инициализируются нулями при старте программы.
Регистры процессора
- Регистр — поле заданной длины во внутрипроцессорной сверхбыстрой оперативной памяти (СОЗУ).
- Используется самим процессором; может быть как доступным, так и не доступным программно.
- Например, при выборке из памяти очередной команды она помещается в регистр команд, обращение к которому программист прописать не может.
- Виды регистров:
- Регистры общего назначения.
- Специальные регистры для приложений.
- Сегментные регистры.
- Специальные регистры режима ядра.
Кадр (stack frame)
- Кадр — множество данных: аргументы функции, все локальные переменные функции и адрес, куда функция должна вернуть результат.
- Размер кадра известен на этапе компиляции.
Важно
- Указатель находится на стэке! (Сам указатель — локальная переменная; объект, на который он указывает, может быть в куче.)
- Из-за того, что процессор отдаёт процессам отдельные страницы виртуальной таблицы, при завершении процесса данные участки памяти будут очищены и отданы другим процессам.