Лекция 3
Обсуждение домашних заданий и прочих вопросов в дальнейшем, вероятно, будет организовано через группу или беседу в ВКонтакте.
Сегодня поговорим про регистры.
Регистр 1 зачастую неявно используется в псевдоинструкциях, так что использовать его следует с аккуратностью.
Кроме того, на уровне архитектуры несколько отличаются от остальных регистров регистр 0 (в нём хранится нестираемый ноль) и регистр 31 (он используется в операции перехода).
Регистры (их 32 штуки) для удобства программиста поименованы.
Регистр 1 (at)
Если вы не знаете в точности, как работает используемая вами инструкция, не используйте в программе at - в нём часто хранятся временные значения.
Регистры 2 и 3 (v0 и v1) используются для системных вызовов (как для хранения номера системного вызова перед непосредственно вызовом, так и для возврата результата).
Регистры 4 - 7 (a0 - a7) используются для передачи параметров системного вызова.
Регистры типа t, 8 - 15, 24 и 25 (t0 - t7, t8, t9) - temporary, временные; не стоит рассчитывать, что после вызова подпрограммы содержимое этих регистров не изменится.
В отличие от них, регистры типа s, 16 - 23, гарантированно (согласно ABI) должны быть восстановлены к исходным (перед вызовом) значениям после завершения вызова подпрограммы.
k0, k1 - для случаев, когда ваша программа внезапно попадет в режим работы ядра (ваша программа вообще не должна трогать эти регистры, они для ядра).
Регистр 31 - для адреса возврата из подпрограммы
Регистр 29 - stack pointer, указатель на вершину стека
Регистр 30 - frame pointer - регистр кадра (сюда иногда сбрасывается stack pointer, мы об этом поговорим позже)
Регистр 28 - global pointer - через него передаются данные при запуске программы
Большинство из указанных выше функций различных регистров - части ABI (application binary interface), физически почти все регистры одинаковы. Однако следование ABI позволяет сделать программирование на языке ассемблера заметно более удобным.
Регистра флагов в MIPS нет.
Регистры HI и LO - используются в командах деления и умножения; из них, по сути, данные напрямую можно только извлекать
Плоская модель памяти.
См. табличку на странице занятия - она красивая и информативная
С точки зрения программы эта память однородная, с точки зрения архитектуры - не совсем
1. С адреса 0 по примерно 4 MB - зарезервированная область
обращение сюда вызывает исключение
Это нулевая страница памяти
Одна из задач этой штуки - если вы разыменовываете (как указатель) какое-то небольшое число, это наверняка ошибка, и должно быть вызвано исключение
2. Далее - область программного кода - примерно 1/16 часть памяти
Изначально MIPS подразумевала раздельное хранение кода и данных, причём в область кода ничего нельзя было писать, а читать только по адресам, кратным 4
Инструкции типа j используют 26 бит, и больше вам как раз и не нужно (вы как раз можете адресовать всю доступную память)
3. Далее - область данных - примерно половина адресуемых в 32-разрядной модели 4 GB
Здесь находятся стек и куча (heap), которые вы используете для хранения данных вашей программы
heap - для динамических данных
Навстречу куче растёт стек (куча растёт снизу вверх, стек растёт сверху вниз)
4. Все старшие 2 GB памяти - область ядра (область кода и область данных)
Эта область недоступна обычным программам
5. Верхний кусочек памяти (MMIO) - эти адреса вообще не соответствуют никакой оперативной памяти в принципе
Эти адреса соответствуют внешним устройствам
Как с ней работать - зависит от конкретных устройств
В MIPS есть виртуальная память, но, к сожалению, в эмуляторе её нет, поэтому продемонстрировать её потом придётся отдельно
Нет никакого способа косвенной индексации регистров (т.е., например, их адреса нельзя будет положить в массив и использовать этот массив в операциях); это тоже следствие того, что в MIPS вычислительные операции максимально избегают работы с памятью
Плюс нет инструкций, с которыми непонятно в момент декодирования машинного слова, что именно должно быть сделано
Область статических данных очень маленькая: предполагается, что данные большего размера вы не будете адресовать вручную; обычно если уж у вас есть большой объём данных, вы его подгружаете из некоего файла в динамическую память
Директивы - специальные команды ассемблера
.data - для задания переменных (.word, .half и т.д.)
В MARS можно выбрать, какой endian использовать
Процессор MIPS имеет программный переключатель endianness
По умолчанию используется little-endian
Метки: произвольный идентификатор + двоеточие
Далее на метку можно ссылаться
Наличие псевдоинструкций приводит к несколько менее эффективному коду
Выравнивание.
Адрес каждой переменной должен быть кратен 4, поэтому если мы будем при объявлении переменных смешивать .word, .byte, .half, у нас будут оставаться "дырки" в памяти
.align - позволяет выполнить выравнивание вручную (0 - байт, 1 - полуслово, 2 - слово, 3 - двойное слово)
В принципе, вы можете перемешивать секции .text и .data
В секции .data можно указать адрес, с которого нужно производить размещение:
.data 0x10010100
Директиву .asciiz мы пока не рассматриваем просто потому, что ещё не говорили о соответствующем системном вызове
Адресация в секции кода:
попытка перехода на адрес, не кратный четырём, вызывает ошибку
Ещё одна особенность MIPS - для некоторых случаев просто нет подходящих простых команд (прибавить -1 проще, чем вычесть 1)
Пример цикла: TODO
Массивы - просто некая область памяти, адресуемая через смещение от начала массива