Основы архитектуры ARM. Стек

/ Просмотров: 941

Статья является вольным переводом этой статьи с добавлением своих наработок.

ARM (Advanced RISC machine) — семейство микропроцессорных ядер разработки компании ARM Limited. Эти процессоры имеют низкое энергопотребление, поэтому находят применение во встраиваемых системах и преобладают на рынке мобильных устройств. В 2007 году около 98% продаваемых мобильных телефонов были оснащены хотя бы одним процессором ARM. Они широко используются в потребительской технике, в том числе в КПК, цифровых плеерах и носителях, игровых консолях, калькуляторах, компьютерных периферийных устройствах, таких как жёсткие диски и маршрутизаторы.

Программисту доступны для использования следующие регистры. Регистры общего назначения: r0-r12 предназначены для хранения данных, с которыми процессор работает в настоящий момент. Специальный регистр r13, он же SP (Stack Pointer) содержит указатель стека выполняемой программы. Следующий специальный регистр — r14, он же LR (Link Register) содержит адрес возврата в инструкциях ветвления. Специальный регистр r15, он же PC (Program Counter) содержит адрес выполняемой программной инструкции. И последний регистр — CPSR (Current Program Status Register) содержит флаги, описывающие текущее состояние процессора. Модифицируется аппаратно при выполнении многих инструкций.

Процессор ARM, как и многие другие, аппаратно поддерживает основную парадигму построения программ — деление программ на куски, которые в С называются функциями, в объектно-ориентированных языках — методами, в ассемблере — подпрограммами. Рассмотрим, как происходит вызов функций на уровне архитектуры набора команд. Каждый процессор имеет регистр Program Counter, который хранит адрес инструкции, которую нужно выполнить в данный момент. При вызове подпрограммы программа должна сохранить информацию о том, куда процессор должен вернуться после завершения подпрограммы. Для этой цели используется регистр LR. Рассмотрим пример использования регистров LR и PC для вызова подпрограммы strcpy. Здесь к регистру PC, который содержит адрес текущей инструкции, прибавляется смещение метки after. Результат этой операции записывается в регистр LR. Таким образом, мы поместили в регистр LR адрес инструкции, на которую нужно будет перейти после вызова подпрограммы.

    ADD LR, PC, #after
    B strcpy
after
    other code
strcpy 
    LDRB R2, [R1], #1
    STRB R2, [R0], #1
    TST R2, R2
    BNE strcpy
    MOV PC, LR

В инструкции B (Branch) происходит переход на метку strcpy и подпрограмма начинает свою работу. Как только подпрограмма закончилась, она копирует регистр LR обратно в PC. Поскольку регистр PC используется для определения выполняемой инструкции, изменение его значения приведёт к исполнению процессором инструкции, следующей за меткой after.

Процесс вызова подпрограмм таким способом выглядит слишком громоздким, поэтому разработчики ARM ввели новую инструкцию, которая заменяет две инструкции из нашего примера. Она называется BL (Branch and Link). Эта инструкция загружает в регистр LR адрес инструкции, следующей за BL, а затем записывает в регистр PC адрес вызываемой подпрограммы.

Во всех программах подпрограммы сами могут вызывать другие подпрограммы. Но из-за этого возникает проблема: как избежать ситуации, когда одной подпрограмме нужны свои значения регистров, а другой нужно изменить значения тех же регистров? Очевидный подход — сохранить значения регистров в память до вызова новой подпрограммы, и восстановить их, как только подпрограмма завершит свою работу. Это реализуется с помощью стека. Когда подпрограмма начинает работу, она размещает блок памяти на вершине стека, а когда завершает — освобождает этот блок, возвращая прежнее состояние.

Стек управляется специальным регистром, который называется указателем стека (SP), и хранит адрес его вершины. Обычно стек располагают на конце доступного диапазона оперативной памяти, и при добавлении нового элемента в стек регистр SP уменьшает своё значение.

Рассмотрим пример. Подпрограмма sumArray использует регистры r4 и r5 для своих внутренних целей, поэтому она сохраняет их текущие значения в стек. В регистрах r0-r3 содержатся параметры подпрограммы, поэтому они не сохраняются. После того, как подпрограмма завершила своё выполнение, она загружает в эти регистры прежние значения из стека.

sumArray 
    STMDB SP!, { R4, R5 }
    MOV R4, #0
sumLoop 
    LDR R5, [R0], #1
    ADD R4, R4, R5
    SUBS R1, R1, #1
    BNE sumLoop
    MOV R0, R4
    LDMIA SP!, {R4, R5}
    MOV PC, LR

Один из самых частых регистров, которые подпрограмма должна сохранять в стеке, является LR, поскольку сама подпрограмма модифицирует этот регистр для вызова других подпрограмм. Для быстрого сохранения и восстановления контекста подпрограммы можно использовать такой трюк: сохранить в стек регистры общего назначения и регистр LR, а по окончании работы подпрограммы содержимое стека записать в те же регистры общего назначения, а то, что раньше было в регистре LR, записать в регистр SP.

subName
    STMDB SP!, {R4-R5,LR}
    LDMIA SP!, {R4-R5,PC}

Чтобы писать большие программы, нужна стандартная система передачи параметров, возврата значений и распределения регистров между подпрограммами. Такая система называется соглашением о вызовах. В процессорах ARM действует соглашение, что передача параметров функции происходит путём помещения их в регистры r0-r3 перед вызовом подпрограммы, а для возврата значения подпрограмма помещает его в регистр r0 перед выходом. В той ситуации, когда подпрограмма принимает более четырёх параметров, оставшиеся параметры помещаются в стек перед входом в подпрограмму.

Каждая подпрограмма имеет право изменять регистры r0-r3 как угодно, но если она использует регистры r4-r12, она должна восстанавливать их предыдущие значения. Она должна также восстанавливать указатель стека, удаляя из него все данные, которые она в него поместила. Таким образом, регистры подразделяются на «сохраняемые вызывателем» и «сохраняемые вызываемым».

Оставьте комментарий!

Комментарий будет опубликован после проверки

Вы можете войти под своим логином или зарегистрироваться на сайте.

(обязательно)