Различия между версиями 3 и 4
Версия 3 от 2019-03-15 19:36:32
Размер: 8873
Редактор: RomanKrivonogov
Комментарий:
Версия 4 от 2019-03-15 19:36:58
Размер: 9769
Редактор: RomanKrivonogov
Комментарий:
Удаления помечены так. Добавления помечены так.
Строка 1: Строка 1:
Лекция 4 Лекция 3
Строка 3: Строка 3:
Вопрос про endianness - little endian Обсуждение домашних заданий и прочих вопросов в дальнейшем, вероятно, будет организовано через группу или беседу в ВКонтакте.
Строка 5: Строка 5:
Зачем align 0? - для макросов Сегодня поговорим про регистры.
Строка 7: Строка 7:
Задача повторного использования исходного кода. Регистр 1 зачастую неявно используется в псевдоинструкциях, так что использовать его следует с аккуратностью.
Строка 9: Строка 9:
Два подхода:
1. макроподход: переиспользование кусков исходного кода;
2. функции: аппаратная поддержка переиспользования одного и того же фрагмента кода (хранящегося в единственном экземпляре).
Кроме того, на уровне архитектуры несколько отличаются от остальных регистров регистр 0 (в нём хранится нестираемый ноль) и регистр 31 (он используется в операции перехода).
Строка 13: Строка 11:
Напомню ещё раз, что имеющиеся на сегодняшний день компьютерные архитектуры - результат решения определённых задач в ограниченных условиях около 50 лет назад. Регистры (их 32 штуки) для удобства программиста поименованы.
Строка 15: Строка 13:
Куда сохранять адрес возврата? Вариантов решения этой задачи много:
1. специальный регистр - program counter; но в MIPS его нет;
2. сохранять адрес возврата в произвольный, удобный нам, регистр.
3. сохранять адрес возврата куда-то в память.
Регистр 1 (at)
Строка 20: Строка 15:
У подхода 2 есть плюсы:
- это быстро
- не нужно придумывать, в какую ячейку памяти сохранять значение
Если вы не знаете в точности, как работает используемая вами инструкция, не используйте в программе at - в нём часто хранятся временные значения.
Строка 24: Строка 17:
В MIPS есть для этого есть команды jal и jr, а также регистр $ra Регистры 2 и 3 (v0 и v1) используются для системных вызовов (как для хранения номера системного вызова перед непосредственно вызовом, так и для возврата результата).
Строка 26: Строка 19:
См. в лекции пример программы, определяющей, существует ли треугольник с заданными сторонами Регистры 4 - 7 (a0 - a7) используются для передачи параметров системного вызова.
Строка 28: Строка 21:
Недостаток такого подхода - невозможность вызова вложенной подпрограммы Регистры типа t, 8 - 15, 24 и 25 (t0 - t7, t8, t9) - temporary, временные; не стоит рассчитывать, что после вызова подпрограммы содержимое этих регистров не изменится.
Строка 30: Строка 23:
Концевая подпрограмма (?) - программа, из которой далее не будет вызова подпрограмм В отличие от них, регистры типа s, 16 - 23, гарантированно (согласно ABI) должны быть восстановлены к исходным (перед вызовом) значениям после завершения вызова подпрограммы.
Строка 32: Строка 25:
Ещё одна проблема - локальность меток k0, k1 - для случаев, когда ваша программа внезапно попадет в режим работы ядра (ваша программа вообще не должна трогать эти регистры, они для ядра).
Строка 34: Строка 27:
И ещё проблема - передача параметров в подпрограмму Регистр 31 - для адреса возврата из подпрограммы
Строка 36: Строка 29:
Здесь мы разделяем понятия рекурсивного вызова и вложенного вызова Регистр 29 - stack pointer, указатель на вершину стека
Строка 38: Строка 31:
При вложенном вызове не-концевой подпрограммы можно сохранить адрес возврата в памяти, но с этим есть проблемы Регистр 30 - frame pointer - регистр кадра (сюда иногда сбрасывается stack pointer, мы об этом поговорим позже)
Строка 40: Строка 33:
Словом, конвенций можно понапридумывать много и очень разных Регистр 28 - global pointer - через него передаются данные при запуске программы
Строка 42: Строка 35:
Для примера приведу конвенцию для концевой подпрограммы: Большинство из указанных выше функций различных регистров - части ABI (application binary interface), физически почти все регистры одинаковы. Однако следование ABI позволяет сделать программирование на языке ассемблера заметно более удобным.
Строка 44: Строка 37:
Мы заранее договариваемся:
1. какие регистры используются для передачи параметров в подпрограмму
2. не вызывать никаких подпрограмм из себя
3. возвращаем управление с помощью регистра $ra
4. какие регистры используются для возврата результата
5. какие регистры можно модифицировать, а какие - нет
Регистра флагов в MIPS нет.
Строка 51: Строка 39:
Отсюда и берутся MIPS-регистры t (temporary) и s (saved) Регистры HI и LO - используются в командах деления и умножения; из них, по сути, данные напрямую можно только извлекать
Строка 53: Строка 41:
$v0 и $v1 - регистры для возврата значений (иногда нужно два)
$a0-$a3 - для передачи параметров
Плоская модель памяти.
Строка 56: Строка 43:
Ещё можно договориться о том, чтобы не использовать регистры $v0 и $v1 в вычислениях, если это возможно См. табличку на странице занятия - она красивая и информативная
Строка 58: Строка 45:
Пример оформленной в соответствии с конвенциями программы см. в лекции С точки зрения программы эта память однородная, с точки зрения архитектуры - не совсем
Строка 60: Строка 47:
Однако как всё-таки нам сохранять актуально бесконечное количество адресов возврата (т.е. справиться с вложенными вызовами и рекурсией)? 1. С адреса 0 по примерно 4 MB - зарезервированная область
Строка 62: Строка 49:
Для этого вводится понятие стека (stack) обращение сюда вызывает исключение
Строка 64: Строка 51:
Стек - это абстракция типа LIFO (last in, first out) Это нулевая страница памяти
Строка 66: Строка 53:
В архитектуре фон Неймана (и во многих существующих) положить в стек или снять с него можно только по машинному слову за раз Одна из задач этой штуки - если вы разыменовываете (как указатель) какое-то небольшое число, это наверняка ошибка, и должно быть вызвано исключение
Строка 68: Строка 55:
Вот так мы внезапно и открыли для себя динамическую память :) 2. Далее - область программного кода - примерно 1/16 часть памяти
Строка 70: Строка 57:
В принципе, аппаратных реализаций этого механизма можно придумать очень-очень много; например, завести отдельную память для стека, непохожую на память кучи Изначально MIPS подразумевала раздельное хранение кода и данных, причём в область кода ничего нельзя было писать, а читать только по адресам, кратным 4
Строка 72: Строка 59:
Пример этого - регистровое окно (RISC-V, Эльбрус) Инструкции типа j используют 26 бит, и больше вам как раз и не нужно (вы как раз можете адресовать всю доступную память)
Строка 74: Строка 61:
В MIPS этого нет, тут адрес возврата просто кладётся в память 3. Далее - область данных - примерно половина адресуемых в 32-разрядной модели 4 GB
Строка 76: Строка 63:
При этом отдельной команды для того, чтобы положить/снять со стека, в языке ассемблера MIPS нет! (Потому что эта команда подразумевает две операции: уменьшить stack pointer и положить значение) Здесь находятся стек и куча (heap), которые вы используете для хранения данных вашей программы
Строка 78: Строка 65:
Что такое "уменьшить значение регистра"? Нужно прочитать значение регистра, изменить его и положить обратно heap - для динамических данных
Строка 80: Строка 67:
Для этого в MIPS есть регистр sp (stack pointer) (тем не менее, это регистр общего назначения), который по конвенции никто никогда не использует для других целей Навстречу куче растёт стек (куча растёт снизу вверх, стек растёт сверху вниз)
Строка 82: Строка 69:
stack pointer в начале работы программы указывает несколько ниже дна стека (до тела основной программы это место уже есть, чем занять) 4. Все старшие 2 GB памяти - область ядра (область кода и область данных)
Строка 84: Строка 71:
Пример работы со стеком см. в лекции Эта область недоступна обычным программам
Строка 86: Строка 73:
Последовательность действий:
1. уменьшить значение $sp на 4
2. положить значение
3. работать дальше
4. потом не забыть вернуть $sp на место
5. Верхний кусочек памяти (MMIO) - эти адреса вообще не соответствуют никакой оперативной памяти в принципе
Строка 92: Строка 75:
Словом, работа со стеком в MIPS - это следование конвенциям работы с памятью, при этом специальных директив (команд) для этого нет Эти адреса соответствуют внешним устройствам
Строка 94: Строка 77:
Обратите внимание, что обращаться мы можем не только к нулевому, но и вообще к элементам стека Как с ней работать - зависит от конкретных устройств
Строка 96: Строка 79:
При этом от того, что мы изменяем указатель стека (уменьшаем или увеличиваем), содержимое стека не меняется В MIPS есть виртуальная память, но, к сожалению, в эмуляторе её нет, поэтому продемонстрировать её потом придётся отдельно
Строка 98: Строка 81:
Локальные переменные подпрограммы также следует размещать на стеке Нет никакого способа косвенной индексации регистров (т.е., например, их адреса нельзя будет положить в массив и использовать этот массив в операциях); это тоже следствие того, что в MIPS вычислительные операции максимально избегают работы с памятью
Строка 100: Строка 83:
Подозреваю, что локальный namespace изобрели позже, чем заполнение/опустошение стека Плюс нет инструкций, с которыми непонятно в момент декодирования машинного слова, что именно должно быть сделано
Строка 102: Строка 85:
Наша программа должна сохранить в стеке, как минимум, адрес возврата (пример вызова подпрограммы, сохраняющей адрес возврата, см. в лекции) Область статических данных очень маленькая: предполагается, что данные большего размера вы не будете адресовать вручную; обычно если уж у вас есть большой объём данных, вы его подгружаете из некоего файла в динамическую память
Строка 104: Строка 87:
Можете смело закрывать книгу, в которой пример рекурсии приводится на вычислении факториала; но мы всё же прибегнем к этому примеру (см. в лекции) Директивы - специальные команды ассемблера
Строка 106: Строка 89:
Рекурсия в любом случае дороже, чем цикл .data - для задания переменных (.word, .half и т.д.)
Строка 108: Строка 91:
Нам нужно сохранять в самом начале подпрограммы не только адрес возврата, но и все s-регистры, которые мы собираемся использовать В MARS можно выбрать, какой endian использовать
Строка 110: Строка 93:
Получается, к функции у нас добавляются понятия пролога (сохранение значений из вызывающей подпрограммы на стеке) и эпилога (восстановления испорченных значений) Процессор MIPS имеет программный переключатель endianness
Строка 112: Строка 95:
Кроме того, пролог и эпилог функции одинаковы для всех функций, которые "портят" значения одних и тех же регистров (имеются в виду s-регистры, $ra и $sp) По умолчанию используется little-endian
Строка 114: Строка 97:
Полный текст конвенции см. в тексте занятия. Метки: произвольный идентификатор + двоеточие

Далее на метку можно ссылаться

Наличие псевдоинструкций приводит к несколько менее эффективному коду

Выравнивание.

Адрес каждой переменной должен быть кратен 4, поэтому если мы будем при объявлении переменных смешивать .word, .byte, .half, у нас будут оставаться "дырки" в памяти

.align - позволяет выполнить выравнивание вручную (0 - байт, 1 - полуслово, 2 - слово, 3 - двойное слово)

В принципе, вы можете перемешивать секции .text и .data

В секции .data можно указать адрес, с которого нужно производить размещение:

.data 0x10010100

Директиву .asciiz мы пока не рассматриваем просто потому, что ещё не говорили о соответствующем системном вызове

Адресация в секции кода:

попытка перехода на адрес, не кратный четырём, вызывает ошибку

Ещё одна особенность MIPS - для некоторых случаев просто нет подходящих простых команд (прибавить -1 проще, чем вычесть 1)

Пример цикла: TODO

Массивы - просто некая область памяти, адресуемая через смещение от начала массива

Лекция 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

Массивы - просто некая область памяти, адресуемая через смещение от начала массива

LecturesCMC/ArchitectureAssembler2019/03_RegistersMemory/conspect (последним исправлял пользователь RomanKrivonogov 2019-03-15 19:36:58)