Вступление

В этом сообщении блога я рассмотрю программу Hello World на ассемблере (архитектура Nios II в стиле RISC). Хотя я не буду вдаваться в подробности того, что означает архитектура Nios II, важно упомянуть, что для запуска программы Hello World требуется компьютерная плата или симулятор, поддерживающий архитектуру Nios II. Я рекомендую использовать симулятор, найденный здесь.

Hello World на других языках

Hello World часто является первой программой, которую пишут новые программисты, поскольку она очень проста. Я продемонстрирую программу Hello World на языках C, Ruby, JavaScript и Java, чтобы подчеркнуть простоту этой программы.

Привет, мир на C

Привет, мир на Ruby

Привет, мир в JavaScript

Привет, мир на Java

Привет, мир в сборке

Прежде чем я представлю сборочную версию Hello World, я должен предупредить вас, что она довольно длинная и пугающая, но позже я объясню все в меру своих возможностей. Без лишних слов, момент, которого мы все ждали, Hello World в сборке:

Да, я знаю, это выглядит очень страшно. Надеюсь, вы еще не закрыли блог. На рисунке 5 строки 1–5 представляют собой константы инициализации. Например, первая строка устанавливает LAST_RAM_WORD равным 0x007FFFFC, который является ячейкой памяти в шестнадцатеричном формате. Строка 8 позволяет компоновщику узнать, где начинается программа. В этом случае программа запускается в строке 11. Строка 9 сообщает ассемблеру, что это ячейка памяти следующей строки. На рисунке 5 строка 9 сообщает ассемблеру, что _start: находится по адресу 0x00000000.

Теперь я объясню, что происходит в метке _start:, начиная со строки 11 на рисунке 5. Первая строка внутри _start: строка 12 перемещает значение LAST_RAM_WORD в указатель стека (sp). Эта строка, по сути, инициировала sp, чтобы он содержал адрес памяти, 0x007FFFFC. Следующая строка перемещает "\ n" или следующий символ строки в регистр 2, внутренний регистр ЦП. Строка 14 вызывает PrintChar. Это означает, что программа начнет выполнение кода, следующего за меткой PrintChar:, начиная со строки 20. Для простоты с этого момента я буду называть _start :, _end :, PrintChar: и PrintString: функциями или подпрограммами, даже если они этикетки.

Функция PrintChar: отвечает за вывод символа на экран. Чтобы уточнить, эта программа фактически печатает символ новой строки, за которым следует строка приветствия. Причина, по которой я решил вставить символ новой строки, заключалась в том, чтобы объяснить функцию PrintChar: перед объяснением функции PrintString :, которая просто вызывает функцию PrintChar: несколько раз. Чтобы объяснить эту функцию, я определю адресную память по слову. Слово можно рассматривать как блок памяти, в котором может храниться информация. В архитектуре Nios II слово составляет 4 байта или 32 бита. Эта функция PrintChar: начинается с вычитания восьми байтов из sp, по сути создавая пространство для двух слов. Строки 22 и 23 сохраняют текущие значения регистров 3 и 4 в сгенерированных пространствах. Это важно, потому что ЦП содержит ограниченное количество регистров, которые можно использовать, поэтому сохранение значения этих регистров позволяет функции использовать регистры и восстанавливать их значения по завершении функции. Строка 24 устанавливает значение регистра три равным 0x10001000, которое является местом ввода / вывода. Строка 25 - это метка (pc_loop :). Актуальность этого ярлыка станет очевидной через мгновение после определения следующих трех строк. Строка 26 загружает статус места ввода / вывода в регистр четыре. Строка 27 логически складывает старшие биты значения в регистре четыре и WSPACE_MASK (0xFFFF), а затем сохраняет результат в регистре четыре. «Beq» в строке 28 означает «ветвь, равная». По сути, строка 28 заставляет программу возвращаться к метке «pc_loop» (строка 25), если значение регистра четыре равно значению регистра нуля. Чтобы уточнить, нулевой регистр - это специальный регистр, который всегда имеет нулевое значение. Строки с 25 по 28 представляют собой цикл while на языке более высокого уровня. Цикл while заставляет программу ждать, пока устройство вывода не будет готово напечатать символ. Строка 29 выводит на консоль содержимое второго регистра. Другими словами, строка 29 - это строка, отвечающая за отображение буквы на экране. Строки 30 и 31 восстанавливают значения регистров три и четыре до их значений до входа в функцию PrintChar :. После восстановления значений указатель стека (sp) больше не нуждается в хранении данных, поэтому строка 32 добавляет 8 байтов к указателю стека, чтобы вернуть sp в исходное состояние. Наконец, программа возвращается к строке, следующей за вызовом PrintChar, или к строке 15 после нажатия «ret» в строке 33.

Как только мы вернемся к строке 15, регистру два будет присвоен адрес строки, которую мы пытаемся напечатать, «Hello World \ n». Затем в строке 16 вызывается метод PrintString :. Для краткости этот метод перебирает строку, отправляя каждую букву методу PrintChar :. Итак, изначально буква «H» в строке «Hello World \ n» отправляется методу PrintChar :. Затем буква «e» отправляется методу PrintChar :. Этот процесс (цикл) будет продолжаться до символа «\ n».

По завершении подпрограммы PrintString: программа возвращается к строке 17 или метке _end :. Затем в строке 18 программа возвращается к строке 17 бесконечное количество раз, что по существу приводит к завершению программы, поскольку она застревает в бесконечном цикле.

Кому интересно, строки 54 и 55 сообщают программе, что строка «Hello World \ n» начинается в ячейке памяти 0x1000.

Заключение

Ассемблер - очень интересный язык, который намного сложнее выучить, поскольку он требует знания оборудования, с которым вы работаете. Как упоминалось ранее, эта ассемблерная программа была написана в стиле RISC архитектуры Nios II (версия ассемблера). Программа Hello World намного сложнее в ассемблере, чем любой язык программирования высокого уровня, поскольку требует, чтобы программист полностью определил программу, например, указал места в памяти. Кроме того, при сборке программист должен указать, что и когда появляется на экране, поэтому программа перебирает строку и печатает по одному символу за раз. Однако сборка позволяет программистам оптимизировать программы и алгоритмы, используя преимущества аппаратных возможностей, которые в противном случае были бы невозможны.