Доброго здоров’ячка! Сегодня я расcкажу вам как решать квадратные уравнения с помощью сопроцесcора . Но, что это такое? Я не буду вдаваться в хардверные дебри, скажу только, что наряду с обычным процессором существует, так называемый, математический сопроцессор. Его функции ясны: чтобы не забивать основной процессор всякими math кодами (некоторые он может вычислить) есть математическая машина.
Математикам: я здесь покажу как программируется сопроцессор на основе решения квадратных уравнений.
Алгоритм решения:
Мы имеем формулу общего квадратного уравнения (вспоминайте школу, класс этак восьмой :-). Она выглядит так: ax^2+bx+c=0
Требуется найти ее дискриминант: D=b^2-4ac
Если D то корней не существует, иначе п.4
Если D=0 то есть один корень: X=-b/2a , иначе п.5
Если D>0 то существуют два корня: X1=(-b-sqrt(D))/2a, X2=(-b+sqrt(D))/2a
С чего же начать? Ответ прост: с очистки математической машины. Почти все программы оставляют свои коды в сопроцессоре (для краткости будем называть его СП). Делается это одной коммандой: finit
Далее, найдем дискриминант:
Ну вот, D нашли. Теперь нужно его сравнить с нулем. Вот тут то и начинаются проблемы: у СП есть момент сравнивания и все, а мне нужно перейти на определенные «полки» программы. Зато у основного процессора (далее ОП) такая возможность есть, а значит, мне нужно перенести определенные флаги СП в ОП. Делается это так:
Теперь мы можем «попрыгать» по программе, но сначала таблица соответсвий:
Условие
Команда «прыжка»
jc j1 ; если D то goto j1 jz j2 ; если D = 0 то goto j2 ; а если D > 0 то [далее] finit ; опять очистили СП fld temp2 ; Введем в стек СП число temp2 (temp2=2) fmul a ; Умножили temp2 на a fld D ; Введем в стек СП дискриминант fsqrt ; Посчитали корень даскриминанта fld b ; Введем в стек СП число b fchs ; Изменили знак у b fsave temp2[4] ; Сохранили полное (регитры и стек) состояние СП (и стерли оное) frstor temp2[4] ; Загрузили полное состояние fsubr ; вычли корень дискриминанта из -b fdivr ; первый корень frstor temp2[4] ; Загрузили еще раз полное состояние fadd ; сложили корень дискриминанта и -b fdivr ; второй корень jmp ex ; «прыгнули» на «выход» ; D j1: mov ah, 09h mov dx, offset mes1 int 21h ; вывели сообщение, что нет корней (под Win32) jmp ex ; «прыгнули» на «выход» ; D = 0 ; X = -b/(2*a) j2: finit ; опять очистили СП fld temp2 ; Введем в стек СП число temp2 (temp2=2) fmul a ; Умножили temp2 на a fld b ; Введем в стек СП число b fdivr ; Разделили b на 2a fchs ; Сменили знак ; Вот и единственный корень
Вот и все! Ошибок я не нашел (может Вы найдете). Можете вводить целые и дробные параметры — все равно получите правильный результат. Если у вас есть TASM (и за мелкими изменениями MASM ) то можете скачать данную программу.
При написании я частично пользовался книгой Юрова ASSEMBLER 2-е издание , за что ему большое спасибо!
Простая программа на ассемблере x86: Решето Эратосфена
Вступительное слово
По своей профессии я не сталкиваюсь с низкоуровневым программированием: занимаюсь программированием на скриптовых языках. Но поскольку душа требует разнообразия, расширения горизонтов знаний или просто понимания, как работает машина на низком уровне, я занимаюсь программированием на языках, отличающихся от тех, с помощью которых зарабатываю деньги – такое у меня хобби.
И вот, я хотел бы поделиться опытом создания простой программы на языке ассемблера для процессоров семейства x86, с разбора которой можно начать свой путь в покорение низин уровней абстракции.
До ее написания я сформулировал такие требования к будущей программе:
Моя программа не должна быть программой под DOS. Слишком много примеров ориентировано на нее в связи с простым API. Моя программа обязательно должна была запускаться на современных ОС.
Программа должна использовать кучу – получать в свое распоряжение динамически распределяемую память.
Чтобы не быть слишком сложной, программа должна работать с целыми беззнаковыми числами без использования переносов.
Задачей для своей программы я выбрал поиск простых чисел с помощью Решета Эратосфена. В качестве ассемблера я выбрал nasm.
Код я писал с упором больше на стиль и понятность, чем на скорость его выполнения. К примеру, обнуление регистра я проводил не с помощью xor eax, eax , а с помощью mov eax, 0 в связи с более подходящей семантикой инструкции. Я решил, что поскольку программа преследует исключительно учебные цели, можно распоясаться и заниматься погоней за стилем кода в ассемблере.
Итак, посмотрим, что получилось.
С чего начать?
Пожалуй, самая сложная вещь, с которой сталкиваешься при переходе от высокоуровневых языков к ассемблеру, это организация памяти. К счастью, на эту тему на Хабре уже была хорошая статья.
Так же встает вопрос, каким образом на таком низком уровне реализуется обмен данными между внутренним миром программы и внешней средой. Тут на сцену выходит API операционной системы. В DOS, как уже было упомянуто, интерфейс был достаточно простой. К примеру, программа «Hello, world» выглядела так:
В Windows же для этих целей используется Win32 API, соответственно, программа должна использовать методы соответствующих библиотек:
Здесь используется файл win32n.inc, где определены макросы, сокращающие код для работы с Win32 API.
Я решил не использовать напрямую API ОС и выбрал путь использования функций из библиотеки Си. Так же это открыло возможность компиляции программы в Linux (и, скорее всего, в других ОС) – не слишком большое и нужное этой программе достижение, но приятное достижение.
Вызов подпрограмм
Потребность вызывать подпрограммы влечет за собой несколько тем для изучения: организация подпрограмм, передача аргументов, создание стекового кадра, работа с локальными переменными.
Подпрограммы представляют собой метку, по которой располагается код. Заканчивается подпрограмма инструкцией ret . К примеру, вот такая подпрограмма в DOS выводит в консоль строку «Hello, world»:
Для ее вызова нужно было бы использовать инструкцию call :
Для себя я решил передавать аргументы подпрограммам через регистры и указывать в комментариях, в каких регистрах какие аргументы должны быть, но в языках высокого уровня аргументы передаются через стек. К примеру, вот так вызывается функция printf из библиотеки Си:
Аргументы передаются справа налево, обязанность по очистке стека лежит на вызывающей стороне.
При входе в подпрограмму необходимо создать новый стековый кадр. Делается это следующим образом:
Соответственно, перед выходом нужно восстановить прежнее состояние стека:
Для локальных переменных так же используется стек, на котором после создания нового кадра выделяется нужное количество байт:
Так же архитектура x86 предоставляет специальные инструкции, с помощью которых можно более лаконично реализовать эти действия:
Второй параметр инструкции enter – уровень вложенности подпрограммы. Он нужен для линковки с языками высокого уровня, поддерживающими такую методику организации подпрограмм. В нашем случае это значение можно оставить нулевым.
Непосредственно программа
Проект содержит такие файлы:
main.asm – главный файл,
functions.asm – подпрограммы,
string_constants.asm – определения строковых констант,
Makefile – сценарий сборки
Рассмотрим код основного файла:
Видно, что программа поделена по смыслу на 5 блоков, оформленных в виде подпрограмм:
input_max_number — с помощью консоли запрашивает у пользователя максимальное число, до которого производится поиск простых; во избежание ошибок значение ограничено константами MIN_MAX_NUMBER и MAX_MAX_NUMBER
allocate_flags_memory — запросить у ОС выделение памяти для массива пометок чисел (простое/составное) в куче; в случае успеха возвращает указатель на выделенную память через регистр eax
find_primes_with_eratosthenes_sieve — отсеять составные числа с помощью классического решета Эратосфена;
print_primes — вывести в консоль список простых чисел;
free_flags_memory — освободить память, выделенную для флагов
Для функций было условлено такое правило: значение возвращается через регистр eax , регистр edx содержит статус. В случае успеха он содержит значение SUCCESS , то есть, 0 , в случае неудачи — адрес строки с сообщением об ошибке, которое будет выведено пользователю.
Видео:АССЕМБЛЕР В 2023. Первый и последний урок.Скачать