Ассемблер для Raspberry Pi. Практическое руководство [Брюс Смит] (pdf) читать онлайн

-  Ассемблер для Raspberry Pi. Практическое руководство  [4-е издание] (пер. М. А. Райтман) 20.27 Мб, 322с. скачать: (pdf) - (pdf+fbd)  читать: (полностью) - (постранично) - Брюс Смит

Книга в формате pdf! Изображения и текст могут не отображаться!


 [Настройки текста]  [Cбросить фильтры]

Raspberry Pi OS

ASSEMBLY
LANGUAGE
Hands-on-Guide
Fourth Edition

Брюс Смит

АССЕМБЛЕР
ДЛЯ RASPBERRY PI
Практическое руководство
4-е издание

Санкт-Петербург

« БХВ-Петербург»
2022

УДК 004.4
ББК 32.973.26-018.2
С50

Смит Б.
С50

Ассемблер для Raspberry Pi. Практическое руководство: Пер. с англ. —
4-е изд.— СПб.: БХВ-Петербург, 2022. — 320 с.: ил.
ISBN 978-5-9775-6801-2

Рассмотрены основы программирования на языке ассемблера для процессоров
ARM на примере Raspberry Pi с операционной системой Raspberry Pi OS. Приведе­
ны подробные сведения об архитектуре и особенностях ARM, вызовах операцион­
ной системы. Подробно описан синтаксис ассемблера для ARM. Рассмотрены ком­
поновщик GCC, отладка с GDB, использование функций языка С в ассемблере
с помощью библиотеки libc. Описаны функции GPIO, система команд ARM Neon
и команды Thumb. Все разделы снабжены практическими примерами. Книга ори­
ентирована на начинающих разработчиков, желающих освоить программирование
на языке ассемблера для устройств с архитектурой ARM.
Электронный архив на сайте издательства содержит исходный код программ из
книги.

Для начинающих программистов
УДК 004.4
ББК 32.973.26-018.2
Группа подготовки издания:
Руководитель проекта
Зав. редакцией
Перевод с английского
Редактор
Компьютерная верстка
Оформление обложки

Павел Шалим
Людмила Гауль
Михаила Райтмана
Григорий Добин
Ольги Сергиенко
Зои Канторович

Copyright © 2021 by Bruce Smith
Translation Copyright © 2021 by BHV All rights reserved
Перевод © 2021 BHV. Все права защищены.

Подписано в печать 01 12.21
Формат 70x100716 Печать офсетная Усл печ л 35,8
Тираж 1000 экз Заказ № 2949
"БХВ-Петербург", 191036, Санкт-Петербург, Гончарная ул , 20
Отпечатано с готового оригинал-макета
ООО "Принт-М", 142300, М О , г Чехов, ул Полиграфистов, д 1

ISBN 978-0-6480987-3-7 (англ.)
ISBN 978-5-9775-6801-2 (рус.)

© Bruce Smith, 2021
© Перевод на русский язык, оформление
ООО "БХВ-Петербург", ООО "БХВ", 2021

Оглавление

Об авторе......................................................................................................................... 13
1. Введение............................................

14

Безграничные возможности..................................................................................................................15
Начинаем экспериментировать........................................................................................................... 16
Компилятор GNU С............................................................................................................................... 16
Учимся на примерах.............................................................................................................................. 17
Что вы узнаете?....................................................................................................................................... 17
Совместимость четвертого издания книги........................................................................................ 18
ОС Raspberry Pi....................................................................................................................................... 19
А что насчет 64-разрядной системы?................................................................................................. 20
Клавиатурные вычисления................................................................................................................... 20
Значимость ARM................................................................................................................................... 20
Raspberry Pi сквозь века....................................................................................................................... 21
Вычислительные модули..................................................................................................................... 23
Используемые обозначения................................................................................................................. 24
Центр истории вычислительной техники......................................................................................... 24
Веб-сайт и бесплатные книги.............................................................................................................. 25
Благодарности.........................................................................................................................................26

2. Начало......................................................................................................................... 27
Числа со смыслом.................................................................................................................................. 27
Команды ARM........................................................................................................................................28
Процесс преобразования...................................................................................................................... 29
А зачем вообще машинный код?........................................................................................................ 30
Языковые уровни................................................................................................................................... 30
На орбиту!............................................................................................................................................... 31
RISC и наборы команд..........................................................................................................................32
Структура ассемблера...........................................................................................................................32
Ошибки на пути..................................................................................................................................... 33
Кросс-компиляторы............................................................................................................................... 33
Чипы Raspberry Pi ARM....................................................................................................................... 33

3. Проба пера.................................................................................................................. 35
Командная строкаЗ. Проба пера..........................................................................................................35
Создание исходного файла.................................................................................................................. 36
Написанное — исполнить!................................................................................................................... 40
Ошибки ассемблера............................................................................................................................... 40
Компоненты............................................................................................................................................41
А если нет метки start?....................................................................................................................... 44
Связывание файлов................................................................................................................................ 44
Прибираемся........................................................................................................................................... 46

Пара слов о комментариях.................................................................................................................. 47
Редактор Geany Programmer’s Editor................................................................................................. 48

4. О битах в RISC-машинах...................................................

49

Преобразование двоичных чисел в десятичные.............................................................................. 50
Преобразование десятичных чисел в двоичные.............................................................................. 51
Преобразование двоичного числа в шестнадцатеричное.............................................................. 51
Преобразуем шестнадцатеричные числа в десятичные и обратно.............................................. 53
Двоичное сложение...............................................................................................................................53
Вычитание...............................................................................................................................................54
Дополнительный код............................................................................................................................ 56
Когда двоичные числа не складываются.......................................................................................... 57
Стандартный калькулятор.................................................................................................................... 58

5. Соглашения ARM...................................................................................................... 59
Длина слов............................................................................................................................................... 59
Доступ к памяти по байтам и словам.................................................................................................60
Регистры.................................................................................................................................................. 61
Регистр R15: программный счетчик................................................................................................... 63
Регистр состояния текущей программы........................................................................................... 63
Биты и флаги.......................................................................................................................................... 64
Установка флагов.................................................................................................................................. 64
Суффикс 5............................................................................................................................................... 65
R14: регистр ссылок..............................................................................................................................66
R13: указатель стека............................................................................................................................. 66

6. Обработка данных..................................................................................................... 67
Команды сложения................................................................................................................................68
Вычитание...............................................................................................................................................71
Умножение.............................................................................................................................................. 72
Теперь о делении................................................................................................................................... 74
Команды перемещения......................................................................................................................... 75
Команды сравнения.......................................................................................................................... ... 76
Сортировка чисел.................................................................................................................................. 77

7. Входы и выходы........................................................................................................ 78
Команды SWI и SKC....................................................................................................................... 78
Вывод на экран...................................................................................................................................... 79
Чтение с клавиатуры..............................................................................................................................81
Регистр eax и прочие.............................................................................................................................83
Программа Маке.................................................................................................................................... 83

8. Логические операции............................................................................................... 86
Логическое И (AND)...................................................................................................................... 86
Логическое ИЛИ (OR)................................................................................................................... 87
Исключающее ИЛИ (EOR)........................................................................................................... 87
Команды логических операций.......................................................................................................... 88
Команда ORR для преобразования регистра символов..................................................................89
Очистка бита командой BIC......................................................................................................... 90
Проверка флагов.................................................................................................................................... 91
Регистры системных вызовов............................................................................................................. 94

9. Условное выполнение............................................................................................. 95
Коды состояния с одним флагом....................................................................................................... 97
EQ: равно......................................................................................................................................... 97
NE: не равно................................................................................................................................... 97
VS: переполнение........................................................................................................................... 98
ИС: нет переполнения................................................................................................................... 98
MI: знак «минус»............................................................................................................................ 98
PL: знак «плюс».............................................................................................................................. 98
CS: имеется перенос (HS: беззнаковое больше или равно)................................................... 99
СС: нет переноса (LO: беззнаковое меньше)............................................................................ 99
A L: безусловное исполнение...................................................................................................... 100
УК: безусловное неисполнение..................................................................................................100
Коды, проверяющие несколько флагов........................................................................................... 100
HI: беззнаковое больше.............................................................................................................. 100
LS: беззнаковое меньше или равно.......................................................................................... 101
GE: знаковое больше или равно............................................................................................... 101
LT: знаковое меньше.................................................................................................................... 101
GT: знаковое больше................................................................................................................... 101
LE: знаковое меньше или равно................................................................................................ 102
Добавление суффикса S'....................................................................................................................... 102

10. Ветви и сравнения................................................................................................. 103
Команды ветвления............................................................................................................................. 103
Регистр ссылок...................................................................................................................................... 104
Использование команд сравнения..................................................................................................... 104
Применяем дальновидное мышление.............................................................................................. 105
Эффективное использование условных операторов...................................................................... 106
Обмен ветвей......................................................................................................................................... 107

11. Сдвиги и вращения............................................................................................... 108
Логические сдвиги................................................................................................................................108
Логический сдвиг вправо.................................................................................................................... 110
Арифметический сдвиг вправо.......................................................................................................... 110
Вращение............................................................................................................................................... 111
Расширенное вращение....................................................................................................................... 112
Использование сдвигов и вращений.................................................................................................112
Прямой постоянный диапазон........................................................................................................... 113
Движение вверх.................................................................................................................................... 115

12. Умные числа.......................................................................................................... 116
Длинное умножение............................................................................................................................ 116
Умножение с накоплением................................................................................................................ 118
Деление и остаток................................................................................................................................ 119
Умное умножение................................................................................................................................ 120
Это только начало.................................................................................................................................121

13. Программный счетчик R15................................................................................. 122
Конвейерная обработка....................................................................................................................... 123
Расчет ветвей......................................................................................................................................... 124

14. Отладка с использованием GDB........................................................................ 126
Когда все зависло................................................................................................................................. 126
Сборка с GDB...................................................................................................................................... 127
Дизассемблер....................................................................................................................................... 130
Точки останова.................................................................................................................................... 132
Дамп памяти.......................................................................................................................................... 136
Сокращения........................................................................................................................................... 137
Параметры сборки GDB..................................................................................................................... 137

15. Передача данных................................................................................................... 139
Директива ADR............................................................................................................................ 139
Косвенная адресация...........................................................................................................................141
Команды ADR и LDR................................................................................................................... 143
Предварительно индексированная адресация................................................................................ 143
Доступ к байтам памяти..................................................................................................................... 144
Обратная запись адреса...................................................................................................................... 146
Постиндексированная адресация..................................................................................................... 146
Байтовые условия................................................................................................................................ 148
Относительная адресация через регистр PC............................................................................. 148

16. Передача блока...................................................................................................... 150
Обратная запись................................................................................................................................... 152
Процедура копирования блока..........................................................................................................153

17. Стеки........................................

155

Тянитолкай;-)...................................................................................................................................... 155
Рост стека.............................................................................................................................................. 157
Применение стеков.............................................................................................................................. 159
Работа в фрейме................................................................................................................................... 160
Указатель фрейма................................................................................................................................ 160

18. Директивы и макросы.......................................................................................... 162
Директивы хранения данных............................................................................................................. 162
Выравнивание данных.........................................................................................................................164
Макросы................................................................................................................................................ 165
Включение макросов........................................................................................................................... 168

19. Работа с файлами.................................................................................................. 171
Права доступа к файлам..................................................................................................................... 176

20. Использование библиотеки libc................................................................................. 179
Использование функций языка С в ассемблере............................................................................. 179
Структура файла исходного кода..................................................................................................... 180
Исследование исполняемого файла................................................................................................. 182
Ввод чисел с помощью функции scanf...................................................................................... 184
Вывод информации............................................................................................................................. 186

21. Пишем функции.................................................................................................... 188
Стандарты функций............................................................................................................................. 188
Использование регистров.................................................................................................................. 190

Больше трех........................................................................................................................................... 190
Сохранение ссылок и флагов............................................................................................................. 192
Надежные процедуры вывода............................................................................................................ 193
Пузырьковая сортировка..................................................................................................................... 195

22. Дизассемблирование программ на С................................................................. 198
GCC — он как швейцарский нож...................................................................................................... 198
Простой фреймворк С......................................................................................................................... 199
Создание файла ассемблера............................................................................................................... 200
Строительные блоки............................................................................................................................ 202
Пример функции print/................................................................................................................ 204
Переменные указателя фрейма..........................................................................................................205
Дизассемблирование системных вызовов...................................................................................... 206

23. Функции GPIO....................................................................................................... 207
Отображение памяти........................................................................................................................... 207
Контроллер GPIO................................................................................................................................. 209
Вводы и выводы GPIO....................................................................................................................... 211
Сборка кода...........................................................................................................................................217
Другие функции GPIO.........................................................................................................................222
Описание контактов GPIO................................................................................................................. 222

24. Числа с плавающей точкой................................................................................. 225
Архитектура VFP................................................................................................................................. 225
Регистровый файл................................................................................................................................ 227
Управление и вывод на экран............................................................................................................229
Сборка и отладка на VFP с помощью GDB.................................................................................... 231
Загрузка, хранение и перемещение.................................................................................................. 233
Преобразование точности.................................................................................................................. 235
Векторная арифметика....................................................................................................................... 236

25. Регистр управления VFP..................................................................................... 237
Условное исполнение..........................................................................................................................238
Скалярные и векторные операции.................................................................................................... 240
Какой тип оператора?......................................................................................................................... 242
Параметры LEN и STRIDE.......................................................................................................... 243

26. Сопроцессор Neon.................................................................................................. 247
Ассемблер Neon................................................................................................................................... 249
Команды и типы данных Neon......................................................................................................... 251
Режимы адресации............................................................................................................................... 253
Параметр Stride команд VLD и VST............................................................................................ 253
Загрузка в прочих форматах.............................................................................................................. 256
Neon Intrinsic........................................................................................................................................ 256
Массивы Neon...................................................................................................................................... 257
Правильный порядок.......................................................................................................................... 261
Матричная математика....................................................................................................................... 262
Матричное умножение....................................................................................................................... 265
Пример использования макроса....................................................................................................... 270

27. Код Thumb...............................

272

Различия............................................................................................................................................... 272
Пишем на Thumb................................................................................................................................ 274
Доступ к старшим регистрам............................................................................................................ 278
Операторы стека..................................................................................................................................279
Одно- и многорегистровые команды.............................................................................................. 279
Функции в Thumb................................................................................................................................279
Команды ARMv7 Thumb.................................................................................................................... 280

28. Единый язык.......................................................................................................... 281
Изменения Thumb................................................................................................................................282
Новые команды А32........................................................................................................................... 283
Сравнение по нулю............................................................................................................................. 284
Сборка UAL.......................................................................................................................................... 284

29. Обработка исключений........................................................................................ 287
Режимы работы.................................................................................................................................... 287
Векторы................................................................................................................................................. 288
Настройка регистров.......................................................................................................................... 290
Обработка исключений...................................................................................................................... 292
Команды MRS и MSR................................................................................................................... 293
Что происходит при возникновении прерывания?....................................................................... 294
Решения о прерываниях..................................................................................................................... 295
Возврат из прерываний...................................................................................................................... 295
Пишем процедуры прерывания........................................................................................................ 296

30. System-on-Chip....................................................................................................... 297
Микросхема и набор команд ARM.................................................................................................. 298
Сопроцессоры...................................................................................................................................... 299
Конвейер................................................................................................................................................ 299
Память и кэши...................................................................................................................................... 300
GPU........................................................................................................................................................ 301
Обзор ARMv8....................................................................................................................................... 301
64-разрядная ОС Raspberry Pi........................................................................................................... 302
А что в итоге?....................................................................................................................................... 302
Принцип Архимеда..............................................................................................................................302

Приложение 1. Коды символов ASCII.................................................................... 304

Приложение 2. Набор команд ARM......................................................................... 306
Команды сравнения и проверки....................................................................................................... 307
Команды ветвления............................................................................................................................ 307
Арифметические команды................................................................................................................. 308
Логические команды.......................................................................................................................... 308
Команды перемещения данных........................................................................................................ 309

Приложение 3. Системные вызовы ROS................................................................ 310
Приложение 4. Описание электронного архива....................................................316
Предметный указатель.............................................................................................. 317

СПИСОК ПРОГРАММ
Программа 3.1. Простой исходный файл............................................................................................ 39
Программа 3.2. Часть I исходного файла........................................................................................... 44
Программа 3.3. Часть 2 исходного файла........................................................................................... 45
Программа 6.1. Простое 32-битное сложение.................................................................................... 69
Программа 6.2. 64-битное сложение................................................................................................... 70
Программа 6.3. 32-битное умножение................................................................................................. 73
Программа 6.4. Использование умножения с накоплением mla....................................................... 73
Программа 6.5. Деление командой sdiv.............................................................................................. 74
Программа 7.1. Системный вызов 4 для вывода строки на экран.................................................... 80
Программа 7.2. Системный вызов 3 и чтение с клавиатуры............................................................ 81
Программа 7.3. Автоматизация сборки и привязки с помощью Маке............................................ 84
Программа 8.1. Преобразование регистра символов.........................................................................89
Программа 8.2. Вывод двоичного числа............................................................................................. 91
Программа 10.1. Условное выполнение позволяет сократить программу.................................... 107
Программа 12.1. Долгое и нудное умножение.................................................................................. 117
Программа 12.2. Долгое нудное деление.......................................................................................... 119
Программа 14.1. Гибкий make-файл для отладки кода.................................................................... 138
Программа 15.1. Использование директивы adr.............................................................................. 140
Программа 15.2. Использование предварительно индексированной косвенной адресации......... 145
Программа 15.3. Использование постиндексированной адресации.............................................. 147
Программа 16.1. Перемещение блоков памяти................................................................................ 153
Программа 18.1. Использование директив .byte и .equ.................................................................. 163
Программа 18.2. Реализация простого макроса................................................................................ 166
Программа 18.3. Многократный вызов макроса............................................................................... 167
Программа 18.4. Файл макроса AddMuit............................................................................................ 168
Программа 18.5. Проверка подключения макроса........................................................................... 169
Программа 19.1. Создание файлов и досгуп к ним.......................................................................... 172
Программа 20.1. Структура исходного файла GCC......................................................................... 181
Программа 20.2. Передача параметров в printf.............................................................................. 183
Программа 20.3. Чтение и преобразование числа с помощью функции scant............................. 185
Программа 20.4. Объединение scant и printf.................................................................................. 187
Программа 21.1. Передача значений функции через стек............................................................... 191
Программа 21.2. Вывод вектора слов.....................
193
Программа 21.3. Проверка printw..................................................................................................... 194
Программа 21.4. Процедура пузырьковой сортировки.................................................................... 195
Программа 21.5. Тест пузырьковой сортировки........................
196
Программа 22.1. Простая программа на языке С для вывода символа.......................................... 200
Программа 22.2. Окончательный вариант кода................................................................................203
Программа 22.3. Код программы hello world на С........................................................................ 204
Программа 22.4. Код для работы с функцией scant........................................................................ 205
Программа 23.1. Файловый доступ к памяти GPIO..........................................................................213
Программа 24.1. Вывод значения с плавающей точкой с помощью функции printf................. 229
Программа 24.2. Вывод двух или более значений с плавающей точкой....................................... 230
Программа 25.1. Условное выполнение в VFP.................................................................................240
Программа 25.2. Использование битов len и stride для сложения векторов..............................245
Программа 26.1. Простая проверка Neon.......................................................................................... 249
Программа 26.2. Поворот 20-матрицы на 90 градусов (по часовой стрелке).............................. 258
Программа 26.3. Сложение двух матриц размером 4x4................................................................. 263
Программа 26.4. Матричное умножение чисел с одинарной точностью...................................... 266
Программа 26.5. Умножение матриц, но с макросами................................................................... 270
Программа 27.1. Вызов режима Thumb и запуск кода Thumb........................................................ 275
Программа 27.2. Использование внешних функций путем взаимодействия кода........................277
Программа 28.1. Единый язык ассемблера.......................................................................................285

Посвящается всем медицинским работникам:
медсестрам, врачам и другому персоналу,
осуществляющим уход за пациентами во всем мире.

Тем, кто выложился по полной в эти годы пандемии.
Память о вас останется в наших сердцах навсегда.
Спасибо!

Об авторе

Брюс Смит приобрел свой первый компьютер Acorn Atom
в 1980 году. Увлечение компьютерами мгновенно затянуло
его, и он стал постоянным автором крупных тематических
изданий, включая Computing Today и Personal Computer
World. С появлением компьютера ВВС Micro его работа пе­
решла от журналов к книгам, и его книга 1982 года «Interfac­
ing Projects for ВВС Micro» стала классикой своего времени.
В ней он рассказал простым пользователям домашних ком­
пьютеров, как подключиться к внешнему миру. Он был од­
ним из первых, кто писал о чипе ARM, когда они были пред­
ставлены на Acorn Archimedes в 1987 году.

Брюс рассказывал обо всех аспектах использования компьютера. Его дружелюбный
и понятный стиль письма вдохновил одного рецензента на вот такой отзыв: «Это
первая книга о компьютерах, которую я читал для удовольствия, лежа в постели,
а не ради того, чтобы поскорее заснуть!» Книги Брюса переведены на многие языки
и продаются по всему миру. Его издавали в ВВС Books, Collins, Virgin Books и
Rough Guides.
Брюс родился в лондонском Ист-Энде, а сейчас живет в Сиднее, Новый Южный
Уэльс.
Его сайт в Интернете: www.brucesmith.info.

Из ОТЗЫВОВ С ПЯТЬЮ ЗВЕЗДАМИ НА AMAZON
НА ПРЕДЫДУЩИЕ ИЗДАНИЯ этой книги:
«Отличная книга. Настоятельно рекомендую ее всем, кто хочет работать с ARM-ас­
семблером».
«Эта книга подойдет не только начинающим, но будет также полезна программистам
среднего уровня, и в ней затронуты некоторые более сложные темы. На мой взгляд,
эта книга заслуживает оценки 10/10 и обязательна к прочтению всем, кто планирует
начать или возвращается к программированию на ассемблере на процессоре ARM v6
(Raspberry Pi). Книга сэкономила мне много времени, поскольку вся необходимая ин­
формация теперь собрана в одном месте. Спасибо Брюсу за такое сокровище!»
«Это отличная книга для начинающих программистов, которые хотят глубже изучить
Raspberry Pi и по-настоящему понять, как работает компьютер. Приведены отличные
и ООподробные примеры».

«Смит не только излагает все нужные материалы и инструкции на языке ассемблера
для процессора ARM Raspberry Pi, но также дает четкие практические указания о том,
как заставить все это работать... Не важно, хотите ли вы просто побаловаться с Rasp­
berry Pi, или рассматриваете Pi как ступеньку к чему-то большему— книга Брюса
Смита определенно должна стоять на вашей полке».

1. Введение

Я ничуть не удивился, когда в начале 2013 года появилась новость о том, что про­
дажи контроллеров Raspberry Pi (рис. 1.1) достигли 1 млн штук. А само это число
заставило меня вернуться на несколько лет (даже десятилетий) назад и вспомнить
аналогичную новость о платах ВВС Micro. Обе платформы производятся на пред­
приятии, расположенном в Кембридже в Великобритании, и общего у них гораздо
больше, чем вы можете себе представить.

Рис. 1.1. Плата Raspberry Pi Model В

Обе системы покорили мое сердце и сердца миллионов других людей. На первую я
в свое время спускал все свои сбережения, а вот Raspberry Pi совершенно не удари­
ла по моему кошельку. Но огромная разница в цене была почти единственным раз­
личием, а прочие черты оказались весьма схожи. Одна из самых важных черт —

1 Введение

15

возможность запускать на них широкий спектр программного обеспечения и обра­
зовательных инструментов, а также легкость в подключении внешних устройств
и управлении внешним миром. Я подозреваю, что у большинства владельцев Rasp­
berry Pi лежит не один, а как минимум парочка контроллеров.

Впервые после ВВС Micro на рынке появилась дешевая и невероятно богатая воз­
можностями система, которую может использовать практически каждый. В то же
время на рынке доминировали платформы PC и Мас и появились другие устройст­
ва, более ориентированные на игры. Но ничего из этого не сказалось на тех, чьим
домашним хобби была работа с техникой. У профессионалов имелось множество
платформ для разработки, с которыми можно было экспериментировать, но у до­
машних любителей компьютеров оставались проблемы с доступностью таких
платформ. А вот Raspberry Pi со своей ценой, соизмеримой с парочкой DVD, изме­
нила все.
Запуск Raspberry Pi Zero в ноябре 2015 года вывел экономические преимущества
платформы на новый уровень. Полноценный компьютер на базе ARM можно полу­
чить всего за 5 долларов. Это ж с ума сойти! И, чтобы окончательно добить фана­
тов, к каждому экземпляру MagPi (официального журнала Raspberry Pi) за декабрь
2015 года прилагался контроллер в подарок. Нельзя ли теперь считать Raspberry Pi
первым в мире полноценным компактным компьютером?
На момент подготовки этой книги (декабрь 2020 г.) продажи плат Rapsberry превы­
сили 30 млн штук. Эта отметка, как сообщается, была достигнута к началу 2020 года,
когда многие компании начали использовать Rapsberry Pi в своей инфраструктуре и
разработках. Компания Oracle назвала кластер из 1060 плат Raspberry Pi самым
большим в мире суперкомпьютером Pi с 4240 ядрами. Jet Propulsion Laboratory
(JPL) используют эти платы в своей марсианской миссии.

Безграничные возможности
Одна из причин появления Raspberry Pi заключалась в том, что у людей имелась
необходимость в дешевой системе программирования, пределы возможностей ко­
торой ограничивались бы только воображением пользователя. Я надеюсь, что эта
книга поможет многим из читателей осуществить свою мечту. А это вполне реаль­
но. Весьма вероятно, что чтение этих страниц станет вашим первым шагом к по­
лезному и познавательному времяпрепровождению. И, кто знает, может быть, вы
станете частью нового поколения компьютерных программистов, творящих на пре­
деле возможного.
Цель этой книги — дать читателю лучшее понимание того, как устроена работа
компьютера на уровне ниже высокоуровневых языков программирования, таких
как С или Python. Глубже разобравшись с работой компьютера, вы сможете гораздо
более продуктивно писать программное обеспечение на языках более высокого
уровня. Именно обучение программированию на ассемблере поможет вам достичь
этой цели.

Эта книга представляет собой начальное руководство по написанию кода на
ассемблере для Raspberry Pi и, в частности, с использованием ОС Raspberry Pi

16

1. Введение

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

Сам я сначала учился программировать на ассемблере на машинах, созданных еще
на ранних этапах разработки Acorn, а позднее стал свидетелем развития чипов
ARM в системах Archimedes и ВВС Micro Second-Processor. В конечном итоге
именно тот ассемблер стал предшественником того, что сегодня используется
в Raspberry Pi. Этим я хочу лишь подчеркнуть, что все, чему вы здесь научитесь,
будет полезно и актуально в течение долгих лет. Однозначно, стоящая инвестиция.

Начинаем экспериментировать
Эта книга адресована не совсем уж полным новичкам, однако вам не потребуется
иметь опыт работы с языком ассемблера или машинным кодом, чтобы понять ее и
начать экспериментировать. Тем не менее вам пригодится любой опыт в програм­
мировании, и любой известный вам структурированный язык поможет понять базо­
вые концепции ассемблера.
Поскольку это в первую очередь практическое руководство, мы с вами напишем
множество программ. Чтобы научиться программировать, нужно экспериментиро­
вать, совершать ошибки, а затем учиться на них. Вне всяческих сомнений, лучший
способ понять происходящее— экспериментировать со значениями и данными,
и такое экспериментирование следует поощрять. Все изложенные в книге програм­
мы можно будет скачать с сайта книги. Книга одинаково применима ко всем верси­
ям Raspberry Pi. Но подробнее об этом чуть позже.

Компилятор GNU С
Существует несколько операционных систем (сред программирования), которые вы
можете скачать для работы с Raspberry Pi. Как следует из ее названия, в этой книге
мы будем работать с ОС Raspberry Pi (ранее называвшейся Raspbian). В ней есть
все, что вам потребуется для написания и запуска ваших программ.

Также для работы нам потребуется GCC — коллекция компиляторов GNU. Перво­
начальным автором компилятора GNU С (GCC) был Ричард Столлман, основатель
проекта GNU. Проект GNU был запущен в 1984 году с целью создания полноцен­
ной свободно доступной операционной системы, которая дала бы свободу сотруд­
ничеству между пользователями компьютеров и программистами. Любой операци­
онной системе нужен компилятор С, и, поскольку в те времена свободно доступных
компиляторов не существовало, проекту GNU пришлось писать свои с нуля.
Работа финансировалась за счет пожертвований физических лиц и компаний
в Фонд свободного программного обеспечения— некоммерческую организацию,
созданную для поддержки проекта GNU. Первая версия GCC вышла в 1987 году.
С тех пор GCC стал одним из важнейших инструментов в разработке бесплатного
программного обеспечения и работает практически на всех существующих ОС.

GCC — бесплатное программное обеспечение, распространяемое под Стандартной
публичной лицензией GNU (GNU General Public Licensee). Это означает, что вы

1. Введение

17

можете использовать и изменять GCC, как и все программное обеспечение GNU.
Если вам нужна поддержка нового ЦП, нового языка или новой функции, вы може­
те добавить ее самостоятельно или попросить кого-нибудь сделать это за вас.
Вы, наверное, знаете, что С — чрезвычайно популярный язык программирования, а
еще он очень тесно связан с микропроцессором Advanced RISC Machine (ARM),
который используется в ядре Raspberry Pi. Но вам не обязательно знать С, чтобы
писать программы на ассемблере, поэтому этим не озабочивайтесь.

GCC — очень умная программа, и использовать ее можно по-разному. Одним из ее
ключевых компонентов является ассемблер, который нас, собственно говоря, и ин­
тересует в первую очередь.

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

Иногда возникают вопросы вроде «курицы и яйца», но я старался свести их к ми­
нимуму. Мы будем вводить новые понятия в порядке, соответствующем получен­
ным знаниям. Однако это не всегда возможно, и на этих моментах я буду останав­
ливаться отдельно. В таких случаях вам придется просто поверить, что это работа­
ет, и однажды станет ясно, почему.
Программирование — это и впрямь весело. Я написал множество книг на эту тему,
и значительная часть из них была посвящена домашним компьютерам и тому, как
их программировать и использовать. Меня никто не учил работе с компьютерами.
А если я могу сделать что-то, значит, сможет и любой другой человек. Но в этом
есть и доля огорчения! Нет на свете ни одного программиста, будь то новичок или
эксперт, который не тратил бы кучу времени на решение некоторой задачи про­
граммирования с тем, чтобы позже понять, что решение было прямо у него под
носом. Настоящее удовлетворение приходит тогда, когда вы сами решаете задачу.

Дам совет: если вы не можете решить проблему, отвлекитесь и займитесь чемнибудь еще. Очень часто решение приходит к вам именно тогда, когда вы занимае­
тесь чем-то другим. Это работает не только в программировании, но и в жизни!

Что вы узнаете?
Если говорить коротко, вы станете опытным программистом на ассемблере. К кон­
цу этой книги, если вы проработаете и примените на практике приведенные в ней
примеры программ, вы сможете разрабатывать, писать и создавать программы
с машинным кодом для выполнения огромного множества задач. Вы также получи-

1 Введение

18

те основу, позволяющую углубиться в другие темы, касающиеся микросхем ARM
и системного программирования.
Вы также познакомитесь с различными способами использования компилятора
GCC, включая написание кода для операционной системы Raspberry Pi и объедине­
ние собранных программ с библиотеками автономных функций.
Вы узнаете, как интерпретировать и управлять возможностями платы Raspberry Pi
на фундаментальном уровне. Вы будете писать инструкции непосредственно для
чипа ARM.

Вам также необходимо научиться решать проблемы. Когда программа с машинным
кодом начинает работать неправильно, причиной этого часто является простая ло­
гическая ошибка. У GCC есть встроенные инструменты отладки,и мы увидим, как
их эффективно использовать. Я также дам несколько полезных советов о том, как
сузить область поиска и в конечном итоге найти источник проблемы.

Совместимость четвертого издания книги
В принципе, эта книга совместима со всей линейкой компьютеров Raspberry Pi.
Один из основных принципов разработчиков аппаратного и программного обеспе­
чения этой системы — обеспечить ее обратную совместимость (насколько это воз­
можно). По сути, это означает, что программа, которая работает на одной модели
Raspberry Pi, будет работать и на другой, если она написана правильно (с точки
зрения программного обеспечения). Стороннее оборудование тоже должно рабо­
тать, за исключением имеющего некоторые аппаратные изменения. Например, порт
HDMI работать будет, но подключаться к нему придется через другой кабель
(к примеру, не стандартный, а с микроразъемом).

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

Рис. 1.2. Raspberry Pi 4 Model В

1 Введение

19

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

К сегодняшнему моменгу плата Raspberry Pi сменила несколько различных версий
чипа ARM, и это может продолжаться и в будущем. В настоящее время просто
имейте в виду, что различия могут быть, но они станут видны вам по мере роста
ваших знаний. Поскольку книга эта, по сути, адресована новичкам, имеющиеся
различия не повлияют на основные понятия программирования. Эта книга — вве­
дение в язык ассемблера ARM, независимо от модели Raspberry Pi, которую вы ис­
пользуете.

ОС Raspberry Pi
Когда появилась новость о выпуске Raspberry Pi 4 с объемом памяти 8 Гбайт (май
2020 г.), компания Raspberry Pi Foundation сообщила, что меняет название своей
официальной операционной системы с Raspbian на Raspberry Pi OS. Целью такого
изменения явилось создание аналогичной и стабильной операционной системы и
среды в стиле Windows для всех платформ, а также по максимуму, насколько воз­
можно, обеспечение постоянной обратной совместимости с программным обеспе­
чением и приложениями.
Имеются две основные категории процессоров: 32-разрядные и 64-разрядные.
Платформа поддерживает как 32-разрядные, так и 64-разрядные операционные сис­
темы, и внешний вид ОС не меняется, т. к. сборка этих версий происходит отдель­
но. Попросту говоря, 64-разрядный процессор более эффективен, чем 32-разрядный, т. к. он может обрабатывать больше данных одновременно. 64-битный про­
цессор способен хранить больше значений, включая адреса памяти, и имеет
в распоряжении в 4 млрд раз больше адресов, чем физическая память 32-разрядного
процессора. Это очень круто!

С точки зрения пользователя, использующего стандартную 32-разрядную операци­
онную систему, функциональной разницы в том, что описано в этой книге, не
будет. Читайте Raspberry Pi OS вместо Raspbian и наоборот. Что еще более важно,
ОС остается обратно совместимой, так что ОС Raspberry Pi будет работать на всех
версиях Raspberry Pi. Вы можете обновить текущую операционную систему до по­
следней версии Raspberry Pi OS в любой момент.

Проект Raspbian не сворачивается и, несомненно, ляжет в основу при создании
32-разрядной версии ОС Raspberry Pi. Учитывая, что несколько моделей Raspberry
Pi, включая очень популярную Raspberry Pi Zero, никогда не будут работать
с 64-разрядной ОС, 32-разрядная платформа еще несколько лет останется востребо­
ванной, если не доминирующей.

1. Введение

20

А что насчет 64-разрядной системы?
Raspberry Pi 2В (vl.2) была первой платой с 64-разрядным процессором ARM, но
для него не было выпущено официальной 64-разрядной ОС. Дело в том, что
Raspberry Pi Foundation сосредоточилась в основном на том, чтобы 32-разрядная
ОС Raspbian работала на всех поколениях RPi. Начиная с упомянутой Raspberry Pi
2В (vl.2), все версии RPi работают на 64-разрядных процессорах, которые также
могут работать в 32-разрядном режиме.
32-разрядный режим микросхемы ARM называется AArch32 (или А32), а 64-разрядный — AArch64 (или А64). Это означает, что 32-разрядное программное обес­
печение может работать на 64-разрядном чипе ARM, а вот наоборот — нет.

На момент подготовки книги уже вышла бета-версия 64-разрядной ОС Raspberry Pi,
и вы можете скачать и попробовать ее. После отработки бета-версии она будет вы­
пущена для всех 64-разрядных плат (см. главу 30). Обратите внимание, что 32-разрядная версия (А32) языка ассемблера ARM отличается от 64-разрядной версии
(А64), поэтому код, написанный на А32, не будет просто так работать на А64 без
определенных изменений.

Клавиатурные вычисления
В ноябре 2020 года произошло неизбежное: наконец-то появились настольные ком­
пьютеры на Raspberry Pi, в которых плата была модернизирована и упакована
в красно-белую клавиатуру для запуска Raspberry Pi 400 (рис. 1.3).

Рис. 1.3. Raspberry Pi 400 — идеальный компьютер для клавиатурных вычислений

Значимость ARM
Все микропроцессоры основаны на определенной архитектуре набора команд
(ISA), и наиболее значимой из них в последние годы была архитектура х86, которая
доминировала на рынке настольных компьютеров и ноутбуков (ПК и Apple). В по­
следнее время также используется 64-разрядная версия архитектуры под названием
х86-64, или AMD64.

Однако большинство планшетов и смартфонов Apple и Android работают на про­
цессорах ARM, несовместимых с архитектурой х86. Чип ARM на этих устройствах

1. Введение

21

используется из-за его низкого энергопотребления и, как следствие, увеличения
времени автономной работы. Но эта несовместимость означает, что программное
обеспечение, скомпилированное для настольных компьютеров и ноутбуков, нельзя
напрямую запустить на мобильных устройствах под управлением ARM.
Вычислительные процессоры с сокращенным набором команд (или процессоры
RISC), применяемые в устройствах ARM, используют для выполнения задачи мно­
жество простых инструкций. Процессоры х86 работают на CISC (сложном наборе
команд) и для выполнения аналогичной задачи задействуют большее число слож­
ных инструкций.

За счет этого в чипах ARM и достигается более низкое энергопотребление, по­
скольку в их структуре ядра меньше транзисторов, чем у чипов на основе CISC.
Это также означает, что, с точки зрения программиста, вам, как программисту
ARM, придется учить меньше инструкций!

Другое очень важное отличие состоит в том, что компания ARM Holdings не явля­
ется производителем микросхем. Она разрабатывает микросхемы, а затем продает
лицензии на их производство, чтобы другие производители могли включить их
в свои собственные разработки.
В середине 2020 года компания Apple Computers объявила, что полностью перейдет
на собственные чипы ARM, которые будут устанавливаться во все новые компью­
теры, что обеспечит работоспособность ее приложений на всех выпускаемых ею
устройствах. Microsoft Office 365, Adobe Creative Cloud и прочие программы, веро­
ятно, тоже станут запускаться на новых компьютерах Mac ARM. Не требуется
большого воображения, чтобы представить, как это будет работать на любой ма­
шине на базе ARM.

Ноутбуки и настольные компьютеры на базе х86 еще какое-то время продолжат
доминировать на рынке, по крайней мере с точки зрения количества выпускаемых
компьютеров, но машины Apple Мас на базе ARM должны вывести на основной
рынок ноутбуки/настольные компьютеры на базе ARM и могут соблазнить этим
других игроков.

А это — еще один повод изучать программирование на ассемблере ARM.

Raspberry Pi сквозь века
Платформа Raspberry Pi развивается с момента первого выпуска в 2012 году. В ка­
ждом новом поколении часто внедряются значительные улучшения типа централь­
ного процессора, емкости памяти, поддержки сети и периферийных устройств.

Некоторые из основных спецификаций для каждой из моделей Raspberry Pi приве­
дены в табл. 1.1. Как правило, Raspberry Pi бывают трех разных моделей:
♦ Model В— это «полноразмерные» платы с портами Ethernet и USB. В версиях
В+ часто добавляются улучшения или особые различия;
♦ Model А— это платы квадратной формы. Их можно считать «более легкой»
версией Raspberry Pi, обычно с более низкими характеристиками, чем у флаг-

1 Введение

22

майской модели В, с меньшим количеством портов USB и часто без Ethernet.
Поэтому они дешевле;
♦ Zero— самый маленький доступный вариант Raspberry Pi. У этих плат меньше
вычислительной мощности, чем у модели В, но они также потребляют меньше
энергии. Ни USB, ни Ethernet - все простенько и со вкусом!

Объем памяти, доступный на каждой Raspberry Pi, для этой книги значения не име­
ет, но имейте в виду, что адреса в памяти, которые будут отображаться в вашей
программе, могут отличаться от приведенных в книге примеров. Однако это не
должно мешать работе программы.
Таблица 1.1. Модели Raspberry Pi и их основные характеристики
Модель Дата
RPI
выпуска

SOC

цп

FPU

GPU

ARM IS

400

Ноябрь
2020 г.

ВСМ 2711 СО 4 х Cortex-A72
с частотой 1,8 ГГц

VFPv4 + Broadcom
NEON
VideoCore VI

ARMv8-A
(64/32-бит)



Июнь 2019 г.;
май 2020 г.
(8 Гбайт)

ВСМ2711В0

4 х Cortex-A72
с частотой 1,5 ГГц

VFPv4 + Broadcom
NEON
VideoCore VI

ARMv8-A
(64/32-бит)

ЗВ +

Май 2018 г.

ВСМ2837В0

4 х Cortex-A53
с частотой 1,4 ГГц

VFPv4 + Broadcom
NEON
VideoCore IV

ARMv8-A
(64/32-бит)

ЗВ

Февраль
2016 г.

ВС2837

4 х Cortex-A53
с частотой 1,2 ГГц

VFPv4 + Broadcom
NEON
Videocore IV

ARMv8-A
(64/32-бит)

ЗА +

Ноябрь
2018 г.

ВСМ2837В0

4 х Cortex-A53
с частотой 1,4 ГГц

VFPv4 + Broadcom
NEON
Videocore IV

ARMv8-A
(64/32-бит)

2BV1.2

Октябрь
2016 г.

ВСМ2837

4 х Cortex-A53
VFPv4 + Broadcom
с частотой 900 МГц NEON
Videocore IV

ARMv8-A
(64/32-бит)



Февраль
2015 г.

ВСМ2836

4 х Cortex-A7
VFPv3 + Broadcom
с частотой 900 МГц NEON
Videocore IV

ARMV7-A
(32-бит)

1 В+

Июль 2014 г.

ВСМ2835

1 х ARM 1176JZF-S VFPv2
с частотой 700 МГц

Broadcom
Videocore IV

ARMv6Z
(32-бит)



Май 2012 г.

ВСМ2835

1 х ARM 1176JZF-S VFPv2
с частотой 700 МГц

Broadcom
Videocore IV

ARMV6Z
(32-бит)

1 А+

Ноябрь
2014 г.

ВСМ2835

1 х ARM 1176JZF-S VFPv2
с частотой 700 МГц

Broadcom
Videocore IV

ARMv6Z
(32-бит)

1 А

Февраль
2013 г.

ВСМ2835

1 х ARM1176JZF-S VFPv2
с частотой 700 МГц

Broadcom
Videocore IV

ARMv6Z
(32-бит)

Zero
W/WH

Февраль
2017 г.

ВСМ2835

1 х ARM 1176JZF-S VFPv2
с частотой 1 ГГц

Broadcom
VideoCore IV

ARMv6Z
(32-бит)

Zero .
v1.3

Май 2016 г.

ВСМ2835

1 х ARM 1176JZF-S
с частотой 1 ГГц

VFPv2

Broadcom
VideoCore IV

ARMV6Z
(32-бит)

Zero
v.1.2

Ноябрь
2015 г.

ВСМ2835

1 х ARM1176JZF-S
с частотой 1 ГГц

VFPv2

Broadcom
VideoCore IV

ARMv6Z
(32-бит)

1. Введение

23

В табл. 1.1 используется много сокращений, которые могут быть вам знакомы, а
могут быть и новыми. Мы кратко рассмотрим каждое из них и при необходимости
повторно вернемся к ним позже в книге.

♦ SOC, System-on-Chip (Система на кристалле) — системный чип. Один из ключе­
вых элементов дизайна Raspberry Pi и, как правило, самый большой чип на пла­
те, который содержит все важные компоненты в одном корпусе. В него входят
процессор ARM и графические процессоры. Обратите внимание, что каждый
чип SoC сопровождается числом — например, на плате Raspberry Pi 4 В уста­
новлен чип ВСМ2771.
♦ ЦП- это процессор ARM или используемые процессоры, интегрированные
в SoC. В таблице указан конкретный чип ARM, а также количество его ядер и
скорость работы. К примеру, на оригинальном Raspberry Pi В установлен про­
цессор ARM 1176JZF-S, работающий на частоте 700 МГц. В новой Raspberry Pi 4 В
используются четыре ядра ARM Cortex-A72, работающие на частоте 1,5 ГГц.

♦ FPU — модуль с плавающей точкой, или математический сопроцессор, который
реализует сложные математические функции. Он тоже входит в SoC, а в более
поздних версиях SoC установлен сопроцессор Neon, позволяющий FPU выпол­
нять несколько операций параллельно. VFP — это сокращение от Vector floating­
point, вектор с плавающей точкой.

♦ GPU — графический процессор. Он входит в SoC Broadcom, производится компа­
нией Broadcom и обычно представляет собой VideoCore IV, а в Raspberry Pi 4 В —
версию VideoCore VI. По сути, это мультимедийный процессор, который может
декодировать различные графические и звуковые форматы (кодеки) для обеспе­
чения отличного качества изображения на мониторе при сохранении низкого
энергопотребления.
♦ В последнем столбце таблицы указана реализованная архитектура набора
команд и отмечено, является она 32-разрядной или 64-разрядной. Как мы уже
говорили ранее, 64-битный набор инструкций доступен на Raspberry Pi, начиная
с RPi 2 В vl .2, т. е. фактически с октября 2016 года.

Вычислительные модули
Вычислительный модуль (Computer Module)— это упрощенная версия Raspberry
Pi, предназначенная для промышленных или коммерческих приложений. Суть
в том, что их может использовать в своих проектах кто угодно (вы приобретаете
плату и дорабатываете ее самостоятельно под свою задачу). Таким образом, вычис­
лительные модули не имеют возможностей подключения, которые вы можете най­
ти на эквивалентной плате Raspberry Pi, но при этом они не столь надежны. Поэто­
му, хоть они и работают на процессорах ARM и на Raspberry Pi, они здесь не рас­
сматриваются.
Но затем появился модуль Computer Module 4. В отличие от предыдущих вычисли­
тельных модулей, у него имеется обновленная плата ввода/вывода, простой доступ

24

1. Введение

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

Используемые обозначения
В этой книге применяются стандартные обозначения. Числовые типы и определен­
ные операции с числами часто используются в книгах по программированию, по­
добных этой, и важно различать их. В табл. 1.2 приведен их краткий список. Точное
значение обозначений будет ясно, когда мы встретим их в тексте книги.

Таблица 1.2. Стандартные обозначения
Обозначение

Пояснение

% или оь

Обозначает, что идущее за этим обозначением число записано в двоичной
системе счисления (с основанием 2).
Например: %ииоооо или оыиюооо

Ох

Обозначает, что следующее за ним число является шестнадцатеричным.
Например: Oxcafe

< >

Угловые скобки часто используются для обозначения слова, которое
не следует понимать буквально, а надо расценивать как объект, исполь­
зуемый с командой. Например, запись означает, что в угловых
скобках следует использовать имя регистра — например: ro, а не само
слово «Регистр»

Dest

Сокращение ОТ destination

Операнд1

Комментарии в тексте часто будут ссылаться на операнд] и его использо­
вание. Вы же мысленно подставляете вместо него те или иные значения.
Обычно операнд!. — это регистр

Операнд2

Комментарии в тексте часто будут ссылаться на операнд2 и его использо­
вание. Вы же мысленно подставляете вместо него те или иные значения.
Обычно операнд]. — ЭТО регистр

0п1

Сокращение от операнд1

Оп2

Сокращение от операнд2

0

Скобки показывают, что указанный в них элемент является необязатель­
ным и может быть опущен, если он не нужен. Например, запись add(S)
означает, что значения s может и не быть

Центр истории вычислительной техники
Центр истории вычислительной техники (ССН, Center of Computing History) — но­
ваторская образовательная благотворительная организация, открывшаяся на своем
нынешнем месте в Кембридже в августе 2013 года. ССН (www.computinghistory.
org.uk) раскрывает историю информационной эпохи на основе изучения историче­
ского, социального и культурного воздействия разработок в области персональных
компьютеров на человеческую цивилизацию. В этом центре имеется огромная кол-

1. Введение

25

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

Цель ССН— вдохновить и дать возможность обучения всем: от дошкольников до
людей старше 70 лет, чтобы они стали уверенными и творческими пользователями
информации и цифровых технологий. Центр предоставляет образовательные услу­
ги, включая семинары по программированию и электронике, а также другое интер­
активное обучение с использованием ВВС Micros и Raspberry Pi 1980-х годов.

Веб-сайт и бесплатные книги
Зайдите на сайт www.brucesmith.info (рис. 1.4), щелкните на интересующей вас
книге и следуйте появляющимся там указаниям. С этого сайта вы можете скачать
все приведенные в книге программы, получить доступ к обновлениям книги, а так­
же к дополнительной информации и функциям. Кроме того, здесь же вы найдете
ссылки на другие полезные сайты и полезные материалы, а также подробную ин­
формацию о предстоящих публикациях Bruce Smith Books, посвященных Rasp­
berry Pi.

Рис. 1.4. Сайт автора. Нажмите на обложку книги для получения дополнительной информации

Примечание к русскому изданию
Электронный архив с файлами приведенных в книге программ можно загрузить с FTPсервера издательства «БХВ» по ссылке: ftp://ftp.bhv.ru/9785977568012.zip или со
страницы книги на сайте https://bhv.ru/ (см. приложение 4). Обратите при этом внима-

1. Введение

26

ние, что автор использует для нумерации программ в книге и в электронном архиве
цифробуквенную запись: За, 6а, 6Ь и т. д., мы же нумеруем программы в книге более
привычной нашему читателю цифровой записью: 3.1,6.1,6.2 и т. д.

Бесплатно скачать с этого сайта можно и PDF-файлы некоторых из моих книг. Это
книги, которые были опубликованы до 2004 года, т. е. до эры электронных книг и
самопубликации. И хотя я написал многие из них (фактически все, кроме двух пер­
вых), используя текстовый процессор, исходный их текст давно утрачен (первые
два были вообще написаны от руки — синими чернилами на листах формата А4
в линейку!)

Благодарности
Спасибо Ричарду Хури за его помощь с кодом С в этой книге и его мастерство ра­
боты с GCC и GDB. Спасибо Майку Гиннсу за идеи некоторых приведенных в кни­
ге программ. Некоторые листинги взяты из его книги Archimedes Assembly
Language, которая впервые была опубликована Dabs Press в 1988 году (представьте,
сколько лет уже существует ARM!) Кроме того, я благодарен Брайану Скаллану,
Стиву Сирелли и Тони Палмеру за их отзывы. Я также признателен многим читате­
лям, которые присылали мне предложения по улучшению этой книги.

2. Начало

Ассемблер как язык позволяет вам обращаться к родному языку Raspberry Pi —
машинному коду. Это собственный язык ARM-чипа, который по сути есть разум и
душа вашей компьютерной системы. ARM расшифровывается как Advanced RISC
Machine (продвинутая машина с сокращенным набором команд), и именно этот чип
заведует всем, что происходит с вашей Raspberry Pi.

Микропроцессоры, такие как ARM, управляют использованием и передачей дан­
ных. Процессор также часто называют ЦП — центральным процессором, и данные,
которые обрабатывает ЦП, текут сквозь него в виде непрерывного, почти беско­
нечного потока единиц и нулей. Порядок этих единиц и нулей не случайный, и оп­
ределенная их последовательность превращается в набор определенных действий.
Это похоже на азбуку Морзе, где правильная последовательность точек и тире пре­
вращается в осмысленные буквы и слова.
.-............... - . .-. (здесь закодировано слово CLEVER, умный)

Числа со смыслом
Сама программа, написанная машинным кодом, представляет собой бессчетное
множество строк из единиц и нулей. Вроде вот этого:
11010111011011100101010100001011

01010001011100100110100011111010
01010100011001111111001010010100
10011000011101010100011001010001

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

28

2. Начало

компилятором и преобразуется в двоичный эквивалент (из единиц и нулей). Про­
грамма на языке ассемблера называется входным, или исходным, файлом, а машин­
ный код — объектным файлом. Ассемблер переводит (или компилирует) исходный
файл в объектный файл.
Язык ассемблера написан с использованием мнемоники. Мнемоника — это меха­
низм быстрого обучения и запоминания, который основывается на ассоциациях
между легко запоминающимися последовательностями букв и информацией, кото­
рую нужно запомнить. В естественном языке ту же функцию выполняют аббревиа­
туры. Например, чтобы запомнить цвета радуги, вы можете использовать фразу:
«Каждый Охотник Желает Знать, Где Сидит Фазан», и взять первую букву каждого
слова.
В среде SMS-сообщений и интернет-чатов тоже возник свой мнемонический язык.
Благодаря ему текстовые сообщения становятся короче и компактнее. Например,
«мп» может означать «мои поздравления», «спс» — «спасибо», а «дв» — «до
встречи».

Команды ARM
У микросхемы ARM есть определенный набор команд машинного кода, которые
она понимает. Именно таким операциям (или «кодам операций») и их использова­
нию посвящена эта книга. ARM— это всего лишь один из типов микропроцес­
соров. Существуют и другие типы, и у каждого из них свой уникальный набор
команд.
Нельзя просто так взять программу машинного кода, написанную для ARM, и ус­
пешно запустить ее на другом микропроцессоре. Она либо не сработает так, как
ожидалось, либо вообще не запустится. Но понятия и концепции, о которых мы по­
говорим, будут применимы в работе с большинством других видов микропроцессо­
ров и в аналогичных задачах. Если вы научитесь программировать на одном ас­
семблере, с программированием на других будет уже проще. По сути, в новом язы­
ке нужно будет просто выучить новый набор команд, причем многие из них будут
похожи на уже известные вам.

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

SUB

Это простой пример, и многие другие мнемоники ARM тоже просты, если рассмат­
ривать их в отдельности. Но вот последовательность строк, объединенная в цель­
ную программу, выглядит уже сложнее. Разбив программу на составные части,
можно без труда понять, как все работает.

2. Начало

29

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

Так, mov— мнемоника команды MOVe (переместить). Эта команда берет информа­
цию из одного места и перемещает ее в другое место. Проще простого, да?

Процесс преобразования
Когда вы разработали свою программу на языке ассемблера, ее нужно преобразо­
вать в машинный код с помощью компилятора ассемблера. Например, когда ас­
семблер встречает мнемонику mov, он подставит вместо нее число, которое для
процессора представляет собой инструкцию. Ассемблер сохраняет собранный ма­
шинный код в виде файла в памяти, после чего этот код можно будет запускать или
выполнять. В процессе сборки программы ассемблер также проверяет ее синтаксис,
чтобы убедиться в том, что все написано правильно. Если он обнаружит ошибку, то
сообщит вам об этом и скажет ее исправить. Исправив проблему, вы можете еще
раз попробовать собрать программу. Обратите внимание, что проверка синтаксиса
гарантирует лишь правильность написания самих команд ассемблера. Но никто не
проверит за вас логику программы, и поэтому, если вы написали сами команды
правильно, но суть изложили неверно, программа соберется, но сработает неверно.
Например, вам нужно сложение, а вы вместо этого вы запрограммировали вычи­
тание!
Написать программу на ассемблере можно разными способами. Первые чипы ARM
были разработаны Acorn и, как и стоило ожидать, они появились в ряде компьюте­
ров на базе Асот, работающих под управлением ОС RISC. К ним относились
Archimedes и RISC PC. На этих машинах использовался компилятор ВВС BASIC,
который был чем-то новым в том смысле, что позволял писать программы на языке
ассемблера как расширение ВВС BASIC. Вы и сейчас можете писать этим методом,
установив RISC OS на свою Raspberry Pi.

Как вы уже поняли, в этой книге мы предполагаем, что вы используете операцион­
ную систему Raspberry Pi и программное обеспечение GNU GCC. Есть и другое ПО
для ассемблера, причем большая часть его бесплатна, и вы без труда найдете его
в Интернете. Основным преимуществом программирования на GCC является то,
что этот компилятор также может собирать программы, написанные на языке С.
Эта книга не посвящена программированию на С, но по ряду причин нам будет по­
лезно познакомиться с некоторыми его моментами, и даже эта малость может по­
мочь вам в написании программ. Подробнее мы поговорим об этом в книге на при­
мерах позже.
В целом вам ничто не мешает попробовать любой другой или вообще все ассемб­
леры, и знания, которые вы получите в этой книге, помогут вам в этом.

30

2. Начало

А зачем вообще машинный код?
На этот вопрос ответить нетрудно. По сути, все, что делает ваш Raspberry Pi, вы­
полняется с использованием машинного кода. Программируя напрямую в машин­
ном коде, вы работаете на самом фундаментальном уровне работы Raspberry Pi.

Когда вы используете язык высокого уровня, такой как ВВС BASIC или Python, все
его операции все равно превращаются в машинный код всякий раз, когда вы запус­
каете программу. Это занимает некоторое время, хотя для нас эти доли секунды и
незаметны. Но тем не менее процесс преобразования, или интерпретации, замедля­
ет работу программного обеспечения. Фактически даже самые эффективные языки
могут быть более чем в 30 раз медленнее, чем их эквивалент машинного кода, и это
еще если повезет!

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

Языковые уровни
Языки вроде С или Python называются языками высокого уровня. На языках высо­
кого уровня часто легче писать, поскольку их синтаксис похож на английский язык,
а еще в них есть команды, которые выполняют большую и сложную последова­
тельность действий, для подробного расписывания которой потребовался бы длин­
ный список команд машинного кода. Машинный код — это язык низкого уровня.
поскольку он работает в самых внутренностях компьютера, описывая каждое дей­
ствие, и из-за этого его труднее понимать.

Это, конечно, преимущество языков высокого уровня по сравнению с языками низ­
кого уровня. Но пока вы набираетесь опыта в языке ассемблера, ничто не мешает
вам создавать библиотеки подпрограмм для выполнения конкретной задачи и про­
сто добавлять их в свои программы по мере их написания. Впрочем, если вы углу­
битесь в мир ARM, то выяснится, что нужные библиотеки уже существуют.
Написанные на ассемблере программы легко переносить на другие компьютеры
или системы, работающие на чипе ARM. Нужно лишь загрузить файл ассемблера
в компилятор на новой машине, собрать код в программу машинного кода и запус­
тить ее.

Компилятор GNU GCC есть практически на всех разновидностях микропроцессо­
ров, поэтому, познакомившись с GCC, вы сможете перенести свои новые навыки на
другие системы, если захотите.
Если раскрыть возможности чипа ARM на полную катушку, вы даже можете на­
прямую передавать и запускать машинный код. Эти возможности невероятно кру-

2. Начал о

31

ты, особенно учитывая тот факт, что почти каждый существующий сегодня смарт­
фон или планшет работает на чипах ARM!

На орбиту!
Чтобы еще раз подчеркнуть возможности чипа ARM и смартфонов в целом, вспом­
ним, что на орбиту Земли было выведено целое новое поколение спутников под
названием CubeSats (рис. 2.1). Они небольшие (порядка 10 см) и выполняют опре­
деленные задачи. Космический центр Суррея на юге Великобритании разработал
несколько спутников CubeSat, которые работают на смартфонах Android. Эти спут­
ники стоят около 100 тыс. долларов каждый, что гораздо меньше, чем стоимость
аппаратов прошлого поколения. Опять же, вычислительная мощность одного со­
временного смартфона, возможно, в десятки тысяч раз больше, чем у компьютеров
всех лунных миссий Аполлона, вместе взятых! И вся эта мощь— вот она, прямо
в Raspberry Pi.

Рис. 2.1. Изготовление CubeSat

Но не все коту масленица! У плат есть различия в версиях ЦП. Как и в случае
с программным обеспечением, чип ARM постоянно совершенствовался и возника­
ли проблемы с новыми версиями. Однако базовый набор команд остается прежним,
поэтому «перенос» будет не так сложен, как может показаться. Реальной пробле­
мой он становится только в том случае, если вы используете более продвинутые
функции микропроцессора. Для нашего вводного руководства эти изменения не
актуальны, так что в этой книге все подойдет к вашей Raspberry Pi.

32

2. Начало

RISC и наборы команд
Буква R в аббревиатуре ARM означает RISC. Это тоже сокращение от Reduced
Instruction Set Computing (вычисления с сокращенным набором команд). Все про­
цессоры работают на машинном коде, и каждая из команд машинного кода или код
операции выполняет свою определенную задачу. Объединенные вместе, команды
образуют набор команд.
Идея RISC заключалась в создании небольшого оптимизированного набора команд.
У этого подхода есть несколько преимуществ — меньше придется запоминать. Но
при этом образуется большее разнообразие в их использовании.

Структура ассемблера
Программирование на любом языке, как и общение на любом языке, подчиняется
некоторому ряду правил. Эти правила определяются структурой и синтаксисом
языка, который мы используем. Чтобы писать программы, нам нужно знать синтак­
сис языка и правила, которые определяют структуру программы.

Самый простой способ разработать программу— просто написать список задач,
который она должна выполнить. Программа начинается с начала и выполняется
команда за командой, пока не дойдет до конца. Другими словами, все команды вы­
полняются по очереди, пока не будет достигнут конец программы. Это работает, но
весьма неэффективно.
Современные языки более структурированы и позволяют писать программы в виде
множества независимо выполняемых процедур, или подпрограмм. Эти подпро­
граммы вызываются из основной программы по мере необходимости. Таким обра­
зом, основная программа управляет потоком выполнения и запускает операции,
которые подпрограммам недоступны.
Программы, написанные с помощью подпрограмм, получаются меньше и лучше
поддаются редактированию. В линейной программе нам, вероятно, пришлось бы
много раз повторять большие участки кода, чтобы выполнить поставленную задачу.

В листинге 2.1 показан некоторый псевдокод, на примере которого я иллюстрирую,
как может выглядеть структурированная программа. В этом примере команды про­
граммы написаны прописными буквами. Разделам кода подпрограммы даются
имена, которые пишутся в нижнем регистре и, как это ни парадоксально, с точкой
в начале записи. Весь поток программы содержится в шести строках, начинающих­
ся с раздела .main и заканчивающихся словом end. Программа получается короткой,
но читается ясно, и с первого взгляда становится понятно, что в ней происходит.
Подпрограммам специально даются осмысленные имена.

В этом примере основная программа просто вызывает некоторые подпрограммы.
В идеальном мире мы бы писали именно такие программы, поскольку такой подход
также упрощает тестирование этих подпрограмм по отдельности, прежде чем мы
включим их в основную программу. Это позволяет гарантировать, что наша про­
грамма будет работать правильно.

2. Начало

33

.main
DO
DO
DO
DO

getkeyboardinput
displayresult
getkeyboardinput
displayresult

END
.getkeyboardinput
; Instructions to read input from keyboard
RETURN
.displayresult
; print the result on the screen
RETURN

Ошибки на пути
Есть довольно серьезная проблема, с которой вы обязательно столкнетесь при изу­
чении любой новой программы, — поиск ошибок. Этот процесс еще называют от­
ладкой. Я гарантирую (и многократно убеждался в этом), что первый вариант ва­
шей программы всегда будет работать неправильно. Вы будете медитировать над
кодом до вечерней зорьки, но ошибки так и не найдете. Затем вы будете настаивать
на своей правоте и валить все на компьютер. И лишь потом на вас снизойдет озаре­
ние, и вы увидите, что ошибка все это время была на самом видном месте.
Создав подпрограмму, а затем протестировав ее отдельно и убедившись, что она
работает, вы будете знать, что и в основной программе все будет работать правиль­
но и что сединой вы раньше времени не обзаведетесь.

Кросс-компиляторы
С этим термином вы, вероятно, будете сталкиваться часто. Компилятор GCC встре­
чается на множестве компьютеров, и даже на тех, которые не работают на чипе
ARM. Вы можете писать и скомпилировать ассемблер ARM вообще на другом
компьютере! Но запустить собранный машинный код уже не сможете. Придется
сперва перенести его с главной машины на целевую (например, Raspberry Pi).
GCC — не единственный компилятор для Raspberry Pi и не единственный кросс­
компилятор. Есть и много других. Загляните на форумы на сайте Raspberry Pi для
получения подобной информации, и еще можете поискать в Интернете, если инте­
ресно.

Чипы Raspberry Pi ARM
Чип ARM, используемый в Raspberry Pi Zero, A, В, A+, B+ — это (приведем полное
название) мультимедийный процессор Broadcom ВСМ2835 System-on-Chip. Термин
«System-on-Chip» (система на кристалле, SoC) означает, что чип содержит почти

34

2. Начало

все необходимое для запуска Raspberry Pi в единой структуре (и именно поэтому
у Raspberry Pi столь малые габариты). В ВСМ2835 используется дизайн ARM11,
который построен на наборе команд ARMv6.
В Raspberry Pi 2 используется чип SoC ВСМ2836. В нем есть все функции
ВСМ2835, но один ARM11 с тактовой частотой 700 МГц заменен на четырехъядер­
ный ARM Cortex-A7 с тактовой частотой 900 МГц, а все остальное остается преж­
ним. Этот чип быстрее, в нем больше памяти, и он может запускать достаточно
серьезное программное обеспечение, такое как Windows 10 и весь спектр дистрибу­
тивов ARM GNU/Linux.
В основе Raspberry Pi 3 лежит чип ARM v8, опять же с использованием структуры
SoC и с еще большей частотой 1,2 ГГц. В его основе четыре высокопроизводитель ­
ных процессора ARM Cortex-A53, работающих в тандеме. А еще это 64-битный
процессор, который может работать как в состоянии AArch32, так и в AArch64.
В Raspberry Pi 4 используется Broadcom 2711, который стал еще быстрее —
1,5 ГГц. ARMv8— это четырехъядерный процессор А72, 64-битный процессор,
который может работать как в состоянии AArch32, так и в AArch64. Версия 8 Гбайт
позволяет использовать память полной 64-разрядной версии для эффективной
работы и обработки приложений — ничуть не хуже обычного ПК!

Что-то я увлекся терминами. Пока вы еще новичок, не стоит слишком забивать го­
лову. По мере того как вы начнете узнавать больше о Raspberry Pi, все эти вещи
станут вашей второй натурой. Мы вернемся к SoC в конце книги и объясним эту
концепцию более подробно.
Кстати, частота в один мегагерц (1 МГц) соответствует миллиону циклов в секун­
ду. Скорость микропроцессоров, называемая тактовой частотой, часто измеряет­
ся в МГц. Например, микропроцессор, работающий на частоте 700 МГц, выполняет
700 млн циклов в секунду. 1,2 ГГц (гигагерц)— это 1,2 млрд циклов в секунду.
Позже мы увидим, как эта скорость влияет на выполнение команд.

Еще прозвучал термин «четырехъядерный». Четырехъядерный процессор имеет
четыре независимых блока, называемых «ядрами», которые одновременно читают
и выполняют команды. В целом четырехъядерный процессор будет работать быст­
рее, чем двухъядерный или одноядерный. Каждая открытая программа может рабо­
тать на собственном ядре, поэтому, если задачи разделены между ядрами, скорость
будет лучше. Эта так называемая параллельная обработка является основной осо­
бенностью ARM.

3. Проба пера

В этой главе мы пошагово рассмотрим процедуру создания и запуска программы
машинного кода, начиная с момента включения Raspberry Pi и заканчивая внесени­
ем корректив в уже готовую программу. Первая программа не будет делать ничего
фантастического. На самом деле не произойдет вообще ничего, кроме возврата
символа приглашения, но даже в написание этой программы вовлечен каждый шаг,
который вам нужно знать и использовать при создании и запуске других программ
из этой книги.
Прямо сейчас я предполагаю, что у вас есть копия образа Raspberry Pi OS (Raspbian) на SD-карте, вставленной в ваш Raspberry Pi, и что вы использовали ее хотя
бы один раз, чтобы выполнить начальную настройку таких важных моментов, как
клавиатура и Интернет. Если вы еще этого не сделали, сделайте это сейчас, чтобы
можно было продолжить.

Командная строкаЗ. Проба пера
При первой загрузке вы автоматически войдете в систему и попадете на рабочий
стол. В правом верхнем углу выполните двойной щелчок мышью на значке мони­
тора. Перед вами откроется окно Terminal, и вы попадете в командную строку, где
увидите примерно такое приглашение ввода:
pi@raspberrypi

$

Командная строка — это консоль, в которую вы вводите команды, которые затем
выполнит ваша ОС. Командная строка начинается там, где мигает курсор. Консоль
ожидает, что вы будете вводить команды, которые затем можно выполнить нажати­
ем клавиши (также называемой клавишей ). Попробуйте ввести
вот такую команду:
dir

Ввести ее нужно в точности так, как показано здесь. При нажатии клавиши
перед вами появится список всех каталогов или файлов, которые находят­
ся в текущем каталоге. (С этого момента я не буду каждый раз говорить о нажатии

36

3. Проба пера

клавиши , но вы имейте в виду, что если нужно ввести что-то с клавиату­
ры, особенно в командной строке, нужно будет нажать клавишу .)

Теперь введите вот это:
Dir

Вы получите такой ответ:
bash: Dir: ccmmand not found

Это сообщение об ошибке. В командной строке регистр имеет значение, поэтому
команды:
dir

И
Dir

ОС считает разными, если учитывает регистр. То же самое и с именами файлов:
programl

И
Programl

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

Создание исходного файла
Чтобы создать программу с машинным кодом, нужно выполнить процесс из трех
действий «написать — собрать — связать», в результате которого мы получим файл,
который можно будет запустить. Первый шаг — написать программу на ассембле­
ре. Поскольку именно этот код является источником программы, файл называется
исходным файлом. У такого файла будет расширение s после имени. Например:
programl.s

Исходные файлы можно писать в любом удобном текстовом редакторе. Существу­
ет много отличных и бесплатных редакторов, поэтому стоит уделить время изуче­
нию их обзоров и проверить пару вариантов самостоятельно. Возможно, у вас уже
есть любимый редактор, и с этим этапом вы разобрались.
В Raspberry Pi OS уже есть набор редакторов, установленных в разделе Recom­
mended Software, и вы можете найти их в главном меню приложения (пункт
Raspberry в меню на рабочем столе). Скорее всего, нужные вам редакторы нахо­
дятся в списке Accessories. Сюда входят (на момент подготовки книги) редакторы
Vim, gVim, а также редактор Geany Programmer’s Editor (советую попробовать каж­
дый из них и выбрать тот, который вам больше нравится).

3. Проба пера

37

Если ни Vim, ни gVim в системе не установлены, вы можете исправить эту пробле­
му с помощью параметра Recommended Software или из командной строки, набрав
команду:
sudo apt-get install vim

После этого надо будет ответить на пару запросов: вас могут спросить о необходи­
мости дополнительных функций. Ответ у вполне подойдет. Установка займет не­
сколько минут, а на сайте Vim тем временем можно найти много полезных советов.
Если вам больше нравится работать в приложении, возьмите версию Vim с графи­
ческим интерфейсом и при необходимости установите ее командой:
sudo apt-get install vim-gtk

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

Таблица 3.1. Важные команды редактора Vim
Клавиша

Действие
Команды перемещения курсора

-

Переместить влево
Переместить вниз

т

Переместить вверх

-

Переместить вправо

W

Переместить до следующего слова

W

Переместить до следующего слова, отделенного пробелом

е

Переместить в конец слова

Е

Переместить в конец слова (без учета пунктуации)

b

Переместить до предыдущего слова

В

Переместить до предыдущего слова (без учета пунктуации)

0 (ноль)

Новая строка

Л

Первый непустой символ

$

Конец строки

G

Перейти к команде (например, 5G - перейти к строке 5)

Примечание, в команде перехода к команде слева приписывается количе­
ство повторений. Например, команда 4j перемещает курсор на 4 строки

3. Проба пера

38

Таблица 3.1(окончание)
Клавиша

Действие

Режим вставки — вставка/дополнение текста
i

Переход в режим вставки с позиции курсора

I

Вставить в начале строки

А

Вставить после курсора

а

Вставить в конце строки

0

Добавить пустую строку после текущей (без нажатия клавиши )

О

Добавить пустую строку перед текущей

Esc

Выход из режима вставки

Режим команд
:w

Запись (сохранение) файла без выхода

:wq

Запись файла (сохранение) и выход из Vim

:Q

Выход (не срабатывает при наличии изменений)

:Q!

Выход без сохранения изменений

:set

Установка нумерации строк

Запустив Vim, вы также можете указать имя файла, который хотите создать. Если
файл уже существует, он загрузится в окно редактора, и вы сможете работать
с ним, как вам нужно. Если такого файла не существует, Vim создаст для вас новый
пустой файл с указанным именем. Откройте новое окно терминала и в командной
строке введите:
vim ргодЗа.s

Обратите внимание, что между vim и ргодЗ. s есть пробел, а также заметьте, что
в конце имени ргодЗ есть расширение s исходного файла. Соглашение гласит, что
расширение s обозначает исходный файл на языке ассемблера.
После этого окно станет в основном пустым, за исключением столбца из символов
тильды
бегущего по левому краю, и имени файла внизу, а также обозначения
того, что это новый файл.

Нажмите клавишу . Обратите внимание что в нижнем левом углу экрана по­
явился текст:
-INSERT —

Это означает, что мы находимся в режиме вставки. Нажмите клавишу .
Надпись исчезла. Теперь мы находимся в командном режиме Vim.

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

3. Проба пера

39

надоест. В командном режиме нажатия клавиш интерпретируются как прямые
команды для Vim.
Имена файлов программ — в нашем случае ргодЗа. s — станут для вас привычны­
ми. Все программы в этой книге будут именоваться по номерам глав. Таким обра­
зом, ргодЗа.s1 означает, что исходный файл программы взят из главы 3 книги. Бук­
ва а1 предполагает, что это 1-я программа в этой главе. Файл с именем prog4b.s1
будет означать, что это 2-я программа из главы 4. Мы будем делать так просто для
удобства. Но вы можете использовать любое имя, какое захотите.

Вернитесь в режим вставки (клавишей ) и обратите внимание на мигающий
курсор в верхнем левом углу экрана. Все, что вы начнете вводить, появится там, где
находится курсор. Перепишите туда код из программы 3.1. Вам нужно ввести толь­
ко тот текст, который в этом листинге расположен между двумя пунктирными
линиями.

Примечание к русскому изданию
Напомним, что электронный архив с файлами приведенных в книге программ можно за­
грузить с FTP-сервера издательства «БХВ» по ссылке: ftp://ftp.bhv.ru/9785977568012.zip
или со страницы книги на сайте https://bhv.ru/ (см. приложение 4).

.global _start
start:

MOV RO, #65
MOV R7, #1
SWI 0

Обратите внимание, что из этих пяти строк программы первая, третья, четвертая
и пятая строки написаны с отступом. Только вторая строка написана без отступа.
Величина отступа, который вы добавляете, и даже место их размещения, на самом
деле не имеют значения. Но зато они просто упрощают чтение программы и позво­
ляют выделить различные уровни программы.

Чтобы создать отступ, нажмите клавишу . Другие клавиши работают так, как
и должны. Например, клавиши со стрелками используются для перемещения, а
клавиши и — для перемещения и редактирования текста. Но
между словами global и -Start имеется пробел, и этот пробел важен. Вскоре мы
рассмотрим, что конкретно делает этот код.

А пока нажмите клавишу и введите:
: wq

Эта команда сохранит ваш файл и закроет Vim. Теперь вы вернулись в командную
строку. Исходный файл готов!

1 Здесь имеется в виду авторская нумерация программ в книге и в электронном архиве (За, ЗЬ, 4а, 4Ь).
Мы же в книге нумеруем программы более привычной нашему читателю цифровой записью: 3.1, 3.2,
4.1,4.2 и т. д. (в электронном архиве нумерация остается авторской).

3. Проба пера

40

Написанное — исполнить!
Следующим шагом является преобразование исходного файла в исполняемый
файл машинного кода. Это делается с помощью двух команд командной строки.
В командной строке последовательно введите две команды:
as -о ргодЗа.о ргодЗа.s

Id -о ргодЗа ргодЗа.о

Эти две команды сначала собирают, а затем привязывают программу на языке
ассемблера (о привязывании позже). После этого машинный код готов к выпол­
нению:
./

Сочетание символов . / означает «запустить», а имя файла, который нужно запус­
тить, пишется сразу после команды без пробелов. Таким образом, для запуска на­
пишем:
./ргодЗа

Когда приглашение появится снова, программа машинного кода будет выполнена.
Проще простого!
Выходит, мы только что написали, скомпилировали (собрали и связали) и выпол­
нили программу машинного кода, сделав все основные шаги этого стандартного
процесса. Конечно, со временем наши программы будут становиться все сложнее,
мы будем шире использовать доступные инструменты, и этот процесс, как мы уви­
дим, станет более сложным.

Ошибки ассемблера
Если в какой-либо момент во время процесса вы получите сообщение об ошибке,
или вообще какое-либо сообщение, — тщательно проверьте то, что вы ввели. Сна­
чала внимательно посмотрите на программу на языке ассемблера, затем на коман­
ды для сборки и связывания и, наконец, запустите программу. Если произошла
ошибка, и вы ее нашли, поздравляем, вы только что отладили свою первую про­
грамму на ассемблере.
Если возникает сообщение об ошибке от ассемблера (оно появляется после того,
как вы нажали в конце первой строки), обычно в этом сообщении указы­
вается номер строки, в которой что-то неладно. Даже если вы не знаете, что именно
означает полученное сообщение, запишите себе номер строки, а затем откройте
исходный файл в Vim. Например, сообщение:
ргодЗа.s:5: Error bad expression

говорит об ошибке в строке 5 исходного файла.
Пока ваши файлы еще крошечные, вы можете отсчитать количество строк и найти
ту, в которую закралась ошибка. В самом редакторе Vim также есть возможность
нумерации строк. В командном режиме Vim введите:
:set number

3. Проба пера

41

Обратите внимание, что в левой части окна появились номера строк. Номера строк
не являются частью файла с кодом и отображаются только для справки. На рис. 3.1
показано, как это все это выглядит в редакторе gVim: видны номера строк и то, что
Vim работает в режиме вставки. Если вы используете gVim, то в нем различные
элементы синтаксиса выделяются разными цветами. Это позволяет легко иденти­
фицировать различные компоненты программы.

Рис. 3.1. Отображение номеров строк в gVim

Вы можете запустить редактор gVim из командной строки с помощью следующей
команды:
gvim

Тогда для создания или редактирования файла prog3a.s можно выполнить команду:
gvim prog3a.s

Компоненты
Давайте еще раз взглянем на описанный здесь процесс и попробуем вникнуть
в анатомию исходного файла и понять, как все это собралось воедино. Посмотрим
еще раз на файл ргодЗа. s. Он состоит всего из пяти строк.

У любого файла с кодом ассемблера должна быть точка начала, и по умолчанию
в ассемблере GCC она выглядит вот так:
_start:

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

42

3. Проба пера

Вторая строка определяет, где находится start: в программе. Обратите внимание
на использование символа : в конце, которое определяет это имя как «метку». Мы
определили start как глобальное имя и теперь отметили, где находится сама метка
_start.

Следующие три строки написаны на мнемонике ассемблера, причем две из них
одинаковые: команда mov. Когда в языке ассемблера используется символ решетки,
он обозначает непосредственное значение. Другими словами, число после решет­
ки— это именно то значение, которое и будет использоваться. Первая команда
перемещает значение 65 в регистр о. Буква r обозначает регистр— специальное
место в микросхеме ARM, о котором мы поговорим чуть позже. Во второй строке
значение 1 перемещается в R7, или в регистр 7.
Последняя команда: swi о. Это специальная команда, которая служит для вызова
самой операционной системы Raspberry Pi. В нашем случае она задействуется для
выхода из программы машинного кода и передачи управления обратно в команд­
ную строку (программа в этот момент должна быть запущена).
Также стоит обратить внимание на регистр символов команд языка ассемблера
в наших исходных файлах. Я использую прописные буквы для мнемоник и регист­
ров, но подошли бы и строчные буквы, поскольку внутри исходных файлов регистр
символов не имеет значения (в отличие от командной строки в консоли, которая
чувствительна к регистру), поэтому
MOV R0, #65

а также
mov гО, #65

делают одно и то же. В этой книге я буду использовать прописные буквы. Это
упрощает чтение команд в тексте книги и позволяет отличить команды от меток,
которые будут написаны в нижнем регистре.

Запустите программу еще раз:
./ргодЗа

В командной строке введите:
echo $?

На экран будет выведено следующее:
65

Вывелось значение, загруженное в ro. Попробуйте изменить 65 на другое число —
скажем, 49. Теперь сохраните код, соберите его заново, заново свяжите и запустите.
Если вы сейчас наберете:
echo $?

будет выведено число 4 9. У ОС есть определенные способы возврата информации
из программ машинного кода, и мы рассмотрим их позже.

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

43

3. Проба пера

а в нижней половине — непосредственно команды на языке ассемблера. Исходные
файлы на языке ассемблера всегда состоят из последовательности операторов, за­
писанных по одному в каждой строке. Каждый оператор имеет следующий синтак­
сис (при этом каких-то его частей может и не быть):
обозначение: >

@ комментарий

Все три компонента можно вводить в одной строке или в разных строках. Это дело
ваше. Но порядок их должен быть именно таким. Например, команда не может рас­
полагаться перед меткой (в той же строке).
Компонент «комментарий» для нас в новинку. Когда ассемблер встречает символ @,
он игнорирует все написанное после него до конца строки. Такую конструкцию
можно использовать для добавления в программу пояснений. Например, вернитесь
и отредактируйте код в файле ргодЗа.s, набрав:
vim ргодЗа.s

В окне редактора отобразится исходный файл. Курсор будет находиться в верхней
части файла. Войдите в режим вставки, создайте новую строку и введите в нее сле­
дующее:
@ ргодЗа.s - простенький файл ассемблера

Строка комментария с символом @ в начале полностью игнорируется компилято­
ром. Из-за этого размер исходного файла, конечно, увеличится, но это никак не по­
влияет на работу исполняемого файла.
Комментарии также можно добавлять с помощью символов /* и */, обозначая ими
начало и конец комментария:
/* Этот комментарий ассемблер проигнорирует ★/

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

Чтобы преобразовать исходный файл в исполняемый, нам потребовалось два шага.
Первый:
as -о ргодЗа.о ргодЗа.s

Команда as в начале вызывает саму программу ассемблера, которая ожидает после
этой команды нескольких аргументов, задающих имена файлов, с которыми она
будет работать, а также необходимые действия. Первый из них: о — сообщает ас­
семблеру, ЧТО МЫ ХОТИМ СОЗДаТЬ объеКТНЫЙ файл ПО ИМеНИ ргодЗа. о из исходного
файла ргодЗа.s. Вы можете выбрать другое имя, если хотите. Сами имена не обяза­
тельно должны совпадать, но, если они одинаковые, проще найти концы. Расшире­
ние в любом случае разное!
Второй и последний шаг: «связать» файл объектного файла и преобразовать его
в исполняемый файл с помощью команды id следующим образом:
Id -о ргодЗа ргодЗа.о

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

44

3. Проба пера

создается исполняемый файл (называемый эльф-файлом). Именно команда id
использует метку start: и благодаря ей понимает, откуда должна запускаться про­
грамма (это может звучать безумно, но начальная точка не всегда находится в на­
чале кода, как мы увидим чуть позднее).

А если нет метки _start?
Можно многое узнать о работе ассемблера и компоновщика GCC, просто поэкспе­
риментировав с ними. Как вы думаете, что произойдет, если мы опустим метку
start: в исходном файле?

Откройте файл prog3a.s в Vim и удалите строку start:, т. е. метку. Выйдите из
Vim и соберите программу:
as -о ргодЗа.о prog3a.s

А теперь свяжите программу:
Id -о ргодЗа ргодЗа.о

Компилятор выдаст следующее сообщение об ошибке (или подобное):
Id: warning: cannot find entry symbol _start; defaulting to 00008054

Сообщение говорит само за себя. Поскольку компилятор не может найти указатель
на то, где начинается программа, компоновщик предполагает, что точка запуска
программы находится в самом начале, а в памяти это адрес 00008054. (Этот адрес
может и, вероятно, будет свой у каждой модели Raspberry Pi.)
Это подстраховка, но не гарантия безопасности. Всегда используйте метку start:
в своих файлах, чтобы определить входную точку программы!

Связывание файлов
Сочетание «Id» обозначает «динамическое связывание». Сама команда связывания
позволяет объединять или последовательно соединять несколько файлов в одну
длинную исполняемую программу. Для таких случаев в этих файлах должна быть
определена только одна метка start:, т. к. у объединенной программы может быть
только одна точка входа. Это легко продемонстрировать на примере нашей про­
граммы.

Создайте новый файл в Vim и присвойте ему имя:
parti.s

В этот файл введите код, показанный в программе 3.2.

/★ файл parti.s
.global _start

*/

3. Проба пера

45

_start:
MOV RO, #65

BAL _part2

Сохраните файл. Теперь создайте новый файл с именем:
part2.s

и добавьте в него код из программы 3.3.

/★ файл part2.s

*/

.global _part2

_part2:
MOV R7, #1

SWI 0

Сохраните и закройте файл. Мы написали два исходных файла, которые теперь
скомпилируем и свяжем для создания одного исполняемого файла. В конце файла
parti. s мы добавили новую инструкцию:
BAL _part2

Команда bal означает безусловный переход— в нашем случае переход к точке
программы, отмеченной меткой part2:. Во втором файле мы определили глобаль­
ную переменную с именем part2 и отметили точку, где эта часть начинается. По­
скольку мы использовали определение глобальных меток, местоположение адреса
будет доступно для всех частей программы.
Теперь нужно скомпилировать оба новых исходных файла:
as -о parti.о parti.s

as -о part2.o part2.s

Затем идентифицировать и связать метки с помощью компоновщика следующим
образом:

Id -о allparts parti.о part2.o

Здесь компоновщик создаст исполняемый файл с именем allparts из файлов
parti.о и part2.o. (Вы можете использовать и другое имя.) Попробуйте запустить
файл:
./allparts
Порядок указания файлов parti .о и part2.o можно поменять местами. Он не имеет
значения, поскольку вопросами порядка выполнения занимается компоновщик.
Ключевым моментом здесь является то, что каждый исходный файл пишется и соз­
дается независимо, но затем все они соединяются механизмом связывания. Если вы
попытаетесь связать только один файл, вы получите сообщение об ошибке, потому
что при связывании каждая часть ссылается на другую. То есть компоновщик в та­
ком случае занимается безопасностью.

3. Проба пера

46

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

Прибираемся...
Если вы выведете корневой каталог командой:
dir

то увидите, что среди прочего в нем находятся три файла ргодЗ, а именно:
♦ ргодЗа. s

— исходный файл;

♦ ргодЗа. о

— объектный файл.

♦ ргодЗа —

запускаемый файл.

В общем-то, вам нужен только исходный файл, т. к. исполняемый файл можно соз­
дать из него в любой момент. Кроме того, можно избавиться от объектного файла
с помощью команды rm (от remove files):
rm ргодЗа.о

Чтобы файлы хранились в порядке, стоит создать отдельный каталог для всех ва­
ших файлов ассемблера с помощью команды mkdir. Чтобы создать каталог с име­
нем aal, выполните команду:
mkdir aal

Теперь вы можете перейти в этот каталог и работать в нем, выполнив команду:
cd aal

Обратите внимание, что в строке приглашения появилась приписка:
/aal $

С этого момента все, что вы будете создавать или делать, будет производиться
в каталоге aal. Чтобы вернуться в корневой каталог Raspberry Pi, введите команду:
cd

Обратите внимание, что в приглашении командной строки всегда написано, в ка­
ком каталоге файловой системы Raspberry Pi вы в текущий момент работаете.
Если при попытке загрузить или собрать файл, вы получите сообщение об ошибке,
стоит проверить, находитесь ли вы в правильном каталоге и что имя файла тоже
написано правильно (включая использование прописных букв). Зачастую проблема
именно в этом!

3. Проба пера_________________________________________________________________ 47

Пара слов о комментариях
Не все со мной согласятся, но я считаю, что комментировать свои программы на
ассемблере нужно обязательно. То, что вы пишете сегодня, в памяти свежо, но если
нужно будет переделать или подредактировать код позже, вам почти наверняка бу­
дет трудно вспомнить, что именно делает каждый фрагмент кода, а другой про­
граммист, которому надо будет поработать с вашим кодом, вообще запутается. На
мой взгляд, комментарии — если они по делу — являются неотъемлемой частью
программ на языке ассемблера. Все ассемблеры позволяют писать комментарии
в исходном файле. Комментарии не входят в конечный машинный код и не замед­
ляют его выполнение. Единственный минус их в том, что они увеличивают размер
вашей исходной программы.

Поэтому комментарии — это хорошо, но не следует писать их «лишь бы написать».
Если писать комментарий в каждой строке, то код становится некрасивым
и лишние комментарии отвлекают внимание от важных комментариев. Например,
посмотрите на эту простую строку и комментарий к команде add:
ADD RO, Rl, R2

@ RO=R1+R2

Комментарий здесь не имеет смысла с точки зрения программной документации,
поскольку мы и так знаем, что делает эта операция. Уместнее было бы пояснить,
что за значения (по смыслу) хранятся в ячейках ri и R2. Лучше было бы написать
вот так:
ADD RO, Rl, R2

@ сложение actl + act2

Если вы разбиваете свою программу на сегменты, используя формат, показанный
в предыдущей главе, обычно бывает достаточно одного или двух комментариев
в начале раздела. Дам несколько рекомендаций:
♦ комментируйте все ключевые моменты вашей программы;
♦ используйте простой язык. Не придумывайте секретные шифры, понятные толь­
ко вам;

♦ если уж комментируете, то комментируйте как следует;
♦ делайте комментарии аккуратными, удобочитаемыми и последовательными;
♦ комментируйте все определения.

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

3. Проба пера

48

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

Редактор Geany Programmer’s Editor
Ранее в этой главе я упоминал, что есть еще один редактор для создания файлов
с кодом в Raspberry Pi. В более поздних версиях рабочего стола Raspberry присут­
ствует подменю Programming, где вы, вероятно, найдете редактор Geany Pro­
grammer’s Editor.

Geany (рис. 3.2)— это IDE, или «интегрированная среда разработки», в которой
есть множество вариантов форматирования для различных проектов и языков.
В ней также имеются вкладки, которые позволяют открывать несколько файлов
с кодом. Автоматически указываются номера строк.
Если вы не забыли сохранить исходный файл как текстовый файл с расширением s,
можете использовать этот редактор. Однако полезно сперва понять, как работают
некоторые традиционные текстовые редакторы.

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

Рис. 3.2. Внешний вид Geany IDE

4.0 битах в RISC-машинах

В мире есть 10 типов людей: те, которые понимают двоичную систему, и те, кото­
рые ее не понимают.
Если вы не поняли, что это шутка, не волнуйтесь. Прочитав эту главу, вы все пой­
мете. Если же она вызвала у вас улыбку прямо сейчас, вы на правильном пути
к изучению этого материала. Начнем мы с объяснения, что вообще такое двоичная
система, как ею можно манипулировать и какое значение она имеет для эффектив­
ного написания машинного кода.
Создавая программы с машинным кодом, вы работаете на самом базовом уровне
компьютера. Проще некуда. В предыдущих главах мы упоминали, что существуют
двоичные и шестнадцатеричные числа. Шестнадцатеричные числа — это компакт­
ный способ записи чисел, которые в двоичном формате представляют собой длин­
ные последовательности единиц и нулей. Архитектура Raspberry Pi с сокращенным
набором команд может делать многое, используя базовый набор команд и извлекая
максимум пользы из каждой единицы или нуля. Чтобы полностью понять, как ра­
ботает RISC-машина, сначала нужно разобраться, как устроены двоичный и шест­
надцатеричный файлы и как они используются микросхемой ARM.

В предыдущих главах мы говорили, что команды, с которыми работает процессор
ARM, состоят из последовательностей чисел. Каждое число представляет собой
инструкцию (код операции) или данные (операнд), некоторой операции машинного
кода. В памяти компьютера эти числа представлены в двоичном виде. Двоичное
число — это просто число, состоящее из единиц или нулей. Двоичный формат тем
удобен, что внутри микропроцессора эти единицы и нули соответствуют состояни­
ям «включено» и «выключено» (с точки зрения электроники это обычно напряже­
ние +5 и 0 В), и нам, как программистам на языке ассемблера, нужно знать состоя­
ние отдельных двоичных цифр, или битов.
Коды операций и операнды получаются путем объединения наборов из восьми
битов, которые вместе называются байтами. Согласно соглашению биты в этих
байтах пронумерованы, как показано на рис. 4.1.
7

6

5

4

3

2

1

Рис. 4.1. Нумерация битов в байте

0

4. О битах в RISC-машинах

50

Увеличение номера идет справа налево (не слева направо), но это не так странно,
как может показаться на первый взгляд.
Рассмотрим десятичное число 2934 — две тысячи девятьсот тридцать четыре. Наи­
большее числовое значение, две тысячи, находится слева, а наименьшее, четыре, —
справа. То есть положение цифры в числе весьма важно, т. к. оно влияет на «вес»
числа.
Во второй строке на рис. 4.2 показано новое представление числа. К основанию
системы приписывается суффикс с небольшим числом, или степенью, которая со­
ответствует его положению в числе. Таким образом, 103 равняется 10 * 10 * 10 =
= 1000. Число в нашем примере состоит из двух тысяч плюс девять сотен плюс три
десятка плюс четыре единицы.
Значение

1000

100

10

1

Представление

10 3

10 -

10 1

10 0

Цифра

2

9

3

4

Рис. 4.2. Десятичные веса обычных чисел

В двоичном представлении вес каждого бита вычисляется путем возведения осно­
вания (2) в степень позиции бита. Например, бит номер 7 (Ь7) имеет условное пред­
ставление 27 или 2х2х2х2х2х2х2 = 128. Вес, или значение, каждого бита по­
казаны на рис. 4.3.
Номер бита

Ь7

Ь6

Ь5

Ь4

ЬЗ

Ь2

Ы

ЬО

Представление

2 7

2 6

2 5

2 4

2 3

2 2

2 1

2 0

Вес

128

64

32

16

8

4

2

1

Рис. 4.3. Двоичные веса чисел

Преобразование двоичных чисел в десятичные
Раз уж мы можем вычислить вес отдельных битов, значит, преобразовать двоичное
число в десятичное будет несложно. У этого преобразования всего два правила:
♦ если бит равен 1, добавьте его вес к сумме;
♦ если бит равен 0, ничего с ним не делайте.

Давайте попробуем для примера преобразовать двоичное число 10101010 в его эк­
вивалентное десятичное.

Глядя на рис. 4.4, складываем значения из третьего столбца и получаем в результа­
те 170. Следовательно, двоичное число 10101010 эквивалентно десятичному 170
(128 + 0 + 32 -1-0-1-8 + 0 + 2 + 0). Аналогично двоичное значение 11101110 эквива­
лентно 238 в десятичном виде, как показано на рис. 4.5.

4. О битах в RISC-машинах

Рис. 4.4. Преобразование
двоичного числа 10101010 в десятичное

51

Рис. 4.5. Преобразование
двоичного числа 11101110b десятичное

Преобразование десятичных чисел в двоичные
Чтобы преобразовать десятичное число в двоичное, нужно выполнить обратную
процедуру— вычитать двоичные веса из заданного числа. Если вычитание воз­
можно, в двоичный столбец помещается 1, а остаток переносится в следующую
строку. Если вычитание невозможно, в двоичный столбец помещается 0, а число
перемещается в следующую строку. Например, на рис. 4.6 показано, как десятич­
ное число 141 преобразуется в двоичное.
Следовательно, десятичное число 141 соответствует двоичному 10001101.

Рис. 4.6. Преобразование десятичного числа 141 в его двоичный эквивалент

Преобразование двоичного числа
в шестнадцатеричное
Двоичная запись, вероятно, лучше всего показывает, как хранятся числа в Raspberry
Pi, но работать с такими числами весьма сложно. Во время чтения длинных рядов
нулей и единиц глаза попросту разбегаются в разные стороны. Поэтому при работе

4. О битах в RISC-машинах

52

с двоичными числами мы чаще используем альтернативную форму их представле­
ния— шестнадцатеричную. Шестнадцатеричные числа— это числа с основани­
ем 16. На первый взгляд такой вариант еще менее удобен, но на самом деле у него
много преимуществ.

Для представления всех возможных цифр шестнадцатеричного числа нужны шест­
надцать различных символов. Поэтому мы сначала используем привычные цифры
от 0 до 9, а затем буквы А, В, С, D, Е, F — для представления значений от 10 до 15.
Двоичные и десятичные значения для каждого шестнадцатеричного числа показа­
ны на рис. 4.7. Если вы внимательно читали о двоичных числах, то на рис. 4.7 мо­
жете найти пару интересных закономерностей.

Рис. 4.7. Десятичные, шестнадцатеричные и двоичные числа

Обратите внимание, что четыре бита двоичного числа представляются одним ше­
стнадцатеричным числом. То есть полный байт (8 двоичных разрядов) можно запи­
сать всего двумя шестнадцатеричными символами. В десятичной же системе счис­
ления для байта потребуются три символа. Таким образом, шестнадцатеричная за­
пись — это очень компактный и простой способ представления двоичного кода.
Чтобы преобразовать двоичное число в шестнадцатеричное, байт делится на два
набора из четырех битов, называемых полубайтами, а затем мы берем соответст­
вующее шестнадцатеричное значение каждого полубайта из таблицы, приведенной
на рис. 4.7.

Превратим число 01101001 в шестнадцатеричное:
ОНО = 6

1001 = 9

4. О битах в RISC-машинах

53

Ответ: 69. Поскольку не всегда очевидно, является ли число шестнадцатеричным
или десятичным (69 может быть и десятичным), шестнадцатеричным числам обыч­
но предшествует уникальный символ— например Ох (в этой книге мы будем
писать именно так):
0x69

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

Преобразуем шестнадцатеричные числа
в десятичные и обратно
Чтобы преобразовать шестнадцатеричное число в десятичное, достаточно сложить
десятичный вес каждой цифры. Преобразуем число 0x31А в десятичное:

♦ 3 имеет вес 3 х 162 = 3 * 162 = 3 * 16 * 16 = 768
♦ 1 имеет значение 1 * 161 = 1 х 16 = 16
♦ А имеет значение 1 х 16° = 10 х 1 = 10
В сумме получаем десятичное число 794.
Преобразование десятичного числа в шестнадцатеричное выполняется чуть слож­
нее: мы многократно делим исходное число на 16, пока не будет получен остаток
меньше 16. Этот остаток записывается, а частное делится дальше. Процесс продол­
жается до тех пор, пока само частное не станет меньше 16.
Например, преобразуем десятичное число 4072 в шестнадцатеричное:

♦ 4072/ 16/ 16= 15

=F

Частное: 4072 - (15 х 16 х 16) = 232

♦ 232/ 16

= 14 = Е

Частное: 232 - (14 х 16) = 8

♦ Частное = 8

=8

Следовательно, десятичное число 4072 равно 0xFE8.
Оба преобразования достаточно сложны, поэтому становится ясно, почему проще
сразу работать в шестнадцатеричном формате и забыть о десятичных числах.
К этому вы привыкнете. Сейчас такой метод может показаться вам чуждым, но
позже, когда вы разовьете свои знания ассемблера, он станет вам родным (а еще,
если вы хотите преобразовать шестнадцатеричное в десятичное, вы можете исполь­
зовать число пи!).

Двоичное сложение
Двоичные числа легко складывать и вычитать. На самом деле если вы умеете счи­
тать до двух, то все нужное вы уже знаете. В общем-то, не обязательно складывать
и вычитать единицы и нули «вручную», но в этой главе мы все же рассмотрим
некоторые важные концепции, которые помогут вам в освоении следующих глав
и в конечном итоге в программировании на ARM.

4. О битах в RISC-машинах

54

В сложении двоичных чисел есть всего четыре простых и понятных правила. Вот
они:

♦ 0+0=0

[ноль плюс ноль равно ноль]

♦ 1+0=1

[один плюс ноль равно один]

♦ 0+1=1

[ноль плюс один равно один]

♦ 1 + 1=0(1) [один плюс один равно ноль, один в уме]
Обратите внимание, что в последнем правиле один плюс один равняется нулю,
и один в уме.
Эта самая «единица в уме» называется битом переноса, и ее наличие сигнализиру­
ет о переполнении разряда с переносом единицы в следующий. Тут следует вспом­
нить, что двоичное число 10 — это десятичное 2 (теперь уже можно понять шутку
в начале главы!). Перенос двоичного разряда подобен переносу, который может
произойти при сложении двух десятичных чисел, если результат получается боль­
ше 9. Например, сложив вместе 9+1, мы получим результат 10 (десять)— т. е.
здесь возникает такое же «переполнение» с переходом в следующий разряд:
9+1 = 10. Точно так же при двоичном сложении, когда результат больше 1, мы бе­
рем бит переноса и прибавляем его к следующему разряду (разряду двоек).

Давайте попробуем применить эти правила и сложим два 4-битных двоичных чис­
ла: 0101 и 0100:
0100

0x5
0x4

1001

0x9

0101

+
=

Двигаясь справа налево, мы получаем:
1 + 0

= 1

0 + 0

= 0

1 + 1
0 -1- 0 + (1)

= 0 (1)
= 1

В этом примере мы получили бит переноса в третьем разряде — он переносится
в четвертый разряд, где добавляется к двум нулям. Сложение 8-битных чисел вы­
полняется аналогичным образом:
01010101
+

01110010

0x55
0x72

11000111

0хС7

Если в восьмом бите, также называемом старшим битом, возникает бит переноса,
эта единица переносится во второй байт. Однако в ЦП большинства микросхем
есть другой вариант обработки такой ситуации — флаг переполнения.

Вычитание
Пока что мы работали исключительно с положительными числами, однако при вы­
читании двоичных чисел нужен способ как-то представлять и отрицательные числа.

4. О битах в RISC-машинах

55

В двоичном вычитании используется техника, немного отличающаяся от привыч­
ного вычитания. На самом деле процессор вообще не выполняет вычитание, а вме­
сто этого прибавляет отрицательное значение числа, которое нужно вычесть. На­
пример, вместо выполнения операции 4-3 (четыре минус три) мы выполняем:
4 + (-3) (четыре плюс минус три).
Взгляните на шкалу, показанную на рис. 4.8, и решите пример: 4 4- (-3). Начальная
точка — ноль. Сначала перейдите к точке 4, обозначенной символом > (сделайте
четыре шага в положительном направлении), и прибавьте к ней 3 (сделайте три
шага в отрицательном направлении). Теперь мы находимся в точке 1, которая обо­
значена символом «. Попробуйте использовать этот метод, чтобы вычесть 8 из 12.

Отрицательные

-2

-3

Положительные

-1

1

0

2

5

4

3

«
Рис. 4.8. Вычитание чисел

Чтобы сделать то же самое в двоичном формате, нужно сперва придумать способ
представления отрицательного числа. Мы используем систему, известную как
«двоичный код со знаком». В двоичном коде со знаком бит 7 используется для хра­
нения знака числа. Традиционно о в бите 7 обозначает положительное число, al —
отрицательное.

На рис. 4.9 показано, как строится двоичное число со знаком. Здесь биты от 0 до 6
содержат само значение— в нашем случае «1». Знаковый разряд здесь указывает
на отрицательное значение, поэтому все вместе это дает число -1.

Бит знака
1

Значения битов от нулевого до шестого
0

0

0

0

0

0

1

Рис. 4.9. Знаковое двоичное представление числа -1

На рис. 4.10 показано знаковое двоичное представление числа 127. Значение
01111111 в двоичном формате со знаком соответствует числу 127. Биты 0-6 дают
собственно число 127, и бит знака очищен.

Бит знака
0

Значения битов от нулевого до шестого
1

1

1

1

1

1

Рис. 4.10. Знаковое двоичное представление 127

1

4. О битах в RISC-машинах

56

Дополнительный код
Просто выделив седьмой разряд под знак, мы не сможем представить отрицатель­
ные числа. Сложение -1-1-1 должно давать результат 0, но обычное сложение дает
результат 2 или-2 в зависимости от того, смотрим ли мы на знаковый разряд.
Представление числа в дополнительном коде позволяет кодировать отрицательные
числа в обычном двоичном формате так, что сложение будет работать правильно
без выделения знаковых разрядов.

Чтобы преобразовать число в его отрицательный аналог, мы должны получить его
значение в дополнительном коде. Это делается путем инвертирования каждого бита
и прибавления единицы. Чтобы представить число -3 в двоичном формате, сначала
запишем двоичное значение числа 3:
00000011

Теперь инвертируем каждый его бит, заменив 0 на 1 и 1 на 0. Мы получим его
дополнительное значение’.
11111100

Прибавим 1:
11111100
+ 00000001
= 11111101

Таким образом, значение -3 в дополнительном коде выглядит так: 1111101. Теперь
решим наш знакомый пример: 4 + (-3):
4
-3
1

00000100
11111101
= (1) 00000001

Мы получили число 1, как и следовало ожидать, и, кроме того, возникло перепол­
нение в разряде 7 (значение, показанное в решении в скобках). В настоящее время
это переполнение можно проигнорировать, но оно будет нам важно позже — при
выполнении вычитания на языке ассемблера — мы это увидим.

Решим еще один пример: 32 - 16, или 32 + (-16):
♦ число 32 в двоичном формате:
00100000

♦ число 16 в двоичном формате:
00010000

♦ дополнительный код числа 16:
11110000

♦ теперь сложим 32 и -16 вместе:
00100000
+
11110000
= (1) 00010000

32
-16
16

Если не обращать внимание на перенос, получаем результат 16.

4. О битах в RISC-машинах

57

Мы продемонстрировали, что, используя правила двоичного сложения, можно
складывать или вычитать числа со знаком. Если при этом не смотреть на перепол­
нение, мы получаем правильный результат, включая знак. Соответственно так же
можно сложить два отрицательных числа и получить правильный (отрицательный)
результат. Проверим — сложим числа -2 + -2:
♦ число 2 в двоичном формате:
00000010

♦ число 2 в дополнительном коде:
11111110

♦ возьмем два таких числа и выполним:

(1)

11111110

-2

11111110

-2

11111100

-4

Без учета переноса мы получили число -4. Проверьте правильность ответа, выпол­
нив ту же операцию привычным вам способом.

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

Когда двоичные числа не складываются
Существует несколько случаев, когда дополнительный код работает по-особенному, и, что интересно, они связаны с числами 0 и -0.
Первый — при работе с 0 (нулем). Дополнительный код числа 00000000 выглядит
так: 10000000. Отбросив самый старший бит, вы получаете 00000000, что, собст­
венно, и должно быть, ведь числа 0 и -0, по сути, одинаковы.

Вторая ситуация возникает с числом 10000000, поскольку оно не может иметь от­
рицательного значения. Инвертировав число 10000000, получаем 01111111, а доба­
вив единицу, чтобы получить его дополнительный код, получим исходное
10000000, с чего вы начали.

Поскольку старший бит в 10000000 равен 1, значение числа отрицательное. Когда
вы инвертируете его и добавляете 1, то получаете 10000000, что является двоичным
представлением 128, поэтому исходное значение числа должно было быть -128.

Эта аномалия является причиной асимметричности целочисленных переменных во
многих формах BASIC. В 8-битном формате значения лежат в диапазоне от -128
до +127, а в 32-битном (четырехбайтовом) — от -2147483648 до +2147483647.

58

4. О битах в RISC-машинах

Стандартный калькулятор
В зависимости от настроек, которые вы задали при установке операционной систе­
мы Raspberry Pi на плату, вы, возможно, найдете в меню Accessories программукалькулятор. Она запускается на рабочем столе и полезна для выполнения многих
числовых задачек, которые мы обсуждали здесь, а также других операций, которые
мы рассмотрим в ходе чтения книги, если выбрать в ней научный режим. Предла­
гаю вам самим исследовать ее возможности. Перевод чисел из одной системы
в другую — например, в двоичную, шестнадцатеричную и десятичную, там тоже
предусмотрен. Есть там даже и восьмеричная система (с основанием 8).

5. Соглашения ARM

У процессоров ARM особый дизайн. Он называется архитектурой, поскольку
определяет, как устроен и как выглядит процессор с точки зрения пользователя.
Понимание этой архитектуры — важный аспект обучения программированию мик­
росхем. Вы должны понимать, как те или иные ее компоненты сочетаются друг
с другом и как взаимодействуют, поскольку большая часть машинного кода, кото­
рый мы будем писать, обращается к различным компонентам процессора ARM
и управляет ими.
Благодаря архитектуре с сокращенным набором команд процессор имеет возмож­
ность делать много всего, используя небольшой набор команд. Способ его работы
определяется режимом. Это означает, что после того, как вы разберетесь с базовы­
ми компонентами чипа ARM, вам нужно будет понять, в каких режимах он может
работать. С другой стороны, во время обучения программированию ARM вы почти
всегда будет работать в режиме пользователя (User Mode).

Длина слов
В предыдущих главах в примерах с двоичными числами мы использовали однобай­
товые значения. И не случайно: на первых популярных компьютерах машинный
код работал именно с такими числами. Поэтому и дизайн печатных плат этих ком­
пьютеров обеспечивал наличие на них всего восьми линий передачи данных. Каж­
дая линия напрямую была связана с битами в байтах процессора. Таким образом,
ЦП мог перемещать данные по плате, переключая логический уровень в каждой
линии, устанавливая его значение в 1 или в 0. Он делал это, изменяя напряжение на
линии между 5 и 0 В.

Микросхема ARM устроена сложнее и может работать намного быстрее, т. к. пере­
мещает за один раз более крупные единицы информации. Процессоры ARM созда­
ны как в 32-битном, так и в 64-битном вариантах. 32-битный вариант соответствует
четырем байтам информации. То есть в нем вместо восьми линий связи использу­
ются 32. Вместе эти четыре байта называются словом. Таким образом, длина слова
в процессоре ARM составляет четыре байта. Однако он может столь же эффектив­
но работать и с однобайтовыми словами. Имейте в виду, что в других компьютер-

5. Соглашения ARM

60

ных системах длина слова может быть другой, но на Raspberry Pi длина слова со­
ставляет четыре байта, или 32 бита.

Самый старший бит (most significant bit, msb) в слове ARM расположен на позиции
31 (ЬЗ1), и, если возникает переполнение, из бита 31 поднимается бит переноса.
Если перенос произошел из бита 7, он будет перенесен в бит 8, т. е. во второй байт.

Доступ к памяти по байтам и словам
Будучи обладателем компьютера или смартфона, вы знаете, зачем и для чего в ком­
пьютере нужна память. Чем больше ее имеется, тем больше данных можно хранить.
Каждый адрес в памяти имеет уникальное расположение. Как правило, с увеличе­
нием разрядности процессора увеличивается и объем памяти, к которой можно об­
ращаться напрямую. В ранних чипах ARM для адресации памяти использовались
только 26 битов из 32. Это накладывало определенные ограничения на процессор и,
конечно же, на объем памяти, к которому можно было обращаться напрямую, но в
более поздних чипах ARM появилась полная 32-битная адресация. Доступ к наи­
меньшему адресу в этом диапазоне осуществляется путем размещения во всех
строках нулей, а к максимальному — путем размещения во всех строках единиц.
Первый адрес: охоооо, а самый высокий: Oxffffffff (или 0x3fffffff на старых 26-битных шинах ARM до Raspberry Pi).

Устройства управления памятью в процессорах ARM допускают 32-битную адре­
сацию. На рис. 5.1 схематично показано, как устроена память в виде блоков слов
длиной в четыре байта. За счет этого диапазон адресов увеличен до охооооооооOxFFFFFFFF.

Бит 31

Бит 00

(Слово0)

ЬОЗ

Ь02

Ь01

ЬОО

(Слово 1)

Ь07

Ь06

Ь05

Ь04

(Слово 2)

ЬОВ

ЬОА

Ь09

Ь08

(Слово 3)

bOF

ЬОЕ

bOD

ЬОС

Рис. 5.1. Блоки слов памяти на ARM

ARM «видит» память в виде таких вот блоков слов, но способен обращаться и
к отдельным байтам в каждом слове. С точки зрения работы вся память организо­
вана в виде блоков, разделенных по словам. На рис. 5.1 эти блоки называются Сло­
во 0, Слово 1, Слово 2, Слово 3. Заметим, что в Слове 0 содержатся байты Ь00, Ь01,
Ь02 и ЬОЗ, в Слове 1 — байты Ь04, Ь05, Ь06 и Ь07 и т. д. Это расположение изме­
нить нельзя. То есть мы не можем получить блок из байтов Ь02, ЬОЗ, Ь04 и Ь05.
(Обратите внимание, что буква b здесь означает «байт», а не «бит», как это было
в предыдущих примерах.)
Позже в ARM увидели потребность в 64-битных процессорах и начали разрабаты­
вать новые проекты. Началось это задолго до того, как команда анонсировала но-

5. Соглашения ARM

61

вую архитектуру ARMv8 — первую архитектуру ARM с 64-битным набором ко­
манд. Кроме того, ARM учли ошибки и успехи других разработчиков микросхем,
которые перешли на 64-битную архитектуру. Новая 64-битная архитектура ARM
полностью совместима с созданной ранее 32-битной архитектурой. Это означает,
что если процессор работает в 64-разрядной операционной системе, он может за­
пускать и 32-разрядный код ARMv7 (или двоичные файлы).
Плата Raspberry Pi 2В vl.2 была первой в серии плат с архитектурой ARMv8, и за­
тем такими же стали Raspberry Pi 3 и 4. Эти платы работают в 32-битном режиме,
но могут работать и в 64-битном. Мы немного забежали вперед и вернемся к этому
моменту позже. Поначалу разница будет незаметна.
Ячейки в памяти адресуются уникальным шестнадцатеричным числом. Адрес
памяти, соответствующий началу слова, называется границей слова и «выровнен
по словам». Адрес памяти выравнивается по словам, если его значение делится на
четыре. Следующие адреса выровнены по словам:
0x00009030

0x00009034
0x00009038
0х0000903С

Выравнивание адресов по словам важно для работы ARM, поскольку именно от
этого зависит, как чип ARM выбирает и выполняет машинный код. Например,
адрес 0x00009032 не выровнен по словам. Сохранить инструкцию машинного кода
ARM на таком адресе нельзя.
В ассемблере GCC есть несколько инструментов, которые помогают правильно
управлять границами слов. Поэтому, если вы соберете код с неправильной адреса­
цией, появится сообщение об ошибке. Но мы пока в начале нашего пути, и подоб­
ные проблемы возникнут не скоро.

Регистры
У процессора ARM есть несколько внутренних областей памяти, в которых он хра­
нит, отслеживает и обрабатывает информацию. Такая память ускоряет работу и
выполнение операций, поскольку для них не требуется доступ к внешней памяти.
Эти внутренние области называются регистрами. В пользовательском режиме
(стандартный режим) доступно 16 регистров, каждый из которых может содержать
слово (четыре байта) информации. Каждый из регистров — отдельное слово ARM.
На рис. 5.2 показано, как устроена эта память, плюс добавлен дополнительный ре­
гистр — регистр состояния (статуса).

Как можно здесь видеть, регистры R0-R12 доступны для использования в любое
время. У регистров R13-R15 свое определенное назначение, при этом R13 и R14 ис­
пользуются лишь изредка и управляются всего несколькими командами. Вы, как
программист, можете тоже пользоваться этими регистрами для своих целей. А вот
регистр R15 лучше не трогать. И не потому, что мы не можем это сделать, а потому,

5. Соглашения ARM

62

что нужно четко понимать, что вы с ним делаете и что неверный шаг может доба­
вить вам проблем. Команды ARM могут обращаться к R0-R14 напрямую, и боль­
шинство команд могут обращаться к R15.

Рис. 5.2. Банк регистров ARM в режиме пользователя

Поскольку каждый регистр имеет ширину в одно слово, в регистре может хранить­
ся один адрес. Другими словами, регистр может содержать число, которое указыва­
ет на любое место на карте памяти Raspberry Pi. Основная функция регистров —
хранить такие адреса.
Ассемблер GCC позволяет нам использовать указанные здесь короткие обозначе­
ния (такие, например, как ro и rio) для обращения к ним.

Команды ldr и str позволяют различными способами загружать регистр из памяти
или сохранять его в память. Вот пара примеров команд:
LDR Rl,[R5]

@ загрузка в R1 данных из адреса R5

STR Rl,[R6]

@ сохранение данных из R1 по адресу, указанному в R6

В обоих примерах ожидается, что в одном из регистров будет содержаться адрес
памяти. Эти регистры заключены в квадратные скобки, давая тем самым ассембле­
ру понять, что они содержат адреса. Такой тип обозначения называется режимом
адресации, и у ARM есть несколько режимов адресации. Мы рассмотрим их в сле­
дующих главах.

5. Соглашения ARM

63

Регистр R15: программный счетчик
Регистр R15, или программный счетчик (Program Counter, PC),— весьма нужный
элемент. Если не проявлять должную осторожность в работе с ним, вся ваша про­
грамма может «сломаться». Его задача проста— отслеживать, где именно ваша
программа выполняет машинный код. По сути, в этом счетчике хранится адрес сле­
дующей команды, которую нужно выполнить. Чуть позднее мы рассмотрим этот
регистр более подробно — ему посвящена глава 13.
Ассемблер GCC позволяет обращаться к программному счетчику как по имени
так и через обозначение рс. Например:

R15,

0 перемещение R0 в R15, программный счетчик

MOV PC, R0

Обозначение рс работает так же, как и R15.

Регистр состояния текущей программы
Регистр состояния текущей программы (CPSR, Current Program Status Register) —
или просто регистр состояния — хранит важную информацию о текущей програм­
ме и результатах операций, которые она выполняет сейчас или уже выполнила. От­
дельные биты в этом регистре обозначают некоторые заранее заданные события и
позволяют понять, произошли они или нет. Как именно информация попадает
в этот регистр? За счет управления значениями отдельных битов в регистре. На
рис. 5.3 показано, как это работает.
31

30

29

28

N

Z

С

V

27...8

7

6

5

I

F

Т

4

3

2

1

0

Режим

Рис. 5.3. Конфигурация регистра состояния

Четыре старших бита содержат так называемые флаги, которые обозначают нали­
чие или отсутствие некоторого события. Вот эти флаги:

♦ N — отрицательный флаг (Negative);
♦ z — нулевой флаг (Zero);

♦ с — флаг переноса (Carry);

♦ v — флаг переполнения (Overflow).
После выполнения команды процессор ARM, если нужно, обновляет регистр
состояния. При возникновении одного из проверяемых условий в нужный флаг по­
мещается значение 1, т. е. флаг поднимается (устанавливается). Если условие не
возникло, флаг сбрасывается — в него помещается 0.

64

5. Соглашения ARM

Биты и флаги
Если вы читали разделы о двоичной арифметике, то некоторые из приведенных да­
лее соображений будут вам понятны. У нас есть отрицательные числа, и флаг
Negative используется для обозначения отрицательного числа. Флаг Carry— это
бит переноса. Мы обсуждали перенос на 8-битных операциях, но и у 32-битных
чисел все работает точно так же. Флаг Zero поднимается, если результат оказывает­
ся равен нулю. Флаг Overflow нам не знаком, но с ним тоже все просто — он под­
нимается, если операция вызвала перенос из бита 30 в верхний бит в бите 31. Если
так произошло после операции с числами со знаком, это может указывать на отри­
цательный результат, даже если отрицательное число на самом деле не получилось
(вспомните, что биты нумеруются с нуля, поэтому 32-й бит фактически имеет но­
мер 31, или ЬЗ1).

Например, если результат операции дал 0, будет установлен флаг Zero. Это бит z на
рис. 5.3. Если команда сложения генерирует бит переноса, тогда поднимается флаг
с. Если перенос не возник, флаг переноса будет снят (с = 0).

В языке ассемблера есть мнемоника, которая позволяет проверять флаги регистра
состояния и выбирать действия в зависимости от их состояния. Вот пара примеров:
BEQ zeroset

0 переход к метке zeroset, если Z = 1

BNE zeroclear

0 переход к метке zeroclear, если Z = 0

Здесь beq (Branch if EQual) — это «переход» к указанной метке, который выполня­
ется в случае, если установлен флаг Zero. Команда bne (Branch if Not Equal) выпол­
няет переход к указанной метке, если флаг Zero сброшен.

Аналогичные команды есть и для других флагов. Команда bne часто используется
для повтора некоторого участка кода в цикле заданное количество раз, пока счет­
чик не уменьшится до 0, после чего будет установлен флаг Zero.
Биты I и F, также показанные на рис. 5.3, называются битами отключения преры­
вания, и о них мы поговорим в главе 29. Бит т касается состояния процессора. На
этом этапе мы предположим, что он всегда равен 0 и обозначает некоторое состоя­
ние процессора ARM (мы вернемся к этому в главе 27). Последние пять битов
используются для обозначения режима процессора— мы будем в основном ис­
пользовать режим пользователя (и об этом тоже в главе 29).

Интересно, что получить доступ ко всему регистру состояния одной инструкцией
нельзя. Вы можете управлять его содержимым только на битовом уровне, выполняя
соответствующие действия.

Установка флагов
Существуют две команды, которые позволяют напрямую управлять флагами реги­
стра состояния: смр (СоМРаге) и cmn (CoMpare Negative). Первая используется часто
и записывается вот так:
СМР

65

5. Соглашения ARM

Команда смр выполняет условное вычитание, определяя разницу между операндом2
и операндом!.. Физический результат вычитания игнорируется, но флаги регистра
состояния обновляются в соответствии с результатом вычитания, который может
быть положительным, нулевым или отрицательным (переноса тут быть не может).
Если бы результат вычитания был равен 0, был бы поднят флаг Zero.
0перанд1 всегда является регистром, а
непосредственно значением. Например:

операнд2

может быть как регистром, так и

CMP RO, R1

@ сравнение RO с Rl. R0 минус R1

CMP R0, #1

@ сравнение RO с 1. R0 минус 1

Команда смр часто используется в сочетании с инструкцией
перехода в другую часть программы:

beq для

создания

СМР RO, R1

BEQ zeroflagset

Этот код передает управление той части программы, которая начинается с метки
zeroflagset, если сравнение R0 и R1 оказывается равным нулю. Если переход не
происходит, это будет означать, что результат операции смр не был нулевым
и выполнять команду не было бы необходимости. Далее мы напишем код, который
все исправит.
смр и cmn — единственные команды, которые напрямую позволяют влиять на ре­
гистр состояния. По умолчанию остальная часть набора команд ARM не обновляет
регистр состояния. Например, если в ro и ri лежат значения 1 и мы выполним
команду:

SUB RO, RO, Rl

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

Суффикс S
Однако в ARM есть метод, позволяющий операциям вроде sub обновлять регистр
состояния. Это делается с помощью суффикса set — достаточно добавить символ s
в конец мнемоники команды, которую мы хотим использовать для изменения фла­
гов:
SUBS RO, RO, Rl

Команда выполнит вычитание содержимого
новит флаги в регистре состояния.

ri из ro, положит

результат в ro и об­

Суффикс s позволяет вам как программисту использовать на один набор команд
меньше. Без него надо было бы написать так:
SUB RO, RO, Rl
CMP RO, #0

BEQ iszero

66

5. Соглашения ARM

Но, используя этот суффикс, мы можем удалить строку

смр:

SUBS RO, RO, Rl
BEQ iszero

Ассемблер GCC распознает использование суффикса s. Можно также использовать
пробелы между инструкцией и буквой s, поэтому оба эти примера сработают от­
лично:
SUBS RO, RO, Rl
SUB S RO, RO, Rl

В этом примере показан один из многих способов применения сокращенного набо­
ра команд. Суффикс Set — лишь один из них, а другие мы рассмотрим в главе 9.

R14: регистр ссылок
Рассмотренные нами команды beq и bne относятся к командам условного перехода.
С ними все просто и понятно — они точно выполняют изменение направления ко­
да, т. к. вы задаете ветвь на случай равенства (beq) и на случай отрицательного ре­
зультата (bne). Существует второй стиль команд ветвления, известный как Branch
(или Branch with Link, переход со ссылкой) — BL. Команда bl реализует работу
подпрограммы, а именно переходит в другое место в программе, а затем позволяет
вернуться к следующей после bl команде.
Когда выполняется команда bl, адрес возврата (адрес следующей команды) попада­
ет в регистр R14, называемый регистром ссылок (lr). После завершения подпро­
граммы регистр ссылок копируется в счетчик программы R15 и программа продол­
жает работу с того места, откуда прервалась ранее.

Вот один из способов скопировать регистр ссылок в программный счетчик:
MOV R15, R14

Ассемблер также примет и такой вариант:
MOV PC, LR

Если вы знакомы с языком BASIC, то команды beq и bne похожи на команду goto, а
BL-- на GOSUB.

R13\ указатель стека
Указатель стека (Stack Pointer, SP) содержит адрес, указывающий на область памя­
ти, которую можно использовать для хранения информации. Эта область памяти
называется стек, и у нее есть парочка особенностей, которые мы рассмотрим в гла­
ве 17. Пока стоит лишь отметить, что в ARM по умолчанию реализован один стек,
но вы можете создать столько стеков, сколько захотите.

6. Обработка данных

В этой главе мы рассмотрим некоторые команды, позволяющие выполнять обра­
ботку данных. Это самая большая группа из 18 команд, предназначенных для ма­
нипулирования информацией. Их можно разделить на следующие подгруппы:







ADD, ADC, SUB, SBC, RSB, RSC;

mov, mvn, cmp, cmn;

AND, ORR, EOR;
bic, tst, teq;
MUL, MLA.

Команды and, orr, eor, Bic, tst и teq мы рассмотрим в главе 8.

Остальным командам нужно передать данные по следующему шаблону:

0перанд2 более гибок, чем операнд^ поскольку его можно указать тремя разными
способами. Это может быть регистр ARM в диапазоне ro-ris. Это может быть

68

6. Обработка данных
конкретное значение или константа— например, число. В случае с константами
в коде указывается само число с хештегом #. Операнд 2 также может быть так
называемым смещенным операндом, и к этому мы вернемся в главе 11, когда
будем рассматривать арифметический сдвиг чисел.

Далее приведено несколько примеров использования команд по обработке данных:
ADD RO, Rl, R2

@ RO = Rl + R2

ADDS R2, R3, #1

@ R2 = R3 + 1 and set flags

MOV R7, #128

@ R7=128

Для некоторых команд не обязательно указывать оба операнда. Например, команде
MOV требуется ТОЛЬКО операнд2. Почему операнд2, а не операнд!.! Потому ЧТО ЭТО
может быть и регистр или постоянное значение (или смещенное, как мы увидим
позже).

Команды сложения
В этом разделе мы более подробно рассмотрим команды add и sub, а также подроб­
нее поговорим о том, что происходит в регистрах, включая регистр состояния и его
флаги.

Сложение выполняется двумя командами: add
с добавлением Carry. Синтаксис одинаковый:

и adc.

Вторая — это та же add, но

ADD {}

:

mov

rl, #3

0x10058 < start+4>:

mov

r2, #4

0x1005с < start+8>:

add

r0, rl, r2

0x10560 < start+12>:

mov

r7 , #1

0x10064 < start+16>:

svc

0x00000000

0x10068 Cannot access memory at address 0x10068

167

18. Директивы и макросы

Обратите внимание, что в ассемблированный код были переданы непосредствен­
ные значения в первых двух строках: и . Это не вызов подпро­
граммы. Просто нужный код был вставлен в нужную точку.

Программа 18.3 представляет собой модифицированную версию той же задачи,
только теперь команда mla используется для сложения произведений каждого
умножения. На этот раз мы определяем макрос muittwo три раза, чтобы передать
макросу три набора значений для вычисления:
(2*2)+(3*4)+(5*6)

Однако здесь есть нюанс: на этом этапе проверка ошибок не производится.

/* Реализуем простой макрос #2 */

.global _start

start:

.macro muittwo vail, val2
MOV Rl, #\vall
MOV R2, #\val2
MLA RO, Rl, R2, RO
. endm

MOV RO, #0
muittwo 2, 2
muittwo 3, 4

muittwo 5, 6

MOV R7, #1

@ выход через системный вызов

SWI о

Соберите и запустите эту программу. Появится приглашение, после чего вы можете
получить результат, используя команду:
echo ?$

Должно получиться: 46.
Дизассемблирование этого кода даст результат, похожий на приведенный в листин­
ге 18.2, если вы посмотрите на него с помощью GDB и команды:
x/20i

start

0x10054

< start>:

mov

0x10058

:

mov

r0, #0
rl, #2

0x1005с

< start+8>:

mov

r2, #2

168

18. Директивы и макросы

0x10560
0x10064
0x10068
0x1006c
0x10070
0x10074
0x10078
0x1007c
0x10080
0x10084:

<
<
<
<
<
<
<
<
<

mla
r0, rl, r2, r0
mov
rl, #3
mov
r2, #4
mla
r0, rl, r2, rO
mov
start+28>:
rl, #5
start+32>:
mov
rl, #6
start+36>:
mla
r0. rl, r2, rO
_start+40>:
mov
r7, #1
start+44>:
svc
0x00000000
Cannot access memory at address 0x10084
start+12>:
start+16>:
start+20>:
_start+24>:

Еще раз обратите внимание на то, как макрос встроился в код. Это позволяет нам
выделить несколько особенностей работы с макросами:

♦ окончательный размер кода собранного файла будет больше, чем можно ожи­
дать (теоретически это может создать проблему со скоростью выполнения про­
граммы, но в среде RISC это обычно не проблема);

♦ отладка затрудняется, т. к. легко потеряться в длинных повторах кода;

♦ потребуется больше усилий по сохранению содержимого регистров в нужных
местах, если это необходимо.

Включение макросов
Программы 18.2 и 18.3 демонстрируют нам полезность макросов, но в них макрос
определен в той же программе, в которой вызывается. Основным преимуществом
макросов является возможность создать из них библиотеку, которая позволит вам
просто подключать нужный макрос или библиотеку макросов, если они, конечно,
правильно написаны!
Давайте повторно рассмотрим ту же задачу (суммирование набора чисел), создав
простой файл с макросами, содержащий две функции из программ 18.2 и 18.3. Он
приведен в программе 18.4. которая на самом деле является не стандартной про­
граммой, а лишь содержит определения макросов. А программа 18.5— это про­
грамма тестирования для всего этого.

/* Макросы: Addtwo и MultTwo *
.macro addtwo vail, val2
@ При выходе в Rl, R2 содержатся vail, va!2
@ В R0 лежит результат
MOV Rl, #\vall
MOV R2, #\val2
ADD R0, Rl, R2
endm
macro multtwo vail, val2
@ При выходе в Rl, R2 содержатся vail, val2
@ В R0 лежит накопленный результат

169

18. Директивы и макросы
MOV Rl, #\vall
MOV R2, #\val2
MLA RO, Rl, R2, RO
. endm

Программу 18.4 можно сохранить как обычный исходный файл с расширением .s.
Нет необходимости собирать и связывать его, т. к. это будет сделано, когда он ока­
жется включен в основную программу при вызове. Достаточно того, чтобы имя
файла, используемое во включении (вторая строка программы 18.5), совпадало
с именем, которое использовалось для сохранения файла определения макроса
(а еще они должны лежать в одной папке).

/* Тестирование внешних макросов */

.include "Progl8d.s"

.global _start

_start:
MOV RO, #0
_add:

addtwo 3, 4
_mult:
multtwo 2, 2

_exit:
MOV R7, #1

@ выход через системный вызов

SWI 0

Соберите и запустите файл
ответ: и.

Progi8-5.s

в обычном режиме — вы должны получить

Дизассемблировав исполняемый файл через GDB, вы получите вывод, похожий на
приведенный в листинге 18.3.

0x10054
0x10058

< start>:

mov

< add>:

mov

0x1005с

< add+4>:

mov

r2, #4

0x10560
0x10064

< add+8>:

add

< mult>:

mov

r0, rl,
rl, #2

0x10068

< mult+4>:

mov

r2, #2

0x1006с

< mult+8>:

mov

0x10070

< exit>:

mov

r0, rl, r2,
R7, #1

r0, #0
rl, #3
r2

18. Директивы и макросы

170
svc

0x00000000

0x10074

:

0x10078:

Cannot access memory at address 0x10078

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

Команды ARM для загрузки 0 в регистр ro были приведены в начале кода. Они вы­
полняются, даже если в этом нет необходимости, т. к. в результате макроса addtwo
содержимое ro все равно будет перезаписано. Результат процедуры addtwo перено­
сился в программу muittwo и накапливался.
Опять же, здесь мы передали макросам статические значения, чтобы проиллюстри­
ровать сам процесс того, как макрос ведет себя при сборке. Но можно также для
передачи значений в такие макропрограммы использовать память, если мы не зна­
ем, какими будут используемые значения на момент создания макроса. Это можно
сделать и с помощью стека, используя методы, описанные в предыдущей главе.
Однако будьте осторожны при использовании фреймов стека в макросах, посколь­
ку лавина ошибок в настройке стека может иметь катастрофические последствия
для ваших данных и управления программой.

Совет
Если в вашем коде есть несколько меток и вы не уверены, где искать начало, а где
конец, тогда можно упростить дизассемблирование кода подобным образом:
disassemble _start, _exit+8

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

Многие из более крупных программ в этой книге могут быть сконструированы
и реализованы как макросы. Я показал вам лишь несколько.
Пока что все наши программы представляли собой единый линейный файл, чтобы
его было легче читать и понимать. Но с этим мы уже освоились и готовы идти
дальше.

19. Работа с файлами

Файлы весьма важны для работы вашей Raspberry Pi. Практически все, что вы де­
лаете, так или иначе связано с использованием файлов. Raspberry Pi OS в основном
как раз и занимается управлением файлами. У этой системы имеется инфраструк­
тура, позволяющая программам взаимодействовать с ней и выполнять большинство
операций с файлами. Это могут быть самые разные операции: от создания файлов
и открытия и закрытия файлов до многих других операций, наличие которых мы
привыкли принимать как должное.
В главе 8 мы видели, как можно взять строку текста и изменить ее или преобразо­
вать из верхнего регистра в нижний. Сам текст ASCII у нас указывался в виде стро­
ки как часть самой программы. То есть мы знали, где находится строка, поскольку
ей соответствовала именованная метка. А что, если бы нужная нам информация
находилась в файле на SD-карте или USB-накопителе?
Файлы являются фундаментальным элементом всех компьютерных операций, осо­
бенно в ОС Raspberry Pi (Raspbian). В этой главе мы рассмотрим, как создавать, от­
крывать, закрывать, читать и записывать файлы. В табл. 19.1 приведены системные
вызовы, которые мы будем использовать с этой целью.

Таблица 19.1. Системные вызовы, используемые для работы с файлами
в программе 19.1 (см. далее)
Операция

Описание

Вызов



Read

Чтение из файла

sys_read

3

Запись в файл

sys write

4

Открыть или создать файл

sys open

5

Close

Закрыть файл

sys close

6

Create

Создать новый файл

sys create

8

Sync

Очистить файл

sys sync

Write
Open

118

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

172

19. Работа с файлами

темного вызова (приведенный в табл. 19.1) должен быть предварительно загружен
в регистр R7, а нужные дополнительные сведения лежат в регистрах R0-R6. Не все
регистры нужны для всех вызовов, но если вы не знаете точно, какие именно нуж­
ны, можете предположить, что все. Регистр ro часто используется для возвращае­
мой информации — например, номера ошибки или результата.

В программе 19.1 показано, как использовать эти вызовы. Это может быть про­
смотр содержимого файла или чтение первых его 26 символов в буфер памяти пе­
ред записью в новый файл. В нашем примере предполагается, что файлы находятся
в текущем каталоге или в том же каталоге, что и сама программа. А 26 символов
здесь — это просто алфавит заглавных букв, который мы преобразуем в нижний
регистр, а затем запишем его в новый файл. Программа также проиллюстрирует
проверку некоторых файлов на наличие ошибок.

/* Создание файла и доступ к нему с помощью системного вызова

*/

/* Создание и открытие файла, чтение из файла, запись в файл

*/

.global _start
_start:
0 Открытие файла для чтения.
0 Предполагается, что файл находится в текущей папке.

0 В противном случае генерируется сообщение об ошибке (errorl).

LDR RO, =inputFile

0 адрес имени файла

MOV Rl, #o_rdonly

0 флаг ’’только для чтения'

MOV R2, #s_rdwr
MOV R7, #sys_open

0 вызов открытия файла

SWI О

MOVS R8, R0
BPL moveon

0 сохранение флага в R8

0 при положительном значении
0 переход на moveon

MOV RO, #1

0 вывод на экран

LDR Rl, =errorl

0 адрес сообщения errorl

MOV R2, #18

0 длина строки

MOV R7, #4

0 запись кода

SWI 0

В finish

0 прерывание программы

moveon:

0 Создать и/или открыть файл для записи
LDR RO, =outputFile

MOV Rl, #(o_create+o_wronly)
MOV R2, #s_rdwr

0 права доступа

19. Работа с файлами
MOV R7, #sys open
SWI 0
MOVS R9,, RO

BPL readlinein

MOV RO, #1
LDR Rl, =error2

173
0 загрузка системного вызова 5
0 вызов

0 сохранение флага
0 при положительном значении переход
0 при отсутствии файла еггог2

MOV R2, #18
MOV R7, #4

SWI 0
В finish

0 прерывание программы

readlinein:
MOV RO, R8
LDR Rl, =inbuffer

MOV R2, #alphabet

0 считывание строки из InFile . txt
0 дескриптор файла R8 > R0
0 расположение inbuffer
0 длина алфавита

MOV R7, #sys read

SWI 0
MOV RIO , RO

MOV Rl,

0 InFile » InBuffer
0 сохранение байт в R10

#0

LDR R0,:=inbuffer

STRB Rl ,

[RO, RIO]

0 запись символа окончания в буфер

convertUpperCase:
PUSH {R8}

PUSH {R9}
MOV R8, #0

0 счетчик

LDR R0, =inbuffer

0 перемещение файла co входа на выход

LDRB Rl,

0 сравнение через ORR

loop:

[RO, R8]

ORR Rl, Rl, #0x20
LDR r0, =outbuffer

STRB Rl,

ADD R8,
CMP R8,

[RO, R8]
#1
#26

0 инкрементный счетчик

0 достигнута ли длина алфавита?

BNE loop

0 если нет, продолжаем

POP {R9}

0 восстановление файлов

POP {R8}

writebuffer:
MOV R0, R9
LDR R1, =outbuffer
MOV R2, #alphabet

0 адрес выходного буфера
0 длина алфавита

MOV R7,

0 запись. буфера после преобразования

#sys write

SWI 0

MOV R1, #0

19. Работа с файлами

174
0 закрытие файла 'infile'

MOV RO, R8
MOV R7, #sys_fsync
SWI 0

MOV RO, R8
MOV R7, #sys_close

SWI 0
0 закрытие файла 'outfile'

MOV RO, R9

MOV R7, #sys_fsync

SWI 0
MOV RO, R9

MOV R7, #sys_close
SWI 0
finish:

MOV RO, #00 код возврата 0

MOV R7, #1
SWI 0

.equ sys_open, 5
.equ sys_read, 3

.equ sys_write, 4
.equ sys_close, 6
.equ sys_fsync, 118
.equ o_rdonly, 0

.equ s_rdwr, 0666
.equ o_wronly, 1

.equ o_create, 0100
.equ alphabet, 26

0 длина файла в байтах

.data

inputFile:

.asciz "infile.txt"

outputFile:

.asciz "outfile.txt"

errorl:

.asciz "Input file error \n"

error2:

.asciz "Output file error\n"

inbuffer:

outbuffer:

.fill (alphabet+1), 1, 65

.fill (alphabet+1), 1, 66

В последней части программы 19.1 есть несколько операторов equ, определяющих
константы (речь идет о строках ниже метки finish:). В областях раздела .data хра­
нится строковая информация. Эти области невероятно важны для программы, по­
скольку в них содержатся значения системных вызовов, значения флагов и имена
файлов. Такие названия и определения меток являются общепринятыми и поэтому
способствуют удобочитаемости программ. Впрочем, многие программисты созда-

19. Работа с файлами

175

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

Для открытия файла системе требуется минимум три параметра, которые лежат
в первых трех регистрах:
♦ ro — указатель на имя файла, которое нужно открыть, хранится в виде строки
ASCII с завершающим null;



ri — флаг, определяющий режим работы с файлом: чтение, запись, чтение/
запись;

♦ R2 — значения режима доступа.
Первые строки программы открывают файл, из которого мы хотим считать данные.
Системный номер вызова: 5. Если запрашиваемого файла не существует, генериру­
ется ошибка. То есть имя файла важно для правильной работы, и его адрес мы за­
гружаем в ro. В регистр ri помещается код, определяющий, что мы делаем с фай­
лом. В нашем случае это код о, поскольку файл открыт только для чтения. В R2
определяются значения разрешений— в нашем случае значение 0666 (восьмерич­
ное 666).

После возврата из вызова в ro будет лежать подробная информация о возникших
ошибках. Если это положительное число, значит, файл был успешно найден и от­
крыт. Если значение отрицательное, файл не удалось либо найти, либо открыть.
Продолжать работу в таком случае невозможно, поэтому в следующем фрагменте
КОДа ВЫВОДИТСЯ Строка Сообщения errorl.

Если все в порядке, значение (оно содержит дескриптор для файла) передается в R8,
делается небольшой переход и работа программы продолжается с метки moveon.
Успешно открыв файл (inputFile), мы делаем то же самое для выходного файла
(outputFile). Правда, в этом случае, если выходного файла не существует, систем­
ный вызов его создаст. Информация снова передается через вызов sys open. Если
все в порядке, происходит переход к метке readlinein, а в противном случае на эк­
ран выводится сообщение еггог2 и программа завершается.
На метке readline in мы сначала перемещаем дескриптор файла, ранее сохраненный
в R8, обратно в ro. В регистре ri лежит адрес, по которому мы сохраняем содержи­
мое читаемого файла, а затем перед вызовом sys-read туда же помещается число,
определяющее количество байтов, которые нам нужно считать. Байты считываются
и сохраняются в определенном месте в памяти RPi. По возвращении из вызова ко­
личество записанных байтов оказывается в ro. Его мы перемещаем в Rio, чтобы
была сохранена информация о том, где мы закончили. Затем в конец буфера запи­
сывается ноль.

Разделы convertupcase и loop выполняют обработку информации, считанной из на­
шего открытого файла. Это может быть любой нужный вам алгоритм. Конкретно
в нашем случае мы считываем каждый байт из inbuffer, выполняем операцию orr
между ним и значением 0x20 для преобразования значения в нижний регистр и со­
храняем его обратно по адресу inbuffer. Поскольку для работы с содержимым нам

176

19. Работа с файлами

нужны регистры R8 и R9, их мы временно помещаем в стек. По завершении значе­
ния R8 и R9 восстанавливаются, и содержимое inbuffer записывается в открытый
выходной файл (с использованием восстановленного R9 в качестве файлового деск­
риптора).
Наконец, оба файла закрываются функциями: sys fsync и sys ciose, опять же,
с использованием соответствующих файловых дескрипторов, расположенных в ре­
гистрах R8 И R9.

Отметим пару моментов. Перед запуском программы создайте входной файл
infiie.txt с помощью подходящего текстового редактора и напишите в начале
файла строку

ABCDEFGHIJKLMNOPQRSTUVWXYZ

Сохраните файл в одной папке с программой 19.1. Убедитесь, что имя файла совпа­
дает с указанным в программе в разделе . data.
В программе 19.1 метки inbuffer и outbuffer используются в качестве буферов для
чтения, преобразования и записи информации. В обоих случаях мы поместим в бу­
феры строки из букв А (64) и В (66), чтобы отличать их друг от друга. Если про­
грамма работает правильно, в них будут использоваться буквы верхнего и нижнего
регистров соответственно. Это удобно, т. к. для отладки программы в любой удоб­
ный момент вы можете выгрузить эти разделы памяти, посмотреть, что в них ле­
жит, и понять тем самым, на каком моменте выполнения находится программа.

Соберите и скомпонуйте программу 19.1. Убедитесь, что файл infiie.txt находит­
ся в той же папке, а затем запустите программу. Если все в порядке, в папке по­
явится файл outfiie.txt. Откройте файл outfiie.txt и проверьте результат.
Если в код закрадутся какие-либо логические ошибки, их можно будет отследить
по содержимому файла outfiie.txt. Одно из преимуществ использования в буфе­
рах памяти символьных строк, таких как буквы А и В, заключается в том, что если
в файле outfiie.txt появляется какой-то неверный результат, вы можете точно
определить, в какой момент возникла ошибка.
Если программа работает правильно, вы при желании всегда можете заменить
А и В на 0.

Права доступа к файлам
Ранее при открытии и чтении файлов нам нужно было задавать несколько значе­
ний, которые мы называли флагами и режимами. Мы помещали их в регистры ri
и R2 как часть процесса системного вызова и задавали их числами.

В листинге 19.1 приведена сводка каталога, содержащего файлы, необходимые
и/или созданные в результате выполнения программы 19.1. Вы можете получить
такой список, введя в консоль следующую команду:

Is -1
В результате отобразятся все лежащие в этом каталоге подкаталоги и файлы.

177

19. Работа с файлами

-rwxr-xr-x
-rw-r—г—
-rw-r—г—
-rw-r—г—
-rw-r—г—

1

1
1
1
1

pi
Pi
Pi
Pi
Pi

2384
2496
2813
28
26

Pi
Pi
Pi
Pi
Pi

Sep
Sep
Sep
Sep
Sep

25
25
25
25
25

09:54
09:54
09:54
09:54
09:54

progl9a
progl9a.о
progl9a.s
infile.txt
outfile.txt

Первый столбец — это столбец с атрибутами файла. Эти атрибуты определяются
десятью символами:

-rwxr-xr-x
Если это файл, то после первого символа (-) следующие девять символов идут на­
борами из трех символов и определяют, может ли файл быть прочитан (г), запи­
сан (w), выполнен (х) или нет (-). Три группы символов отвечают за разрешения
«владельца», «группы» и «других пользователей».
Например, первый файл из этого примера может быть прочитан, в него можно за­
писывать значения, и его можно выполнить. Если строка начинается с буквы d, то
перед нами каталог (папка), а не файл.

Атрибуты файла infiie.txt:

-rw-r—г— 1 pi pi

28 Sep 25 09:39 infiie.txt

Здесь мы видим, что первый символ в описании этого файла не а, а дефис (-),
и знаем, что (infiie.txt)— это файл, а не каталог. Затем идут разрешения вла­
дельца - rw-, т. е. владелец может читать и писать, но не может выполнять файл.
Может показаться странным, что у владельца нет всех трех разрешений, но разре­
шение х тут и не требуется, поскольку это текстовый файл, который читается тек­
стовым редактором и не является исполняемым. Разрешения группы установлены
на г—, поэтому группа может читать файл, но не может каким-либо образом записывать/редактировать его — это, по сути, похоже на настройку чего-либо только
для чтения. Те же разрешения применяются и ко всем остальным пользователям.

Сравните это с файлом ргод19а, атрибуты которого:
-rwxr-xr-x 1 pi pi 2384 Sep 25 09:54 progl9a

Здесь первая буква — тоже дефис (-). И мы знаем, что это файл, а не каталог. Затем
разрешения владельца: rwx, так что у владельца есть возможность читать, записы­
вать и выполнять файл. Разрешения для других групп заданы так: гх, поэтому они
могут читать и выполнять файл, но не могут ни записывать, ни редактировать его.
Как эти разрешения соотносятся с числовыми значениями, которые мы использова­
ли в программе? В регистре, отвечающем за режим, мы указали трехзначное вось­
меричное число.

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

19. Работа с файлами

178
г=4

w=2
х=1

Вернемся к атрибутам файла ргод19а. Разбив его на три группы по три, мы полу­
чим:
Owner:

rwx

= 4+2+1 = 7

Group:

r-x

= 4+0+1 = 5

Other:

r-x

= 4+0+1 = 5

То есть нам нужно значение

0755

(помните, что ведущий

У нас также есть два текстовых файла:
Owner: rw-=4+2+0=6

Group: г—=4+04-0=4

Other: г—=4+0+0=4

То есть значение для них получается: 0644.

о

означает основание 8).

20. Использование библиотеки ПЬс

Ассемблер и компоновщик, которые мы использовали для написания и создания
машинных программ,— это лишь небольшая часть компилятора GCC. В самом
начале книги я упоминал, что компилятор GNU GCC — это компилятор С. Он при­
нимает программы, написанные на языке программирования С, и преобразовывает
их в машинный код. Даже круче — он берет исходный файл С и переводит его
в исходный файл на языке ассемблера, который, в свою очередь, переводится в ис­
полняемую программу машинного кода. Мы с вами работали лишь с последней па­
рой процессов, но это лишь верхушка айсберга.

Хотя эта книга не о программировании на С, это не значит, что мы не можем ис­
пользовать инструментарий языка С и компилятора GCC. Например, пьс— стан­
дартную библиотеку функций С. Как показано в предыдущей главе, мы можем
задействовать системный вызов операционной системы для выполнения часто
используемых операций: таких как ввод/вывод, управление памятью и операций со
строками.

Использование функций языка С в ассемблере
У языка С нет встроенных средств для выполнения этих функций, но зато есть ин­
терфейс, позволяющий получить к ним доступ без необходимости вникать во внут­
реннее устройство системного вызова. Кроме того, многие компоненты программ,
которые вы, возможно, захотите написать, уже есть в готовом виде в пьс или дру­
гих библиотеках С, и их можно подключить к вашему коду. То есть существуют
уже собранные библиотеки, сразу готовые к подключению во время компиляции.
На рис. 20.1 схематически показано, где библиотека пьс находится в общем ин­
терфейсе.

Рис. 20.1. libc и пользовательское пространство

180

20. Использование библиотеки libc

Ядром в нашем случае является ОС Raspberry Pi. Область над ядром — это про­
странство пользователя. Здесь находятся наши файлы. Вспомните адреса, которые
отображались, когда мы использовали утилиту GDB для дизассемблирования про­
грамм. Код библиотеки иьс находится непосредственно поверх ядра, а код наших
приложений находится поверх него. Хотя для нашей работы это не имеет особого
значения, на схеме видно, насколько просто функциям libc подключиться к ядру.
Обычно приложение, будучи часто написанным на С, использует интерфейс libc
для доступа к системным вызовам. Реже программист напрямую обращается к сис­
темному вызову.
Основная причина использования системных вызовов напрямую вместо libc за­
ключается в экономии места и потенциальном ускорении. Некоторые программи­
сты также считают такой подход к программированию более «чистым» по сравне­
нию с кодом, создаваемым после интеграции с иьс. Кроме того, библиотека иьс
тоже занимает некоторый объем памяти, и большая часть ее функционала нам не
нужна. Обычно это не проблема, но для шустрой и небольшой процедуры, где важ­
на скорость и затраты памяти, от иьс стоит отказаться, поскольку технически ваша
собственная программа становится процедурой, использующей ресурсы, предос­
тавляемые libc.

Пользователю важно иметь под руками экземпляр «Справочного руководства по
библиотеке GNU С». Но не для того, чтобы изучать С (хотя и это неплохо, чтобы
осознать его фундаментальное значение для системного и прикладного программи­
рования), а ради информации о функциях, которыми можно воспользоваться.
В описаниях функций сказано, какая информация им требуется, а какая информа­
ция возвращается. В этой главе мы рассмотрим несколько проработанных приме­
ров по ним, опираясь на упомянутый документ. Справочное руководство по биб­
лиотеке GNU С можно скачать с веб-сайта GNU. Еще один быстрый способ найти
информацию — онлайн-руководство. В командной строке введите:
man printf

и перед вами появится много информации об использовании и директивах указан­
ной функции С. Команда man означает «руководство», и она выводит справку по
указанной функции.

Структура файла исходного кода
Формат файла исходного кода, используемого с полным компилятором GCC, не­
много отличается от того, которые мы видели до сих пор. Создать его столь же
просто, а компилировать его даже проще, поскольку нам не нужно выполнять эта­
пы сборки и компоновки отдельно— это делается с помощью одной команды.
Взгляните на программу 20.1. Это дополненная версия кода вывода строки, которая
у нас называлась программой 7.1.

20. Использование библиотеки libc

181

/★ Вывод строки с помощью libc - новые требования

*/

/* строка должна заканчиваться нулем, используется функция printf */

.global main
.funс main
main:

STMFD SP!,

{LR}

0 сохранение LR

LDR RO, =string

0 в RO хранится указатель на метку string

BL printf

0 вызов libc

LDMFD SP!,

{PC}

0 восстановление PC

_exit:

MOV PC, LR

0 выход

.data

string:
.asciz "Hello World StringXn"

Прежде всего отметим, что определение global

start

было заменено на global

main:
.global main

.func main
main:

Эта структура важна, поскольку именно с ее помощью компилятор сообщает иьс,
где находится основная программа. Поскольку все процедуры С и lib С написаны
как именованные функции, мы должны объявить эту основную часть нашего кода
как функцию, а затем с помощью метки указать, где именно начинается функция.
Именно это делают приведенные три строки (по сути, они выполняют ту же задачу,
что и метка start при использовании только компоновщика ассемблера, несмотря
на добавление определения функции).

Две команды в начале и в конце основной функции сохраняют, а затем восстанав­
ливают регистр ссылок в стеке. Об этих командах и их использовании мы говорили
в главе 17. Строго говоря, здесь они не нужны, но сохранение регистра ссылок тем
не менее является хорошим тоном. Так что будем придерживаться этого правила.
Функция printf из библиотеки libc выводит строку asciz, определенную в конце
листинга программы. Сама printf— это не команда С, а функция, определенная
в библиотеке, которую мы можем использовать. Это очень универсальная функция,
и перед ее вызовом нужно лишь передать в регистр ro адрес строки. Функция
printf всегда требует, чтобы строка оканчивалась нулем, поэтому используется
директива asciz (и всегда нужно использовать именно ее).
Наконец, мы отказались от привычной функции выхода в пользу более простой
команды mov. Можно было бы применить и команду swi, но полному компилятору

182

20. Использование библиотеки libc

GCC подойдет именно такой метод выхода, более распространенный в мире про­
граммирования. Впрочем, вы можете задействовать метод swi, если для отображе­
ния возвращаемого содержимого вам хочется пользоваться командой echo.
Если теперь мы попробуем собрать и связать эту команду описанным ранее обра­
зом, ничего не выйдет, потому что у программы нет точки входа start. Однако
компиляцию с помощью GCC можно выполнить за один шаг:
дсс

Так что для программы 20.1 компиляция выполняется следующим образом:
дсс -о prog20a prog20a.s

Теперь выполним программу с помощью следующей команды:
./ргод20а

Исследование исполняемого файла
На этом этапе стоит посмотреть на полученный код с помощью GDB.Перекомпи­
лируйте файл, добавив параметр -д для создания данных отладки:
дсс -д -о prog20a prog20a.s

а затем откройте дизассемблер:
gdb ргод20а

Если вы сейчас дизассемблируете код командой
disassemble main

ИЛИ
x/44i main

то по используемым меткам должны увидеть, что библиотечный компонент файла
находится после метки exit:. Однако если вы просмотрите код, то увидите от­
ветвления к адресам, расположенным ранее вашей основной точки входа. Изучите
эти области. Вы увидите, что метки в программе говорят о том, что это код ини­
циализации libc. Мы почти подключили libc к нашей программе! В коде програм­
мы вы, вероятно, заметите некоторые команды, с которыми мы ранее не встреча­
лись (расскажем о них в следующем примере программы).

Подобное изучение листингов программ отлично помогает понять больше в про­
граммировании машинного кода. Помните, что вы можете выполнить этот код по­
шагово и в любое время вывести значения регистров с помощью GDB, чтобы луч­
ше понять, что и как происходит в программе.
Функция printf удивительно универсальна. В программе 20.2 показано, как пере­
дать в printf значения и использовать их при печати результатов.

20. Использование библиотеки libc

183

/★ Печать и передача строки с использованием libc

*/

/* используемые параметры функции в printf

★/

.global main
.func main

main:
PUSH {LR}

0 псевдодиректива

LDR RO, =string

0 RO указывает на строку

MOV Rl, #10

0 первое значение в Rl

MOV R2, #15

0 второе значение в R2

MOV R3,

0 результат в R3

#25

BL printf

0 вызов libc

POP {PC}

0 возвращение исходного значения в РС

MOV PC, LR

0 выход

exit:

. data

string:
.asciz "If you add %d and %d you get %d.\n"

Первое, на что следует обратить внимание, — это то, что команды входа и выхода
раздела main: изменились. Мы используем команды: push и pop. Это директивы
компилятора, а не команды ARM, но они делают то же самое, что и используемые
в программе 20.1. Работать с ними намного проще, т. к. вам не приходится слиш­
ком много думать о том, какой тип стека вы собираетесь использовать и в каком
порядке задействуются регуляторы стека (но если вы решите воспользоваться
другим ассемблером, директивы могут измениться и оказаться несовместимыми
с вашим кодом. И почти наверняка вам придется скорректировать формат кода
с помощью новой программы сборки).
Определение строки выполняется с тремя параметрами. Они обозначены симво­
лом %. Если вы скомпилируете и запустите эту программу, то получите следующий
результат:
If you add 10 and 15 you get 25.

Из листинга программы 20.2 мы видим, что эти три значения были помещены в ri,
R2 и R3. Используя функций libc, такие как printf, мы обычно следуем стандарт­
ному способу передавать и возвращать в них информацию (мы рассмотрим его
в следующей главе, посвященной написанию функций).
В табл 20.1 приведены некоторые параметры вывода функции printf. Список не­
большой, но тем не менее открывает некоторые возможности для экспериментов
с программой 20.2.

184

20. Использование библиотеки libc
Таблица 20.1. Параметры вывода функцииprintf

Код

Назначение

%d

Вывод целого числа в десятичном формате со знаком



Вывод целого числа в восьмеричном формате без знака



Вывод целого числа в десятичном формате без знака



Вывод целого числа в шестнадцатеричном формате без знака (строчными буквами)



Вывод целого числа в шестнадцатеричном формате без знака (заглавными буквами)

%s

Вывод одного символа

%%

Вывод символа %

Ввод чисел с помощью функции scant
Вы, вероятно, подумали, что функция scanf выполняет задачу, обратную printf, но
я прощаю вам это заблуждение. Функция scanf принимает строку символов, вве­
денную с клавиатуры, преобразует ее в числовое значение и сохраняет в памяти.
Например, если при использовании scanf вы набрали:
255

то scanf сохранит двоичный эквивалент числа в памяти. В шестнадцатеричном
формате оно выглядит так:
OxFF

Мы решили обсудить здесь именно эту функцию, а не ее обратного printf собрата,
потому что на ее примере можно проиллюстрировать другой метод обмена данны­
ми с функцией библиотеки libc. Не все функции ожидают данные одинаковым об­
разом. Вам нужно будет иметь это в виду, когда вы научитесь получать доступ
к функциям libc и писать свои собственные функции.
Как и printf, функция scanf распознает множество различных форматов, и вы мо­
жете уделить время изучению и экспериментам с обеими. В следующем примере
мы будем использовать целочисленные значения в формате %а, представленном ра­
нее в программе 20.2.

Формат использования функции scanf следующий (но на С он не буквально такой):

scanf

,

ИЛИ

scanf "%d", integernumber

Порядок использования функции scanf:
1. Объявляем переменную памяти, содержащую адрес строки форматирования.
В нашем примере это будет строка %d.

20. Использование библиотеки libc

185

2. Объявляем переменную памяти, содержащую адрес, куда должно быть помеще­
но значение.
3. Освобождаем место в стеке для сохранения преобразованной строки ASCII.

Обратите внимание, как мы указываем, где лежат данные: мы передаем адреса их
расположения. Это важно, т. к. означает, что для использования косвенных адресов
мы должны объявить переменные, и в нашем случае у нас есть директива .word
в текстовой области. Сами определения строк должны оставаться за пределами тек­
стового раздела и находиться в сегменте данных кода. Также важно знать, что
функция scanf сохраняет свой результат в стеке, поэтому, чтобы предотвратить его
повреждение, нам нужно сдвинуть стек на одно слово, выделив в нем безопасное
место. Программа 20.3 поможет вам с этим разобраться (номера строк нужны лишь
для пояснения программы, и при вводе указывать их не надо).

1

/* Чтение числа с помощью scanf

★/

2
3

/★ через регистры и стек

*/

4

5
6
7

.global main

.func main

main:
PUSH {LR}

8

SUB SP, SP, #4

9

LDR RO, addr format

@ получение адреса формата

10

MOV Rl, SP

@ помещение SP в R1 и

11

BL scanf

@ сохранение записи в стеке

12

LDR R2,

13

LDR R3, addr number

@ освобождение места в стеке

[SP]

14

STR R2,

15

ADD SP, SP, #4

16

POP {PC}

[R3]
@ восстановление РС

17

18

19

exit:

MOV PC, LR @ выход

20
21 /★ поскольку scanf нужны адреса строк, ★/

22 /* сборка их в текстовой области
23
24 addr_format:

.word scanformat

25 addr_number:

.word number

26
27

.data

28 number:

.word 0

29 scanformat:

.asciz "%d”

★/

186

20. Использование библиотеки libc

Рассмотрим этот код. Строки 6 и 18 должны быть вам знакомы — они являются
частью стандартной процедуры. В строке 8 мы сдвигаем указатель стека на четыре
байта, чтобы освободить место для функции scanf. Перед вызовом scanf нам нужно
поместить адрес sp в ri (строка 10) и адрес строки формата в ro (строка 9). Строка
формата содержит информацию о значении, которое будет введено с клавиатуры
и прочитано функцией scanf. За счет этого мы гарантируем правильное преобразо­
вание значения.

После вызова scanf (строка 11) преобразованное двоичное значение хранится в сте­
ке, поэтому оно извлекается (строка 12), а адрес, где оно должно быть сохранено,
помещается в строку 13. Затем значение сохраняется с помощью косвенной адреса­
ции (строка 14). Теперь мы должны вернуть на место указатель, добавив четыре
дополнительных байта, которые мы изначально вычли из него (строка 15).
В строках с 25-й по 29-ю мы создаем адреса, указывающие на фактические данные,
которые будут использоваться. Фактические данные находятся в подразделе .data
(строки 27-29), и адреса этих двух мест хранятся в адресах длины слова в тексто­
вой области, определенной строками 24 и 25. Таким образом, при сборке четырех­
байтовое пространство, созданное в строке 24, будет содержать адрес строки %а.
Это строка форматирования, с которой мы уже знакомы. Строка 25 создает место
для адреса, куда будет помещен результат, возвращаемый функцией scanf.

Когда вы запустите эту программу, то не увидите приглашения к вводу. Просто
введите число, например 255, и нажмите клавишу . Приглашение по­
явится. Скомпилировав программу с включенной отладочной информацией (опция -д),
вы можете использовать GDB для пошагового выполнения кода и опроса регистров
на каждом этапе. Это очень полезное упражнение, и то, что сейчас кажется запу­
танным способом, на самом деле после некоторого осознания оказывается проще
простого!
В приведенной далее программе 20.4 мы добавим функционала, а именно— неко­
торое взаимодействие с помощью printf: запрос значения и последующую печать
результата.

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

Вывод информации
Если у вас нет опыта работы с языком С, вам может быть интересно, как лучше
всего получить всю информацию о функции и понять, как ее использовать. Хоро­
ший вопрос. Если честно, никакого официального ресурса на эту тему нет, и при­
ходится работать методом проб и ошибок, а также искать информацию самим.
Можно полазить по веб-сайтам и форумам. Еще вы можете посмотреть, что делает
функция, создав для нее исходный код и проанализировав его с помощью GDB. По
мере того как ваши знания о машинном коде ARM будут расти, этот вариант станет
вам ближе, и в одной из следующих глав мы рассмотрим, как это сделать.

20. Использование библиотеки libc

187

/★ Чтение числа с помощью функции scanf

*/

/* и вывод с помощью функции prinf

*/

.global main
.func main

main:

PUSH {LR}
SUB SP, SP, #4

0 использование псевдодирективы
0 отступ на слово в стеке

LDR RO, addr_messin
BL printf

0 получение адреса сообщения
0 и вывод его

LDR RO, addr_format
MOV Rl, SP

0 получение адреса формата
0 размещение SP в Rl

BL scanf

0 и сохранение записи в стеке

[SP]

LDR Rl,

LDR RO, addr_messout
BL printf

ADD SP, SP, #4

0 получение адреса scanf

0 получение адреса сообщения
0 вывод всего

POP {PC}

0 подстройка стека
0 восстановление регистра PC

MOV PC, LR

0 выход

exit:

addr messin:

.word messagein

addr format:

.word scanformat

addr messout:

.word messageout

.data

messagein:

.asciz "Enter your number: "

scanformat:

.asciz "%d"

messageout:

.asciz "Your number was Ox%X\n"

21. Пишем функции

Функции — это базовые строительные блоки, которые вы можете использовать для
создания своих программ. У функции есть имя и некоторая цель, и пишется она
таким образом, чтобы при каждом вызове возвращался результат. Функция прини­
мает информацию и выдает результат в зависимости от нее. Она также может пере­
давать информацию обратно в вызывающую программу. Все функции имеют опре­
деленную структуру, и если мы хотим написать функцию сами, то тоже должны ее
соблюдать.

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

Но написание такого кода сопряжено с определенным усилиями, потому что функ­
ция должна соответствовать определенным стандартам, определяющим условия
входа и выхода. Очевидно, что длина функции должна быть больше нескольких
строк, чтобы затраты на само использование функции стоили того.
Часто этап планирования выполняется программистом не так хорошо, как следова­
ло бы. Мне нравится возвращаться к коду после написания и пытаться его реструк­
турировать. Это полезное занятие, и один из методов реструктуризации — посмот­
реть, что можно разбить на функции.

Стандарты функций
В предыдущей главе мы уже рассмотрели пару функций libc, а именно: printf и
scanf, используемые для получения и возврата информации. Мы также увидели,
что передавать информацию в эти функции можно через регистры ro, ri, R2 и R3.
Конкретные используемые регистры определяются тщательно разработанным
стандартом двоичного интерфейса приложения (Application Binary Interface, ABI).

21. Пишем функции

189

Именно он определяет, как должны выполняться функции. Смысл его разработки
заключается в том, что если все программисты будут следовать стандарту и писать
свои функции одинаково, то все они смогут использовать написанные друг другом
функции (программисты на С пользуются стандартом AAPCS, проработанным еще
подробнее). Насколько нам известно, влияние на код у обоих стандартов аналогич­
но, и при желании вы можете почитать и о том и о другом в Интернете.

В табл. 21.1 представлено назначение всех регистров при вызове функций. Если
обобщить эти сведения, получаем, что функция должна работать следующим обра­
зом:
♦ она может свободно изменять значения регистров ro, ri, R2 и R3 и находить
в них информацию, необходимую для своей работы;

♦ она может свободно изменять регистры R4-R12 при условии, что перед возвра­
том в вызывающую процедуру значения будут восстановлены;
♦ она может изменять указатель стека при условии, что позже будет восстановле­
но значение, сохраненное при входе в функцию;
♦ она должна сохранять адрес, находящийся в регистре связи, чтобы программа
позже могла правильно вернуться в вызывающую программу;
♦ она не должна в своей работе опираться на содержимое регистров состояния.
Для функции состояние флагов n, z, с и v неизвестно.

Итак, давайте теперь разберемся в этом чуть подробнее.
Таблица 21.1. Назначения регистров для функций
Регистр

Назначение

Сохранение значения

R0

Аргумент и результат

Нет

R1

Аргумент

Нет

R2

Аргумент

Нет

R3

Аргумент

Нет

R4

Регистр общего назначения

Да

R5

Регистр общего назначения

Да

R6

Регистр общего назначения

Да

R7

Регистр общего назначения

Да

R8

Регистр общего назначения

Да

R9

Регистр общего назначения

Да

R10

Регистр общего назначения

Да

R11

Регистр общего назначения

Да

R12

Регистр общего назначения

Да

LR

Адрес возврата

Нет

SP

Указатель стека

Да

190

21. Пишем функции

Использование регистров
Стандарт гласит, что регистры ro, ri, R2 и R3 (именно в этом порядке) могут являть­
ся для функции источником входных данных. Но это только в том случае, если
функции требуется четыре элемента данных. Если достаточно одного, он помеща­
ется в первый регистр ro, если двух, то второй помещается в ri, и аналогично для
R2 и R3. Если функции достаточно одного входного аргумента, то не имеет значе­
ния, что находится в других регистрах, поскольку они не будут использоваться.
Если функция возвращает значение, оно всегда будет находиться в ro — по край­
ней мере первый байт. Поэтому мы можем использовать функцию echo $?, чтобы
вернуть значение.

Кроме того, необходимо сохранить значения регистров с R4 по R12 включительно,
чтобы, когда вызывающей программе обратно возвращается управление от функ­
ции, содержимое R4-R12 вернулось в состояние до вызова функции. Но это не зна­
чит, что мы не можем использовать эти регистры. Если они нужны в процессе ра­
боты функции, то одно из первых действий, которое она должна делать (но не обя­
зательно самое первое, как станет видно позднее), — это поместить их содержимое
в стек, а затем восстановить его из стека. Для этого достаточно двух команд:
STMFD SP!,

{R4-R12}

@ сохранение регистров R4-R12

LDMFD SP!,

{R4-R12}

@ восстановление регистров R4-R12

Больше трех
Вы можете спросить, что произойдет, если требуется передать в вызываемую
функцию более четырех элементов? Правило на этот счет следующее: мы можем
снова изменить указатель стека (sp) при условии, что мы точно вернем его на место
после завершения функции. Однако это правило не всегда строго верно, потому
что, если нам нужно передать в функцию больше информации, управление sp
должно осуществляться вызывающей подпрограммой, особенно когда объем дан­
ных неизвестен. Если функции всегда нужны дополнительные четыре элемента
данных, то и сама функция может управлять sp, но в этом случае вам нужно быть
очень внимательными.

Как вы помните, в программе 20.2 мы вызывали функцию printf для отображения
трех элементов данных, передавая данные через регистры R1-R3. В программе 21.1
мы станем передавать вызывающей программе шесть значений. Строки в ее лис­
тинге для удобства пронумерованы.
Программа 21.1 почти идентична программе 20.2, за исключением фрагмента со
строки 13, где мы начнем брать значения слов, хранящиеся в строках 31-33 вклю­
чительно, и помещать их в стек. К тому времени, когда мы дойдем до строки 21,
в ro у нас будет содержаться адрес строки, а в ri, R2 и R3 — значения 1, 2 и 3 соот­
ветственно. В стеке будут — сверху вниз — лежать числа 6, 5 и 4. И хотя числа эти
всего лишь одноразрядные, для программы это слова со значениями, каждое из ко­
торых занимает четыре байта.

191

21. Пишем функции

1

/★★ Вывод строки с помощью printf **/

2

/** и передача ей параметров

3
4

через регистры и стек

.global main

5
6
7

★★!
*★!

.func main

main:

8

PUSH {LR}

0 использование псевдодирективы

9

LDR RO, =string

10

MOV Rl, #1

0 RO указывает на строку
0 Первое значение в R1

11

MOV R2, #2

12

MOV R3, #3

0 Второе значение в R2
0 Результат в R3

13

LDR R7,=valuel

0 получение адреса параметра

14

LDR R8,

15

PUSH {R8}

0 загрузка valuel в R8
0 помещение в стек

16

LDR R7,=value2

0 то же самое для value2

17

LDR R8,

18

PUSH {R8}

19

LDR R7,=value3

20

LDR R8,

[R7]

[R7]
0 то же самое для value3

[R7]

21

PUSH (R8>

22

BL printf

23

ADD SP, SP, #12

0 вызов libc
0 балансировка стека

24

POP (PC}

0 восстановление РС

MOV РС, LR

0 выход

25

26

exit:

27
28

.data

29
30 string:

.asciz "Values are: %d, %d, %d and %d\n"

31 valuel:

.word 4

32 value2:

.word 5

33 value3:

.word 6

Если вы запустите эту программу, то увидите на экране следующий результат:
Values are: 1, 2, 3 and 6

Дело в том, что мы сказали функции printf вывести дополнительное значение,
и искала функция его на верхушке стека. Добавление еще пары а% в строку printf
позволяет вывести еще два дополнительных значения. Если вы хотите, чтобы числа
отображались в правильном порядке, нужно будет изменить порядок отправки
чисел в стек.
Строка 23 тоже любопытная. Мы сдвинули sp на 12 байтов за счет трех команд
push. В этой строке sp сдвигается обратно на эти же 12 байтов, что обеспечивает

21. Пишем функции

192

работоспособность системы. Того же самого результата можно было бы добиться
с помощью трех команд pop, но показанный метод более изящен для восстановле­
ния исходного состояния, если вы сбрасываете в стек большой объем данных.

Если в процессе работы функции используются регистры, мы должны сохранить их
содержимое (и восстановить его после завершения функции). Самый простой спо­
соб сделать это — поместить значения из регистров в стек, но это может привести
к тому, что нужные функции данные тоже попадут в стек и не дойдут до нее. В та­
ких ситуациях лучший выход — сохранить содержимое регистра в области памяти,
которую вы отвели под рабочее пространство. В качестве альтернативы вы можете
сначала поместить данные в стек, при необходимости «подвинув» указатель sp до
того, как поместить туда параметры функции.

Если вы пишете функцию, которой через стек передается дополнительная инфор­
мация, и требуется сохранение регистров, вы можете задействовать стек для всего и
сразу, поскольку стеком вы управляете сами. Если ваша функция ожидала трех
элементов в стеке, а вам нужно было сохранить R4, вы можете трижды обратиться
к стеку, используя простое смещение:
LDR R4, SP+4 @ получение первого значения данных в стеке

Второй элемент будет лежать по адресу sp + 8, а третий — в sp + 12.

Помните, что эти команды не изменяют само значение sp, поэтому вы должны
сбросить его по завершении, как мы уже говорили ранее (если сложно все это пред­
ставить, нарисуйте стек прямо на бумажке и попробуйте определить, где находится
каждый элемент данных).

Сохранение ссылок и флагов
При написании функций всегда следует помнить, что по завершении функция
должна будет вернуть управление программой туда, откуда она его забрала. Поэто­
му крайне важно не потерять значение в регистре ссылок. Если в какой-то момент
нужно будет вызвать другую функцию или подпрограмму, вы можете использовать
для этого команды вь или вьх. В этом случае содержимое lr будет перезаписано и,
следовательно, утрачено. Поэтому значение регистра ссылок нужно надежно при­
прятать в рабочем пространстве памяти или поместить в стек для последующего
восстановления.

Сохранять флаги состояния необходимости нет. Функции обычно не работают
с ними. Но функции может потребоваться наличие того или иного флага в момент
возврата обратно в вызывающий код, и этот способ позволяет вернуть информацию
из функции, учитывая, что один из стандартных способов возврата значения — это
регистр ro. Например, при возврате из функции можно поднять флаг N, чтобы ука­
зать на наличие ошибки, а сам код ошибки поместить в ro.

21. Пишем функции

193

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

Надежные процедуры вывода на экран тоже нужны. Мы рассмотрели некоторые из
них в предыдущих главах, поэтому здесь используется функция printf, которую
можно подстраивать в соответствии с вашими потребностями. В следующем при­
мере будет выведен на печать вектор значений ширины слов. Программа 21.2 —
это сама процедура вывода, а программа 21.3 позволит нам написать текст, с кото­
рым она будет работать. Не удивительно, что нам потребуется библиотека пьс,
поскольку в программе используется функция printf. Поэтому сборку нужно будет
выполнить с помощью GCC. Для работы подпрограмме необходим адрес вектора,
содержащего элементы слова, количество элементов и пара указателей, чтобы под­
программа могла отслеживать свое положение в векторе.

/★ printw - Эта процедура печатает вектор слов

*/

/* R0 = адрес вектора

*/

/* R1 = количество элементов вектора

★/

/* R2 = строка указателя для печати первого элемента

★/

/* R3 = строка указателя для печати следующих элементов ★/
.global _printw

.equ _wsize,4

.align 2

printw:
STMFD SP!,
СМР R1,

(R4, R5, R6, R7, LR}

#0

BLE _last
MOV R4, RO

0 выход, если нет элементов

0 сохранение параметров локально

MOV R5, Rl
MOV R6, R2
MOV R7, R3
LDR Rl, [R4], #_ wsize 0 загрузка первого элемента
0 адрес первой строки
MOV RO, R6
BL printf

@ вывод его

SUBS R5, R5, #1

0 декрементный счетчик

BEQ

last

0 выход, если ноль

printw_loop:
LDR Rl,

[R4], #_wsize 0 загрузка следующего элемента

MOV RO, R7

0 адрес следующей строки

BL printf

0 вывод его

194

21. Пишем функции
SUBS R5, R5, #1

@ декрементный счетчик

BNE _printw_loop

@ продолжение цикла, если больше

_last:
LDMFD SP!,

{R4, R5, R6, R7, PC}

@ восстановление значения
0 и возврат

В приведенном далее тестовом коде (программа 21.3) после метки values идет спи­
сок элементов — всего их 11. Их вы можете редактировать по своему усмотрению.

/* Тестовая процедура printw */

.equ _size, 4

.equ _items, 11
.global main

.align 2
.section .rodata
first: .asciz "Vector of words - values : %d"
rest: .asciz ”, %d”

final:

.asciz "\n”

values:

.word 10,

15, 20, 25, 30, 35, 40, 45, 50, 55,

60

.align 2

. text
main:
LDR R0, =values
MOV Rl, = items
LDR R2, =first
LDR R3, =rest
BL —printw

LDR R0, =final
BL printf

MOV R7, #1
SWI 0

Предполагая, что оба исходных файла программ находятся в вашем текущем ката­
логе, вы можете собрать их следующим образом:
дсс -о testprint prog21b.s prog21c.s

Затем вы можете выполнить код с помощью команды
./testprint

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

21. Пишем функции

195

Пузырьковая сортировка
Пузырьковая сортировка, иногда называемая сортировкой по убыванию, является
простейшим алгоритмом сортировки, принцип работы которого заключается в мно­
гократном обмене местами соседних элементов, если они стоят в неправильном
порядке. Алгоритм проходит по списку и повторяется до тех пор, пока список не
будет отсортирован. Алгоритм назван «пузырьковой» сортировкой потому, что
меньшие или большие элементы как бы «всплывают» вверх по списку. Коммента­
рии в коде должны помочь вам понять, что происходит.

Этот простой алгоритм плохо работает в реальном мире и используется в основном
в учебных целях. В жизни используются более эффективные алгоритмы: быстрая
сортировка, временная сортировка или сортировка слиянием, и они уже встроены
в популярные языки программирования, такие как Python.
В коде программы 21.4 мы проверим пузырьковую сортировку. Тестовый набор
(см. программу 21.5} состоит из 16 элементов и включает отрицательные числа со
знаком. Затем процедура печатает последовательность до сортировки и после нее.
Количество элементов вы можете изменить по своему усмотрению.

/★ Пузырьковая сортировка векторов слов

*/

/* R0 = начало вектора элементов
/* R1 = количество элементов для сортировки

*/
*/

/★ R4 = текущий элемент

*/

/* R5 = внутренний счетчик

*/

/* R6 = флаг keep_going
/* R7 = первый элемент

*/
*/

/★ R8 = второй элемент

*/

/* R9 = регистр обмена

*/

.global _bubble
. text

bubble:
STMFD SP! ,
СМР

(R4, R5, R6, R7, R8, R9, LR}
0 должно быть > 1

Rl, #1
exit

0 выход, если ничего не нужно делать

MOV

R5, Rl, #1
R4, RO

0 установка внутреннего счетчика
0 установка текущего указателя

MOV

R6, #0

0 регистр для обмена

LDR
LDR

R7,
R8,

СМР

R7, R8
no swap

BLE
SUB

loop:

BLE

[R4] , #size 0 загрузка элемента
[R4]
0 и следующего элемента
0 сравнение их

0 переход, если второй элемент больше

196

21. Пишем функции
MOV

R6, #1
R4, #size

SUB
LDR

0 поднятие флага keep going

@ сброс на первый элемент

[R4]

@ загрузка слова по нужному адресу

STR

R9,
R8,

[R4]

0 сохранение меньшего значения обратно

STR

R9,

[R4, #size] ! 0 завершение обмена

0 в память

>:
R5, #1

SUBS

BNE

loop

0 декрементный счетчик
0 продолжение цикла, если алгоритм
0 не закончен

end inner:
СМР

R6, #0

BEQ

ex it

MOV

R6, #0

0 выход из цикла, если флаг сброшен
0 сброс флага

MOV

R4, RO

0 сброс указателя

SUB

R5, Rl, #1

0 сброс счетчика

В

_lo},
U16 = {0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
u32 = {0x1, 0x0, 0x0, 0x0},

{0x1, 0x0},
f32 = {0x0, 0x0, 0x0,

u64

0x0},

f64 = {0x0, 0x0} }
q2
{u8 = {0x2, 0x0 < повторяется 15 раз >},
U16 = {0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},

u32 = {0x2, 0x0, 0x0, 0x0},
u64 = {0x2, 0x0},
f32 = {0x0, 0x0, 0x0, 0x0},
f64 = {0x0, 0x0} }

26. Сопроцессор Neon

251

Команды и типы данных Neon
В табл. 26.1 приведены типы данных, которые можно использовать в командах
Neon. Имя типа данных состоит из размера значения в битах и буквенного обозна­
чения формата. Как можно видеть, не все типы данных доступны во всех размерах.
В программе 26.1 в команде vadd указан тип 132. Из табл. 26.1 видно, что это
32-битное целочисленное (или неопределенного типа) сложение. Важно понимать,
что регистры могут содержать один или несколько элементов одного и того же
типа данных. Таким образом, мы можем собрать не общее значение в регистре, а
отдельные значения элементов в векторе.

Таблица 26.1. Обозначения типов данных Neon
Тип

8 битов

16 битов

32 бита

64 бита

Беззнаковое целое

U8

U16

U32

U64

Целое со знаком

S8

S16

S32

S64

Целое
неопределенного типа

18

116

132

164

Число с плавающей
точкой

Не используется

F16

F32

Не используется

Многочлен по {0,1}

Р8

Р16

Не используется

Не используется

Количество элементов, участвующих в операции, указывается через размер регистра:
VADD.I16 QO, QI, Q2

Эта команда говорит, что мы работаем с 16-битными целыми элементами, храня­
щимися в 128-битных Q-регистрах. Вычисления производятся путем деления векто­
ра на несколько дорожек, после чего операция выполняется параллельно на восьми
16-битных дорожках. Схематично этот процесс проиллюстрирован на рис. 26.3 , где
также наглядно видно, как организованы эти самые «дорожки». Команда выполня­
ет параллельное сложение восьми дорожек из 16-битных элементов, которые
берутся из векторов Q1 и Q2, а результат сохраняется в Q0.

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

Далее приведен пример, в котором беззнаковое 16-битное содержимое регистров do
и D1 складывается в четырех 16-битных параллельных дорожках. При этом элемен­
ты do и di складываются, образуя четыре числа-результата, которые помещаются
в D2:
VADD.U16 D2, DI, DO

Если предположить, что в do и di лежат значения, показанные на рис. 26.4 в верх­
них двух строках, то результатом сложения будут значения, показанные на рис. 26.4
в нижней строке.

26. Сопроцессор Neon

252

Рис. 26.3. Управление дорожками данных в Neon

Рис. 26.4. Сложение дорожек в Neon

Не забываем, что регистры do и di вместе называются qo, a D2 является «нижней
половиной» Q1. Вы можете использовать эту информацию в своих интересах, если
хотите манипулировать отдельными дорожками данных. Несколько примеров этого
будет приведено в этой главе позже.

У некоторых команд используются регистры ввода и вывода разного размера. На­
пример, этот код:
VMULL.S16 QO, D2, D3

параллельно умножает четыре 16-битные дорожки, создавая четыре 32-битных
произведения в 128-битном векторе назначения! В теле команды указано, что хра­
нится в этих векторах.
У Neon нет флагов состояния для каждой дорожки в отдельности. Если требуется
получить некоторый общий результат и есть вероятность того, что образуется
большой перенос, то его необходимо обработать, используя более общий результат.
Например так, как показано в примере, приведенном ранее. В противном случае
регистры Neon будут использовать флаги в FPSCR процессора VFP и команды, ра­
ботающие в зависимости от их значений.

Тип данных всегда соответствует регистру источника. Нельзя получить размер
больше 64-битного или меньше 8-битного. Некоторые команды могут повышать
или понижать разрядность в рамках своей нормальной работы:

26. Сопроцессор Neon
VADDL.S32 QO, DO, DI

253
@2 знаковых 32-битных числа превращаются в

@ 64-битные, а затем складываются
VADDW.S32 QO, QO, D2

@ повышение уровня D2 до S64 и выполнение

@ 2x64-бит сложения с Q0

Режимы адресации
Возможности Neon в установке режима адресации ограничены. Например, сле­
дующая команда загружает в do содержимое адреса, хранящегося в ro:
VLD1.64 {DO},

[RO]

Этот режим адресации использовался в сочетании с регистром
тора в программе 26.1,

q для

загрузки век­

Следующий код делает то же самое, но добавляет размер передачи к ro после вы­
полнения передачи, что удобно, когда вы сохраняете данные в последовательно
расположенных блоках памяти.
VLD1.64 {DO},

[R0]!

Наконец, следующая команда складывает содержимое ri и ro, но перед этим в do
загружаются данные, лежащие по адресу, хранящемуся в ro:
VLD1.64 {DO},

[RO], Rl

Параметр Stride команд VLD и VST
Команды загрузки и сохранения в Neon невероятно хороши. Синтаксис команды
состоит из пяти частей:
♦ сама команда: vld — для загрузки или vst — для хранения;
♦ число, задающее промежуток между соответствующими элементами в каждой
структуре (шаблон чередования);
♦ тип элемента, определяющий количество битов в элементах, к которым осуще­
ствляется доступ;

♦ набор 64-битных регистров Neon для чтения или записи (в зависимости от шаб­
лона чередования можно указать до четырех регистров);
♦ регистр ARM, содержащий адрес в памяти, к которому необходимо получить
доступ. Адрес можно изменить после доступа к нему в зависимости от исполь­
зуемого режима адресации.

В программе 26.1 использовалась команда vld, которая загружала в Q1 и Q2 числа из
в памяти, адрес которых лежит в ro:
VLD1.32 {QI},

[R0]

Это простейшая команда загрузки. Шаблон чередования здесь — 1. В этом случае
доступ к данным осуществляется как есть и элементы передаются прямо: один за

254

26. Сопроцессор Neon

другим, последовательно и по порядку. Обычно в качестве значения шаблона чере­
дования используются значения 1, 2, 3 или 4, т. е. берется от одного до четырех
элементов одинакового размера, где элементы представляют собой обычные под­
держиваемые Neon числа шириной 8, 16 или 32 бита:


загружает от одного до четырех регистров данных из памяти без нарушения
чередования. Используется при обработке массива данных без чередования;

vldi

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

♦ VLD2

загружает три регистра и выполняет чередование. Полезно для разделения
пикселов RGB на разные каналы;

♦ VLD3

загружает четыре регистра и выполняет чередование. Применяется для об­
работки данных изображений ARGB.

♦ VLD4

Эти команды широко используются в среде обработки аудиовизуальных материа­
лов. Вы можете задать значение 2 — для разделения стереофонических аудиодан­
ных на левый и правый каналы, 3 — для разделения пикселов RGB на отдельные
каналы и 4 — для обработки изображений ARGB. И это лишь несколько примеров.
Во всех этих примерах команда vst также может делать то же самое перед сохране­
нием данных в памяти.
Рассмотрим следующий пример:
VLD 2.8 {D14, D15},

[R0]

Здесь:
шаблон чередования (шаг). Может быть равен 1, 2, 3 или 4;



2—



8 — тип



D14, D15 — список используемых регистров Neon. Можно использовать до четы­
рех регистров;

данных: 8, 16 или 32 бита;

♦ [ ro ] — регистр ARM, содержащий адрес данных.

На рис. 26.5 показана часть работы команды:
VLD2.16 {DO, Dl},[R0]

Команда загружает в регистры do и di (qo) четыре 16-битных элемента в первом
регистре (do) и четыре 16-битных элемента во втором (di), при этом смежные пары
(х и y) каждого регистра разделены.

Изменение размера на 32 бита позволит загрузить тот же объем данных, но теперь
только два элемента составляют каждый вектор, снова разделенный на элементы х
и у. Работа команды
VLD2.32 {DO, DI}, [R0]

показана на рис. 26.6.

26. Сопроцессор Neon

255

Рис. 26.5. Выборочная загрузка данных из памяти в регистры
С ПОМОЩЬЮ команды VLD2.16 {DO, DI}, [R0]

Рис. 26.6. Выборочная загрузка данных из памяти в регистры
с помощью команды VLD2.32 {DO, DI}, [R0]

Размер элемента может повлиять на объект данных, и, как правило, если вы укаже­
те в командах загрузки и сохранения правильный размер элемента, байты будут
считываться из памяти в правильном порядке. Если нет, то в какой-то момент вам
может потребоваться выполнить пару настроек вручную. Сохранение данных на­
прямую в памяти — всегда хорошо. Например, при загрузке 32-битных элементов
нужно выровнять адрес первого по крайней мере на 32 бита.

Вернемся к программе 26.1. Она берет некоторые простые визуальные данные и
выполняет на них команду vld. Если вы скомпилируете ее, добавив параметр -д, то
можете перейти в GDB, просмотреть регистры, и вам станет понятно, что случи-

256

26. Сопроцессор Neon

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

Одним из наиболее распространенных применений этого вида команд Neon являет­
ся чтение и сортировка данных в формате RGB. Если мы предположим, что инфор­
мация была сохранена в виде последовательности R,G,B, R,G,B, R,G,B в 24-битном
формате, вы можете отсортировать данные на каналы R, G и В, используя команду:
VLD3.8 {DO, DI, D2],

[R0]

При этом информация о красном цвете (R) окажется в do, о зеленом (G) — в di
и о синем (В) — в D2.

Некоторые команды могут ссылаться на отдельные скалярные элементы, для кото­
рых используется синтаксис массива vn[x] (порядок элементов в массиве всегда
начинается с младшего бита).

Рассмотрим такую команду:
VLD4.8 [D0[2], Dl[2], D2[D2], D3[2]},

[RO]

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

Загрузка в прочих форматах
Помимо структурной загрузки и сохранения у Neon также есть два других формата
команд:
♦ vldr и vstr — для загрузки или сохранения одного регистра в виде 64-битного
значения;



vldm

и vstm — для загрузки нескольких регистров в виде 64-битных значений.

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

Neon Intrinsic
Из-за сложности приложений на основе Neon (например, в задачах декодирования
видео или звука на Raspberry Pi) для написания исходного кода и компиляции
в машинный код ARM используется язык С или его производные (например, C++).
Но наиболее прямой способ задействовать Neon — это писать код на ассемблере,
и не в последнюю очередь потому, что код, производимый из файла С, часто быва­
ет расточителен по отношению к ресурсам. Кроме того, С не всегда наилучшим об­
разом использует регистры при компиляции.

Термин «Neon Intrinsic» (внутренний Neon) часто применяется для обозначения
компиляции в Neon из С. Внутренняя функция — это функция языка программиро­
вания, реализация которой специально обрабатывается компилятором. Обычно
внутренний механизм заменяет последовательность автоматически сгенерирован-

257

26. Сопроцессор Neon

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

Внутренние функции часто используются для явной реализации векторизации и
распараллеливания на языках, в синтаксисе которых такие конструкции не преду­
смотрены. Компилятор анализирует встроенные функции и преобразует их в век­
торный математический или многопроцессорный код, подходящий для целевой
платформы.
Neon Intrinsics — это набор определений, которые побуждают использовать Neon
при компиляции программы на С. Некоторые программисты их любят, другие —
нет. Я не фанат программ, которые нужно оптимизировать по производительности.
Компилятор постоянно добавляет между внутренними операциями дополнитель­
ные шаги выгрузки/загрузки регистров. Трудно заставить его упростить эту рабо­
ту — проще просто написать код на чистом ассемблере Neon. На этом уровне хо­
рошо знать, что происходит, и управлять происходящим самостоятельно. Особенно
если важна скорость выполнения.

Массивы Neon
Поскольку Neon позволяет управлять большими объемами данных с помощью од­
ной команды, он часто используется для написания ПО для работы с графикой. На­
пример, если вы поворачиваете изображение на своем смартфоне или планшете,
этот процесс, вероятно, выполняется путем управления блоками данных и команд
Neon.
В следующем примере код поворачивает содержимое блока из четырех регистров q
на 90 градусов. На рис. 26.7 показана матрица до и после поворота. Цифры слева —
это регистры Q, а числа в матрице приведены для того, чтобы было понятнее, как
все выглядит до и после. Данные загружаются в регистры q, и поскольку порядок
массивов всегда начинается с младшего значащего бита, регистры D, связанные
с ними, также будут иметь соответствующие значения.
После поворота

До поворота

Q0

0

1

2

3

12

8

4

0

Q1

4

5

6

7

13

9

5

1

Q2

8

9

10

11

14

10

6

2

Q3

12

13

14

15

15

11

7

3

Рис. 26.7. Поворот данных на 90 градусов

26. Сопроцессор Neon

258

В программе 26.2 приведен код ассемблера, который выполняет этот поворот. Для
наглядности мы используем простые числа, чтобы вы могли визуально оценить
начальный и конечный результаты с помощью дампа регистров в GDB. Давайте
проработаем каждый блок кода и рассмотрим все команды.

/* Поворот матрицы 4x4 на 90 градусов */

.global _start
start:

@ получение указателей данных
LDR R0,=matrix0
LDR Rl,=matrixl
LDR R2,=matrix2
LDR R3,=matrix3

0 загрузка данных в Q0 - Q3
VLD1.32 {Q0},
VLD1.32 {Q1},
VLD1.32 {Q2},
VLD1.32 {Q3},

[R0]
[R1]
[R2]
[R3]

0 транспонирование матрицы, а затем чередование внутренних пар

bpl:
VTRN.32 Q0, Q1

VTRN.32 Q2, Q3
VSWP DI, D4
VSWP D3, D6
0 зеркально отраженная матрица
VREV64.32 Q0, Q0
VREV64.32 QI, Q1

VREV64.32 Q2, Q2

VREV64.32 Q3, Q3

0 обмен местами старшей и младшей половин

VSWP DO, DI
VSWP 02, D3

VSWP D4, D5
VSWP D6, D7
0 сохранение результата

Ьр2:
VST1.32 {Q0},

[R0]

VST1.32 {QI},

[R1]

259

26. Сопроцессор Neon
VST1.32 (Q2),

[R2]

VST1.32 {Q3},

[R3]

MOV R7, #1
SWI 0

. data
matrixO:

.word 0,1,2,3

matrixl:

.word 4,5,6,7

matrix2:

.word 8,9,10,11

matrix3:

.word 12,13,14,15

Некоторые приведенные в этом коде команды мы использовали впервые, и если вы
хотите копнуть глубже, то можете поработать с ними подробнее, заменяя числа
в матрице на цвета, например.
Данные для нашего массива хранятся в конце листинга программы в блоке .data.
В матрице используются цифры от 0 до 15. После запуска кода регистры ro, ri, R2 и
R3 указывают на свои соответствующие строки и загружаются как 32-битные зна­
чения в qo, Qi, Q2 и Q3 соответственно. Если вы посмотрите на значения в регистрах
до выполнения двух команд транспонирования, все будет видно,особенно, если
посмотреть на вывод регистра и32. В регистрах qo, qi, Q2 и Q3 изначально будут на­
ходиться следующие значения (рис. 26.8).

Рис. 26.8. Исходные значения в регистрах QO, QI, Q2 и Q3

Первая операция — это транспонирование самой матрицы. Команда vtrn (Vector
Transpose, транспонирование вектора) обрабатывает элементы своих векторовоперандов как элементы матриц 2>