Перейти к содержанию

Устройство памяти процесса и аллокация

Каждый процесс в 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:

$ size ./a.out
   text    data     bss     dec     hex filename
   1234     512   40000   41746    a312 ./a.out

Просмотр адресного пространства

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.

Связанные темы

Источники

  • man 2 brk, man 2 mmap
  • man 5 proc — описание /proc/<pid>/maps
  • man 1 size — утилита для просмотра размеров ELF-секций
  • Linux x86-64 memory map