InterReklama
InterReklama Advertising


Ассемблер? Это просто! Учимся программировать
______________________________________

Выпуск N 025 (Оболочка)

Я пiду до рiченкi стрiчати зiрочки,
Зазiраць як падаюць, ловити iх жменями.
Забiрусь на райдуго, взлечу по-пiд хмарами.
Перадам по радiо: "Прощай рiдна Бацьковщино!"
Весна, весна, весна прийде,
Весна, весна, весна вгамуе...

Здравствуйте, уважаемые подписчики!

Долгожданная весна наступила! УРА!

Сегодня в номере:

  • Командировка. Иные вопросы

  • Основы работы с сопроцессором

  • Оболочка. Вывод десятичных чисел. Длинные имена файлов



  • Командировка. Прочие вопросы

    Уважаемые подписчики! У нас был небольшой перерыв в связи с тем, что мне пришлось съездить в Санкт-Петербург в командировку почти на целую неделю, о чем я написал на сайте. У меня просто не было времени напечатать рассылку. Я искренне извиняюсь за такое долгое молчание.

    Ну а что можно сказать о Питере? Это красивый город, который отличается своей архитектурой. Впечатляют размеры разводных мостов (кстати, зимой не разводятся). Запомнились станции метро, в которых двери открываются не только в поездах, но и на самих станциях. Ощущения, скажу вам, немного жутковатые. Словно в бункер какой-то попадаю, особенно, когда много народу...

    Побывал в классном ночном клубе "Сахара". К сожалению, адрес так и не запомнил. Однако, рекомендую всем туристам!

    _________

    Ко мне пришло много писем, в которых вы просили выслать файлы-приложения. Но т.к. я не имел возможности получить почту, то высылка файлов также была с большой задержкой...

    Кстати, о файлах-приложениях. Почему получается так, что вы скачиваете все выпуски рассылки, но без файлов-приложений? В высылаемом архиве, а также в архиве на сайте эти файлы присутствуют. Неужели вы качаете выпуски по одному? Ведь проще получить архив целиком.

    Выпуски вынесены для того, чтобы новые посетители смогли ознакомиться с любым номером (не перекачивая весь архив) и решить для себя: стоит ли подписываться на рассылку или нет. Вот, собственно, и все!

    _________

    Наши экспертные группы начинают хорошо работать. Приходит большое количество вопросов. Эксперты справляются с поставленной задачей. В следующем выпуске я напишу подробней о наших экспертах, посчитаю количество отвеченных вопросов, грамотность ответов и опубликую лидеров в рассылке.

    _________

    Я теперь разместил на сайте полный дистрибутив MASM 6.12. Если вы еще не запаслись им, то - качайте! На сайте теперь также найти TurboDebugger 5.0, Multi-Editor 8.0 и многое другое. Пользуйтесь!!!

    _________

    В предыдущих выпусках я писал про уникальный сайт Эдуарда Дмитриева "Библиотека программиста" (http://prog.agava.ru), где можно найти много полезной информации по программированию на различных языках. К сожалению, я поспешил немного. Т.к. у AGAVA были проблемы, то вся информация потерялась. В результате, многие, обратившиеся на данный сайт, не нашли там того, что искали. Теперь ситуация более-менее исправлена. Заходите! Это действительно неплохая вещь! А я пока с Эдуардом подумаю по поводу объединения наших рассылок...



    Основы работы с сопроцессором

    Итак, уважаемые мои подписчики, пришло время разобрать работу сопроцессора.

    FAQ по сопроцессору, придуманный мной лично.

    Что такое сопроцессор?
    Сопроцессор (FPU - Floating Point Unit - устройство для работы с плавающей точкой) - это, как понятно из перевода, специальное устройство, устанавливающееся либо на материнскую плату, либо встраиваемое внутрь основного процессора (располагающееся на одном кристалле с ним).

    Для чего нужен сопроцессор?
    Сопроцессор служит для выполнения арифметических операций (сложение, вычитание, умножение, деление, вычисление квадратного корня и пр.) с плавающей точкой. Обычно для выполнения простых операций с целыми числами используется основной процессор, однако, иногда можно прибегать к помощи сопроцессора. Сразу подчеркну, что арифметические операции с целыми числами с помощью сопроцессора выполняются медленней, чем основным процессором.

    "Есть ли в моем компьютере сопроцессор?"
    В старых машинах сопроцессор устанавливался на материнскую плату как отдельное устройство. Начиная с 80486DX сопроцессор встраивается внутрь основного процессора. Смею вас уверить, что если на вашем столе стоит 80486DX-40 или (на худой конец!) Pentium, то сопроцессор в нем присутствует.

    Наличие сопроцессора в машине с некоторой уверенностью можно было определить из названия (типа) (386SX - сопроцессор отсутствует, 386DX - сопроцессор присутствует. Аналогичная ситуация и с "четверками"). Хотя были "умельцы", которые умудрялись выпаивать (!) сопроцессоры из машин класса DX! Наверное, ценились они раньше... Я имею в виду сопроцессоры...

    Во всех Pentium'ах, как я уже сказал, сопроцессор обязательно присутствует. В любом случае, всегда можно программным путем проверить наличие сопроцессора в машине.

    А как вообще узнать, использует ли сопроцессор та или иная программа во время работы?
    В принципе, большинству программ не требуется помощь сопроцессора. Они просто не производят сложные математические расчеты. Однако я встречал даже антивирусы, которые требовали наличие FPU! Что там чрезвычайно сложное считали они - не совсем понятно...

    Программы можно условно разделить на следующие типы:

    1. Не использующие сопроцессор;

    2. Использующие сопроцессор. При его отсутствии выводят соответствующее сообщение на экран и завершаются;

    3. Использующие сопроцессор, но при его отсутствии обходятся командами центрального процессора. Причем скорость работы в таком случае существенно замедляется относительно того, если бы сопроцессор присутствовал. Наличие сопроцессора проверяется программой сразу после ее загрузки.

    Определить, использует ли та или иная программа команды FPU возможно только одним способом: дизассемблировать ее и поискать команды сопроцессора (либо сделать это в процессе отладки).

    "А если мне нужно произвести сложные математические расчеты, могу ли я не использовать сопроцессор вообще, а обойтись инструкциями центрального процессора?"
    В принципе, да. Но это будет о-о-очень сложно и довольно-таки медленно...

    Сложно ли программировать сопроцессор на Ассемблере?
    Да, в общем-то, нет. Главное - навык...

    P.S. Вдумайтесь в фразу: "Да, в общем-то, нет". Все-таки русский язык - самый гибкий...

    "А почему мы начали рассматривать сопроцессор в данном выпуске? Что, наша оболочка будет работать только с ним?"
    Да.
    "А зачем? Можно ведь обойтись без него."
    Во-первых, мы должны изучить не поверхности программирования на Ассемблере, а затронуть по возможности все области. В том числе и сопроцессор.

    Во-вторых, наши программы носят сугубо обучающий характер. Пройдя полный курс программирования на Ассемблере, вы сможете написать свою собственную программу так, как вам этого хочется.

    В-третьих, то, что мы делаем с помощью сопроцессора, сложно реализовать без него.

    "А что мы делаем с сопроцессором?"
    Мы будем выводить целые десятичные числа на экран. Например, размеры файлов. Это, скажу вам, гораздо проще, чем использовать только процессор.

    "А что, в Ассемблере очень сложно вывести десятичное число на экран? В Бейсике, например, достаточно набрать PRINT 25*4 и мы увидим результат умножения..."
    На Ассемблере очень просто вывести на экран шестнадцатеричные и двоичные числа, но никак не десятичные. Поэтому только в 25 выпуске мы затрагиваем данную тему, так как вы должны уже неплохо разбираться в командах Ассемблера и понимать принцип программирования на этом мощном и очень простом языке.

    Сильно ли отличаются ассемблерные команды сопроцессора и принцип его работы от программирования процессора?
    Да, довольно-таки сильно. Сегодня вы сами лично "нащупаете" эту маленькую микросхемку. По крайней мере, увидите, как она работает...

    "А если я ничего не пойму?"
    Не отчаивайтесь! Все станет понятно со временем. Вспомните, у вас, вероятно, были трудности с тем или иным оператором, процедурой. Но в процессе экспериментов все стало на свои места. Так и здесь. Если вы сразу поймете все, что написано в данном выпуске, то, можно сказать, что я добился того, чего хотел. Если нет - экспериментируйте, спрашивайте. Эту тему желательно усвоить ВСЕМ!

    ___________

    Итак, приступим.

    Прежде всего стоит отметить, что для того, чтобы использовать инструкции сопроцессора, необходимо включить в начале программы директиву .8087 (.287, .387). Т.е. указать, команды какого сопроцессора мы будем использовать. Принцип такой же, как и при указании процессора: либо только 8086 (.8086), либо 8086 и 80286 (.286) и т.д. Вот пример включения директивы, которая указывает программе-ассемблеру (MASM / TASM) инструкции какого сопроцессора будут использоваться:

    .386 - не забывайте указывать процессор, если вы собираетесь использовать команды не только 8086!
    .287 - будем использовать команды не только 386 процессора, но и 8087, а также 80287 сопроцессоров.

    В некоторых случаях TASM может выдавать сообщение при попытке ассемблирования программы, начинающейся с указанных выше строк. Что-то типа: "Внимание: Вы используете команды 386 процессора и 287 сопроцессора!" Зачем нужно было это делать - ума не приложу! Программист может использовать, например, команды 486 процессора, а также 8087 сопроцессора. Ничего страшного в этом нет и быть не может! В любом случае просто игнорируйте это сообщение.

    _________

    В распоряжении сопроцессора имеется набор нескольких инструкций и 8 регистров (я бы даже сказал, 8 ячеек памяти). Принцип загрузки числа в тот или иной регистр похож на принцип работы стека. Как так? Да очень просто! Ниже будем много тренироваться. Пока только теория.

    Регистры имеют следующие названия: ST(0), ST(1), ST(2) ... ST(7). Иногда для краткости ST(0) называют просто ST. Загрузить в любой из указанных регистров число командой MOV не получится. Для этого существуют специальные команды сопроцессора.

    Хорошим тоном перед использованием сопроцессора является его инициализация командой FINIT. Вообще, все команды сопроцессора начинаются с символа F, что является их отличительной чертой.

    Инструкция FINIT инициализирует сопроцессор. При этом сбрасываются все регистры, биты, флаги, а также очищаются 8 регистров ST. Т.о. после инициализации сопроцессора программист может быть уверен в том, что никакой регистр не занят и не получится "каши" или переполнения стека (регистров) при попытке выполнить ту или иную операцию.

    Сопроцессор может работать с 16 - 80 разрядными числами. Следовательно, в регистры ST(0) - ST(7) можно загружать число в указанных выше пределах. Сопроцессор сам определит, 16-разрядное ли это число или 64-х разрядное.

    Все числа из переменных загружаются в регистр сопроцессора ST(0) и выгружаются из него в обычные переменные. Регистр ST(0) является, скажем так, главным. Только в него можно загружать числа из переменных и выгружать в переменные.

    Например. Возьмем команду FILD, которая загружает в регистр ST(0) число из переменной в памяти:

    FILD Number1

    ...

    Number1 dw 10

    Как видите, здесь мы указываем только имя переменной, значение которой будет загружено в регистр ST(0) и только в него (как уже упоминалось).

    Допустим, нам нужно сложить два целых числа, используя сопроцессор. Для этого мы загружаем в ST(0) число 10. Затем загружаем второе число - 3 (для сложения ведь нужно как минимум два числа):

    (1) FILD Number1
    (2) FILD Number2

    ...

    Number1 dw 10
    Number2 dw 3

    Теперь внимание! После выполнения строки (1) ST(0) будет содержать число 10. После выполнения строки (2) число из ST(0) переместится в регистр ST(1), а его место в ST(0) займет число 3. Т.е. система похожа на работу стека! Далее нужно сложить числа, которые находятся в ST и ST(1). Вот так:

    FADD

    Команда FADD без параметров складывает два числа, которые находятся в регистрах ST(0) и ST(1), при этом очищая ST(1) и помещая результат сложения в ST(0).

    Рассмотрим, что происходит в регистрах в процессе выполнения данных команд. Итак, пока никаких действий мы не выполняли:

    ST(0) Пусто
    ST(1) Пусто
    ST(2) Пусто
    ... ...
    ST(7) Пусто

    Заносим первое число в регистр ST командой FILD Number1:

    ST(0) 10
    ST(1) Пусто
    ST(2) Пусто
    ... ...
    ST(7) Пусто

    Теперь второе число командой FILD Number2:

    ST(0) 3
    ST(1) 10
    ST(2) Пусто
    ... ...
    ST(7) Пусто

    Обратите внимание, что числа как бы вталкиваются внутрь. Как уже отмечалось, заносить числа в сопроцессор можно только в регистр ST(0). Поэтому-то второй параметр в команде FILD отсутствует: и так все понятно...

    Теперь складываем оба числа командой FADD:

    ST(0) 13
    ST(1) Пусто
    ST(2) Пусто
    ... ...
    ST(7) Пусто

    Вот, что получилось!

    Как нам теперь получить результат?

    Существует также команда, противоположная FILD:

    FIST Result

    Эта команда сохранит число, которое находится в регистре ST(0), в переменную Result.

    Ниже приведена короткая программа, которая производит сложение двух чисел с использованием сопроцессора:

    Теперь ассемблируйте, запускайте отладчик и смотрите, что происходит. Полагаю, что многие из вас столкнутся с проблемой: AFD выводит на экран совсем не то, что мы набрали, а именно: какие-то WAIT, ESC и пр. Здесь ошибки нет. Просто одна команда сопроцессора на самом деле преобразуется в выражения вида:

    WAIT
    ESC ...

    Однако, лучше всего отлаживать программу, которая использует инструкции сопроцессора, в отладчике TurboDebugger. Если у вас нет этого отладчика, то его можно найти на нашем сайте (версия 5.0). Я настоятельно рекомендую отлаживать программы и смотреть результаты выполнения именно в этом отладчике, т.к. он очень удобен и наглядно отображает все, что вам нужно. Более того, я специально настроил рабочий стол TD 5.0 так, чтобы вы видели все необходимое (регистры сопроцессора, результаты вычислений, код программы, дамп памяти).

    Если вы посмотрели работу приведенной выше программы в TD, то читаем дальше...

    Вопрос: вот мы сложили два числа, получили результат, но ведь в ST(0) осталось число 13! Получается, что нам нужно перед каждой арифметической операцией инициализировать сопроцессор командой FINIT чтобы очистить регистры от результатов вычислений? Можно ли как-то убрать это число иным способом? Например, за один раз (одной командой) перенести его в переменную Result и освободить ST(0)?

    Ответ: конечно, можно. Существует ряд команд, облегчающий работу с сопроцессором. Изучить все в одной рассылке невозможно. В данном выпуске мы рассмотрим определенное количество команд, используя которые вы уже сможете выполнять многие операции.

    Как правило, мнемоника команд сопроцессора имеет определенную систему, подобную мнемоникам команд процессора, а именно: команды представляют из себя сокращения английских слов (например, известная вам инструкция XCHG - eXCHanGe - обменять). Вот пример:

    Если мы заменим

    FIST Result

    на

    FISTP Result

    из приведенной выше программы, то в ST(0) ничего не останется. Думаю, что это достаточно удобно. Советую поэкспериментировать с этим...

    ________

    Прежде, чем пойдем дальше, я хотел бы сказать еще несколько слов о работе сопроцессора.

    Сопроцессор имеет специальный регистр управления. Он необходим, в частности, для указания сопроцессору метода округления действительного числа (вещественного числа, числа с точкой или еще говорят "числа с плавающей точкой"). Например, число 23,8 может быть округлено следующим образом (в зависимости от установленных битов в регистре управления): до 24, либо до 23, либо без округления вообще. Или, например, если установлен тот или иной бит определенного регистра, то сопроцессор производит вычисления с более высокой точностью или с более низкой. Как в некоторых калькуляторах стоит переключатель разрядности (количества знаков после запятой).

    Для чего я это говорю? Дело в том, что мы будем устанавливать биты регистра управления сопроцессором RC, которые отвечают за округление числа. На практике все увидите... Позже рассмотрим все подробней.

    ________

    Теперь практикуемся на более сложном примере. Ниже приведена программа, которая выводит в центре нулевой строки заданное число в десятичной форме. Все очень просто! Самое главное, что нужно сделать, - посмотреть эту программу под отладчиком TD да так, чтобы были видны регистры сопроцессора при отладке.

    Ну-с, друзья мои, приступим!!!

    Как вывести десятичное число на экран в Ассемблере?

    Возьмем простое число 1234. Естественно, в десятичном формате.

    На первый взгляд кажется, что достаточно отделить четверку и вывести ее на экран, затем 3, дальше - 2 и т.д. Например, используя известную нам команду AND:

    mov AX,1234
    and AX,0004 или and ax,0Fh

    Либо как-то иным способом. Но проблема в том, что число 1234 будет представлено в шестнадцатеричном формате или (что вернее) в двоичном. Вот, что мы увидим в отладчике:

    mov ax,4D2h
    and AX,4

    И что же мы получаем в AX? В первом случае (and AX,0004) - нуль, а во втором (and ax,0Fh) - 2. Почему? Подумайте хорошо! Ведь мы уже проходили двоичную и шестнадцатеричную системы счисления, а также логические команды. Прежде, чем читать дальше, вы должны понять почему так происходит... Еще раз рекомендую отладчик и калькулятор DN или Win для перевода чисел из одной системы в другую.

    Как тут не крути, но подобные способы не годятся.

    Тогда как? Нужно делить число на десять, записывая остаток от деления, до тех пор, пока не останется нуль. Вот пример:

    1234/10=123 остаток 4
    123/10=12 остаток 3
    12/10=1 остаток 2
    1/10=0 остаток 1

    Что получили: 4321. Т.е. 1234 в обратном порядке. Следовательно, и выводить на экран будем "задом наперед".

    "А можно сразу в "нормальном" порядке выводить?" Можно, конечно, разделить на 1000, затем на 100, 10... Но ведь числа бывают разные! Длинные, короткие, средние... Можно даже завести массив из 20-30 байт, в каждый байт которого заносить одну цифру.

    Однако, проще всего, как уже отмечалось, делить на десять до тех пор, пока делимое (т.е. выводимое число, т.е. в нашем примере - 1234) не будет равно нулю, хоть и придется для этого выводить в обратном порядке.

    "Хорошо, тогда как нам разделить число на 10 и вывести остаток от деления на экран?" Вот это мы и будем делать с помощью сопроцессора. Подобный алгоритм используется в нашей оболочке (пока!). Я специально вынес его в отдельный файл, чтобы вам было удобнее разобраться, что происходит. Сразу отмечу, что более "корявых" алгоритмов уже не придумаешь. Я имею в виду, что наш алгоритм будет слишком сложный и замудреный. Зачем я сделал так? Хоть этот алгоритм и далек от совершенства, но зато он даст вам возможность понять принцип работы сопроцессора. Для этого (еще раз подчеркну) следует разобрать приведенную ниже программу в отладчике TurboDebugger или подобном ему.

    Видите, сколько разных программ нужно иметь под рукой для того, чтобы писать на Ассемблере! И это еще не предел! Скоро будем работать с SoftIce'ом, дизассемблером и пр. Так что, готовьтесь и привыкайте потихоньку... Зато ваши знания будут несравнимы со знаниями программистов, работающих с языками высокого уровня. После того, как вы изучите Ассемблер (если хватит терпения), вы сможете без особого труда разобраться с любой программой, написанной на языке высокого уровня. Так или иначе, можно будет ее скомпилировать, а затем дизассемблировать и посмотреть код. Но это так, "лирическое отступление"...

    Файл !coproc!.asm

    Читайте описания, ассемблируйте, запускайте под отладчиком и... познавайте!



    Оболочка


    Sshell25.asm (головной файл)

    Display.asm

    Files.asm

    Keyboard.asm

    Main.asm


    Messages.asm

    Data.asm

    Файл-приложение в Интернете (включая "!coproc!.asm"): http://www.Kalashnikoff.ru/Assembler/Issues/Enclosures/Sshell25.rar.

    Ну а что тут? Да, в принципе, все просто. Как всегда. Теперь мы выводим длинные имена файлов, а также их размер.

    Как получить и вывести длинное имя файла? Для этого следует воспользоваться НЕ функциями 4Eh и 4Fh, а 714Eh и 714Fh прерывания 21h. Вот так:

    mov ax,714Eh ;Функция поиска первого файла
    xor di,di ;DI должен указывать на буфер, куда будут записываться данные о найденном файле (типа DTA).
    xor si,si ;SI пока остается тайной!
    mov cx,0FFh ;Ищем все возможные файлы. Это что-то вроде атрибутов файла
    mov dx,offset All_files ;Маска поиска (*.*)
    int 21h ;Теперь в ES:DI находится информация о найденном файле!
    mov Handle,ax ;Номер процесса поиска файлов

    Отличие данной функции еще в том, что она требует указания номера, который мы сохраняем после вызова 714Eh.

    Все остальное подробно описано в файле-приложении (см. FILES.ASM).

    Процедура вывода десятичных чисел находится в файле DISPLAY.ASM. Я очень надеюсь, что труда разобраться с программой не составит.

    Как обычно все файлы-приложения содержат большое количество комментариев. Как обычно совет: пользуйтесь отладчиком!

    Ваши варианты вывода десятичных чисел на экран можно присылать по адресу: Assembler@Kalashnikoff.ru. В последующих выпусках они будут опубликованы в обязательном порядке.

    А я закругляюсь, т.к. байты заканчиваются. И вообще, надо придумать что-нибудь другое. Я имею в виду файл-приложение, который входит в рассылку. Очень уж много места он занимает...

    До встречи через неделю!!!


    С уважением,

    Калашников Олег: Assembler@Kalashnikoff.ru
    ICQ No.: 68951340
    URL сайта подписчиков: http://www.Kalashnikoff.ru

    ______________

    По вопросам сотрудничества, рекламы и спонсорства обращайтесь:

  • Публичное размещение материала из рассылки: Cooperation@Kalashnikoff.ru
  • Реклама на сайте: http://www.Kalashnikoff.ru/Reklama.html, Reklama@Kalashnikoff.ru
  • Издание книги (спонсорство): Sponsor@Kalashnikoff.ru

  • (C) Москва, 2001. Авторское право принадлежит Калашникову О.А. Публичное размещение материала из рассылки, а также его использование полностью или частично в коммерческих или иных подобных целях без письменного согласия автора влечет ответственность за нарушение авторских прав.

    [Следующий выпуск] [На главную страницу]