Возможно, вы задумывались, почему VirtualAlloc выделяет память, выравненную на границу 64 Кб, хотя гранулярность страниц памяти всего 4 Кб.
За это вам нужно поблагодарить процессор Alpha AXP.
У Alpha AXP нет инструкции "загрузить 32-х битное число". Для загрузки 32-х битного целого, вам придётся загрузить два 16-ти битных числа и скомбинировать их.
Поэтому, если бы гранулярность выделения была бы меньше 64 Кб, то любая DLL, которую нужно было бы переместить в памяти, требовала бы двух исправлений на один перемещаемый адрес: одно исправление для верхних 16 бит, а второе - для нижних. А при заёме или переносе между этими половинками всё становится ещё хуже (например, при переносе адреса 4 Кб с $1234F000 на $12350000 происходит обновление как старшей, так и младшей части адреса. Несмотря на то, что перемещение идёт на размер много меньший 64 Кб, оно всё ещё влияет на старшую часть из-за переноса).
Но постойте, это ещё не всё.
Процессор Alpha AXP на самом деле может комбинировать два знаковых 16-ти битных целых для формирования 32-х битного числа. Например, чтобы загрузить значение $1234ABCD, вам сперва нужно использовать инструкцию LDAH для загрузки числа $1235 в верхнюю часть регистра-назначения. Потом вы используете инструкцию LDA для добавления значения -$5433 (поскольку $5433 = $10000 - $ABCD). В результате у вас получится желаемое значение $1234ABCD.
LDAH t1, $1235(zero) // t1 = $12350000 LDA t1, -$5433(t1) // t1 = t1 - $5433 = $1234ABCDПоэтому, если перемещение приводило бы к переносу адреса между "нижней частью" блока в 64 Кб и "верхней частью" (т.е. на размер не кратный 64 Кб), то нужно было бы произвести дополнительные действия, чтобы быть уверенным, что верхняя часть адреса была корректно исправлена.
Самое главное, что компилятор может быть умён и если ему нужно вычислить адреса для двух переменных в одном и том же регионе в 64 Кб, то он использует всего одну инструкцию LDAH для обоих вычислений. Если смещения на размер не кратный 64 Кб были бы разрешены, то компилятор не смог бы применить такую оптимизацию, потому что теперь после перемещения две переменные могли бы оказаться в разных блоках по 64 Кб - а значит для каждой переменной теперь нужна своя индивидуальная инструкция LDAH.
Введение ограничения на гранулярность выделения памяти в 64 Кб решает все эти проблемы.
Если вы были действительно внимательны при чтении, то вы могли уже заметить, почему блок памяти в 64 Кб около границы в 2 Гб является "ничейной землёй". Рассмотрим метод формирования значения $7FFFABCD: поскольку младшие 16 бит лежат в верхней половине 64 Кб, то значение нужно вычислять с помощью вычитания, а не сложения. Наивное решение могло бы быть таким:
LDAH t1, $8000(zero) // t1 = $80000000, верно? LDA t1, -$5433(t1) // t1 = t1 - $5433 = $7FFFABCD, верно?Только вот это не сработает. +$8000 не влезает в 16-ти битное знаковое целое (максимальное положительное 16-ти битное число - это +$7FFF - прим. пер.), поэтому вам придётся использовать -$8000 - отрицательное число. Кроме того, Alpha AXP - это 64-х битный процессор. Поэтому на самом деле произойдёт:
LDAH t1, -$8000(zero) // t1 = $FFFFFFFF`80000000 LDA t1, -$5433(t1) // t1 = t1 - $5433 = $FFFFFFFF`7FFFABCDВам нужна ещё третья инструкция, чтобы обнулить старшие 32 бита. Для этого есть остроумный трюк: добавить к регистру ноль и сказать процессору, чтобы он рассматривал результат как 32-х битное число и знаково расширил бы его до 64 бит.
ADDL t1, zero, t1 // t1 = t1 + 0, с суффиксом L // Суффикс L означает знаковое расширение результата с 32 бит до 64 // t1 = $00000000`7FFFABCDЕсли бы адреса в районе 64 Кб от границы в 2 Гб были бы разрешены, то к каждому бы вычислению адреса памяти нужно было бы добавлять эту третью инструкцию ADDL на тот случай, если адрес будет перемещён в "опасную зону" около границы в 2 Гб.
Это была бы ужасно высокая цена за доступ к этим последним 64 Кб адресного пространства (падение производительности на 50% для всех случаев вычисления адреса, только для того, чтобы защититься от случая, который на практике мог ни разу и не возникнуть), поэтому пометка этой зоны как недоступной была более мудрым решением.
>>в "опасную зону" около границы в 2 Гб
ОтветитьУдалитьа как вычислить эту зону?
Ээээ.... прочитайте ещё раз, начиная с "Если вы были действительно внимательны при чтении, то вы могли уже заметить, почему блок памяти в 64 Кб около границы в 2 Гб является "ничейной землёй"." Особенно обращайте внимания на адреса и вычисления.
ОтветитьУдалитьПрошу прощения, был невнимателен =)
ОтветитьУдалитьАлександр, большое спасибо.
Долго вникал... Сейчас адреса набираются двумя операциями: сложения и вычитания. А если разрешить использование "опасной зоны", то адреса набирались бы тремя операциями: вычитания, вычитания и обнуления. Лишний такт, это понятно.
ОтветитьУдалитьА если не обнулять старшие байты, то число из примера $FFFFFFFF`7FFFABCD процессор будет рассматривать как отрицательное знаковое, верно?
Ну, для сложения с нулём не важно какое это число :) Но да, если рассматривать его как знаковое, то оно будет отрицательным 64-битным числом. Но суть не в том, что оно отрицательное, а в том, что это не тот адрес, который нам нужен. Нам нужен 32-битный адрес 7FFFABCD (или, что то же самое - 000000007FFFABCD).
Удалить