Стандартный способ работает везде - это выполнить системный вызов exit (0):
li $v0, 10 # call number
syscall # exit()
Это также позволяет избежать необходимости сохранять входящие $ra
где-нибудь в main
, если вы хотите использовать jal
внутри своей программы, так что это удобно.
Это также более реалистично для реальных рукописных asm-программ, работающих в основной ОС, такой как Linux, а не только для игрушечной системы, моделируемой MARS / SPIM.
(В отличие от Linux exit(int)
, игрушечный системный вызов MARS / SPIM не принимает статус выхода в $a0
или где-либо еще. Он просто завершается.)
В MARS очевидно, что падение со дна является допустимым вариантом для игрушечной системы, которую он имитирует. Однако это никогда не работает ни в одном реальном аппаратном процессоре; всегда есть что-то следующее в памяти, и ЦП попытается извлечь и выполнить это 1.
Ни MARS, ни SPIM не пытаются эмулировать настоящую ОС, такую как Linux, просто предоставляют свою собственную специфическую среду 2. Системы, моделирующие MARS и SPIM, имеют некоторые незначительные отличия друг от друга, в том числе и та, которую вы нашли.
Ни один из них не является правильным или неправильным, просто разные: нет реальной среды, которой они пытались бы сопоставить / подражать.
SPIM может даже иметь возможность включить некоторый код ядра или что-то подобное в память моделируемой системы, IIRC. Возможно, я заблуждаюсь, но в противном случае часть обработки системных вызовов могла бы фактически выполняться большим количеством кода MIPS, приближаясь к реальному процессору MIPS, работающему под управлением ОС. (В отличие от MARS, где реализация системного вызова выполняется исключительно на Java внутри симулятора, который вы вызываете через syscall
, а не с точки зрения инструкций MIPS и драйверов устройств для имитируемого оборудования.)
В реальной ОС (например, GNU / Linux с gcc и glibc) main
будет правильной функцией, вызываемой обычно из точки входа процесса _start
(косвенно через __libc_start_main, чтобы сделать еще кое-что для инициализации перед фактическим вызовом main). _start
- реальная точка входа в процесс, первая инструкция, которая выполняется в пользовательском пространстве (по модулю динамического связывания), и не является функцией (нигде нет адреса возврата); ваш единственный вариант - выполнить системный вызов выхода (или дать сбой, или продолжать работать вечно). Когда main
возвращается, _start
передает свое возвращаемое значение (int
, таким образом, в $v0
) в качестве аргумента библиотечной функции exit
который выполняет очистку таких вещей, как очистка буферов stdio, а затем выполняет _exit
системный вызов.
Очевидно, SPIM намеревается, что их main
метка будет похожа на функцию C main
, или, по крайней мере, получит действительный адрес возврата. IDK, если он получает int argc
и char *argv[]
в $a0
и $a1
.
Чтобы jr $ra
работал, SPIM должен устанавливать начальный $ra
на какой-то адрес, например, ваш main был вызван откуда-то. Вы, вероятно, найдете код, который копирует $ v0 в $ a0, а затем выполняет системный вызов выхода.
Некоторые люди ошибочно используют main
в качестве имени для точек входа, которые, к сожалению, не могут вернуться, я думаю, даже при реальной разработке встраиваемых систем. В стандартных наборах инструментов для систем GNU / Linux (gcc / clang) точка входа в процесс по умолчанию называется _start
.
main
сбивает с толку, потому что это стандартное имя для функции C (вызываемой asm startup stuff), которая может возвращаться. То, из чего вы не можете вернуться, не является функцией, но в C main определенно является функцией. C - это низкоуровневый язык системного программирования Unix / Linux, и многие другие языки построены на основе этой стандартной инструментальной цепочки кода запуска libc и CRT.
Сноска 1: у большинства ISA есть правила о том, как ПК может переходить с 0xffffffc
на 0
или что-то еще, поэтому даже размещение кода в самом конце адресного пространства не может заставить его остановиться самостоятельно при достижении конец. Или, если да, то это будет какая-то неисправность, не выход в ОС. (В этом случае MARS или SPIM действуют как ОС, обрабатывая, помимо прочего, syscall
инструкции, которые вы выполняете). Обратите внимание, что настоящая ОС на «голом железе» не имеет возможности выйти, а только перезагрузить компьютер или выключить его. Он не работает ни под чем, из чего может вернуться.
Сноска 2. При очень ограниченном количестве системных вызовов, например нет движения курсора, и некоторые системные вызовы, которые делают то, что библиотечные функции (не системные вызовы) будут делать в реальной системе, например int ‹-› строковое преобразование. Но MARS / SPIM предоставляет это только как часть ввода-вывода, а не atoi
или sprintf(buf, "%d", num)
. Вот почему ярлык игрушки применяется, особенно к набору системных вызовов, которые они предоставляют, что очень не похоже на набор системных вызовов Linux.
Но также и такие вещи, как простая растровая графика, которую имеет MARS, и параметр по умолчанию без задержки ветвления, как для MARS, так и для SPIM по умолчанию. Реальные процессоры MIPS имеют слот задержки ветвления, пока MIPS32r6 не переупорядочит коды операций и не предоставит новые инструкции ветвления без задержки.
По крайней мере, MARS (и, возможно, SPIM) имеют довольно ограниченную поддержку констант времени сборки в своем встроенном ассемблере, например вы не можете использовать .equ
или msglen = . - msg
для вычисления длины msg: .ascii "hello"
во время сборки, как вы могли бы это сделать в ассемблере GNU для MIPS.
person
Peter Cordes
schedule
05.02.2021
$ra
на вашmain
, позволяя ему возвращаться, какmain
компилятора C в реальной системе. (SPIM и MARS являются игрушечными системами, а также имеют свои собственные системные вызовы, в отличие от GNU / Linux MIPS или чего-то подобного). Но да, системный вызов выхода, я думаю, более стандартен для этих эмуляторов, поскольку нигде нет_start
точки входа, к которой вы могли бы вернуться. - person Peter Cordes   schedule 05.02.2021