ARM - потрясающая архитектура. Я не сомневаюсь в этом. Мне это всегда нравилось. Это RISC, поэтому у нас нет множества избыточных инструкций, и в большинстве случаев есть только один способ достичь заданного результата. Все инструкции имеют длину 32 бита и одинаковую ширину. У нас есть множество регистров общего назначения. Но за это приходится платить. Невыровненный доступ, как известно, сложен и, следовательно, с этой проблемой мы сталкиваемся слишком часто. Тем не менее, потому что когда-то это не работало.
В первую очередь, что вообще означает выравнивание? Что означает, что адрес выровнен по X? Проще говоря, это означает, что условие addr% X == 0 истинно, где% представляет собой целочисленную операцию по модулю. Например, 8000 выравнивается по 2, 4, 8 и т.д., но не по 3. Интересно отметить, что каждый адрес выровнен по 1, что также, что забавно, означает, что он не выровнен. Следовательно, невыровненный доступ просто означает, что адрес памяти, к которому осуществляется доступ, не выровнен по правильному значению, некоторые инструкции, такие как LDRH, требуют 2-байтового выравнивания, тогда как инструкции, такие как LDR и STR, требуют 4- выравнивание байтов для оптимальной производительности. Подробнее о производительности позже.
Давайте посмотрим. До ARMv5 ARM не поддерживала невыровненный доступ должным образом. Для STR, STM и LDM запрошенный адрес просто округлялся до четырех. Однако для LDR после округления адреса в меньшую сторону он поворачивается по байтам вправо на значение в битах [1: 0] исходного адреса. Это инструкция LDR r0, [r1] может быть реализована в программном обеспечении следующим образом, если r1 не выровнен:
BIC rT, r1, #3 // bit-clear the bottom 2 bits to align it LDR r0, [rT] // load the value at aligned address AND rT, r1, #3 // select the bottom 2 bits from the original MOV rT, rT, LSL #3. // multiply it by 8 MOV r0, r0, ROR rT // rotate right by the appropriate value
(Заметим, что rT - это произвольный временный регистр)
А что насчет LDRH? Если бит 1 установлен во время доступа к полуслову, результат будет просто неопределенным и, следовательно, непредсказуемым. Аналогично для LDRD (передача двойного слова) адрес должен быть выровнен по 4 байтам, в противном случае он будет непредсказуемым.
Однако, начиная с ARMv7, стал поддерживаться невыровненный доступ. Теперь он выполняет ожидаемое, то есть разбивает доступ на несколько более мелких операций чтения и увеличивает значение, как это сделал бы «традиционный» процессор x86. Однако в этом есть временные задержки. Однако стоит отметить тот факт, что инструкции LDM и STM по-прежнему требуют 4-байтового выравнивания, и если у них его нет, результат будет непредсказуемым. Эта версия также добавила бит A в SCTLR (системный контрольный регистр), где вы можете включить проверку выравнивания. По сути, если этот бит установлен, то каждый невыровненный доступ приведет к тому, что ARM захватит ваш код в ловушку, указанную вектором ловушки, начинающимся с нулевого адреса.
А как насчет ARMv6? ARMv6 был промежуточным ISA в этом смысле, где разработчики набора команд решили, что они будут поддерживать как путь ARMv5, так и путь ARMv7. Теперь в SCTLR появился новый бит U для управления тем, следует ли следовать пути ARMv5 или ARMv6.
Но почему невыровненный доступ такой сложный или плохой? Проблема заключается в нескольких уровнях, как в программном, так и в аппаратном обеспечении. Одним из самых простых является тот факт, что если невыровненный доступ охватывает несколько страниц (то есть областей ОЗУ), могут быть различия в разрешениях. Например, как пользователь я могу иметь доступ к байту, начинающемуся с 0x000FF, но не к байту с 0x00100. Следовательно, нам нужно выполнить несколько проверок разрешений, что приведет к немедленному снижению производительности.
Еще одна проблема заключается в том, что на ядрах, начиная с ARMv6 и более поздних версий, чтобы оборудование «исправило» невыровненный доступ, оно разделяется он разбивается на несколько меньших байтовых загрузок. Однако они не атомарны!. Следовательно, между двумя частями значения может произойти даже прерывание данных. Также интуитивно понятно, что производительность может быть снижена из-за принуждения ядра к замене доступа, который должен был быть единственным, с большим количеством доступов меньшей ширины. (Обратите внимание, что в ядре Linux есть параметр в / proc / cpu / alignment, который позволяет ядру имитировать это поведение в программном обеспечении, ну да ладно с производительностью!)
Итак, почему я пишу о невыровненном доступе? Недавно мы создали waccOS, а до этого 3D-движок игры на голой сборке ARM11. К сожалению, у нас было много проблем с невыровненным доступом, особенно с поведением LDR! В конце концов, подобный пост помог бы нам понять, насколько интересен несогласованный доступ в ARM.
Некоторые дополнительные материалы для чтения для более любопытных:
- Согласование данных ради скорости: миф или реальность?
- Как компилятор ARM поддерживает невыровненный доступ?
Использованная литература: