Ты уже сказал про старшие 20 бит, скорее всего знаешь про сегментную организацию виртуальной памяти.
20 бит используются в 32 битных системах, а сейчас большая часть 64 битная, но это не важно.
Принцип следующий:
Существует 3 таблицы:
- PGD - Page Global Directory
- PMD - Page Middle Directory
- PTE - Page Table Entry
Они иерархические, т.е. запись в PGD указывает на запись в PMD, а PMD - на PTE.
В итоге, ты приходишь с 3 "числами" - индексы для этих таблиц и последовательно приходишь к нужной PTE.
Но тебе нужно еще 4 число - смещение относительно полученного в PTE значения (там хранятся "начала" выделенных сегментов/интервалов памяти)
Теперь последовательно запиши эти адреса и получишь виртуальный адрес.
TLB в данном случае - это просто кэш, чтобы ты постоянно не ходил через этот ад указателей. Он в процессе выделения памяти не участвует.
Как выделяется реальная память - деталь реализации, о которой знать не нужно.
Если интересно -
вот статья про память в линуксе.
P.S. пример показан на C# - там собственная виртуальная память и GC. Поэтому показывает не то, что выделила ОС.