Устройство памяти процесса и аллокация¶
Каждый процесс в Linux работает с виртуальным адресным пространством — приватным видом памяти, которое ОС предоставляет процессу. Физическая RAM распределяется ядром отдельно.
Сегменты адресного пространства¶
Высокие адреса (0xffff…)
┌──────────────────────────────┐
│ Kernel Space │ недоступно из user-mode
├──────────────────────────────┤
│ Stack (стек) │ ↓ растёт вниз
│ (локальные переменные, │
│ аргументы функций) │
├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─┤
│ mmap-регионы │ библиотеки .so, анонимные маппинги
├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─┤
│ Heap (куча) │ ↑ растёт вверх (brk/mmap)
├──────────────────────────────┤
│ .bss │ неинициализированные глобальные (rw-)
├──────────────────────────────┤
│ .data │ инициализированные глобальные (rw-)
├──────────────────────────────┤
│ .rodata │ константы, строки (r--)
├──────────────────────────────┤
│ .text │ машинный код (r-x)
└──────────────────────────────┘
Низкие адреса (0x0000…)
Секции ELF в памяти¶
| Секция | Содержимое | Права | В файле ELF? |
|---|---|---|---|
.text |
Машинный код | r-x | Да |
.rodata |
Строковые литералы, константы | r-- | Да |
.data |
Инициализированные глобальные/статические переменные | rw- | Да |
.bss |
Неинициализированные/нулевые глобальные/статические | rw- | Нет (только размер) |
int x = 5; // .data — значение хранится в файле ELF
int y; // .bss — только размер; при загрузке обнуляется
static int z = 0; // .bss — явный ноль тоже попадает в .bss
const char msg[] = "hi"; // .rodata — строковый литерал read-only
const char *s = "hello"; // указатель s — .data; строка "hello" — .rodata
В ELF-файле .bss не занимает места — хранится только информация «нужно выделить N байт и обнулить».
Это экономит место на диске: массив int buf[1000000]; без инициализации добавляет к ELF всего ~16 байт
(запись секции), а не 4 МБ.
Размеры секций можно посмотреть утилитой size:
Просмотр адресного пространства¶
cat /proc/<pid>/maps # полная карта: адреса, права, backing file
pmap <pid> # более читаемый вывод
cat /proc/self/maps # для текущего процесса
Пример строки из /proc/self/maps:
7f8a1c000000-7f8a1c200000 rw-p 00000000 00:00 0 [heap]
7ffe3d200000-7ffe3d221000 rw-p 00000000 00:00 0 [stack]
Стек¶
Стек главного потока занимает фиксированный виртуальный регион (обычно 8 МБ по умолчанию в Linux).
Он растёт вниз: при вызове функции указатель стека (rsp на x86-64) уменьшается, при возврате —
увеличивается. Ниже текущего дна стека ядро помещает guard page (страницу с правами ---):
попытка записи туда немедленно вызывает SIGSEGV, сигнализируя о переполнении стека.
# Узнать лимит стека (в KB)
ulimit -s # обычно 8192
# Изменить лимит для текущей сессии
ulimit -s unlimited
Каждый поток (thread) получает отдельный стек, выделяемый через mmap. Его размер задаётся
атрибутом PTHREAD_STACK_SIZE или функцией pthread_attr_setstacksize. Поэтому в /proc/<pid>/maps
видно несколько анонимных rw-p регионов — по одному на каждый поток.
Расширение heap: brk и sbrk¶
Heap расширяется через перемещение program break — границы конца heap:
#include <unistd.h>
void *sbrk(intptr_t increment); // сдвинуть program break на increment байт
int brk(void *addr); // установить program break на addr
// Пример: явное расширение heap (демонстрационное, не делайте так в продакшне)
void *start = sbrk(0); // текущая граница
char *mem = (char *)sbrk(4096);// расширить на 4 КБ
printf("allocated at %p\n", mem);
brk(start); // вернуть границу обратно
Напрямую вызывать brk/sbrk не рекомендуется — это нарушает работу malloc, который тоже
управляет program break. Для больших аллокаций (≥ 128 KB) malloc использует mmap(MAP_ANONYMOUS),
а не brk.
Связанные темы¶
- Виртуальная память — MMU, TLB, таблицы страниц, Page Fault
- mmap и маппинг файлов —
mmapдля анонимных регионов и файлов - Реализация malloc и free — как устроена куча внутри glibc
- ELF-формат — секции ELF и их отображение в сегменты
- Фреймы стека и вызовы функций — ABI стека x86-64
Источники¶
man 2 brk,man 2 mmapman 5 proc— описание/proc/<pid>/mapsman 1 size— утилита для просмотра размеров ELF-секций- Linux x86-64 memory map