8873
Комментарий:
|
← Версия 4 от 2019-03-15 19:36:58 ⇥
9769
|
Удаления помечены так. | Добавления помечены так. |
Строка 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
Массивы - просто некая область памяти, адресуемая через смещение от начала массива