LINUX.ORG.RU

[x86_64] Виртуальная адресация


1

1

Добрый день.

Читаю про адресацию в Линуксе, но никак не могу понять почему логический (линейный) адрес в ядре _всегда_ соответствует физическому, только смещен на константу. Читал, что это должно как-то увеличивать скорость, но ведь все равно адрес преобразовывается или через таблицу страниц или через tlb и, как мне кажется, ему должно быть тогда все равно. Смысл вижу только, если память будет использоваться при вводе/выводе (DMA, например).

И почему ядро старается разместиться в ZONE_NORMAL, а не в ZONE_HIGH, например? Ведь адреса-то виртуальные.

> логический (линейный) адрес в ядре _всегда_

ну, не всегда... но если не вспоминать про vmalloc/etc
и user mapping, то да.

соответствует физическому, только смещен на константу.

Читал, что это должно как-то увеличивать скорость



скорость это, само по себе не увеличивает. это просто
удобно.

собственно говоря, а какие есть альтернативы linear mapping?

дрес преобразовывается или через таблицу страниц или через tlb


именно. ускорение из-за того, что этот linear mapping
использует _PAGE_PSE, те «huge» 2/4M страницы.

кстати, если уж в subject у нас x86_64, то полезно знать,
что у нас 2 liniear/trivial mapping. один (одна?) «сдвинут»
на PAGE_OFFSET, another one на __START_KERNEL_map для .text

idle ★★★★★
()
Ответ на: комментарий от idle

использует _PAGE_PSE, те «huge» 2/4M страницы

Наконец-то я увидел смысл в таком распределении.

собственно говоря, а какие есть альтернативы linear mapping?

Мне кажется, что если мы закрепим какое-то физическое пространство адресов, то мы будет способствовать его фрагментации. И выходит тогда, что ядро совсем не может свопиться? Я читал, что есть страницы, которые двигать нельзя, но думал, что их будет немного.

DesertFox
() автор топика
Ответ на: комментарий от DesertFox

> способствовать его фрагментации.

ядро совсем не может свопиться?


ох. ты, похоже как-то путаешь совсем разные вещи.

«ядро» вообще не свопится. те, его код/данные.

есть страницы, которые двигать нельзя, но думал,

что их будет немного.



ты путаешь разные вещи. что значит «много» ? linear
mapping отображает вообще _всю_ память. это же не
означает что «двигать» ничего нельзя. но «двигается»
это в другой адресации зависящей от процесса.

idle ★★★★★
()
Ответ на: комментарий от idle

Я думал, что наоборот. Виртуальный адрес всегда постоянен, а физический меняется. При чем если страницы идут подряд по виртуальным адресам, то физические адреса они могут иметь какие угодно.

Линейный же адрес должен соответствовать физическому, с отличием на константу. То есть если страницы идут подряд по линейным адресам, то они должны идти подряд и физически. Выходит ядро занимает нижнюю память, фрагментирует ее и когда-то не сможет выделить новую память. Хотя реально свободной памяти может быть достаточно. Конечно такое может произойти, только если ядро захочет выделить несколько страниц. Так как в частично занятых страницах память никак нельзя будет выделить, если это не кэш.

DesertFox
() автор топика
Ответ на: комментарий от DesertFox

> Я думал, что наоборот.

у тебя точно путаница в голове ;) это нормально,
у всех так, пока не разберешься.

Виртуальный адрес всегда постоянен, а физический меняется.


виртуальный адрес _чего_ ? и _какой_, те в какой адресации ?

вот с этого надо начинать. у нас одна и та же страница памяти
(или физический адрес) может быть видна по нескольким адресам.

ядро занимает нижнюю память,


наоборот, верхнюю. с точки зрения виртуальной адресации.
это если говорить про код/данные ядра самого.

фрагментирует ее


кого? если говорить о фрагментации, то можно говорить о
всей памяти. забудь пока про ZONE_NORMAL/HIGHMEM/etc, это
только запутает сейчас. (кстати, на x86_64 ZONE_HIGHMEM
нету).

не сможет выделить новую память.


да, но

только если ядро захочет выделить несколько страниц.


именно. поэтому у нас есть vmalloc().

но. c дефрагментацией бороться можно, ядро-то (само по
себе) использует малую часть доступной памяти. user space
память можно и подвигать. или, скажем, page cache.

idle ★★★★★
()
Ответ на: комментарий от DesertFox

Есть несколько отоборажений, в частности одно для ядра, которое отображает линейно всю физическую память, это нужно для удобство, например, захотелось ядру к памяти произвольного процесса обратиться, зачем ее мапить, оно уже может прочитать любой адресс памяти.

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

frey ★★
()
Ответ на: комментарий от idle

виртуальный адрес _чего_ ? и _какой_, те в какой адресации ?

Допустим, какой-либо переменной. Для процесса он не может меняться. Именно поэтому при реальной адресации никак нельзя бороться с фрагментацией. А при виртуальной, мы можем переместить страницу с переменной куда-нибудь в другое место.

Ну а для разных процессов виртуальный адрес одной и той же переменной может быть разным.

наоборот, верхнюю.

Это меня запутало то, что в mm.txt младшие адреса сверху)

именно. поэтому у нас есть vmalloc().

Тогда странно, что в slab аллокаторе размеры кэшей достигают 32-х мегабайтов. Хотя в slub уменьшился до двух страниц, а дальше, как я понял выделяется, как в куче.

(кстати, на x86_64 ZONE_HIGHMEM нету).

Какая хорошая новость для меня) Я никак не мог понять зачем она нужна.

А в x86_32 она нужна была потому что у нас физической памяти могло быть до 64 Гб, виртуальной только 4?

DesertFox
() автор топика
Ответ на: комментарий от DesertFox

> Допустим, какой-либо переменной.

нельзя бороться с фрагментацией.


вот. а откуда здесь случится фрагментация? ее быть
не может, и бороться тут не с чем.

в slab аллокаторе размеры

Хотя в slub



забудь, это другое. память выделяется все равно через
alloc_pages & co.

но MAX_ORDER у нас большой, и KMALLOC_MAX_SIZE сильно
вырос в последнее время, да. однако... тут можно долго
говорить.

если нужно «много» страниц памяти - vmalloc().

потому что у нас физической памяти могло быть до 64 Гб,

виртуальной только 4



именно.

idle ★★★★★
()
Ответ на: комментарий от idle

> вот. а откуда здесь случится фрагментация? ее быть

не может, и бороться тут не с чем.


только это... я же ну утверждаю, что проблем c фрагментацией
нет. они есть, но бороться можно. просто ты не там видишь
главные проблемы. linear mapping тут ничего не усложняет.

idle ★★★★★
()
Ответ на: комментарий от idle

Спасибо, кажется немного прояснилось.

И вопрос не в тему: slub считается лучшим и поэтому он является аллокатором по умолчанию. Если какие-то случаи, когда slab все-таки оказывается лучше?

Я заметил, что в кэшах выделенной информации может быть до 40% больше, чем было запрошено. Если суммарный объем информации выделенной kmalloc небольшой (на моем компьютере — несколько мегабайт), то такая фрагментация не существенна. А бывают такие случаи, когда это все-же существенно?

DesertFox
() автор топика
Ответ на: комментарий от DesertFox

> slub считается лучшим

slab все-таки оказывается лучше


тут я просто не знаю, никогда не интересовался.
я даже навскидку не скажу, чем они отличаются.
killer feature, вроде бы, more cache-friendly.
но могу соврать, не моя область.

в кэшах выделенной информации может быть до 40% больше

чем было запрошено.



этого я не понял...

А бывают такие случаи, когда это все-же существенно?


да наверное... сразу сейчас не скажу. хотя вот,
TASK_ORDER == 1, нужно две страницы на kernel stack
процесса (там еще thread_info).

кстати, это хороший пример. представь, сколько геморроя
мы поимели бы, если бы пытались «двигать» _эту_ память.

idle ★★★★★
()
Ответ на: комментарий от idle

этого я не понял...

К примеру мы kmalloc-ом выделяем 12 байт. Аллокатор поместит этот объект в кэш с 16-ти байтными объктами для slub-а и 32-байтными для slab-а. Выходит для slab-а мы потеряли 20/32 = 62% памяти. 20 байт конечно не много, но если у нас много таких выделений будет много, то такая фрагментация будет существенной.

да наверное... сразу сейчас не скажу. хотя вот, TASK_ORDER == 1, нужно две страницы на kernel stack процесса (там еще thread_info).

К сожалению, не знаю что такое TASK_ORDER, но если выделяется ровно 2 страницы, то фрагментации не будет.

DesertFox
() автор топика
Ответ на: комментарий от DesertFox

> 12 байт. Аллокатор поместит этот объект в кэш с 16-ти

а, да. но это другое. это уже к издержкам sl*b.

К сожалению, не знаю что такое TASK_ORDER


см alloc_thread_info()->__get_free_pages(THREAD_ORDER)

но если выделяется ровно 2 страницы, то фрагментации

не будет.



как это??? нам же нужно 2 страницы подряд.

idle ★★★★★
()
Ответ на: комментарий от idle

как это??? нам же нужно 2 страницы подряд.

Если нет 2-х страниц подряд, то тут есть 2 варианта. Если kmalloc был вызван, например, с GFP_KERNEL, то аллокатор постарается что-то отравить в свап, а может и убьет кого-то, но память скорее всего выделит. Если же, например, с GFP_ATOMIC, то kmalloc сразу вернет NULL. Но, фрагментации быть не должно.

Может я неправильно понял, что будет фрагментироваться?

DesertFox
() автор топика
Ответ на: комментарий от DesertFox

> то аллокатор постарается что-то отравить в свап

да это понятно. но это же и есть борьба с фрагментацией!
у нас свободно (в теории) половина памяти, а мы не можем
выделить жалкие 2 странички.

и да, своп/etc и есть (ну, упрощая) метод борьбы. не надо
пытаться дефрагментировать данные ядра.

Может я неправильно понял, что будет фрагментироваться?


да может... все, что больше одной страницы - уже может
пострадать из-за фрагментации. ты же сам про это говорил?

idle ★★★★★
()
Ответ на: комментарий от DesertFox

> Но, фрагментации быть не должно.

Выделение по две и более страницы в памяти не столько создает фрагментацию, сколько чувствительно к существующей. Например, если свободна половина памяти, но через страницу, то выделить 2 страницы нельзя. Кроме того, нельзя выделить две страницы, если с точки зрения buddy allocator они не являются парой.

Murr ★★
()
Ответ на: комментарий от idle

у нас свободно (в теории) половина памяти, а мы не можем выделить жалкие 2 странички.

Тогда выходит это недостаток линейной адресации, что у нас ограничены возможности по дефрагментации. В LDD писали, что это может быть проблемой и там рекомендовалось использовать allocate_bootmem.

все, что больше одной страницы - уже может пострадать из-за фрагментации. ты же сам про это говорил?

Просто я говорил еще про фрагментацию, когда аллокатор выделяет больше чем требуется.

DesertFox
() автор топика
Ответ на: комментарий от DesertFox

> Тогда выходит это недостаток линейной адресации,

мы пошли по кругу ;) ты не там видишь главную проблему.

хорошо, представь, что она нелинейная. вот дальше что?

idle ★★★★★
()
Ответ на: комментарий от DesertFox

>> у нас свободно (в теории) половина памяти, а мы не можем выделить жалкие 2 странички.

Тогда выходит это недостаток линейной адресации

Линейная адресация тут вообще не причем.

tailgunner ★★★★★
()
Ответ на: комментарий от idle

хорошо, представь, что она нелинейная. вот дальше что?

Отлично, мы можем страницы выделять не по порядку, а в разброс. Т.е. уже не будет проблемы, которую описал http://www.linux.org.ru/jump-message.jsp?msgid=6042103&cid=6043287.

Но как ты говорил раньше линейная адресация удобнее и помогает выиграть в скорости на преобразовании адресов. А большие объемы мы выделяем редко т.е. проблема не столь существенная.

DesertFox
() автор топика
Ответ на: комментарий от DesertFox

>Тогда выходит это недостаток линейной адресации, что у нас ограничены возможности по дефрагментации. В LDD писали, что это может быть проблемой и там рекомендовалось использовать allocate_bootmem.

alloc_bootmem нельзя использовать после загрузки, поскольку его код вместе со всем разделом .init освобождается в free_initmem. ;)

Murr ★★
()
Ответ на: комментарий от idle

хорошо, представь, что она нелинейная. вот дальше что?

Отлично, мы можем страницы выделять не по порядку, а в разброс. Т.е. уже не будет проблемы, которую описал Murr.

Но как ты говорил раньше линейная адресация удобнее и помогает выиграть в скорости на преобразовании адресов. А большие объемы мы выделяем редко т.е. проблема не столь существенная.

DesertFox
() автор топика
Ответ на: комментарий от DesertFox

Но как ты говорил раньше линейная адресация удобнее и помогает выиграть в скорости на преобразовании адресов. А большие объемы мы выделяем редко т.е. проблема не столь существенная.

Подумай, что выполнится быстрее:

1)phys_addr = virt_addr - PAGE_OFFSET

или

2)анализ таблицы страниц

ttnl ★★★★★
()
Ответ на: комментарий от Murr

Ну да, это вариант только для драйверов, встроенных в ядро. Но если нам нужно много памяти можно ее зарезервировать заранее.

DesertFox
() автор топика
Ответ на: комментарий от ttnl

1)phys_addr = virt_addr - PAGE_OFFSET

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

DesertFox
() автор топика
Ответ на: комментарий от Murr

> Кроме того, нельзя выделить две страницы, если с точки

зрения buddy allocator они не являются парой.


не-не-не, в данном случае (alloc_pages) buddy не причем.

idle ★★★★★
()
Ответ на: комментарий от DesertFox

> Отлично, мы можем страницы выделять не по порядку, а в разброс.

отлично. вот мы нашли две странички, P1 and P2.

дальше?

idle ★★★★★
()
Ответ на: комментарий от DesertFox

забыл...

Но как ты говорил раньше линейная адресация удобнее


да, и она все равно нужна. но забудем про это на минутку.

idle ★★★★★
()
Ответ на: комментарий от DesertFox

>Это будет еще не физический адрес, а логический (линейный) и он все равно анализ таблицы страниц нужен

Когда тебе в своем коде нужно будет выполнить преобразование, быстрее и удобнее в таком случае сделать вычитание, чем анализировать таблицу страниц.

ttnl ★★★★★
()
Ответ на: комментарий от idle

Сначала создадим запись в таблице страниц, то есть P1 и P2 получат виртуальный адрес. Флаг присутствие должен быть сброшен.

А реально будем выделять для них память по #PF. И может так выйти, что вторая страница нам вовсе и не понадобится, если мы выделили память для стека.

DesertFox
() автор топика
Ответ на: комментарий от idle

> > вместе со всем разделом .init освобождается в free_initmem



да ладно. это ты спутал.



а. не, это я дурак. ты про _код_ alloc_bootmem() говорил,
сорри. я чо-то подумал, ты имел в виду allocated memory.

idle ★★★★★
()
Ответ на: комментарий от DesertFox

> Сначала создадим запись в таблице страниц

ага. где?

сначала нужно будет «придумать» новый виртуальный адрес.
как?

idle ★★★★★
()
Ответ на: комментарий от idle

Этого я уже не знаю, как оно делается, поэтому тут мои догадки:

Наша цель найти пустую запись в таблице страниц, поэтому можно просто просмотреть все каталоги вглубь и найти пустую запись. Можно придумать как-то оптимизировать. У нас 256 TB виртуального адресного пространства, думаю место для 2 записей подряд там найдется.

DesertFox
() автор топика
Ответ на: комментарий от DesertFox

>Ты _не_ можешь ее _не_ анализировать, если у тебя включена страничная адресация (флаг PG в CR0).

Речь про другое. Вот смотри:

grep -R 'virt_to_phys' .

Куча мест, где используется это преобразование. Если ты заменишь каждое из них на анализ таблицы страниц, это значительно ухудшит производительность. И ещё придется вводить дополнительные блокировки.

ttnl ★★★★★
()
Ответ на: комментарий от DesertFox

>Но если нам нужно много памяти можно ее зарезервировать заранее.

Ну заранее можно и с помощью buddy allocator выделить много. Для фрагментации памяти ведь нужно время.

Murr ★★
()
Ответ на: комментарий от DesertFox

> А реально будем выделять для них память по #PF.

Выделять память, например, драйверу, по страничому отказу? O_O По-моему, ты просто не поимаешь, о чем говоришь.

tailgunner ★★★★★
()
Ответ на: комментарий от ttnl

Так анализ таблицы страниц делает сам процессор при каждом обращении в память, а для оптимизации он в tlb хранит уже расшифрованные адреса. При чем анализ таблицы происходит независимо от того равен ли виртуальный адрес физическому или нет.

DesertFox
() автор топика
Ответ на: комментарий от tailgunner

а что плохого, если мы выделяем, например, с GFP_KERNEL? И все же, если это плохо, то я не настаиваю и пусть физическая страница выделяется сразу же.

DesertFox
() автор топика
Ответ на: комментарий от DesertFox

>Сначала создадим запись в таблице страниц, то есть P1 и P2 получат виртуальный адрес. Флаг присутствие должен быть сброшен.

А реально будем выделять для них память по #PF. И может так выйти, что вторая страница нам вовсе и не понадобится, если мы выделили память для стека.

Аналоги mmap, в т.ч. анонимные, в памяти ядра есть в winnt. Хотя и в linux можно зарезервировать виртуальную память ядра без выделения через get_vm_area (а потом вручную заполнять через vmap), все это, наверное, будет плохо работать для отличных от vmalloc функций.

Murr ★★
()
Ответ на: комментарий от DesertFox

>Так анализ таблицы страниц делает сам процессор

У тебя каша в голове.

Адреса преобразовываются аппаратно, как они происходят тебе не видно. Другое дело, что часто внутренним алгоритмам ядра это преобразование тоже нужно. Вовсе не для того, чтобы непосредственно в тот же момент обратится по этому адресу.

Правда, есть некоторые исключения. На sparc, например, есть такое говно, как tsb. Но это не тво случай.

ttnl ★★★★★
()
Ответ на: комментарий от ttnl

Вот оно в чем дело... DMA... Значит нам не подходят странички в разброс, а то выйдет vmalloc. А если они виртуально по порядку, физически по порядку, то выходит линейная адресация)

DesertFox
() автор топика
Ответ на: комментарий от DesertFox

> а что плохого, если мы выделяем, например, с GFP_KERNEL?

Ы? Память, выделенная с GFP_KERNEL, может быть необходима в обработчике прерывания. Ты и в нем собираешься страничный сбой обрабатывать? или, как сказно выше, я могу передать адрес выделенной памяти устройству.

Короче, почитай что-нибудь об ОС и управлении памятью в них. Например: http://www.kernel.org/doc/gorman/html/understand/

tailgunner ★★★★★
()
Ответ на: комментарий от ttnl

>Правда, есть некоторые исключения. На sparc, например, есть такое говно, как tsb. Но это не тво случай.

Там, как и на alpha, буфер трансляций заполняется программно?

Murr ★★
()
Ответ на: комментарий от DesertFox

>А если они виртуально по порядку, физически по порядку, то выходит линейная адресация)

Ну типа. Если iommu нет, то процессор работает по виртуальным, устройства по физическим, и все довольны.

ttnl ★★★★★
()
Ответ на: комментарий от tailgunner

Память, выделенная с GFP_KERNEL, может быть необходима в обработчике прерывания.

Я об этом не подумал.

Например: http://www.kernel.org/doc/gorman/html/understand/

А есть более новое издание? Так как там есть такая крутая штука, как комментарии к коду, но за 7 лет они устарели.

DesertFox
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.