Программа ассемблер это: Почему Ассемблер — это круто, но сложно

Содержание

Структура программы на ассемблере

 

Программирование на уровне машинных команд — это тот минимальный уровень, на котором возможно составление программ. Система машинных команд должна быть достаточной для того, чтобы реализовать требуемые действия, выдавая указания аппаратуре вычислительной машины.

Каждая машинная команда состоит из двух частей:

  • операционной — определяющей, «что делать»;
  • операндной — определяющей объекты обработки, «с чем делать».

Машинная команда микропроцессора, записанная на языке ассемблера, представляет собой одну строку, имеющую следующий синтаксический вид:

метка        команда/директива  операнд(ы)    ;комментарии

При этом обязательным полем в строке является команда или директива.

Метка, команда/директива и операнды (если имеются) разделяются по крайней мере одним символом пробела или табуляции.

Если команду или директиву необходимо продолжить на следующей строке, то используется символ обратный слеш: \.

По умолчанию язык ассемблера не различает заглавные и строчные буквы в написании команд или директив.

Примеры строк кода:

Count    db     1        ;Имя, директива, один операнд
mov    eax,0     ;Команда, два операнда
cbw            ; Команда

Метки

Метка в языке ассемблера может содержать следующие символы:

  • все буквы латинского алфавита;
  • цифры от 0 до 9;
  • спецсимволы:  _, @, $, ?.

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

Первым символом в метке должна быть буква или спецсимвол (но не цифра). Максимальная длина метки – 31 символ. Все метки, которые записываются в строке, не содержащей директиву ассемблера, должны заканчиваться двоеточием : .

Команды

Команда указывает транслятору, какое действие должен выполнить микропроцессор. В сегменте данных команда (или директива) определяет поле, рабочую область или константу. В сегменте кода команда определяет действие, например, пересылка (mov) или сложение (add).

Директивы

Ассемблер имеет ряд операторов, которые позволяют управлять процессом ассемблирования и формирования листинга. Эти операторы называются директивами. Они действуют только в процессе ассемблирования программы и, в отличие от команд, не генерируют машинных кодов.

Операнды

Операнд– объект, над которым выполняется машинная команда или оператор языка программирования.
Команда может иметь один или два операнда, или вообще не иметь операндов. Число операндов неявно задается кодом команды.
Примеры:

  • Нет операндов   ret                ;Вернуться
  • Один операнд    inc    ecx            ;Увеличить ecx
  • Два операнда    add eax,12        ;Прибавить 12 к eax

Метка, команда (директива) и операнд не обязательно должны начинаться с какой-либо определенной позиции в строке. Однако рекомендуется записывать их в колонку для большего удобства чтения программы.

В качестве операндов могут выступать

  • идентификаторы;
  • цепочки символов, заключенных в одинарные или двойные кавычки;
  • целые числа в двоичной, восьмеричной, десятичной или шестнадцатеричной системе счисления.
Идентификаторы

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

Правила записи идентификаторов.

  • Идентификатор может состоять из одного или нескольких символов.
  • В качестве символов можно использовать буквы латинского алфавита, цифры и некоторые специальные знаки: _, ?, $, @.
  • Идентификатор не может начинаться символом цифры.
  • Длина идентификатора может быть до 255 символов.
  • Транслятор воспринимает первые 32 символа идентификатора, а остальные игнорирует.
Комментарии

Комментарии отделяются от исполняемой строки символом ; . При этом все, что записано после символа точка с запятой и до конца строки, является комментарием. Использование комментариев в программе улучшает ее ясность, особенно там, где назначение набора команд непонятно. Комментарий может содержать любые печатные символы, включая пробел. Комментарий может занимать всю строку или следовать за командой на той же строке.

Структура программы на ассемблере

Программа, написанная на языке ассемблера, может состоять из нескольких частей, называемых модулями. В каждом модуле могут быть определены один или несколько сегментов данных, стека и кода. Любая законченная программа на ассемблере должна включать один главный, или основной, модуль, с которого начинается ее выполнение. Модуль может содержать сегменты кода, сегменты данных и стека, объявленные при помощи соответствующих директив. Перед объявлением сегментов нужно указать модель памяти при помощи директивы . MODEL.

Пример «ничего не делающей» программы на языке ассемблера:

.686P
.MODEL FLAT, STDCALL
.DATA
.CODE
START:

  RET
END START

В данной программе представлена всего одна команда микропроцессора. Эта команда RET. Она обеспечивает правильное окончание работы программы. В общем случае эта команда используется для выхода из процедуры.
Остальная часть программы относится к работе транслятора.
.686P — разрешены команды защищенного режима Pentium 6 (Pentium II). Данная директива выбирает поддерживаемый набор команд ассемблера, указывая модель процессора. Буква P, указанная в конце директивы, сообщает транслятору о работе процессора в защищенном режиме.
.MODEL FLAT, stdcall — плоская модель памяти. Эта модель памяти используется в операционной системе Windows. stdcall — используемое соглашение о вызовах процедур.
.DATA — сегмент программы, содержащий данные.
.CODE — блок программы, содержащей код.
START — метка. В ассемблере метки играют большую роль, что не скажешь о современных языках высокого уровня.
END START — конец программы и сообщение транслятору, что начинать выполнение программы надо с метки START.
Каждый модуль должен содержать директиву END, отмечающую конец  исходного кода программы. Все строки, которые следуют за директивой END, игнорируются. Если опустить директиву END, то генерируется ошибка.
Метка, указанная после директивы END, сообщает транслятору имя главного модуля, с которого начинается выполнение программы. Если программа содержит один модуль, метку после директивы END можно не указывать.

Назад

Назад: Язык ассемблера

Ассемблер — это… Что такое Ассемблер?

Эта статья — о компьютерных программах. О языке программирования см. Язык ассемблера.

Ассе́мблер (от англ. assembler — сборщик) — компьютерная программа, компилятор исходного текста программы, написанной на языке ассемблера, в программу на машинном языке.

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

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

Архитектура x86

Ассемблеры для DOS

Наиболее известными ассемблерами для операционной системы DOS являлись Borland Turbo Assembler (TASM), Microsoft Macro Assembler (MASM) и Watcom Assembler (WASM). Также в своё время был популярен простой ассемблер A86.

Windows

При появлении операционной системы Windows появилось расширение TASM, именуемое TASM 5+ (неофициальный пакет, созданный человеком с ником !tE), позволившее создавать программы для выполнения в среде Windows. Последняя известная версия TASM — 5.3, поддерживающая инструкции MMX, на данный момент включена в Turbo C++ Explorer. Но официально развитие программы полностью остановлено.

Microsoft поддерживает свой продукт под названием Microsoft Macro Assembler. Она продолжает развиваться и по сей день, последние версии включены в наборы DDK. Но версия программы, направленная на создание программ для DOS, не развивается. Кроме того, Стивен Хатчессон создал пакет для программирования на MASM под названием «MASM32».

GNU и GNU/Linux

В состав операционной системы GNU входит пакет binutils, включающий в себя ассемблер gas (GNU Assembler), использующий AT&T-синтаксис, в отличие от большинства других популярных ассемблеров, которые используют Intel-синтаксис (поддерживается с версии 2. 10).

Переносимые ассемблеры

Также существует открытый проект ассемблера, версии которого доступны под различные операционные системы, и который позволяет получать объектные файлы для этих систем. Называется этот ассемблер NASM (Netwide Assembler).

Yasm — это переписанная с нуля версия NASM под лицензией BSD (с некоторыми исключениями).

flat assembler (fasm) — молодой ассемблер под модифицированной для запрета перелицензирования (в том числе под GNU GPL) BSD-лицензией. Есть версии для KolibriOS, Linux, DOS и Windows; использует Intel-синтаксис и поддерживает инструкции x86-64.

Архитектуры RISC

MCS-51

MCS-51 (Intel 8051) — классическая архитектура микроконтроллера. Для неё существует кросс-ассемблер ASM51, выпущенный корпорацией MetaLink.

Кроме того, многие фирмы — разработчики программного обеспечения, такие как IAR или Keil, представили свои варианты ассемблеров. В ряде случаев применение этих ассемблеров оказывается более эффективным благодаря удобному набору директив и наличию среды программирования, объединяющей в себе профессиональный ассемблер и язык программирования Си, отладчик и менеджер программных проектов.

AVR

На данный момент существуют 3 компилятора производства Atmel (AVRStudio 3, AVRStudio 4, AVRStudio 5 и AVRStudio 6).

В рамках проекта AVR-GCC (он же WinAVR) существует компилятор avr-as (это портированный под AVR ассемблер GNU as из GCC).

Также существует свободный минималистический компилятор avra[1].

ARM

PIC

Пример программы на языке Assembler для микроконтроллера PIC16F628A:

 
        LIST    p=16F628A
         __CONFIG 0309H 
 
STATUS  equ     0x003
TRISB   equ     0x086
PORTB   equ     0x006
RP0     equ     5
        org  0
        goto start
start:
        bsf     STATUS,RP0             
        movlw   .00      
        movwf   TRISB                    
        bcf     STATUS,RP0        
led:
        movlw   .170   
        movwf   PORTB
        goto    led
end

AVR32

MSP430

Пример программы на языке Assembler для микроконтроллера MSP430G2231 (в среде Code Composer Studio):

 
 . cdecls C,LIST,  "msp430g2231.h"
;------------------------------------------------------------------------------
            .text                           ; Program Start
;------------------------------------------------------------------------------
RESET       mov.w   #0280h,SP               ; Initialize stackpointer
StopWDT     mov.w   #WDTPW+WDTHOLD,&WDTCTL  ; Stop WDT
SetupP1     bis.b   #001h,&P1DIR            ; P1.0 output
                                            ;                                                           
Mainloop    bit.b   #010h,&P1IN             ; P1.4 hi/low?
            jc      ON                      ; jmp--> P1.4 is set
                                            ;
OFF         bic.b   #001h,&P1OUT            ; P1.0 = 0 / LED OFF
            jmp     Mainloop                ;
ON          bis.b   #001h,&P1OUT            ; P1.0 = 1 / LED ON
            jmp     Mainloop                ;
                                            ;
;------------------------------------------------------------------------------
;           Interrupt Vectors
;------------------------------------------------------------------------------
            . sect   ".reset"                ; MSP430 RESET Vector
            .short  RESET                   ;
            .end

PowerPC

Программный пакет The PowerPC Software Development Toolset от IBM включает в себя ассемблер для PowerPC.

MIPS

Архитектуры MISC

SeaForth

Существуют:

  • 8-разрядные Flash-контроллеры семейства MCS-51
  • 8-разрядные RISC-контроллеры семейства AVR (ATtiny, ATmega, classic AVR). На данный момент семейство classic AVR трансформировано в ATtiny и ATmega
  • 8-разрядные RISC-контроллеры семейства PIC (PIC10,PIC12,PIC16,PIC18)
  • 16-разрядные RISC-контроллеры семейства PIC (PIC24HJ/FJ,dsPIC30/33)
  • 32-разрядные RISC-контроллеры семейства PIC (PIC32) с архитектурой MIPS32 M4K
  • 32-разрядные RISC-контроллеры семейства AVR32 (AVR32)
  • 32-разрядные RISC-контроллеры семейства ARM Thumb высокой производительности (серия AT91)

Макроассемблер

Не следует путать с MASM.

Макроассемблер (от греч. μάκρος — большой, обширный) — макропроцессор, базовым языком которого является язык ассемблера.[2]

Ассемблирование и компилирование

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

Примечания

См. также

Литература

  • Вострикова З. П. Программирование на языке ассемблера ЕС ЭВМ. М.: Наука, 1985.
  • Галисеев Г. В. Ассемблер для Win 32. Самоучитель. — М.: Диалектика, 2007. — С. 368. — ISBN 978-5-8459-1197-1
  • Зубков С. В. Ассемблер для DOS, Windows и UNIX.
  • Кип Ирвина. Язык ассемблера для процессоров Intel = Assembly Language for Intel-Based Computers. — М.: Вильямс, 2005. — С. 912. — ISBN 0-13-091013-9
  • Калашников О. А. Ассемблер? Это просто! Учимся программировать. — БХВ-Петербург, 2011. — С. 336. — ISBN 978-5-9775-0591-8
  • Магда Ю. С. Ассемблер. Разработка и оптимизация Windows-приложений. СПб.: БХВ-Петербург, 2003.
  • Нортон П., Соухэ Д. Язык ассемблера для IBM PC. М.: Компьютер, 1992.
  • Владислав Пирогов. Ассемблер для Windows. — СПб.: БХВ-Петербург, 2002. — 896 с. — ISBN 978-5-9775-0084-5
  • Владислав Пирогов. Ассемблер и дизассемблирование. — СПб.: БХВ-Петербург, 2006. — 464 с. — ISBN 5-94157-677-3
  • Сингер М. Мини-ЭВМ PDP-11: Программирование на языке ассемблера и организация машины. М.: Мир, 1984.
  • Скэнлон Л. Персональные ЭВМ IBM PC и XT. Программирование на языке ассемблера. М. : Радио и связь, 1989.
  • Юров В., Хорошенко С. Assembler: учебный курс. — СПб.: Питер, 2000. — С. 672. — ISBN 5-314-00047-4
  • Юров В. И. Assembler: учебник для вузов / 2-е изд. СПб.: Питер, 2004.
  • Юров В. И. Assembler. Практикум: учебник для вузов / 2-е изд. СПб.: Питер, 2004.
  • Юров В. И. Assembler. Специальный справочник. СПб.: Питер, 2000.

Ссылки

Написание резидентных программ для MS DOS

Написание резидентных программ для MS DOS

Написание
резидентных программ для MS DOS



В статье описаны основные
шаги, используемые при написании
резидентных программ на ассемблере
для операционной системы MS DOS. В
конце статьи подробно разобран
пример резидентной программы.


Резидентная программа для
MS DOS представляет собой фрагмент
кода, постоянно находящийся в
оперативной памяти компьютера и
вызываемый при возникновении
определённых условий. Далее будет
показано как написать резидентную
программу на ассемблере, постоянно
находящуюся в памяти и вызываемую
при возникновении в системе
прерываний. Сначала рассмотрим
определения и основные типы
прерываний для процессоров x86.


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


Для процессоров x86
существуют следующие виды
прерываний: аппаратные,
программные и внутренние
прерывания процессора. Аппаратные
прерывания, в свою очередь,
разделяются на маскируемые и
немаскируемые. Маскируемые
аппаратные прерывания при
определённых условиях могут быть
проигнорированны процессором, а
немаскируемые прерывания
обрабатываются всегда.


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


Программные прерывания
представляют собой вызов
каких-либо функций или сервисов
операционной системы и прикладных
программ с использованием команды
INT XX, где XX — номер прерывания от 0 до
255. Внутренние прерывания
процессора возникают при
выполнении программой каких-либо
операций, вызывающих фатальные
ошибки (например, деление на 0,
переполнение при делении, выход за
границы сегмента и т. д.), а также при
использовании режима отладки.


В любом случае, при
возникновении прерывания
какого-либо типа вызывается
обработчик этого прерывания,
который представляет собой
специальным образом оформленную
процедуру. Для аппаратных
прерываний обработчик прерывания
должен помимо работы с устройством,
вызвавшим прерывание, выполнить
некоторые операции по управлению
аппаратурой механизма прерываний
процессора x86.


Рассмотрим процесс
написания процедуры обработчика
прерывания на ассемблере,
вызываемого при возникновении
программного прерывания. Общая
структура и синтаксис для
обработчика программного
прерывания:




NAME proc
     ; 1. сохранение модифицируемых регистров

               . . .

     ; 2. инициализациясегментных регистров

               .  . .

     ; 3. выполнение необходимых действий

               . . .

     ; 4. восстановление используемых регистров

               . . .

     IRET
NAME ENDP


Идентификатор NAME
определяет имя процедуры
обработчика, которое может быть
любой последовательностью
разрешённых в ассемблере символов,
но не должно быть служебным или
зарезервированным словом.


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


В секции 2 выполняется
инициализация сегментных
регистров DS, ES или SS для обращения
процедуры обработчика прерывания к
своим внутренним данным, стеку или
некоторому дополнительному
сегменту. Значения
инициализируемых регистров должны
быть сохранены в секции 1.


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


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


Команда IRET выполняет
возврат из процедуры обработчика
прерывания в вызвавшую его
программу.


Рассмотрим подробнее
какие действия выполняют команды INT
и IRET.


Так как при выполнении
команды INT XX должен быть вызван
некоторый обработчик прерывания с
номером XX необходимо по номеру
узнать адрес обработчика в памяти
(сегмент и смещение). Для этого
служит специальная таблица
векторов прерываний,
располагающаяся по адресу 0000:0000 в
оперативной памяти компьютера. Эта
таблица содержит 256
четырёхбайтовых значений,
определяющих адреса обработчиков
прерываний в памяти. Первые 15
четырёхбайтовых значений в таблице
зарезервированны для аппаратных
прерываний (маскируемых и
немаскируемых) и для внутренних
прерываний процессора. Остальные
значения в таблице определяют
адреса обработчиков программных
прерываний. Среди этих значений
есть и такие, которые предназначены
для пользовательских обработчиков
программных прерываний. Первые два
байта для каждой ячейки в таблице
определяют смещение обработчика
соответствующего программного
прерывания. Следующие два байта
определяют сегмент обработчика
прерывания. При вызове команды INT XX
выполняются следующие действия:


  1. В стеке сохраняются в
    следующей последовательности:
    регистр флагов, сегментный
    регистр CS, регистр указателя
    команд IP. Сбрасываются флаги IF
    и TF в регистре флагов.

  2. Вычисляется смещение
    относительно начала таблицы
    векторов прерываний:
    смещение=XX * 4, где XX — номер
    прерывания.

  3. В сегментный регистр CS
    по вычисленному смещению из
    таблицы векторов прерываний
    заносится значение сегмента
    обработчика прерывания, а в
    регистр IP — смещение
    обработчика прерывания.

  4. Происходит передача
    управления на обработчик
    программного прерывания. При
    этом все регистры кроме CS, IP и
    регистра флагов сохраняют своё
    значение таким, каким оно было
    до вызова команды INT XX.

Таким образом, при входе в
обработчик программного
прерывания, в стеке находятся
значения регистров CS, IP и регистра
флагов. Эти значения находились в
данных регистрах до вызова команды
INT XX. В вершине стека располагается
значение регистра IP.


При вызове команды IRET
выполняются следующие действия:


  1. Из стека
    восстанавливается значение
    регистра IP.

  2. Из стека
    восстанавливается значение
    регистра CS.

  3. Из стека
    восстанавливается значение
    регистра флагов.

  4. Происходит передача
    управления в прерванную
    программу, на команду,
    находящуюся непосредственно
    за командой программного
    прерывания INT XX.

После выполнения команды
IRET структура стека становится
такой же, какой она была до вызова
команды INT XX.


Таковы основные моменты,
используемые при написании
обработчиков программных
прерываний. Рассмотрим теперь
структуру и работу обработчиков
аппаратных прерываний.


В отличие от обработчиков
программных прерываний,
обработчики аппаратных прерываний
вызываются не командой INT, а самим
процессором. Выше было сказано, что
при написании обработчиков
аппаратных прерываний они должны
выполнять ещё и некоторые действия
по управлению аппаратурой
механизма прерываний процессора x86.
В простейшем случае, структура
такого обработчика выглядит
следующим образом:




NAME PROC
     ; 1. сохранение модифицируемых регистров

               . . .

     ; 2. инициализациясегментных регистров

               . . .

     ; 3. выполнение необходимых действий

               . . .

     ; 4. восстановление используемых регистров

               . . .

     MOV AL, 20h
     OUT 20h, AL
     IRET
NAME endp


Команда OUT 20h, AL выполняет
действия по управлению аппаратурой
механизма прерываний процессоров
x86. Конкретно, она посылает сигнал EOI
(End Of Interrupt — конец прерывания) в
контроллер прерываний, сообщая ему
таким образом, что обработка
аппаратного прерывания завершена.


При возникновении
аппаратного прерывания от
некоторого периферийного
устройства контроллер прерываний
выполняет проверку, не
замаскировано ли это прерывание.
Если оно не замаскировано, то
контроллер выполняет сравнение
приоритетов этого прерывания с
другим, если несколько прерываний
поступили в контроллер
одновременно. Если прерывание
замаскировано или заблокировано,
то оно игнорируется контроллером.
После выбора прерывания с более
высоким приоритетом (логика
назначения приоритетов
прерываниям может быть
запрограммирована пользователем)
контроллер посылает сигнал INTR
(Interrupt Request — запрос прерывания) в
процессор. Если в процессоре в
регистре флагов сброшен флаг
прерывания IF, то сигнал INTR
игнорируется. Если флаг IF
установлен, то процессор отвечает
контроллеру сигналом INTA (Interrupt
Acknoledge) на что контроллер, в свою
очередь, посылает процессору номер
вектора прерывания для выбранного
прерывания и блокирует все
прерывания этого и более низкого
приоритета. Процессор по
полученному номеру вектора
прерывания отыскивает в таблице
векторов прерываний адрес
соответствующего обработчика
аппаратного прерывания и вызывает
его.


Команда OUT 20h, AL, вызываемая
в конце обработчика аппаратного
прерывания, разблокирует
контроллер прерываний, разрешая
ему работу с ранее
заблокированными прерываниями.


Если требуется написать
обработчик аппаратного прерывания,
который должен только выполнять
определённые действия при
возникновении аппаратного
прерывания (например, выдавать
звуковой сигнал при нажатии на
любую клавишу), всю работу по
управлению соответствующей
аппаратурой можно возложить на
системный обработчик этого
аппаратного прерывания. В таком
случае, структура обработчика
будет следующей:




SYS_HANDLER DD ?

               . . .

NAME PROC
     PUSHF
     CALL CS:SYS_HANDLER

     ; 1. сохранение модифицируемых регистров

               . . .

     ; 2. инициализациясегментных регистров

               .  . .

     ; 3. выполнение необходимых действий

               . . .

     ; 4. восстановление используемых регистров

               . . .

     IRET
NAME endp


Команда CALL CS:OLD_HANDLER
вызывает системный обработчик
нужного аппаратного прерывания,
который выполняет все необходимые
действия по управлению аппаратурой
и контроллером прерываний. OLD_HANDLER
определяет ячейку памяти размером
в двойное слово (4 байта) для
хранения адреса системного
обработчика прерывания. Команда PUSHF
создаёт в стеке структуру для
команды IRET, вызываемой в системном
обработчике. Подобный подход можно
использовать и для программных
прерываний, когда помимо тех
действий, которые выполняет
системный обработчик программного
прерывания (например, INT 10h -
прерывание BIOS) нужно выполнить
какие-либо дополнительные
действия. Также можно определить
структуру обработчика
программного или аппаратного
прерывания, когда системный
обработчик вызывается в конце
процедуры нашего обработчика:




SYS_HANDLER DD ?

               . . .

NAME PROC
     ; 1. сохранение модифицируемых регистров

               . . .

     ; 2. инициализациясегментных регистров

               . . .

     ; 3. выполнение необходимых действий

               . . .

     ; 4. восстановление используемых регистров

               . . .

     JMP SYS_HANDLER
NAME endp


В этом случае команда JMP
SYS_HANDLER выполняет дальний переход на
системный обработчик прерывания,
поэтому в конце нашего обработчика
не нужно вызывать команду IRET — она
будет вызвана в системном
обработчике.


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


Как уже было сказано выше,
в оперативной памяти компьютера по
адресу 0000:0000 располагается таблица
векторов прерываний, элементы
которой определяют адреса
обработчиков прерываний в памяти.
Для обработчика программного или
аппаратного прерывания без вызова
системного обработчика нужно лишь
записать в соответствующий элемент
таблицы векторов прерываний
значение сегмента и смещения этого
обработчика. Рассмотрим
необходимые операции для записи
сегмента и смещения в таблицу для
обработчика программного или
аппаратного прерывания с номером N:




MOV AX, 0000H                   ; запись в ES значения
MOV ES, AX                      ; сегмента 0000h
MOV DI, N                       ; запись в DI номера обработчика
MOV CL, 2                       ; умножение DI
SHL DI, CL                      ; на 4
MOV AX, OFFSET HANDLER          ; запись в AX смещения обработчика
STOSW                           ; сохранение смещения в таблице
MOV AX, SEGMENT HANDLER         ; запись в AX сегмента обработчика
STOSW                           ; сохранение сегмента в таблице


После выполнения этих
действий и при выполнении команды
INT N будет вызван обработчик, адрес
которого был установлен в таблице
векторов прерываний.


Рассмотрим теперь
необходимые операции для установки
сегмента и смещения в таблице для
обработчика программного или
аппаратного прерывания, в котором
будет вызван системный обработчик
этого прерывания. Для этого перед
записью в таблицу новых значений
сегмента и смещения нужно сначала
сохранить значения сегмента и
смещения системного обработчика:




SYS_HANDLER DD ?                        ; определение ячейки памяти для хранения
                                        ; адреса системного обработчика
     . . .

MOV AX, 0000H                           ; запись в ES значения
MOV ES, AX                              ; сегмента 0000h
MOV DI, N                               ; запись в DI номера обработчика
MOV CL, 2                               ; умножение DI
SHL DI, CL                              ; на 4
MOV WORD PTR SYS_HANDLER, ES:[DI]       ; сохранение смещения системного обработчика
MOV AX, OFFSET HANDLER                  ; запись в AX смещения нового обработчика
STOSW                                   ; сохранение смещения в таблице
MOV WORD PTR SYS_HANDLER+2, ES:[DI]     ; сохранение сегмента системного обработчика
MOV AX, SEGMENT HANDLER                 ; запись в AX сегмента нового обработчика
STOSW                                   ; сохранение сегмента в таблице


При установке значений
сегмента и смещения обработчика
аппаратного прерывания нужно до
этого сбросить флаг IF (команда CLI), а
после установки новых значений
установить флаг IF (команда STI). Это
необходимо для того, чтобы в
процессе установки значений
сегмента и смещения не возникло
аппаратное прерывание.


Приведённые выше
фрагменты кода можно упростить,
используя функции прерывания DOS INT
21h. Функция DOS 35h позволяет получить
адрес обработчика прерывания. При
этом в регистр AH записывается номер
функции (35h), в регистр AL
записывается номер прерывания.
После вызова прерывания INT 21h в
регистре ES возвращается значение
сегмента обработчика указанного
прерывания. В регистре BX
взвращается значение смещения
обработчика указанного прерывания:




SYS_HANDLER DD ?

     . . .

MOV AH, 35H
MOV AL, N
INT 21H
MOV WORD PTR SYS_HANDLER, BX
MOV WORD PTR SYS_HANDLER+2, ES


Функция DOS 25h позволяет
установить адрес обработчика
прерывания. В регистр AH
записывается номер функции (25h), в
регистр AL записывается номер
прерывания. В регистр DS
записывается значение сегмента
обработчика прерывания, адрес
которого нужно установить в
таблице векторов прерываний. В
регистр DX записывается значение
смещения обработчика прерывания:




MY_HANDLER PROC

     . . .

MY_HANDLER_ENDP

     . . .

MOV AH, 25H
MOV AL, N
MOV DS, SEGMENT MY_HANDLER
MOV DX, OFFSET MY_HANDLER
INT 21H


При написании обработчика
аппаратного прерывания нужно
учитывать то, что он должен быть
завершен до возникновения
очередного аппаратного прерывания
для этого обработчика. Если это
условие не выполнено, то, в лучшем
случае, возникшее прерывание будет
потеряно. В худшем случае
невыполнение этого условия может
повлечь за собой потерю данных или
зависание компьютера. Например, при
написании аппаратного обработчика
прерываний от таймера, код
обработчика должен выполнятся по
времени менее 1/18 с., так как
прерывания от таймера по умолчанию
генерируются 18.2 раз в секунду. То же
можно сказать и об обработчике
аппаратных прерываний от
клавиатуры — код обработчика должен
выполняться не дольше задержки
повторения символов.


Также при написании любого
обработчика прерывания нужно
инициализировать сегментный
регистр DS, если обработчик
обращается к каким-либо внутренним
ячейкам памяти. Вместо этого можно
использовать обращение к ячейкам
памяти с использованием префикса
замены сегмента (например CS:[BX]), но
это увеличивает размер
соответствующей команды.
Использование префикса замены
сегмента эффективно в том случае,
когда количество обращений к
внутренним ячейкам памяти
обработчика невелико (2 — 3).


Рассмотрим теперь
средства для завершения
резидентной программы и сохранении
части её кода в памяти для
последующего использования при
возникновении прерываний.


  1. Прерывание DOS INT 27h.
    Прерывание предназначено для
    завершения программы и
    сохранения её резидентной в
    памяти. Для этого в регистр DX
    нужно занести количество байт
    для сохраняемой части
    программы плюс один байт.
    Начало сохраняемой части
    программы совпадает со
    смещением 0000h относительно
    кодового сегмента, поэтому для
    COM программ нужно учитывать
    размер префикса программного
    сегмента (PSP — Program Segment Prefix) — 256
    байт.

  2. Прерывание DOS INT 21h,
    функция 31h. Функция 31h
    прерывания DOS INT 21h выполняет те
    же действия, что и прерывание DOS
    INT 27h, но в регистр DX заносится
    размер сохраняемой части
    программы в параграфах (блоки
    памяти длиной 16 байт). Также
    имеется возможность
    определить код возврата при
    завершении резидентной
    программы (заносится в регистр
    AL).

Ниже приведёна общая
структура резидентной программы:




;
данные для резидентной части
программы

     ...          

HANDLER
PROC

     ...          

HANDLER
ENDP
;
данные для нерезидентной части
программы

     ...          

;
основная часть программы
(нерезидентная)

     ...          

;
установка (и получение) адреса
обработчика
;
завершение программы и
сохранение её резидентной в
памяти


Следующий фрагмент кода
даёт пример определения основной
структуры резидентной программы на
ассемблере (программа типа COM):




CODE SEGMENT                         ; определение кодового сегмента
ASSUME CS:CODE, DS:CODE              ; CS и DS указывают на сегмент кода
ORG 100H                             ; размер PSP для COM файла
BEGIN: JMP START                     ; переход на нерезидентную часть программы

SYS_HANDLER DD ?                     ; определение ячейки памяти для хранения
                                     ; адреса системного обработчика
A DW 0                               ; определение внутренних ячеек памяти для
B DB 1                               ; резидентной части программы 

     . ..

KB_HANDLER PROC                      ; процедура обработчика прерываний
                                     ; от клавиатуры
PUSHF                                ; создание в стеке структуры для IRET
CALL CS:SYS_HANDLER                  ; вызов системного обработчика

     ...

IRET                                 ; возврат из обработчика
KB_HANDLER ENDP
KB_END:                              ; метка для определения размера резидентной
                                     ; части программы
C DB 2                               ; ячейки памяти для нерезидентной части
D DW 3                               ; программы

     ...

START:                               ; начало нерезидентной части программы

     ...

MOV AH, 35H                          ; получение адреса системного обработчика
MOV AL, 09H                          ; прерываний от клавиатуры
INT 21H
MOV WORD PTR SYS_HANDLER, BX
MOV WORD PTR SYS_HANDLER+2, ES

MOV AH, 25H                          ; установка адреса нового обработчика
MOB AL, 09H                          ; прерываний от клавиатуры
MOV DX, OFFSET KB_HANDLER
INT 21H

MOV DX, (KB_END + 10FH) / 16         ; вычисление размера резидентной части
INT 31H                              ; завершение резидентной программы с
                                     ; сохранением части её кода в памяти
CODE ENDS                            ; конец сегмента кода
END BEGIN                            ; конец программы


Рассмотрим пример
резидентной программы «часы»
на ассемблере (CLOCK2. ZIP — 827
байт). Программа перехватывает
обработчик прерываний от таймера и
с возникновением очередного
прерывания выводит в левом верхнем
углу экрана текущее время. Ниже
представлен исходный текст
программы с комментариями.


code    segment                          ; определение кодового сегмента
        assume  cs:code,ds:code          ; CS и DS указывают на сегмент кода
        org     100h                     ; размер PSP для COM программы
start:  jmp     load                     ; переход на нерезидентную часть
        old     dd  0                    ; адрес старого обработчика 
        buf     db  ' 00:00:00 ',0       ; шаблон для вывода текущего времени

decode  proc                             ; процедура заполнения шаблона
        mov     ah,  al                  ; преобразование двоично-десятичного 
        and     al,  15                  ; числа в регистре AL
        shr     ah,  4                   ; в пару ASCII символов
        add     al,  '0'
        add     ah,  '0'
        mov     buf[bx + 1],  ah         ; запись ASCII символов
        mov     buf[bx + 2],  al         ; в шаблон
        add     bx,  3      
        ret                              ; возврат из процедуры
decode  endp                             ; конец процедуры 

clock   proc                             ; процедура обработчика прерываний от таймера
        pushf                            ; создание в стеке структуры для IRET
        call    cs:old                   ; вызов старого обработчика прерываний
        push    ds                       ; сохранение модифицируемых регистров
        push    es
	push    ax
	push    bx
        push    cx
        push    dx
	push    di
        push    cs
        pop     ds

        mov     ah,  2                   ; функция BIOS для получения текущего времени
        int     1Ah                      ; прерывание BIOS

        xor     bx,  bx                  ; настройка BX на начало шаблона
        mov     al,  ch                  ; в AL - часы
        call    decode                   ; вызов процедуры заполнения шаблона - часы
        mov     al,  cl                  ; в AL - минуты
        call    decode                   ; вызов процедуры заполнения шаблона - минуты
        mov     al,  dh                  ; в AL - секунды
        call    decode                   ; вызов процедуры заполнения шаблона - секунды 

        mov     ax,  0B800h              ; настройка AX на сегмент видеопамяти
        mov     es,  ax                  ; запись в ES значения сегмента видеопамяти
        xor     di,  di                  ; настройка DI на начало сегмента видеопамяти
        xor     bx,  bx                  ; настройка BX на начало шаблона
        mov     ah,  1Bh                 ; атрибут выводимых символов
@@1:    mov     al,  buf[bx]             ; цикл для записи символов шаблона в видеопамять
        stosw                            ; запись очередного символа и атрибута
        inc     bx                       ; инкремент указателя на символ шаблона
        cmp     buf[bx],  0              ; пока не конец шаблона,
        jnz     @@1                      ; продолжать запись символов

@@5:    pop     di                       ; восстановление модифицируемых регистров
        pop     dx
        pop     cx
        pop     bx
        pop     ax
        pop     es
        pop     ds
        iret                             ; возврат из обработчика
clock   endp                             ; конец процедуры обработчика
end_clock:                               ; метка для определения размера резидентной
                                         ; части программы
load:   mov     ax,  351Ch               ; получение адреса старого обработчика
        int     21h                      ; прерываний от таймера
        mov     word ptr old,  bx        ; сохранение смещения обработчика
        mov     word ptr old + 2,  es    ; сохранение сегмента обработчика
        mov     ax,  251Ch               ; установка адреса нашего обработчика
        mov     dx,  offset clock        ; указание смещения нашего обработчика
        int     21h                      ; вызов DOS
        mov     ax,  3100h               ; функция DOS завершения резидентной программы
        mov     dx, (end_clock - start + 10Fh) / 16 ; определение размера резидентной
                                                    ; части программы в параграфах
        int     21h                      ; вызов DOS
code    ends                             ; конец кодового сегмента
        end     start                    ; конец программы


Рассмотрим работу
программы.


Идентификатор old
определяет ячейку памяти размером 4
байта, которая хранит адрес старого
обработчика прерываний от таймера.
Эта ячейка будет нужна, когда будет
вызываться старый обработчик
прерываний от таймера.
Идентификатор buf
определяет шаблон для формирования
строки, содержащей значение
текущего времени в формате
часы:минуты:секунды. Последний байт
в шаблоне — нулевой — нужен для
определения длины шаблона.


Процедура decode
преобразует двоично-десятичное
число в регистре AL в два ASCII символа,
соответствующих значению часов,
минут или секунд (это зависит от
конкретного значения, записанного
в регистре AL). Процедура прибавляет
к значению разряда числа (младший
разряд находится в первых четырёх
битах регистра AL, старший — в
старших четырёх битах) ASCII код
символа ‘0’, тем самым формируя ASCII
код для цифры младшего или старшего
разряда. Далее этот ASCII код
записывается в текущую позицию
шаблона. После записи в шаблон
значение указателя на текущий
символ шаблона увеличивается на 3
(две цифры для часов, минут или
секунд, плюс символ ‘:’).


Процедура clock
является обработчиком прерываний
от таймера. Дело в том, что номер
аппаратного обработчика
прерываний от таймера — 08h. Но в
системном обработчике этого
аппаратного прерывания есть вызов
INT 1Ch. Прерывание 1Ch определяет
пользовательский обработчик
прерываний от таймера, который
вызывается системным. Таким
образом, ячейка old хранит
адрес старого пользовательского
обработчика прерываний от таймера.
В начале процедуры clock
командой PUSHF в стеке
подготавливается структура для
команды IRET и затем вызывается
старый пользовательский
обработчик прерываний от таймера.
Далее в процедуре clock
сохраняются в стеке все
модифицируемые регистры (в том
числе и сегментные). После этого
инициализируется сегментный
регистр DS для последующего
обращения к ячейкам памяти
резидентной части программы.
Следующим шагом является получение
значения текущего времени из BIOS при
помощи прерывания BIOS 1Ah. После
вызова команды INT 1Ah в регистре CH
находится значение часов, в
регистре CL — значение минут и в
регистре DH — значение секунд. Каждое
значение представлено в
двоично-десятичном формате -
младший разряд числа находится в
младших четырёх битах регистра, а
старший разряд числа — в старших
четырёх битах. После получения
значения текущего времени регистр
BX настраивается на начало шаблона
для записи в шаблон значений часов,
минут и секунд. Далее три раза
вызывается процедура decode
для записи в шаблон соответственно
часов, минут и секунд. После записи
в шаблон необходимой информации
происходит вывод символов шаблона
в левый верхний угол экрана. Для
этого регистр ES настраивается на
сегмент видеопамяти, а регистр DI
настраивается на начало сегмента
видеопамяти. Далее в цикле
происходит вывод в видеопамять
символов шаблона и атрибутов. После
этого восстанавливаются значения
всех модифицируемых процедурой
регистров и командой IRET происходит
возврат из обработчика.


В основной (нерезидентной)
части программы при помощи функции
DOS 35h происходит получение адреса
старого пользовательского
обработчика прерываний от таймера
(прерывание 1Сh). Значения сегмента и
смещения старого обработчика
записываются в ячейку old.
Далее устанавливается адрес нашего
обработчика прерываний от таймера.
Для этого в регистр DX записывается
смещение нашего обработчика
(смещение процедуры clock) и
вызывается функция DOS 25h (регистр DS
уже содержит значение сегмента
нашего обработчика). После этого
вычисляется размер резидентной
части программы в параграфах. Для
этого сначала вычисляется размер
резидентной части в байтах, не
считая префикса программного
сегмента. Затем к полученному
значению прибавляется размер
префикса программного сегмента — 256
и число 0Fh. Прибавление числа 0Fh
необходимо, чтобы округление при
делении на 16 осуществлялось в
большую сторону. После вычисления
размера резидентной части в
параграфах происходит вызов
функции DOS 31h, которая завершает
программу и сохраняет часть её кода
в памяти. Резидентная программа
запущена.



 



Copyright © 2000 by
HackMaster

ASSEMBLER-Учебник по основам языка программирования ASSEMBLER







3.1. Организация приложений MS-DOS

Как уже отмечалось выше, язык ассемблера является отражением архитектуры процессора,
и изучение языка в сущности означает изучение системы команд и способов адресации,
реализуемых процессором. Одна ко любой язык программирования полезен лишь постольку,
поскольку на нем можно написать какие-то работоспособные программы. В то же
время трудно представить себе реальную программу, которая выполняет чисто логические
или вычислительные действия, ничего не вводя и не выводя и не взаимодействуя
с другими программами. Однако такие вопросы, как организация выполнимой программы,
ее запуск, взаимодействие с разнообразными аппаратными и программными объектами
вычислительной системы (клавиатурой, дисками, таймером, памятью, системными
драй верами и проч.) и, наконец, завершение являются прерогативой операционной
системы. Поэтому в программах на языке ассемблера всегда широко используются
системные средства, например, для вывода па экран или ввода с клавиатуры, чтения
или записи файлов, управления памятью и проч. Более того, сама внутренняя организация
программы, ее структура и, в определенной степени, алгоритмы поведения в сильной
степени определяются правилами организации вычислительного процесса, заложенными
в DOS. Изучение языка ассемблера в отрыве от конкретной операционной системы
вырождается в схоластическое занятие, результатом которого будет знание формальных
правил написания программных предложений без возможности применить эти правила
для создания работоспособных программ.

В то же время возможности даже такой относительно простой операционной системы,
как MS-DOS, весьма велики и многообразны, и их изучение составляет самостоятельный
раздел программирования. В настоящей книге средства DOS рассматриваются лишь
в том минимальном объеме, который необходим для создания простых, но работоспособных
программ на языке ассемблера, а также для демонстрации основных алгоритмов и
приемов программирования.

Желающие получить более глубокое представление о возможностях MS-DOS и использовании
функций DOS в прикладном программировании, могут обратиться к книге: К.Г.Финогенов
«Самоучитель по системным функциям MS-DOS», M., Радио и связь, Энтроп,
1995.

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

Программы, предназначенные для выполнения под управлением MS-DOS, можно классифицировать
по разным признакам. По внутренней организации все программы принадлежат к одному
из двух типов, которым соответствуют расширения имен программных файлов .ЕХЕ
и .СОМ. По взаимодействию с самой DOS программы подразделяются на транзитные
и резидентные. Наконец, следует выделить важнейший класс программ, служащих
для обработки аппаратных или программных прерываний, и называемых обычно обработчиками
прерываний. Мы не касаемся здесь таких специфических программ, как устанавливаемые
драйверы устройств, командные процессоры (к их числу принадлежит COMMAND.COM)
или оболочки DOS (например, широко распространенная программа Norton Commander),
которые можно выделить в самостоятельные классы.

Первый пример законченной программы, рассмотренный нами в гл. 2, относился к
наиболее распространенному типу .ЕХЕ-приложений. Для такой программы характерно
наличие отдельных сегментов команд, данных и стека; для адресации к полям каждого
сегмента используется свой сегментный регистр. Удобство .ЕХЕ-программы заключается
в том, что ее можно почти неограниченно расширять за счет увеличения числа сегментов.
В случае большого объема вычислений в программу можно включить несколько сегментов
команд, обеспечив, разумеется, переходы из сегмента в сегмент с помощью команд
дальних переходов или дальних вызовов подпрограмм. Если же программа должна
обрабатывать большие объемы данных, в ней можно предусмотреть несколько сегментов
данных. Каждый сегмент не может иметь размер более 64 Кбайт, однако в сумме
их объем ограничивается только наличной оперативной памятью. Правда, в реальном
режиме затруднительно обратиться к памяти за пределами 1 Мбайт адресного пространства,
так что максимальный размер программы, если не предусматривать в ней какие-то
специальные средства поочередной загрузки сегментов, ограничен величиной 550
… 600 Кбайт. Наличие в МП 86 лишь двух сегментных регистров данных (DS и ES)
несколько усложняет алгоритмы обработки больших объемов данных, так как приходится
постоянно переключать эти регистры с одного сегмента на другой. Однако реально
в современных процессорах имеются не два, а четыре сегментных регистра данных
(DS, ES, FS и GS), которые вполне можно использовать в приложениях DOS, упростив
тем самым процедуры обращения к данным и ускорив выполнение программ. Позже
все эти возможности будут рассмотрены более подробно.

Во многих случаях объем программы оказывается невелик — меньше, а часто и много
меньше, чем 64 Кбайт. Такую программу нет никакой необходимости составлять из
нескольких сегментов: и команды, и данные, и стек можно разместить в единственном
сегменте, настроив на его начало все 4 сегментных регистра. Для односегментных
программ в MS-DOS существует специальный формат и специальные правила их составления.
Программные файлы с программами, составленными по этим правилам, имеют расширение
.СОМ. В формате .СОМ обычно пишутся резидентные программы и драйверы, хотя любую
прикладную программу небольшого объема можно оформить в виде .СОМ-приложения.
Если посмотреть список системных программ, входящих в DOS, и реализующих, в
частности, внешние команды DOS, то можно заметить, что приблизительно треть
этих программ написана в формате .COM (COMMAND.COM, FORMAT.COM, SYS.COM и др.),
а две трети — в формате .EXE (FC.EXE: PRINT.EXE, XCOPY.EXE и т.д.). Ниже мы
рассмотрим правила составления и особенности исполнения как .ЕХЕ-, так и .СОМ-программ.

Другой критерий классификации программ определяет способ взаимодействия прикладной
программы с другими программами и самой DOS. По этому критерию программы делятся
на два вида: транзитные и резидентные.

Ход выполнения транзитной программы (а к транзитным относится подавляющее большинство
приложений DOS) выглядит следующим образом. Пользователь запускает программу,
вводя с клавиатуры ее имя, завершаемое нажатием клавиши Enter. Соответствующие
программы-компоненты DOS отыскивают на диске файл с указанным именем, загружают
его в память и передают управление на входную точку этой программы. Далее программа
выполняется, фактически монополизируя ресурсы компьютера. Пока она не завершилась,
пользователь не имеет доступа к DOS и, соответственно, лишен возможности запустить
другую программу или выполнить какую-либо команду DOS. Ввод с клавиатуры возможен
только в ответ на запрос текущей программы, если в ней предусмотрено обращение
к клавиатуре за получением каких-либо данных.

Совсем по-другому функционирует резидентная программа. Пользователь запускает
ее точно так же, как и транзитную, вводя с клавиатуры ее имя. Программы DOS
загружают программный файл в память и передают управление на точку входа. Однако
дальше вычислительный процесс развивается поиному. Программа выполняет только
свой начальный, инициализирующий фрагмент, после чего вызывает специальную функцию
DOS (с номером 31h). Эта функция завершает программу и возвращает управление
в DOS, но не освобождает память от завершившейся программы, а оставляет эту
программу в памяти, делая ее резидентной. Программа остается в памяти и, можно
сказать, ничего не делает. Поскольку управление передано DOS, пользователь может
вводить с клавиатуры любые команды и, в частности, запускать другие транзитные
(или резидентные) программы. Когда будет активизирована находящаяся в памяти
резидентная программа? Как правило, резидентные программы включают в себя обработчики
аппаратных или программных прерываний. Если, например, в резидентной программе
имеется обработчик прерываний от системного таймера, который, как известно,
выдает сигналы прерываний приблизительно 18 раз в секунду, то каждое такое прерывание
будет предавать управление резидентной программе, которая может, например, периодически
выводить на экран текущее время или какую-то иную информацию. Работа резидентной
программы будет протекать независимо от других программ и параллельно с ними.
Другим классическим примером резидентной программы является русификатор клавиатуры,
который получает управление при нажатии любой клавиши, независимо от того, какая
программа сейчас выполняется. Задача русификатора — определить по имеющемуся
в нем флагу, на каком языке работает пользователь, и в необходимых случаях сформировать
соответствующий нажатой клавише код ASCII русской буквы.

Следует заметить, что необходимость в резидентных программах возникла лишь потому,
что MS-DOS является существенно однозадачной системой. В многозадачной операционной
системе Windows понятие резидентной программы в принципе отсутствует.

Разумеется, своими особенностями составления и функционирования обладают и обработчики
прерываний — чрезвычайно важный класс программ, обслуживающих многочисленные
внешние устройства компьютера — клавиатуру, мышь, магнитные диски и проч., а
также нестандартную аппаратуру, если компьютер используется для управления научной
установкой или технологическим процессом.

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

Программа типа .ЕХЕ

Характерные особенности программ типа .ЕХЕ подробно рассматри-вались в предыдущих
главах. Приведем еще несколько обобщающих сооб-ражений. Структура типичной программы
на языке ассемблера выглядит следующим образом.

Программа начинается с директивы ассемблера .586, разрешающей использовать в
тексте программы весь набор команд процессора Pentium (кроме привилегированных).
Если программа будет использовать только базовый набор команд МП 86, указание
этой директивы не обязательно.

С другой стороны, ее указание не обязывает нас обязательно использовать команды
Pentium. Если в программе предполагается использовать лишь дополнительные команды
процессоров 486 или 386, то вместо .586 можно написать .486 или .386.

Указание любого номера 32-разрядного процессора приведет к тому, что по умолчанию
программа будет транслироваться, как 32-разрядное приложение, в то время как
нам нужно создать обычное 16-разрядное приложение. Для того, чтобы все адреса
в программе рассматривались, как 16-битовые, необходимо придать сегментам команд
и данных описатели use16. Для сегмента стека этот описатель не нужен, так как
в стеке нет поименованных ячеек.

Программа состоит из трех сегментов — команд, данных и стека. Имена сегментов
выбраны произвольно. Собственно программа обычно состоит из процедур. Деление
на процедуры не обязательно, но повышает ее наглядность и облегчает передачу
управления на подпрограммы. В рассматриваемом примере сегмент команд содержит
единственную процедуру main, открываемую оператором ргос (от procedure, процедура)
и закрываемую оператором endp (end procedure, конец процедуры). Перед обоими
операторами указывается имя процедуры, которое в дальнейшем может использоваться
в качестве относительного адреса процедуры (в сущности, относительного адреса
первого выполнимого предложения этой процедуры). У нас это имя выступает в качестве
параметра завершающей программу директивы end. Имена процедур, так же, как и
имена сегментов, выбираются произвольно.

Если программа имеет сегмент данных с какими-либо данными, то для того, чтобы
к этим данным можно было обратиться, необходимо занести сегментный адрес сегмента
данных в один из сегментных регистров. Обычно в качестве такого регистра выбирают
DS. Таким образом, предложения, с которых начался текст главной процедуры

mov AX,data ;Инициализация

mov DS,АХ ;сегментного регистра DS

где data — имя, данное
сегменту данных, практически являются обяза-тельными для любой программы.

Точно также обязательными яачяются и завершающие предложения

mov AX,4C00h ;Вызов функции
DOS

int 21h ;завершения программы

в которых вызывается функция
DOS с номером 4Ch. Эта функция, как уже отмечалось, завершает программу, освобождает
занимаемую ею память и передает управление командному процессору COMMAND.COM.
Еще два замечания следует сделать относительно процедуры трансляции и компоновки
программы. Если сегмент данных расположить после сегмента команд, как это сделано
в нашем примере, то у транслятора возникнут сложности при обработке встречающихся
в программных пред-

ложениях имен полей данных, так как эти имена еще неизвестны транслятору. Для
того, чтобы такие, как говорят, «ссылки вперед» могли правильно обрабатываться,
следует в команде вызова транслятора TASM заказать два прохода. Это делается
указанием ключа /m2.

С другой неприятностью мы столкнемся, если попытаемся включить в программу операции
с 32-разрядными операндами (даже и с командами МП 86). Компоновщик TASM по умолчанию
запрещает такого рода операции. Чтобы преодолеть этот запрет, следует в команде
вызова компоновщика указать ключ /3.

Таким образом, приведенный в гл. 1 командный файл должен выглядеть (для подготовки
программы P.ASM) следующим образом:

tasm /z /zi /n /m2 p,p,p


tlink /x /v /3 p,p

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

Приведем в качестве еще одного примера простую законченную программу типа .ЕХЕ,
которая выясняет букву — обозначение текущего диска и выводит ее на экран с
поясняющей надписью.

Рассмотрим текст приведенного
примера. После настройки сегментного регистра DS на сегмент данных, вызывается
функция DOS с номером 19h, которая позволяет получить код текущего диска. У
этой функции нет никаких параметров, а результат своей работы она возвращает
в регистре AL в виде условного кода. 0 обозначает диск А:, 1 диск В:, 2 диск
С: и т.д. Если, например, пользователь работает на диске F, то функция 19h вернет
в AL код 5.

Для преобразования кода диска в его буквенное обозначение, мы воспользовались
широко распространенным приемом. В полях данных определена символьная строка,
которая будет выводиться на экран. Для удобства работы она разделена на две
части, каждая из которых имеет свое имя. Началу строки присвоено имя msg, а
той ее части, которая начинается с обозначения диска А:, имя disk (разумеется,
имена выбраны произвольно). Если посмотреть на таблицу кодов ASCII, то можно
заметить, что код каждой следующей буквы алфавита на 1больше предыдущей. Таким
образом, если к коду латинской буквы A (41h) прибавить 1, получится код буквы
В, а если прибавить, например, 5, получится код буквы F. Именно эта операция
и выполняется в предложении

add disk,AL ;Преобразуем
номер в код ASCII

где к байту с адресом disk
прибавляется код, возвращенный функцией DOS.

Выполнив модификацию строки, мы выводим ее на экран уже знакомой нам функцией
DOS 09h. Она выводит все символы строки, пока не встретится с символом $, которым
наша строка и завершается. Перед знаком S в строке имеются два числа: 13 и 10.
При выводе текстовой строки на экран любой функцией DOS код 13 трактуется DOS,
как команда вернуть курсор в начато строки («возврат каретки»), а
код 10 — как команда на перевод строки. Два эти кода переводят курсор в начало
следующей строки экрана. В данном случае, когда на экран ничего больше не выводится,
можно было обойтись и без этих кодов, которые включены лишь в познавательных
целях.

Между прочим, правильная работа программы основана на том предположении (безусловно
правильном), что ассемблер расположит наши данные в памяти в точности в том
же порядке, как они описаны в программе. Именно это обстоятельство и позволяет
дробить единую строку на части, не опасаясь, что в память они попадут в разные
места, что привело бы, разумеется, к непредсказуемому результату.После
вывода на экран сообщения о текущем диске в программе вызывается функция DOS
с номером 01h. Эта функция вводит с клавиатуры один символ. Если символов нет
(мы после запуска программы не нажимали на клавиши), функция 01h ждет нажатия
фактически любой клавиши (более точно — любой алфавитно-цифровой или функциональной
кла-виши). Такой весьма распространенный прием позволяет остановить выполнение
программы до нажатия клавиши, что дает возможность программисту посмотреть,
что вывела программа на экран, и оценить правильность ее работы.

Наконец, последнее действие носит, как уже отмечалось, сакраментальный характер.
Вызовом функции DOS 4Ch программа завершается с передачей управления DOS.

Взглянем еще раз на текст программы 3-1. Если не считать первых предложений
инициализации регистра DS, то в программе имеется лишь одна строка, носящая,
можно сказать, вычислительный характер — это прибавление полученного кода диска
к содержимому байта памяти. Все остальные строки служат для вызова тех или иных
функций DOS — получения информации о текущем диске, вывода строки на экран,
остановки программы и, наконец, ее завершения. Это подтверждает высказанное
выше утверждение о важности изучения системных средств и широком использовании
их в программах на языке ассемблера. Разумеется, в программе могут быть и сколь
угодно сложные и протяженные участки обработки данных и других вычислений, но
такие операции, как ввод с клавиатуры, вывод на экран, работа с файлами, получение,
как в нашем примере, системной информации и многое другое выполняется исключительно
с помощью вызова тех или иных функций DOS (или BIOS). Программу на языке ассемблера
просто невозможно написать без использования системных средств.

Структура и образ памяти программы .СОМ

Как уже отмечалось, программа типа .СОМ отличается от программы типа .ЕХЕ тем,
что содержит лишь один сегмент, включающий все компоненты программы: PSP, программный
код (т.е. оттранслированные в машинные коды программные строки), данные и стек.
Струкгура типичной программы типа .СОМ на языке ассемблера выглядит следующим
образом:

Программа содержит единственный
сегмент code. В операторе ASSUME указано, что сегментные регистры CS и DS будут
указывать на этот единственный сегмент. Оператор ORG 100h резервирует 256 байт
для PSP. Заполнять PSP будет попрежнему система, но место под него в начале
сегмента должен отвести программист. В программе нет необходимости инициализировать
сегментный регистр DS, поскольку его, как и остальные сегментные регистры, инициализирует
система. Данные можно разместить после программной процедуры (как это показано
в приведенном примере), или внутри нес, или даже перед ней. Следует только иметь
в виду, что при загрузке программы типа .СОМ регистр IP всегда инициализируется
числом 100h, поэтому сразу вслед за оператором ORG 100h должна стоять первая
выполнимая команда программы. Если данные желательно расположить в начале программы,
перед ними следует помес-тить оператор перехода на фактическую точку входа,
например jmp entry.

Образ памяти программы типа .СОМ показан на рис. 3.2. После загрузки программы
все сегментные регистры указывают на начато единственного сегмента, т.е. фактически
на начато PSP. Указатель стека автоматически инициализируется числом FFFEh.
Таким образом, независимо от фактического размера программы, ей выделяется 64
Кбайт адресного пространства, всю нижнюю часть которого занимает стек. Поскольку
верхняя граница стека не определена и зависит от интенсивности и способа использования
стека программой, следует опасаться затирания стеком нижней части программы.
Впрочем, такая опасность существует и в программах типа .ЕХЕ, так как в реальном
режиме нет никаких механизмов защиты, и при сохранении в стеке большего объема
данных, чем может так поместиться, данные начнут затирать поля того сегмента,
который рас-положен за стеком (если таковой сегмент существует).

Рис. 3.2. Образ памяти
программы .СОМ

Программы типа .СОМ отличаются
от .ЕХЕ-программ не только отсутствием сегментов данных и стека. В гл. 2 было
показано, что при выравнивании сегментов на байт, что делается с помощью описателя
byte

data segment byte

системные программы располагают
сегменты загружаемой программы с некоторым перекрытием, что позволяет избежать
пустых промежутков между сегментами в памяти, возникающих из-за того, что размеры
сегментов могут быть не кратны величине параграфа — 16 байт. Такое распо-ложение
сегментов требует изменения значений ссылок на адреса ячеек памяти. В состав
программного файла с расширением .ЕХЕ входит таблица с перечнем байтов программы,
содержимое которых может подвергнуться изменению в процессе загрузки программы
в память. Поэтому, кстати, размер файла с расширением .ЕХЕ может превышать истинный
размер программы в памяти.

Программа типа .СОМ состоит из единственного сегмента, и проблема настройки
ссылок не возникает. Файл с расширением .СОМ почти в точности отражает содержимое
памяти после загрузки программы. Отличие заключается только в том, что в программном
файле отсутствует префикс программы PSP, который система вставляет в программу
в процессе ее загрузки. Таким образом, файл с расширением .СОМ обычно оказывается
на 256 байт короче своего образа в памяти.

Если оттранслировать и скомпоновать программу, написанную в формате .СОМ, обычным
образом, образуется программный файл с расширением .ЕХЕ. Этот файл можно запустить
на выполнение, однако работать он будет неверно. Дело в том, что система, загружая
файл типа .ЕХЕ в память, пристраивает перед загруженной программой префикс и
настраивает на него регистры DS и ES. В результате значение DS окажется на 10h
меньше, чем сегментный адрес сегмента с командами и данными, что приведет к
неправильной адресации при обращении к полям данных. Программу, написанную в
формате .СОМ, можно запускать только в виде файла с расширением .СОМ, для которого
в DOS предусмотрен CBI алгоритм загрузки и запуска. Для того, чтобы компоновщик
создал файл с расширением .СОМ, в строке запуска компоновщика необходимо предусмотреть
ключ /t (при использовании компоновщика TLINK.EXE):

tlink /x /v /3 /t p,p

Для того, чтобы избежать
ошибок при подготовке программ, целее образно подготовить два командных файла
для трансляции и компоновки программных примеров — один для программ типа .ЕХЕ,
и другой для программ типа .СОМ. Разумеется, файлам надо назначить различающие
имена.

Рассмотрим пример законченной программы типа .СОМ, которая выводит на экран
строку текста.

В начале программы отведено
256 байт под PSP; в программе отсутствует инициализация регистра DS; поле данных
размещено в программном сегменте непосредственно после последней команды. Для
разнообразия в строку, выводимую на экран, включены коды 16 и 17, которые отображаются
на экране в виде залитых треугольников (рис. 3.3). Как видно из этого рисунка,
программа имела имя Р. СОМ и запускалась из каталога F:\CURRENT.

Рассмотрим важный в принципиальном плане вопрос о месте размещения данных в
.СОМ-программе. В нашем примере данные описаны в конце программного сегмента
вслед за процедурой main, которая, как и в предыдущих примерах, введена скорее
для порядка, чем по необходимости.

Рис. 3.3. Вывод программы
3.2.

С таким же успехом можно
было предложение с именем msg поместить после вызова int21h, внутри процедуры
main. Третий возможный вариант, с которым мы еще столкнемся в примерах резидентных
программ, приведен ниже.

Таким образом, данные могут быть размещены как после программы, так и среди
выполнимых предложений программы. Важно только соблюсти обязательное условие:
ни при каких обстоятельствах на данные не должно быть передано управление. В
первом случае (пример 3-2) данные помещены за вызовом функции DOS, завершающей
программу. Ясно, что после выполнения этой функции управление уже не вернется
в нашу программу, а будет передано командному процессору, поэтому размещение
здесь данных вполне возможно. В последнем фрагменте данные описаны, можно сказать,
в середине программы. Однако перед ними стоит команда безусловного перехода
jmp, которая приводит при выполнении программы к обходу данных.
А вот чего нельзя
было сделать, так это разместить данные после зак-рытия сегмента, как это сделано
в приведенном ниже (неправильном!) фрагменте:

Это второе обязательное условие: из чего бы ни состояла программа, все ее компоненты
должны входить в те или иные сегменты. Вне сегментов допускаются только нетранслируемые
директивы ассемблера типа .586 или assume.

Наконец, третье условие, о котором уже говорилось, относится только к программам
типа .COM. DOS, загрузив программу в память, инициализирует указатель команд
числом 100h, т.е. адресом первой команды вслед за оператором org 100h. Поэтому
главная процедура .СОМ-программы (если в ней имеется несколько процедур) обязательно
должна быть первой, причем первое предложение этой процедуры должно быть выполнимой
командой (например, командой jmp, как это показано выше).

Обработчики аппаратных
прерываний


Обработчики прерываний являются важнейшей составной частью многих программных
продуктов. Как было показано в гл. 1, прерывания разделяются на внутренние,
возникающие в самом микропроцессоре в случае определенных сбоев (попытка деления
на 0, несуществующая команда), внешние, приходящие из периферийного оборудования
(клавиатура, мышь, диски, нестандартные устройства, подключенные к компьютеру)
и программные, являющиеся реакцией процессора на команду int с тем или иным
номером. В прикладных программах приходится обрабатывать, главным образом, внешние
и программные прерывания. Общие принципы обслуживания тех и других прерываний
одинаковы, однако условия функционирования обработчиков аппаратных прерываний
имеют значительную специфику, связанную, главным образом, с тем, что прерывания
от аппаратуры приходят в произвольные моменты времени и могут прервать текущую
программу в любой ее точке. Обработчик прерывания должен быть написан таким
образом, чтобы его выполнение ни в какой степени не отразилось на правильном
функционировании текущей (прерываемой) программы.

Рассмотрим схематически структуру и функционирование программного комплекса,
включающего собственный обработчик какого-либо ап-паратного прерывания (рис.
3.4).

Рис. 3.4. Функционирование
программного комплекса с обработчиком прерываний.

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

Программа, начиная свою работу, прежде всего должна выполнить инициализирующие
действия по установке обработчика прерываний. В простейшем случае эти действия
заключаются в занесении в соответствующий вектор полного адреса (сегмента и
смещения) обработчика. Поскольку обработчик входит в состав программы, его относительный
адрес известен; это имя его процедуры или метка входной точки. Что же касается
сегментного адреса, то обработчик может входить в сегмент основной части программы,
если она невелика по объему и занимает один сегмент, но может образовывать и
самостоятельный сегмент. В любом случае в качестве сегментного адреса можно
использовать имя соответствующего сегмента.

Часто инициализация обработчика, помимо установки вектора, предполагает и другие
действия: сохранение исходного содержимого вектора прерывания, размаскирование
соответствующего уровня прерываний в контроллере прерываний, посылка в устройство
команды разрешения прерываний и проч.

Установив обработчик, программа может приступить к дальнейшей работе. В случае
прихода прерывания, процессор сохраняет в стеке флаги и текущий адрес программы,
извлекает из вектора адрес обработчика и передает управление на его входную
точку. Все время, пока выполняется программа обработчика, основная программа,
естественно, стоит. Завершающая обработчик команда irct извлекает из стека сохраненные
там данные и возвращает управление в прерванную программу, которая может продолжить
свою работу. Последующие прерывания обслуживаются точно так же.

Функции обработчика прерываний зависят от решаемой задачи и назначения того
устройства, от которого поступают сигналы прерываний. В случае прерываний от
клавиатуры задача обработчика прерываний — принять и сохранить код нажатой клавиши.
Прерывания от мыши свидетельствуют о ее перемещении, что требует обновления
положения курсора на экране. Если обслуживаемым устройством является физическая
установка, то сигнал прерывания может говорить о том, что в установке накоплен
определенный объем данных, которые надо перенести из памяти установки в память
компьютера. В любом случае обработчик прерываний должен быть программой несложной,
для выполнения которой не требуется много машинного времени.

Рассмотрим структуру программы с обработкой аппаратных прерываний. Наиболее
удобным аппаратным прерыванием, которое можно использовать в исследовательских
целях, является прерывание от системного таймера, которое генерируется 18.2
раза в секунду и служит источником сигналов для хода системных часов, отсчитывающих
время, истекшее после включения машины. Замена системного обработчика на прикладной
не приводит к каким-либо неприятностям, кроме, возможно, остановки на некоторое
время системных часов.

Будем считать, что наш программный комплекс представляет собой программу типа
.ЕХЕ и что обработчик прерываний входит в общий с основной программой программный
сегмент. Для определенности будем использовать вектор 08h, хотя, разумеется,
для любого другого аппаратного вектора структура программы останется той же.
Поначалу приведем текст программы с некоторыми купюрами.

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

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

Хотя и чтение, и заполнение вектора прерываний можно выполнить с помощью простых
команд mov, однако предпочтительнее использовать специально предусмотренные
для этого функции DOS. Для чтения вектора используется функция с номером 35h.
В регистр AL помещается номер вектора. Функция возвращает исходное содержимое
вектора в парс регистров ES:BX (легко догадаться, что в ES сегментный адрес,
а в ВХ смещение). Для хранения исходного содержимого вектора в сегменте данных
предусмотрена двухсловная ячейка old_08. В младшем слове этой ячейки (с фактическим
адресом old_08) будет хранится смещение, в старшем (с фактическим адресом old_08+2)
— сегментный адрес. Для того, чтобы обратиться к словам, составляющим эту ячейку,
приходится использовать описатель word ptr, который как бы заставляет транслятор
на время забыть о начальном объявлении ячейки и позволяет рассматривать ее,
как два отдельных слова. Если бы мы отвели для исходного вектора две 16-битовые
ячейки, например

то к ним можно было бы
обращаться без всяких описателей.

Сохранив исходный вектор, можно установить в нем адрес нашего обработчика. Для
установки вектора в DOS предусмотрена функция 25h. Она требует указания номера
устанавливаемого вектора в регистре AL, a его полного адреса — в парс регистров
DS:DX. Здесь нас подстерегает неприятность. Занести в регистр DX смещение нашего
обработчика new_08 не составляет труда, это делается командой

mov DX,offset new_08 ;Смещение
нашего обработчика

Однако регистр DS у нас
занят — в нем хранится сегментный адрес сегмента данных. Придется на какое-то
время сохранить этот адрес, для чего удобнее всего воспользоваться стеком. Откуда
взять сегментный адрес обработчика? Между прочим, в языке ассемблера существует
специальная конструкция, позволяющая определить сегментный адрес любого поля.
В нашем случае она выглядела бы таким образом:

В примере 3-3 использован
другой прием — содержимое CS отправляется в стек и тут же извлекается оттуда
в регистр DS:

push CS pop DS

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

Перед завершением программы необходимо поместить в вектор 8 адрес исходного,
системного обработчика, который был сохранен в двухсловном поле old_08. Перед
вызовом функции 25h установки вектора в регистры DS:DX надо занести содержимое
этого двухсловного поля. Эту операцию можно выполнить одной командой Ids, если
указать в качестве ее первого операнда регистр DX, а в качестве второго — адрес
двухсловной ячейки, в нашем случае old_08. Именно имея в виду использование
этой команды, мы и объявили поле для хранения вектора двухсловным, отчего возникли
некоторые трудности при его заполнении командами mov. Если бы мы использовали
второй предложенный выше вариант и отвели для хранения вектора две однословные
ячейки (old_08_offs и old_08_seg), то команду Ids пришлось бы снабдить описателем
изменения размера ячейки:

Ids DX,dword ptr old_08_offs

Между прочим, здесь так
же разрушается содержимое DS, но поскольку сразу же вслед за функцией 25h вызывается
функция 4Ch завершения программы, это не имеет значения.

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

прерываний команду конца прерываний. Дело в том, что контроллер прерываний,
передав в процессор сигнал прерывания INT, блокирует внутри себя линии прерываний,
начиная с той, которая вызвала данное прерывание, и до последней в порядке возрастания
номеров IRQ. Таким образом, прерывание, пришедшее, например, по линии IRQ 6
(гибкий диск) заблокирует дальнейшую обработку прерываний по линиям 6 и 7, а
прерывание от таймера (IRQ0) блокирует вообще все прерывания (IRQ0…IRQ7, а
также и IRQ8…IRQ15, поскольку все они являются разветвлением уровня IRQ2,
см, гл. 1, рис. 1.11). Любой обработчик аппаратного прерывания обязан перед
своим завершением снять блокировку в контроллере прерываний, иначе вся система
прерываний выйдет из строя. Снятие блокировки осуществляется посылкой команды
с кодом 20h в один из двух портов, закрепленных за контроллером прерываний.
Для ведущего контроллера эта команда посылается в порт 20h, для ведомого — в
порт A0h. Таким образом, если бы мы обрабатывали прерывания от часов реального
времени (линия прерываний IRQ8, вектор 70h, ведомый контроллер), то команда
конца прерывания выглядела бы так:

Указанную последовательность
команд иногда называют приказом, или командой EOI (от end of interrupt, конец
прерывания).

Разобравшись в этих общих вопросах, рассмотрим пример реальной программы, включающей
обработчик прерываний от таймера. Для того, чтобы приведенную выше фрагментарную
программу преобразовать в действующую, надо написать содержательную часть самого
обработчика, а также придумать, что будет делать основная программа после инициализации
прерываний. Все это сделать очень просто.

Пусть наш обработчик в ответ на каждое прерывание от таймера выводит на экран
какой-нибудь символ. Для этого можно воспользоваться функцией 0Eh прерывания
BIOS 10h. Это прерывание обслуживает большое количество различных функций, обеспечивающих
управление экраном. Сюда входят функции вывода символов и строк, настройки режимов
видеосистемы, загрузки нестандартных таблиц символов и многие другие. Функция
0Eh предназначена для вывода на экран отдельных символов. Она требует указания
в регистре AL кода выводимого символа. Процедура new_08 будет выглядеть в этом
случае следующим образом:

Что же касается основной
программы, то самое простое включить в нее (после завершения действий по инициализации
обработчика прерываний) функцию DOS 0 Hi ожидания ввода с клавиатуры:

mov AH,01h
int
21h

В результате программа,
дойдя до этих строк, остановится (фактически будет выполняться цикл опроса клавиатуры
в ожидании нажатия клавиши, включенный в состав программы реализации функции
01h DOS), а на экран непрерывной чередой будут выводиться символы коммерческого
at (рис. 3.5). После нажатия на любую клавишу программа завершится.

Рис. 3.5. Вывод программы
3-3.

Приведенный пример позволяет
обсудить чрезвычайно важный вопрос о взаимодействии обработчиков аппаратных
прерываний с прерываемой программой и операционной системой. Особенностью аппаратных
прерываний является то, что они могут возникнуть в любой момент времени и, соответственно,
прервать выполняемую программу в любой точке. Текущая программа, разумеется,
использует регистры, как общего назначения, так и сегментные. Если в обработчике
прерывания мы разрушим содержимое хотя бы одного из регистров процессора, прерванная
программа по меньшей мере продолжит свое выполнение неправильным образом, а
скорее всего произойдет зависание системы. Поэтому в любом обработчике аппаратных
прерываний необходимо в самом его начале сохранить все регистры, которые будут
использоваться в программе обработчика, а перед завершением обработчика (перед
командой iret) восстановить их. Регистры, которые обработчиком не используются,
сохранять не обязательно.

В нашем простом обработчике используется только один регистр АХ. Его мы и сохраняем
в стеке первой же командой push AX, восстанавливая в самом конце обработчика
командой pop AX.

Вторая неприятность может возникнуть из-за того, что в обработчике аппаратного
прерывания мы воспользовались для вывода на экран функцией BIOS. Вообще говоря,
считается, что в обработчиках аппаратных прерываний нельзя использовать никакие
системные средства. Причина такого запрета заключается в том, что аппаратное
прерывание может придти в любой момент, в частности тогда, когда прерываемая
программы сама выполняет какую-либо функцию DOS или BIOS. Однако, если мы прервем
выполнение системной функции на полпути, и начнем выполнять ту же самую или
даже другую функцию с начала, произойдет разрушение системы, которая не предусматривает
такое «вложенное» выполнение своих программ. В настоящее время разработаны
программные приемы, позволяющие эффективно обойти указанный запрет, однако использование
их в программе драматически увеличивает ее сложность и объем, и рассматривать
эти приемы мы здесь не будем.

В нашем случае дело усугубляется тем, что прерывания от таймера не только могут
придти в тот момент, когда выполняется функция DOS, но и неминуемо приходят
только в такие моменты, так как мы остановили программу с помощью вызова функции
01h прерывания 2Hi и, следовательно, все время, пока наша программа ждет нажатия
клавиши, в действительности выполняются внутренние программы DOS. Именно поэтому
мы отказались от использования в обработчике прерывания функции DOS и выводим
на экран символы с помощью прерывания BIOS. Выполнение функции BIOS «на
фоне» DOS не так опасно. Надо только следить за тем, чтобы наше прерывание
BIOS в обработчике не оказалось вложенным в такое же прерывание BIOS в прерываемой
программе.

Рассмотренный пример имеет существенный недостаток. Записав в вектор прерываний
8 адрес нашего обработчика, мы затерли исходное содержимое вектора и тем самым
ликвидировали (в логическом плане) исходный, системный обработчик. Практически
это приведет к тому, что на время работы нашей программы остановятся системные
часы, и если в системе есть какие-то другие программы, использующие прерывания
от таймера, они перестанут работать должным образом. Ликвидировать указанный
недостаток очень просто: надо «сцепить» наш обработчик с системным
так, чтобы в ответ на каждый сигнал прерывания активизировались последовательно
оба обработчика. Рассмотрим методику сцепления обработчиков.

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

Как будет работать такая
программа? После того, как процессор выполнит процедуру прерывания, в стеке
прерванного процесса оказываются три слова: слово флагов, а также двухсловный
адрес возврата в прерванную программу (рис.3.6, нижние три слова стека).

Рис. 3.6. Стек прерванной
программы в процессе выполнения прикладного обработчика прерываний.
CS1 — сегментный
адрес прерванного процесса;

IP1 — смещение точки возврата в прерванную программу;

CS2 — сегментный адрес прикладного обработчика;

IP2 — смещение точки возврата в прикладной обработчик.

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

Первая команда нашего обработчика pushf засылает в стек еще раз слово флагов,
а команда дальнего вызова процедуры call cs:old_08 (где ячейка old_08 объявлена
с помощью оператора dd двойным словом) в процессе передачи упражнения системному
обработчику помещает в стек двухсловный адрес возврата на следующую команду
прикладного обработчика. В результате в стеке формируется трехсловная структура,
которая нужна команде iret.

Системный обработчик, закончив обработку данного прерывания, завершается командой
iret. Эта команда забирает из стека три верхние слова и осуществляет переход
по адресу CS2:IP2, т.е. на продолжение прикладного обработчика.

Завершающая команда нашего обработчика iret снимает со стека три верхних слова
и передает упражнение по адресу CS1:IP1.

Описанная методика сцепления прикладного обработчика с систем-ным используется
чрезвычайно широко и носит специальное название перехвата прерывания.

Обработчики программных
прерываний


Программные прерывания вызываются командой int, операндом которой служит номер
вектора с адресом обработчика данного прерывания. Команда int используется прежде
всего, как стандартный механизм вызова системных средств. Так, команда int 2
Hi позволяет обратиться к многочисленным функциям DOS, а команды int 10h, int
13h или int 16h — к группам функций BIOS, отвечающим за управление теми или
иными аппаратными средствами компьютера. В этих случаях обработчики прерываний
представляют собой готовые системные программы, и в задачу программиста входит
только вызов требуемого программного средства с помощью команды int с подходящим
номером.

В некоторых специальных случаях, однако, программисту приходится писать собственный
обработчик прерывания, которое уже обслуживается системой. Таким образом, например,
осуществляется управление резидентными программами, которые для связи с внешним
миром обычно используют прерывание 2Fh. В каждой резидентной программе имеется
собственный обработчик этого прерывания, который, выполнив свою долю действий,
передает управление «предыдущему», адрес которого находил-ся ранее
в векторе 2Fh, и был сохранен обработчиком в своих полях данных. Другой пример
— перехват прерываний BIOS в обработчиках аппаратных прерываний с целью обнаружения
моментов времени, когда ни одна из наличных программ не использует данное прерывание
и, следовательно, сам обработчик может им воспользоваться.

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

Резидентные программы

Большой класс программ, обеспечивающих функционирование вычислительной системы
(драйверы устройств, оболочки DOS, русификаторы, интерактивные справочники и
др.), должны постоянно находиться в памяти и мгновенно реагировать на запросы
пользователя, или на какие-то события, происходящие в вычислительной системе.
Такие программы носят названия программ, резидентных в памяти (Terminate and
Stay Resident, TSR), или просто резидентных программ. Сделать резидентной можно
как программу типа .СОМ, так и программу типа .ЕХЕ, однако поскольку резидентная
программа должна быть максимально компактной, чаще всего в качестве резидентных
используют программы типа .СОМ.

Программы, предназначенные для загрузки и оставления в памяти, обычно состоят
из двух частей (секций) — инициализирующей и рабочей (резидентной). В тексте
программы резидентная секция размещается в начале, инициализирующая — за ней.

При первом вызове программа загружается в память целиком и управление передается
секции инициализации, которая заполняет или модифицирует векторы прерываний,
настраивает программу на конкретные условия работы (возможно, исходя из параметров,
переданных программе при ее вызове) и с помощью прерывания DOS Int 21h с функцией
31h завершает программу, оставляя в памяти ее резидентную часть. Размер резидентной
части программы (в параграфах) передается DOS в регистре DX. Указывать при этом
сегментный адрес программы нет необходимости, так как он известен DOS. Для определения
размера резидентной секции ее можно завершить предложением вида

ressize=$-main

где main — смещение начала
программы, а при вызове функции ЗШ в регистр DX заслать результат вычисления
выражения (rcssLze+10Fh)/16.

Разность S — main представляет собой размер главной процедуры. Однако перед
главной процедурой размещается префикс программы, имеющий размер 100h байт,
который тоже надо оставить в памяти. Далее, при целочисленном делении отбрасывается
остаток, т.е. происходит округление результата в сторону уменьшения. Для компенсации
этого дефекта можно прибавить к делимому число 15 = Fh. Деление всего этого
выражения на 16 даст требуемый размер резидентной части программы в параграфах
(возможно, с небольшим кусочком секции инициализации величиной до 15 байт).

Функция 31h, закрепив за резидентной программой необходимую для ее функционирования
память, передает управление командному процессору COMMAND.СОМ, и вычислительная
система переходит, таким образом, в исходное состояние. Наличие программы, резидентной
в памяти, никак не отражается на ходе вычислительного процесса за исключением
того, что уменьшается объем свободной памяти. Одновременно может быть загружено
несколько резидентных программ.

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

Кроме того, специально для взаимодействия с резидентными программами в DOS предусмотрено
мультиплексное прерывание 2Fh.

Рассмотрим типичную структуру резидентной программы и системные средства оставления
ее в памяти. Как уже отмечалось, резидентные программы чаще всего пишутся в
формате .СОМ:

При первом запуске программы
с клавиатуры управление передается на начато процедуры main (первый байт после
префикса программы). Командой jmp осуществляется переход на секцию инициализации,
в которой, в частности, подготавливаются условия для дальнейшей активизации
программы уже в резидентном состоянии. Последними строками секции инициализации
вызывается функция ЗШ, которая выполняет завершение программы с оставлением
в памяти указанной ее части. С целью экономии памяти секция инициализации располагается
в конце программы и отбрасывается при ее завершении.

Содержательная часть резидентной программы, начинающаяся с метки entry, активизируется,
как уже отмечаюсь выше, с помощью аппаратного или программного прерывания и
заканчивается командой iret. На рис. 3.7 приведена типичная структура резидентной
программы.

Рис. З.7. Структура
резидентной программы.

Как видно из рис. 3.7,
резидентная программа имеет по крайней мере две точки входа. После загрузки
программы в память командой оператора, вводимой на командной строке, управление
передается в точку, указанную в поле завершающего текст программы оператора
end (на рисунке — начало процедуры main). Для программ типа .СОМ эта точка входа
должна соответствовать самой первой строке программы, идущей вслед за префиксом
программы. Поскольку при загрузке программы должна выполниться ее установка
в памяти, первой командой программы всегда является команда перехода на секцию
инициализации и установки (jmp init на рисунке).

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

К сожалению, резидентные программы, выполняющие полезную работу, оказываются
довольно сложными. Мы же в качестве примера можем рассмотреть только совсем
простую резидентную программу, в принципе правильную и работоспособную, но не
претендующую на практическую ценность. Программа активизируется прерыванием
от клавиши Print Screen и выводит на экран содержимое сегментного регистра CS,
что позволяет определить ее положение в памяти.

Как известно, клавиша Print Screen в DOS выполняет печать содержимого экрана
на принтере. Каков механизм этой операции? При нажатии на любую клавишу клавиатуры
возникает сигнал прерывания, инициирующий активизацию обработчика прерываний
от клавиатуры, находящегося в ПЗУ BIOS. При нажатии на алфавитно-цифровые и
некоторые другие клавиши (например, функциональные клавиши <F1>…F<12>)
обработчик сохраняет в определенном месте памяти код нажатой клавиши и завершается.
Текущая программа может с помощью соответствующих функций DOS или BIOS извлечь
этот код и использовать его в своих целях. Если же пользователь нажимает на
клавишу Print Screen, то обработчик прерываний, в числе прочих действий, выполняет
команду hit 5, передавая управление через вектор 5 на обработчик этого программного
прерывания, который тоже располагается в ПЗУ BIOS. Задача обработчика прерывания
5 заключается в чтении содержимого видеобуфера и выводе его на устройство печати.

Таким образом, если мы напишем собственный обработчик прерывания и поместим
его адрес в вектор с номером 5, он будет активизироваться нажатием клавиши Print
Screen. Обратите внимание на то обстоятельство, что прерывание 5 является прерыванием
программным; оно возбуждается командой int 5 и не имеет отношения к контроллеру
прерываний. Однако активизируется это прерывание не командой int в прикладной
программе, а нажатием клавиши, т.е., фактически, аппаратным прерыванием.

Перехват прерывания 5 осуществляется значительно проще, через перехват «истинного»
аппаратного прерывания от клавиш клавиатуры, из-за чего мы и воспользовались
им в нашем примере.

Структура программы соответствует
описанной ранее. В секции инициализации выполняется установка обработчика прерывания
05h, при этом исходное содержимое вектора 5 не сохраняется. Это, разумеется,
очень плохо, так как лишает нас возможности этот вектор восстановить. С другой
стороны, восстанавливать перехваченные векторы надлежит при за-вершении программы,
а применительно к резидентной программе — при ее выгрузке из памяти. Однако
в нашей простой программе не предусмотрено средств выгрузки (процедура выгрузки
довольно сложна), и программе придется находиться в памяти до перезагрузки машины.

Установив вектор, программа завершается с оставлением в памяти ее резидентной
части с помощью функции 31h.

Резидентная часть программы является классическим обработчиком программного
прерывания. В первых же предложениях сохраняются регистры АХ и ВХ, используемые
далее в программе, а затем содержимое сегментного регистра CS переносится в
регистр ВХ. С таким же успехом можно было скопировать содержимое любого из регистров
DS, ES или SS, так как в программе типа .СОМ все регистры настроены на один
и тот же сегментный адрес (см. рис. 3.2). Копирование из сегментного регистра
в регистр общего назначения понадобился потому, что в дальнейшем нам придется
работать с отдельными половинками сегментного адреса, а у сегментных регистров
половинок нет.

Далее старшая половина сегментного адреса заносится в регистр AL, и вызовом
уже знакомой нам функции BIOS 0 Eh этот код выводится на экран. Затем таким
же образом выводится младшая половина сегментного адреса. Наконец, после восстановления
регистров ВХ и АХ (в обратном порядке по отношению к их сохранению) командой
iret управление возвращается в прерванную программу, которой в данном случае
является COMMAND.COM.

Вывод программы (ей для наглядности было дано имя TSR.COM) для конкретного прогона
показан на рис. 3.8.

Рис. 3.8. Вывод программы
3-4.

Полученный результат далек
от наглядности. Действительно, разделив сегментный адрес на две половины длиной
в байт каждая, мы просто записали в видеобуфер эти числа. Каждое число размером
в байт можно трактовать, как код ASCII какого-то символа. При выводе числа на
экран эти символы и отображаются. Изображение пикового туза соответствует коду
06, а знак равенства имеет код 3Dh (см. таблицу кодов ACSII на рис. 3.1). Таким
образом, сегментный адрес находящейся в памяти резидентной программы оказался
равен 063Dh, что соответствует приблизительно 25 Кбайт. Так и должно быть, так
как конфигурация компьютера, использованного для подготовки примеров, предусматривала
хранение большей части DOS в расширенной памяти, в области НМА. В основной памяти
в этом случае располагается кусочек DOS вместе с драйверами обслуживания расширенной
памяти и частью программы COMMAND.COM общим объемом около 25 Кбайт.

Для того, чтобы получить на экране сегментный адрес в привычной нам форме, его
двоичное машинное представление необходимо преобразовать в коды ASCII, отображающие
шестнадцатеричное (или, если угодно, десятичное) представление этого числа.
В нашем примере, чтобы получить на экране изображение числа 063Dh, надо было
сформировать та-кую цепочку кодов ASCII (в шестнадцатеричном представлении):

30 36 33 44 68

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

Читатель может, подготовив рассмотренный пример, загрузить несколько экземпляров
программы и посмотреть, как изменяются в этом случае их начальные адреса.







Лекция 4. Основы языка Ассемблер. Синтаксис языка Ассемблер. Ассемблерные вставки в языке C/C++.

4.1. Назначение языков
ассемблера.

Ассемблер (от англ. assemble — собирать) —
компилятор с языка ассемблера в команды машинного языка.

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

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

Язык ассемблера — тип языка программирования
низкого уровня. Команды языка ассемблера один в один соответствуют командам
процессора и представляют собой удобную символьную форму записи (мнемокод)
команд и аргументов. Язык ассемблера обеспечивает связывание частей программы и
данных через метки, выполняемое при ассемблировании (для каждой метки
высчитывается адрес, после чего каждое вхождение метки заменяется на этот
адрес).

Каждая
модель процессора имеет свой набор команд и соответствующий ему язык (или
диалект) ассемблера.

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

Связывание ассемблерного кода с
другими языками.

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

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

2.
Модульная компиляция. Большинство современных компиляторов работают в два
этапа. На первом этапе каждый файл программы компилируется в объектный модуль.
А на втором объектные модули линкуются (связываются) в готовую программу.
Прелесть модульной компиляции состоит в том, что каждый объектный модуль
будущей программы может быть полноценно написан на своем языке программирования
и скомпилирован своим компилятором (ассемблером).

Достоинства языков ассемблера.

1.
Максимально оптимальное использование средств процессора, использование
меньшего количества команд и обращений в память и, как следствие, большая
скорость и меньший размер программы.

2.
Использование расширенных наборов инструкций процессора (MMX, SSE, SSE2, SSE3).

3.
Доступ к портам ввода-вывода и особым регистрам процессора (в большинстве ОС
эта возможность доступна только на уровне модулей ядра и драйверов)

4.
Возможность использования самомодифицирующегося (в том числе перемещаемого)
кода (под многими платформами она недоступна, так как запись в страницы кода
запрещена, в том числе и аппаратно, однако в большинстве общедоступных систем
из-за их врожденных недостатков имеется возможность исполнения кода,
содержащегося в сегменте (секции) данных, куда запись разрешена).X используется в OpenBSD , в других BSD-системах, в Linux. В Microsoft
Windows (начиная с Windows XP SP2) применяется схожая технология DEP.

Недостатки языков ассемблера.

1.
Большие объемы кода, большое число дополнительных мелких задач, меньшее
количество доступных для использования библиотек по сравнению с языками
высокого уровня.

2.
Трудоемкость чтения и поиска ошибок (хотя здесь многое зависит от комментариев
и стиля программирования).

3.
Часто компилятор языка высокого уровня, благодаря современным алгоритмам
оптимизации, даёт более эффективную программу (по соотношению качество/время
разработки).

4.
Непереносимость на другие платформы (кроме совместимых).

5.
Ассемблер более сложен для совместных проектов.

4.2. Синтаксис ассемблера.

Синтаксис
общих элементов языка. В отличие от языков программирования высокого уровня,
где основным элементом языка является оператор, синтаксически программа на
ассемблере состоит из последовательности строк. Строка – основная единица
ассемблерной программы.

Синтаксис
строки имеет следующий общий вид:

<метка:>
<команда или директива> <операнды> <;комметарий>

Все
эти четыре поля необязательны, в программе вполне могут присутствовать и
полностью пустые строки для выделения каких либо блоков кода. Метка может быть
любой комбинацией букв английского алфавита, цифр и символов _, $, @, ?, но
цифра не может быть первым символом метки, а символы $ и ? иногда имеют
специальные значения и обычно не рекомендуются к использованию. Большие и
маленькие буквы по умолчанию не различаются, но различие можно включить, задав
ту или иную опцию в командной строке ассемблера. Во втором поле, поле команды,
может располагаться команда процессора, которая транслируется в исполняемый
код, или директива, которая не приводит к появлению нового кода, а управляет
работой самого ассемблера. В поле операндов располагаются требуемые командой
или директивой операнды (то есть нельзя указать операнды и не указать команду
или директиву). И наконец, в поле комментариев, начало которого отмечается
символом ; (точка с запятой), можно написать все что угодно — текст от символа
«;» до конца строки не анализируется ассемблером.

4.3. Ассемблерные вставки в
языках высокого уровня.

Языки
высокого уровня поддерживают возможность вставки ассемблерного  кода. 
Последовательность  команд  Ассемблера 
в  С-программе размещается в
asm-блоке:

asm

{

команда_1 //комментарий_1

команда_2 /* комментарий_2 */

————————

команда_n ; комментарий_n

}

Язык ассемблера — Введение в программирование


ИЕРАРХИЯ ЯЗЫКОВ


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


1.«Загрузить» содержимое первой ячейки памяти в регистр общего назначения, используемый в качестве накопителя. По команде загрузки производится выборка содержимого первой ячейки памяти (оставляя содержимое самой ячейки неизменным) и запоминание его в нашем регистре-накопителе, при этом предыдущее содержимое этого регистра теряется.


2.«Сложить» содержимое второй ячейки памяти с содержимым регистра-накопителя. Как мы знаем, содержимое второй ячейки остается неизменным, а результат сложения по окончании выполнения команды оказывается в регистре-накопителе.


3.«Запомнить» результат, находящийся пока в регистре, в некоторой ячейке памяти. Таким образом, третья ячейка памяти, как и регистр, содержит результат сложения содержимых первых двух ячеек.


Предположим, что в рассмотренном примере первое число хранилось в ячейке 256, второе — в ячейке 260, и результат требовалось поместить в ячейку 249- Регистр 6 был выбран в качестве накопителя. Соответствующие команды машинного языка IBM 360 таковы:


В большинстве программ команд значительно больше. Однако и в рассмотренном выше примере для задания необходимых команд на машинном языке потребовалось задать 96 (3×32) нулей и единиц. Ошибка хотя бы в одном бите привела бы к неверному конечному результату. Кроме того, программы на машинном языке очень трудны в составлении и для понимания из-за отсутствия соответствия между языком машины и обычным «человеческим» языком.


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


Это значительное улучшение по сравнению с двоичной формой машинного языка. Для программирования сложения двух чисел вместо 96 двоичных цифр теперь требуется написать всего лишь 3×8=24 шестнадцатеричных. В самом деле, наиболее удобной формой представления двоичной информации является шестнадцатеричная форма. Но все же команды машинного языка независимо от формы их представления лишь очень отдаленно напоминают о действиях, которые в соответствии с, ними выполняются.


Нужно попытаться представить операции машинного языка и адреса в форме более понятной программисту. Этой цели служит язык ассемблера. Символические формы представления, или мнемоники кодов операций, позволяют нам, например, писать Lвместо кода операции «Загрузка» (LOAD), А вместо кода операции «Сложение» (ADD),


STвместо кода операции «Запись в память» (STORE). Применение мнемоник исключает необходимость использования двоичных кодов операций машинного языка.


Но гораздо более важным является то, что в языке ассемблера можно использовать символические имена для указания адресов памяти, регистров и констант. Результат применения символа в программе будет тот же, как если бы мы указали его двоичный эквивалент. Но поскольку выбор символа для представления некоторого числа или ячейки памяти зависит только от нас, то мы можем выбрать для этой цели символ, характеризующий данное число. Например, если программа вычисляет среднее значение нескольких чисел, то для обозначения адреса ячейки, в которой будет находиться результат, можно выбрать символ AVERAGE(СРЕДНЕЕ). Если мы не используем символов для обозначения ячеек памяти, то необходимо знать физические адреса тех ячеек, в которых хранится обрабатываемая информация. Использование символов облегчает программирование, поскольку оно устраняет необходимость иметь дело с действительными машинными адресами.


Возвращаясь к нашему примеру, назовем три ячейки памяти NUM- BER1, NUMBER2 и NUMсоответственно и регистр 6 — REGSIX. Теперь мы можем переписать команды так:


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


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


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


Существуют языки, такие, как, например, ФОРТРАН, программы на которых гораздо больше походят на математическое или словесное описание необходимых вычислений. На ФОРТРАНе рассмотренная выше программа будет выглядить так:


NUM=NUMBER1+NUMBER2


Рис. 3.1. Иерархия языков.


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


ФОРТРАН, КОБОЛ, АЛГОЛ, ПЛ/1 и многие другие символические языки принадлежат классу языков, называемых компилируемыми языками. Вообще говоря, одно предложение или команда такого языка после обработки программой, называемой компилятором, преобразуется в несколько команд машинного языка. Мы уже видели, что оператор языка ФОРТРАН, задающий сложение двух чисел, эквивалентен трем машинным командам. В этом принципиальная разница между компилируемыми языками и языком ассемблера. Выражаясь математическим языком, между предложениями языка ассемблера и командами машинного языка существует взаимно однозначное соответствие, тогда как между предложениями компилируемых языков и командами машинного языка такого соответствия нет.


Компилируемые языки вообще являются языками более высокого уровня, чем машинный язык или язык ассемблера. Существует понятие иерархии языков, уровень некоторого языка в которой определяется «степенью отличия» его от машинного языка, языка «низшего» уровня. Рис. 3.1 дает представление об интересующей нас части иерархической лестницы языков.


Использование программы, написанной на одном из символических языков, состоит из двух этапов: 1) программа, написанная на символическом языке, должна быть переведена на язык машины, т. е. исходный код должен быть преобразован в объектный код; 2) под управлением полученного объектного кода работает ЭВМ, иначе говоря, объектный код является выполняемой программой. На этапе трансляции ЭВМ выполняет программу-компилятор или ассемблер, при этом исходный код рассматривается как входные данные. На этапе выполнения выполняется программа в объектном коде, в качестве входных используются данные, определенные программистом.

Зачем изучать язык ассемблера?


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


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


Во многих случаях программу для решения конкретной задачи удобнее писать на языке высокого уровня, чем на языке ассемблера, так как большинство языков высокого уровня специально ориентировано на выполнение некоторых видов работ. ФОРТРАН (FORmulaTRANslator) создан главным образом учеными; он соединяет вычислительные возможности с относительной простотой программирования алгебраических выражений, что сделало его наиболее употребимым языком для программирования научных вычислений. КОБОЛ (COmmonBusinessOrientedLanguage) был создан для программирования экономических задач; обработка больших объемов информации, например результатов инвентаризации товаров или списков сотрудников, наиболее легко программируется на КОБОЛе. АЛГОЛ (ALGOrithmicLanguage) является языком для описания алгоритмов, или правил вычислений.


Каждый из перечисленных языков служит вполне определенной цели. Но попытки выйти за пределы области ориентации языка приводят к значительным осложнениям. Обработку экономических данных трудно запрограммировать на ФОРТРАНе; чем больше требуется действий невычислительного типа для решения данной задачи, тем менее пригоден ФОРТРАН. С другой стороны, программирование на КОБОЛе сложных алгебраических выражений по меньшей мере утомительно. Кроме того, в КОБОЛе отсутствуют некоторые существенные особенности языков для научных вычислений, например библиотека стандартных подпрограмм для вычисления математических функций, и он слишком «словесен» для подобных применений. АЛГОЛ — международный язык описания алгоритмов; алгоритмы, написанные на АЛГОЛе, можно найти, например, в таком журнале, как САСМ. Однако большинство программистов согласится, что существует много задач, для которых этот язык неудобен. Язык сам по себе не содержит специальных операторов ввода-вывода, кроме того, синтаксис или структура языка, удобная для описания алгоритмов, затрудняет организацию вычислений. Таким образом, каждый из рассмотренных специализированных языков служит определенной цели, но чем он удобнее для программирования в конкретной области, тем больше теряет он в общности.


ПЛ/I(ProgrammingLanguageI) претендует на общность, которой так не хватает перечисленным выше языкам. Синтаксически этот язык является комбинацией АЛГОЛа, ФОРТРАНа и КОБОЛа и вдобавок обладает некоторыми чертами, которые делают возможным написание на нем полной операционной системы. Платой за эту гибкость является чрезвычайная сложность языка для начинающего программиста. Именно эта сложность не позволила и, вероятно, не позволит этому языку стать универсальным языком программирования для машин фирмы IBM. Для начинающего изучать вычислительную науку студента тот факт, что ПЛ/Iявляется языком высокого уровня, означает, что изучение его не может дать того, что дает изучение ассемблера — знания структуры и функций вычислительной системы.


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


Программисту, пишущему на языке высокого уровня, знание языка ассемблера дает дополнительные возможности. Чем ближе он знаком с ЭВМ и ее возможностями, тем большую выгоду он может извлечь для себя независимо от того, на каком языке он программирует. Например, человеку, знакомому с ассемблером, понятны форматы, используемые в ФОРТРАНе, и предложения определения файлов в КОБОЛе. Ему известно, для чего употребляется знак = в ФОРТРАНе и какова разница между целыми и вещественными переменными. Встретившись с вопросом: «Можно ли это сделать на ФОРТРАНе?», он сможет использовать свои знания возможностей машины для определения, имеет ли смысл этот вопрос с машинной точки зрения. Этого обычно достаточно для получения по крайней мере частичного ответа на вопрос.


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


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


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


Системные программы служат цели освобождения пользователя от выполнения машинно- и системно-ориентированных задач, не представляющих непосредственного интереса для него. Например, программист использует компилятор или ассемблер для трансляции исходной программы на машинный язык; пользователь, который был бы вынужден прежде, чем написать программу на ФОРТРАНе, написать компилятор, вероятно, откажется от такого способа решения своей задачи. Такие необходимые действия, как преобразование исходных данных из символьного кода в двоичное представление, удобное для выполнения арифметических операций, управление периферийными устройствами и управление вычислениями в целом непосредственно не связаны с проблемной программой и выполняются системными программами. Короче говоря, системные программы служат связующим звеном между программистом и аппаратурой вычислительной машины.


Поскольку системные программы расположены как бы между пользователем и машиной, то в своем большинстве они пишутся на языке ассемблера. Системные программы имеют дело с деталями конкретных машинных операций, обращение к которым невозможно при использовании языков высокого уровня. Например, программист на ФОРТРАНе обычно не управляет конкретным расположением программ в памяти машины, и ему все равно, какое конкретно устройство ввода с перфокарт будет использовано для ввода данных его программы. Действительно, поскольку он программирует на языке высокого уровня, ему нет необходимости заботиться об этих деталях. Но очевидно, что некоторые системные программы вынуждены работать с такого рода детальной информацией для обеспечения правильности работы программ пользователя и получения этими программами нужных данных. Язык ассемблера — это язык, дающий возможность выполнить эти задачи.


Теперь, как и было обещано, возвратимся к вопросу эффективности. Машинное время, т. е. время, в течение которого центральный процессор находится в распоряжении пользователя, стоит очень дорого, обычная цена составляет от 250 до 500 долларов в час. В некоторых случаях дополнительная плата взимается за использование памяти и загрузку периферийных устройств. Решение некоторых задач требует использования огромного количества памяти и машинного времени, в этих случаях возможность и экономичность их решения определяется эффективностью используемой программы.


Именно на ассемблере можно составить наиболее эффективную программу. Кроме того, программисту на ассемблере значительно •облегчается проблема использования всех возможностей ЭВМ. Так, он может увеличить скорость вычислений, используя большое количество памяти, и наоборот.


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


Особую важность имеет эффективность для системных программистов. Вряд ли пользователь пожелает оплачивать неэффективность операционной системы. Таким образом, мерой полезности системного программиста является его способность организации более эффективного использования всех ресурсов вычислительной установки.


Можно сказать, что ЭВМ напоминают многие другие машины тем, что, чем больше мы о них знаем, тем лучше мы можем их использовать. Знание языка ассемблера полезно системному программисту во многих аспектах: оно позволяет решить те задачи, решение которых сложно или невозможно с использованием лишь процедурных языков, помогает в отыскании ошибок и наиболее эффективном использовании всех ресурсов ЭВМ. Вследствие своих широких возможностей и высокой эффективности язык ассемблера незаменим для системных программистов.


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

Ассемблер это что? Компилятор/интерпритатор/нечто уникальное?

 
wHammer ©
 
(2007-02-08 09:24)
[0]

Помогите разобраться пожалуйста? Информация на различных информационных сайтах весьма противоречива.

С наилучшими…
Александр.


 
Алхимик ©
 
(2007-02-08 09:28)
[1]

http://ru.wikipedia.org/wiki/%D0%90%D1%81%D1%81%D0%B5%D0%BC%D0%B1%D0%BB%D0%B5%D1%80


 
wHammer ©
 
(2007-02-08 09:35)
[2]


> Алхимик ©   (08.02.07 09:28) [1]

Это все понятно. Но я могу тоже привести http://www.softcraft.ru/translat/lect/t01-02.shtml и отттуда:

Транслятор — обслуживающая программа, преобразующая исходную программу, предоставленную на входном языке программирования, в рабочую программу, представленную на объектном языке.

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

Интересно личное мнение форумчан по этому поводу?


 
palva ©
 
(2007-02-08 09:35)
[3]

Компилятор.


 
@!!ex ©
 
(2007-02-08 09:42)
[4]

Язык программирования? 🙂


 
TUser ©
 
(2007-02-08 09:43)
[5]

> Интересно личное мнение форумчан по этому поводу?

Ну, что ж. Скажу тебе свое личное мнение — мне пофиг. Хоть горшком назови, только в печь не ставь.


 
wHammer ©
 
(2007-02-08 09:44)
[6]


> Ну, что ж. Скажу тебе свое личное мнение — мне пофиг. Хоть
> горшком назови, только в печь не ставь.

Понятно, а мне вот нет.


 
Сергей М. ©
 
(2007-02-08 10:02)
[7]


> wHammer ©   (08.02.07 09:44) [6]

Ассемблер — это, прежде всего, язык программирования.

Любую среду программирования, использующую этот язык для создания программ, часто тоже называют «ассемблером», такие среды используют механизм компилирующего типа, т.к. результатом ассемблирования является код, ориентированный на исполнение той или иной исполняющей системой, например, процессором.


 
Сергей М. ©
 
(2007-02-08 10:06)
[8]


> wHammer

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


 
wHammer ©
 
(2007-02-08 10:14)
[9]


> Сергей М. ©   (08.02.07 10:02) [7]

Язык ассемблера. Язык программирования низкого уровня.
Язык получил свое название от слова ассемблер (lang-en|assembler — сборщик) — названия транслятора (компилятора) c языка ассемблера. Язык ассемблера, в некоторых случаях, для краткости, называют «ассемблером» (а что-то связанное с ним — «ассемблерный»), но в общем случае это неправильно. Также, сам ассемблер (программу) никогда не называют -компилятором языка ассемблера или компилятором ассемблера (разве что в редких случаях).


 
wHammer ©
 
(2007-02-08 10:16)
[10]

…отсюда http://ru.wikipedia.org/wiki/%D0%AF%D0%B7%D1%8B%D0%BA_%D0%B0%D1%81%D1%81%D0%B5%D0%BC%D0%B1%D0%BB%D0%B5%D1%80%D0%B0


 
TUser ©
 
(2007-02-08 10:20)
[11]

А какая разница, как классифицировать этот объект. О чем речь идет — и там всем понятно.


 
wHammer ©
 
(2007-02-08 10:27)
[12]


> TUser ©   (08.02.07 10:20) [11]

Да нет, не все, т.к. интересует процесс преобразования ассемблер-программы в машинные коды. Как мне кажется, этот процесс — практичеки типичный процесс компиляции, ну за некоторыми исключениями. Но есть иное мнение, например:

Ассемблер (транслятор языка ассемблера), своего рода компилятор – берётся программа, написанная на языке ассемблера (исходный текст) и переводится в машинный код. Однако есть некоторые различия. Когда компилятор делает перевод с одного языка на другой, предполагается получить программу на целевом языке, который будет управлять программой теми же самыми методами и давать те же самые результаты. Подобно выбору между языками программирования различного строения, но выполняющих одну и ту же работу, компилятор имеет свободу выбора – одновременно предполагая, что выбор будет оптимальным. Различные компиляторы, переводящие программы с языка на язык могут давать весьма различные результаты, даже при том, что программы будут делать одно и тоже. В этом плане танслятор языка ассемблера немного отличается, так как инструкции языка ассемблера точно соответствуют машинным кодам, на которые они переведены. В большинстве случаев мы точно знаем какие байты будут переведены языком ассемблера. В данном случае работа транслятора подобна работе интепритатора. Это наиболее очевидно при рассмотрении следующей директивы:
       db 90h
котороя указывает транслятору на необходимость поместить один байт с информацией (90h) в текущую позицию в шестнадцетиричном коде. В данном случае работа транслятора ассемблера больше похожа на работу интерпритатора, а машинный код произведённый транслятором – просто перевод с языка исходной программы.
Когда кто-то пишет программу на языке ассемблера, он обычно думает, что программа, которую он пишет, написана на машинном коде – ассемблере, только выполняется эта задача по созданию программы, на более простом к восприятию машинном языке, обеспечивающем более лёгкое запоминание имён инструкций (называемых мнемоникой). Ещё этот машинный язык разрешает маркировать различные места в памяти, а затем вычислять соответствующие адреса и смещения автоматически. При написании некоторой простой последовательности инструкций на языке ассемблера:
       mov ax, 4C00h
       int 21h
никто обычно не думает о том, как интерпретируются эти директивы, каким получается машинный код.
Но фактически имеются две программы, слитые вместе (компилятор и интерпритатор). Это наделяет ассемблер новым качеством: инструмент, который, оказывается и компилятором и интерпритатором одновременно.
Рассмотрим две простые команды языка ассемблера. Они прибавляют EAX к самому себе, повторяя этот процесс пять раз. Первая команда (mov) использует ECX, чтобы считать количество повторений:
       mov   ecx,5
square: add   eax,eax
       loop   square  
Транслятор переводит три инструкции в машинный код и приводит их в действие, которое добавляет EAX к самому себе. Это повторяется пять раз, пока процессор выполняет машинный код. Каждый проход уменьшает ECX на единицу и, в случае если ECX – все еще не ноль, переходит обратно к метке (square).
Так как ассемблер является и компилятором и интерпритатором одновременно, то ни один из этих терминов не даёт полного представления о работе транслятора языка ассемблера.

Оригиал:

Assembler as a compiler and assembler as an interpreter
The implementations of programming languages are divided into two main classes: compilers and interpreters. The interpreter is a program which takes a program written in some language and executes it. And the compiler is simply a program that translates program written in one language into another one — the most commonly the target language is the machine language, so the result can be then executed by the machine.
From this point of view, the assembler appears to be a kind of compiler — it takes the program written in assembly language (the source code) and translates it into machine language. However there are some differences. When compiler does the translation from one language to another, it is expected to make the program in the target language that will run (when executed by some interpreter or processor) the same way and give the same results. But in exact details like choosing between many possible language constructs that would do the same, the compiler has freedom of choice — while it is expected to make possibly the best choice, the various compilers translating between the same two languages may give quite different results, even though the programs will do the same. The assembler is a bit different in this aspect, as there is a kind of exact correspondence between the instructions of assembly language and the instructions of machine language they are translated to. In fact in most cases you know what bytes exactly will be generated by the assembly language construct. This is what in fact makes assembler behave a bit like interpreter. This is the most obvious in the case of directives like:
       db      90h
which tells the assembler to put the one byte with value 90h at the current position in the output code. This is more like if the assembler was an interpreter, and the machine language generated by assembler was simply an output of the interpreted program. Even the instructions, which in fact represent the machine language instructions they are translated to, can be considered to be actually the directives that tell assembler to generate the opcode of given instruction and place it at current position in output.
Also one can put no instruction mnemonics at all into the source code, and use only DB directives to create, for instance, just some text. In such case the output is not a program at all, as it doesn»t contain any machine language instructions. This makes assembler appear to be really an interpreter.
But when someone writes the program in assembly language, he usually thinks about the program he writes in machine language — assembler just makes the task of creating program in machine language easier, providing easy to memorize names for instructions (called mnemonics for that very reason), allowing to label various places in memory and other values with names and then calculating the appropriate addresses and displacements automatically. When writing some simple sequence of instructions in assembly language:
       mov     ax,4C00h
       int     21h
one usually doesn»t think about them as interpreted directives that generate the machine language instructions. One does think as if they actually were the instructions which they generate, one thinks in terms of the program he writes with the assembly language, not the program he writes in assembly language. But there are actually those two programs merged together, two different layers of thinking in one source code. This makes assembler a new quality: a tool that appears to be both compiler and interpreter at the same time.


 
wHammer ©
 
(2007-02-08 10:30)
[13]


Объяснение не очень, но ключевая фраза здесь:

Так как ассемблер является и компилятором и интерпритатором одновременно, то ни один из этих терминов не даёт полного представления о работе транслятора языка ассемблера.


 
@!!ex ©
 
(2007-02-08 10:40)
[14]

Бред. Или у енмя уж совсем не правильное определение «интрепритатора» забито.


 
evvcom ©
 
(2007-02-08 10:41)
[15]

> [12] wHammer ©   (08.02.07 10:27)
> В данном случае работа транслятора ассемблера больше похожа
> на работу интерпритатора

Чушь какая-то. В моем понимании:
1. Компилятор — программа, преобразующая исходный код в объектный.
2. Транслятор — то же, что и компилятор, за исключением того, что выполняется однозначное преобразование: одна инструкция — одна машинная команда.
3. Интерпретатор — программа, которая преобразует исходный текст или некий оптимизированный, но все же еще виртуальный, во время выполнения в некие инструкции и далее их выполняет.

Транслятор с языка ассемблера не выполняет исходный код (текст), написанный программистом, потому называть его интерпретатором просто в корне неверно.


 
Desdechado ©
 
(2007-02-08 11:00)
[16]

Сложности с толкованием связаны, имхо, с тем, что ранние компиляторы выполняли трансляцию не сразу в машинные коды, а в промежуточный ассемблерный код, который уже дополнительно транслировался в машинный. Эта двухфазность (не считая компоновки) и вызывает «три основные группы: ассемблеры, компиляторы и интерпретаторы».
Такая двухфазность хорошо документирована на советских многотерминальных машинных комплексах, которые я застал в нашем ХНУРЭ.


 
Сергей М. ©
 
(2007-02-08 11:19)
[17]


> evvcom ©   (08.02.07 10:41) [15]

1. Совершенно верно. Следует лишь уточнить, что результирующий «объектный код» нельзя рассматривать без жесткой привязки к целевой исполняющей системе, каковой м.б. как «железо» (процессор), так и «софт» (виртуальная машина, например, java-машина)

2. Не согласен. Трансляторы выполняют преобразование языковых конструкций входного языка программирования в логически эквивалентные им языковые конструкции выходного языка программирования. Среды же транслирующего типа, как правило, сочетают в себе функции транслятора и компилятора — программа, к примеру, на Фортране в ходе обработки средой транслируется сначала в промежуточное языковое представление (низкоуровневое, традиционно ассемблерное), а уж затем (опционально) компилирует промежуточный рез-т в объектный код.

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


 
Плохиш ©
 
(2007-02-08 11:23)
[18]

Классная ветка, не думал, что философов уже тоже заставляют изучать программирования, са ещё и на ассемблере :-))


 
wHammer ©
 
(2007-02-08 11:46)
[19]


> Desdechado ©   (08.02.07 11:00) [16]
> Сложности с толкованием связаны, имхо, с тем, что ранние
> компиляторы выполняли трансляцию не сразу в машинные коды,
>  а в промежуточный ассемблерный код, который уже дополнительно
> транслировался в машинный. Эта двухфазность (не считая компоновки)
> и вызывает «три основные группы: ассемблеры, компиляторы
> и интерпретаторы».
> Такая двухфазность хорошо документирована на советских многотерминальных
> машинных комплексах, которые я застал в нашем ХНУРЭ.

Вот это, как мне кажется основное. Спасибо. Теперь более менее понятно откуда ноги растут.


> Плохиш ©   (08.02.07 11:23) [18]
> Классная ветка, не думал, что философов уже тоже заставляют
> изучать программирования, са ещё и на ассемблере :-))

Это почемуже сразу такое предвзятое обо мне мнение? :))


 
evvcom ©
 
(2007-02-08 12:58)
[20]

> [17] Сергей М. ©   (08.02.07 11:19)
> 2. Не согласен.

Ну пусть будет так. Не возражаю. Мое понимание в общем основывалось на некотором опыте знакомства в былые времена с ассемблером и только. С Фортраном же я близко не знакомился. В принципе твое пояснение лишь немного расширило мое представление о трансляторах. Согласен, что я слишком узко его изобразил в [15] 🙂


 
Сергей М. ©
 
(2007-02-08 13:06)
[21]


> evvcom ©   (08.02.07 12:58) [20]

Строго говоря, сравнивать или противопоставлять «Трансляторы/Компиляторы» vs «Интерпретаторы» не уместно.


 
evvcom ©
 
(2007-02-08 13:15)
[22]

> [21] Сергей М. ©   (08.02.07 13:06)

Угу, согласен. Я и не пытался. Кстати,

> [17] Сергей М. ©   (08.02.07 11:19)
> 3. Ассемблерные среды интерпретирующего типа

я бы все ж назвал просто эмуляторами. Понятное дело, что все они интерпретирующего типа, причем совсем неважно, что берется за источник: текст на языке ассемблера или «исполняемый» код.


 
Чапаев ©
 
(2007-02-08 14:29)
[23]

> Бред. Или у енмя уж совсем не правильное определение «интрепритатора»
> забито.
У етбя слова с ошибками. Откуда правильному определению взяться? %-)

> [17] Сергей М. ©   (08.02.07 11:19)
Компилятор и интерпретатор — два вида трансляторов, которые так или иначе исходный текст программы транслируют в машинный код.


 
evvcom ©
 
(2007-02-08 14:30)
[24]

> [23] Чапаев ©   (08.02.07 14:29)
> Компилятор и интерпретатор — два вида трансляторов, которые
> так или иначе исходный текст программы транслируют в машинный
> код.

Это интерпретатор транслирует в машинный код? LOL 🙂


 
Сергей М. ©
 
(2007-02-08 14:46)
[25]


> Чапаев ©   (08.02.07 14:29) [23]

Интерпретатор ничего не «транслирует», он интерпретирует вх.языковые конструкции «шаг за шагом», вызывая при этом на выполнение уже имеющиеся участки нативного кода, реализующего логику текущей интерпретируемой языковой конструкции.


 
Чапаев ©
 
(2007-02-08 16:04)
[26]

> Это интерпретатор транслирует в машинный код?
Если программа исполняется, то она так или иначе транслируется в машинный код, нес па?


 
Сергей М. ©
 
(2007-02-08 16:09)
[27]


> Чапаев ©   (08.02.07 16:04) [26]
>


> Если программа исполняется

Кем исполняется ? Процессором ? Виртуальной машиной ?
Определись уже …


 
evvcom ©
 
(2007-02-08 16:12)
[28]

> [26] Чапаев ©   (08.02.07 16:04)

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

> нес па?

это на каком? Французский я не знаю совсем.


 
Чапаев ©
 
(2007-02-08 16:17)
[29]

> Кем исполняется ? Процессором ? Виртуальной машиной ?
А процессор — не «виртуальная машина для машинного кода»? 😉


 
Чапаев ©
 
(2007-02-08 16:19)
[30]

> [29] Чапаев ©   (08.02.07 16:17)
+ туда же проц, который нативно исполняет жабовский байт-код. Не помню только, сумели его сделать или плюнули


 
Сергей М. ©
 
(2007-02-08 16:40)
[31]


> Чапаев ©   (08.02.07 16:17) [29]


> процессор — не «виртуальная машина для машинного кода»?

С каких пор «железный» процессор стал «виртуальной машиной» ?


> проц, который нативно исполняет жабовский байт-код

Пример такого «железного» процессора ты, разумеется, не приведешь, ибо «не помнишь»)


 
Чапаев ©
 
(2007-02-08 16:57)
[32]

> Пример такого «железного» процессора ты, разумеется, не
> приведешь, ибо «не помнишь»)
Могу, конечно, поискать в стопке журналов статью об этом… Но, как я уже сказал, я даже не знаю, довели ли разработку до конца.


 
Чапаев ©
 
(2007-02-08 17:05)
[33]

> [32] Чапаев ©   (08.02.07 16:57)
Гм… Статью нашёл в «Компьютерном обозрении» №10 за 1998 год.

Вот ещё статьи нагуглил: http://www.osp.ru/nets/1997/01/142118, http://www.osp.ru/os/1999/01/179635.


 
Stexen ©
 
(2007-02-08 18:39)
[34]


> Помогите разобраться пожалуйста? Информация на различных
> информационных сайтах весьма противоречива.

Компилятор — это программа
Интерпритатор — программа
Ассемблер — язык программирования низкого уровня — стандарта вообще говоря нет, для каждого процессора обычно свой… Хотя, вообще говоря, последнее утверждение вполне противоречиво, все опять таки же будет зависеть от компилятора, но с другой стороны для создания компилятора для языка ассемблера под конкретный процессор должен быть стандарт, т.о. для каждого процессора могут быть свои стандарты, соответсвенно Язык ассемблера некий абстрактный класс, имеющий множество классов детей — являющимися реализациями языка под конкретный тип процессора. А вообще wasm.ru есть!
Может ничего нового не сказал(лень все перечитывать) так что извените за флуд если что…


 
oldman ©
 
(2007-02-08 18:43)
[35]

Анекдот из прошлого:

Купили мы как-то новую версию ассемблера.
Скопировали ее на диск.
Папку-то надо как-то назвать…
Так и назвали — ASS (блин, мозгов не хватило назвать TASM)
Пришел зав.отделом.
Долго пялился в монитер с медленно расширяющимися глазами.
Потом аккуратно подвел курсор к данной папке и нажал Ентер.
А ТАМ АССЕМБЛЕР!!! :))))))))))))


 
GrayFace ©
 
(2007-02-08 19:10)
[36]

Уж точно не интерпретатор.

wHammer ©   (08.02.07 10:27) [12]
Бред какой-то. Вначале автор четко пишет, что интерпретатор исполняет программу, потом говорит о какой-то однозначности соответствия, чотя ежу понятно, Ассемблер ничего не исполняет, значит не удволитворяет его же определению.

evvcom ©   (08.02.07 10:41) [15]
3. Интерпретатор — программа, которая преобразует исходный текст или некий оптимизированный, но все же еще виртуальный, во время выполнения в некие инструкции и далее их выполняет.

Преобразовывать он ничего не должен. Если преобразует, то это уже Just in Time компилятором попахивает.

Чапаев ©   (08.02.07 16:04) [26]
Если программа исполняется, то она так или иначе транслируется в машинный код, нес па?

Не она. Исполняется код интерпретатора, программа ни к чему не приобразуется.


 
GrayFace ©
 
(2007-02-08 19:11)
[37]

P.S. ИМХО


 
oldman ©
 
(2007-02-08 19:14)
[38]


> GrayFace ©   (08.02.07 19:10) [36]
> Чапаев ©   (08.02.07 16:04) [26]
> Если программа исполняется, то она так или иначе транслируется
> в машинный код, нес па?
> Не она. Исполняется код интерпретатора, программа ни к чему
> не приобразуется.

Слова спиши…
Буду потомкам показывать в назидание…


 
oldman ©
 
(2007-02-08 19:18)
[39]


> GrayFace ©   (08.02.07 19:10) [36]

Без обид:

По анкете — 20 лет.
Пора бы уж где-нибудь почитать, чем компилятор отличается от интерпретатора и как они работают с исходным текстом программы…
Чтобы больше вышеизложенного тобой не писать в форум программистов.


 
GrayFace ©
 
(2007-02-08 22:25)
[40]

oldman ©   (08.02.07 19:18) [39]
По анкете — 20 лет.

19.

oldman ©   (08.02.07 19:18) [39]
Пора бы уж где-нибудь почитать, чем компилятор отличается от интерпретатора и как они работают с исходным текстом программы…

http://ru.wikipedia.org/wiki/%D0%98%D0%BD%D1%82%D0%B5%D1%80%D0%BF%D1%80%D0%B5%D1%82%D0%B0%D1%82%D0%BE%D1%80
Видимо, Вы плохо читали мой пост.


Что такое ассемблер? — Определение с сайта WhatIs.com

От

Ассемблер — это программа, которая принимает базовые компьютерные инструкции и преобразует их в набор битов, которые процессор компьютера может использовать для выполнения своих основных операций. Некоторые люди называют эти инструкции языком ассемблера, а другие используют термин язык ассемблера .

Вот как это работает:

  • Большинство компьютеров поставляются с определенным набором самых простых инструкций, которые соответствуют основным операциям, которые компьютер может выполнять.Например, команда «Загрузить» заставляет процессор перемещать строку битов из места в памяти процессора в специальное место хранения, называемое регистром. Предполагая, что процессор имеет не менее восьми регистров, каждый из которых пронумерован, следующая инструкция переместит значение (последовательность битов определенной длины) из ячейки памяти 3000 в место хранения, называемое регистром 8:
     L 8,3000 
  • Программист может написать программу, используя последовательность этих инструкций ассемблера.
  • Эта последовательность инструкций ассемблера, известная как исходный код или исходная программа, затем указывается программе ассемблера при запуске этой программы.
  • Программа на ассемблере принимает каждый оператор программы в исходной программе и генерирует соответствующий битовый поток или шаблон (последовательность нулей и единиц заданной длины).
  • Выходные данные программы ассемблера называются объектным кодом или объектной программой относительно исходной программы ввода. Последовательность нулей и единиц, которые составляют объектную программу, иногда называют машинным кодом.
  • После этого объектную программу можно запустить (или выполнить) в любое время.

На самых ранних компьютерах программисты фактически писали программы в машинном коде, но вскоре были разработаны языки ассемблера или наборы инструкций для ускорения программирования. Сегодня программирование на ассемблере используется только там, где требуется очень эффективный контроль над операциями процессора. Однако для этого требуется знание набора команд конкретного компьютера. Исторически сложилось так, что большинство программ было написано на языках «более высокого уровня», таких как COBOL, FORTRAN, PL / I и C.Эти языки легче изучать и быстрее писать программы, чем язык ассемблера. Программа, обрабатывающая исходный код, написанный на этих языках, называется компилятором. Как и ассемблер, компилятор принимает операторы языка более высокого уровня и сводит их к машинному коду.

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

Последний раз обновлялся в ноябре 2005 г.


Продолжить чтение об ассемблере

Узнайте подробнее о стратегии серверного оборудования

Введение в Ассемблер — GeeksforGeeks

Ассемблер — это программа для преобразования инструкций, написанных на низкоуровневом ассемблерном коде, в перемещаемый машинный код и генерации дополнительной информации для загрузчика.

Он генерирует инструкции, оценивая мнемонику (символы) в поле операции и находя значение символа и литералов для создания машинного кода. Теперь, если ассемблер выполняет всю эту работу за одно сканирование, он называется однопроходным ассемблером, в противном случае, если он выполняет несколько сканирований, он называется многопроходным ассемблером. Здесь ассемблер разделяет эти задачи на два прохода:

  • Pass-1:
    1. Определите символы и литералы и запомните их в таблице символов и таблице литералов соответственно.
    2. Отслеживание счетчика местоположения
    3. Обработка псевдоопераций
  • Pass-2:
    1. Создание объектного кода путем преобразования символьного кода операции в соответствующий числовой код операции
    2. Создание данных для литералов и поиск значений символов

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

 [Метка] [Код операции] [операнд]

  Пример:  M ADD R1, = '3'
где, M - метка; ADD - символический код операции;
R1 - операнд символьного регистра; (= '3') - буквальный

  Программа сборки: 
Метка Операционный код операнда значение LC (счетчик местоположения)
ДЖОН СТАРТ 200
       MOVER R1, = '3' 200
       MOVEM R1, X 201
L1 ДВИГАТЕЛЬ R2, = '2' 202
       LTORG 203
Х ДС 1 204
       КОНЕЦ 205
 

Давайте посмотрим, как работает эта программа:

  1. START: Эта инструкция запускает выполнение программы с позиции 200, а метка с START дает имя программе.(JOHN — имя программы)
  2. MOVER: Перемещает содержимое литерала (= ’3 ′) в регистровый операнд R1.
  3. MOVEM: Перемещает содержимое регистра в операнд памяти (X).
  4. MOVER: Он снова перемещает содержимое литерала (= ’2 ′) в регистровый операнд R2, а его метка указывается как L1.
  5. LTORG: Назначает адрес литералам (текущее значение LC).
  6. DS (пространство данных): Он назначает пространство данных 1 символу X.
  7. END: Завершает выполнение программы.

Работа Pass-1: Определите символьную и буквальную таблицу с их адресами.
Примечание: буквальный адрес указывается как LTORG или END.

Шаг 1: START 200 (здесь нет символа или литерала, поэтому обе таблицы будут пустыми)

Шаг 2: MOVER R1, = ‘3 ′ 200 (=’ 3 ‘- литерал, поэтому буквенная таблица)

Литерал Адрес
= ‘3 ′ — — —

Шаг 3: MOVEM R1, X 201
упоминается до его объявления, поэтому он сохраняется в таблице символов с пустым полем адреса.

Символ Адрес
X — — —

Step-4: L1 MOVER R2, = ‘2 ′ 202
L1 — метка 2 ′ является буквальным, поэтому сохраните их в соответствующих таблицах

Символ Адрес
X — — —
L1 202
Адрес
= ‘3’ — — —
= ‘2’ — — —

Шаг 5: первый LTORG 203
Назначить указанный адрес указанному адресу по значению LC, т.е.e., 203

9141

Буквальный Адрес
= ‘3’ 203
= ‘2’ — — — Шаг

: X DS 1 204
Это оператор объявления данных, т.е. X назначается пространство данных 1. Но X — это символ, который упоминался ранее на шаге 3 и определен на шаге 6. Это состояние называется проблемой прямой ссылки, где переменная упоминается раньше. к его декларации и может быть решен путем обратного исправления.Итак, теперь ассемблер назначит X адрес, указанный значением LC текущего шага.

Шаг 7: END 205
Программа завершает выполнение, и оставшийся литерал получит адрес, указанный значением LC инструкции END. Вот полная таблица символов и букв, составленная на первом этапе ассемблера.

Литерал Адрес
= ‘3’ 203
= ‘2’ 205

передаст

вместе со своими таблицами, сгенерированными с помощью таблицы Now перейти на второй этап ассемблера для дальнейшей обработки псевдо-кодов операций и машинных кодов операций.

Работа второго этапа:
Второй этап ассемблера генерирует машинный код путем преобразования символьных машинных кодов операций в их соответствующую битовую конфигурацию (машинно-понятная форма). Он хранит все машинные коды операций в таблице MOT (таблица кодов операций) с символическим кодом, их длиной и их битовой конфигурацией. Он также будет обрабатывать псевдооперации и сохранять их в таблице POT (таблица псевдоопераций).

Различные Базы данных, необходимые для Pass-2:

1. Таблица MOT (таблица кодов операций станка)
2.Таблица POT (таблица псевдо-кодов операций)
3. Базовая таблица (хранящая значение базового регистра)
4. LC (счетчик местоположения)
 

Взгляните на блок-схему, чтобы понять:

В целом ассемблер работает как:

Разработка программного обеспечения на языке ассемблера от Valvano

Разработка программного обеспечения на языке ассемблера от Valvano

Разработка программного обеспечения на языке ассемблера
Обзор
Джонатан В. Вальвано

В этой статье, в которой обсуждается программирование на языке ассемблера,
прилагается к книге Embedded Microcomputer Systems: Real Time Interfacing , опубликованной Brooks-Cole 1999.В этом документе всего четыре
частей
Обзор (этот документ)
Синтаксис (поля, псевдооперации)
Локальные переменные
Примеры

Программное обеспечение на языке ассемблера аналогично другому программному обеспечению
процессы разработки. Для получения общей информации о процессе
по разработке программного обеспечения см. главу 2 в документе Embedded Microcomputer Systems: Real Time Interfacing by Jonathan W. Valvano. Опять же, в этой статье речь пойдет о сборке.
языковая разработка с использованием приложения TExaS .Есть два типа команд, которые существуют в нашем
сборка исходный код . Коды операций — это обычные инструкции, которые переводятся в машинный код для
выполняться компьютером при запуске программы. Мы используем псевдооперацию
коды в нашем исходном коде, чтобы дать инструкции ассемблеру
сам. Ниже перечислены настройки цвета по умолчанию для
Редактор TExaS .
Метки показаны фиолетовым цветом
Операционные коды показаны синим
Псевдооперационные коды показаны серым
Числа показаны темно-синим цветом
Строки показаны в магните
Операнды показаны черным
Комментарии показаны зеленым цветом
Ошибки сборки показаны красным цветом

————————————————- ————————————-
Пример: замок на базе микрокомпьютера
Для иллюстрации процесс разработки программного обеспечения, мы реализуем
простой цифровой замок.Система блокировки имеет 7 тумблеров и
соленоид, как показано на следующем рисунке. Если 7-битный двоичный
шаблон на порте A биты 6-0 становится 0100011 минимум на 10 мс,
тогда соленоид активируется. Задержка 10 мс компенсирует
для дребезга переключателя. Для получения информации о переключателях и соленоидах
см. главу 8 книги «Встроенные микрокомпьютерные системы : интерфейс в реальном времени » Джонатана В. Вальвано. На данный момент нам нужно понять, что
что биты 6-0 порта A являются входными сигналами для компьютера и порта
Бит 7 — это выходной сигнал.

Прежде чем мы напишем ассемблерный код, нам нужно разработать план программного обеспечения.
Разработка программного обеспечения — это итеративный процесс. Хотя мы перечисляем
шагает процесс разработки в порядке 1,2,3 …, на самом деле
мы повторяем эти шаги снова и снова.
1) Начнем со списка входов и выходов. Мы указываем
диапазон значений и их значимость. В этом примере мы
будет использовать ПОРТУ. Биты 6-0 будут входами. 7 входных сигналов представляют
беззнаковое целое число от 0 до 127.Порт Бит 7 будет выходом.
Если PA7 равен 1, соленоид сработает, и дверь откроется.
быть разблокированным. В языке ассемблера мы используем псевдупы equ pseudoops для присвоения символьных имен PORTA DDRA соответствующим адресам портов $ 0000 $ 0002 .
PORTAequ $ 0000; PA6-PA0switches, PA7solenoidlock
DDRAequ $ 0002; задает вход или выход

2) Далее мы составляем список необходимых структур данных.
Структуры данных используются для сохранения информации. Если данные нужны
чтобы быть постоянным, то он размещается в глобальном пространстве.Если
программное обеспечение изменит свое значение, после чего оно будет размещено в ОЗУ.
В этом примере нам нужен 16-битный беззнаковый счетчик. Мы используем
org для размещения структур данных в ОЗУ, $ 0800 . Псевдопетля rmb резервирует байты для структуры. Эти строки также присваивают
символьное имя CNT на соответствующий адрес информации $ 0800 .
cntrmb2; 16-битный счетчик
org $ F000; EEPROM

Если структура данных может быть определена во время сборки и останется
исправлено, то его можно разместить в EEPROM.В этом примере мы
определит 8-битную фиксированную константу для хранения кода клавиши, который
оператору необходимо настроить разблокировку двери. Мы разместим эти
строки сразу после программы, чтобы они были определены
в памяти ROM или EEPROM. Псевдопетля fcb определяет 8-битную константу. Эта строка также присваивает
символическое имя ключ к соответствующему адресу информации.
keyfcb% 00100011; код клавиши

На данный момент не совсем понятно, где именно в EEPROM это
константа будет, но, к счастью для нас, ассемблер вычислит
точный адрес автоматически.После сборки программы
мы можем посмотреть строку в файле листинга или в таблице символов
чтобы увидеть, где в памяти размещена каждая структура.
3) Далее мы разрабатываем программный алгоритм, который представляет собой последовательность
операций, которые мы хотим выполнить. Есть много подходов к
описание плана. Опытные программисты могут разработать алгоритм
прямо на ассемблере. С другой стороны, большинству из нас нужно
абстрактный метод документирования желаемой последовательности действий.
Блок-схемы, псевдокод и код языка высокого уровня — это три
общие описательные форматы.Приложение TExaS уникально тем, что если вы нарисуете блок-схему
на компьютере, вы можете вставить его прямо в программу как
комментарий. Нет никаких формальных правил относительно псевдокода, скорее
это сокращение для описания того, что и когда делать.
Мы можем поместить наш псевдокод в качестве документации в комментарий
области нашей программы. Ниже показана блок-схема
слева и псевдокод и код C справа для нашего цифрового замка
пример.

Обычно мы помещаем программы в ROM или EEPROM.На MC68HC812A4
EEPROM начинается с $ F000, поэтому мы помещаем следующую псевдопетлю org перед программой.
org $ F000; EEPROM

Даже если эта программа не будет использовать стек, мы инициализируем
указатель стека на последнее место ОЗУ. На 6812 мы
выполните следующую инструкцию для инициализации стека.
mainlds # $ 0C00
Затем мы пишем ассемблерный код для реализации алгоритма, как показано.
в приведенной выше блок-схеме и псевдокоде. На шаге 1) мы инициализируем
Порт A, так что PA7 является выходом, а PA6-PA0 — входами.Для информации
о портах и ​​регистрах направления см. главу 1 из Встроенные микрокомпьютерные системы: интерфейс в реальном времени Джонатана В. Вальвано.
movb # 80, DDRA; PA6-PA0 вход, PA7 выход

На шаге 2) отключаем соленоид.
bclrPORTA, # 80 долларов США; отключить электромагнитный замок

На шаге 3) мы инициализируем счетчик до 4000, что является числом
циклов требуется ждать 10 мс.
movw # 4000, cnt; 10,000,000 нс / (125 * 20)

На шаге 4) мы реализуем неопределенный цикл.Размещаем сборку
метка в местах программы, к которым мы хотим перейти. В
bra инструкция является безусловным переходом.
петля
бюстгальтер

Внутри неопределенного цикла мы проверяем,
соответствует ключевому коду. В этой реализации мы переходим к с , если переключатели не соответствуют ключевому коду. Если они совпадают, мы
выполнит инструкцию сразу после bne off .
loopldaaPORTA; [3] inputfrom7switches
anda # $ 7F; [1]
cmpakey; [3] matchkeycode?
bneoff; [3]

Если переключатели соответствуют коду клавиши, то 16-битный счетчик
уменьшено.
ldxcnt; [3]
dex; [1]
stxcnt; [3]

Если счетчик обнуляется, дверь разблокирована. Команда bne перейдет в цикл, если cnt не равно нулю. Команда bset установит PA7 в 1.
bneloop; [2] = 20циклов / цикл
bsetPORTA, # $ 80; enablesolenoidlock
braloop

Если переключатели не совпадают с кодом ключа, значит соленоид неисправен.
выключен, и cnt вернется к 4000.
offmovw # 4000, cnt; 10,000,000ns / (125 * 20)
bclrPORTA, # 80; disablesolenoidlock
braloop

Мы соединили вышеупомянутые части, чтобы создать исходный код.В
порядок инструкций очень важен, потому что он определяет
последовательность исполнения. Последние две строки определяют, где
компьютер начнет выполнение после сброса.
; ****** lock.rtf ************
; активировать соленоид (PA7 = 1) ifswitchesmatchkeycode
; переключить bounceisles меньше 10 мс
PORTAequ $ 0000; PA6-PA0switches, PA7solenoidlock
DDRAequ $ 0002; указывает вход или выход org $ 0800; RAM
cntrmb2; 16-битный счетчик
org $ F000; EEPROM
mainlds # $ 0C00
movb # 80 $, DDRA; PA6-PA0input, PA7output
bclrPORTA, # 80 $; disablesolenoidlock
,000,000,000,000 / movw # 4000, cnt; (125 * 20)
loopldaaPORTA; [3] inputfrom7switches
anda # $ 7F; [1]
cmpakey; [3] matchkeycode?
bneoff; [3]
ldxcnt; [3]
dex; [1]
stxcnt; [3]
bneloop; [2] = 20cycles / loop
; 7switchmatchkeycodeformorethan10ms
bsetPORTA, # 80; enablesolenoidlock
braloop
, cnt; 10,000,000 нс / (125 * 20)
bclrPORTA, # $ 80; disablesolenoidlock
braloop
keyfcb% 00100011; код клавиши
org $ FFFE
fdbmain

4) Последний этап — отладка.Вы можете запустить этот пример на
симулятор TExaS путем загрузки файлов lock.rtf , lock.uc и lock.io . Есть версии для всех четырех микрокомпьютеров, расположенных в
Каталоги MC6805, MC6808, MC6811 и MC6812. Для информации
по отладке см. главу 2 из Embedded Microcomputer Systems: Real Time Interfacing by Jonathan W. Valvano.

————————————————- ————————————-
Основы ассемблера
Ассемблеры — это программы, обрабатывающие язык ассемблера. источник
программные операторы и переводить их в исполняемую машину
языковые объектные файлы.Кросс-ассемблеры (такие как TExaS ) позволяют писать и редактировать исходные программы на одном компьютере (
host) для генерации исполняемого кода для другого компьютера (цели).
В нашем случае целью является симулятор микрокомпьютера, но поскольку TExaS генерирует записи S19, вы можете загрузить файл S19 на реальный компьютер.
для выполнения.
Символьный язык, используемый для кодирования исходных программ, которые будут
обрабатываемый Ассемблером, называется языком ассемблера. Язык
представляет собой набор мнемонических символов, представляющих: операции (т.е.,
мнемоники машинных инструкций или директивы ассемблеру),
символьные имена, операторы и специальные символы. Язык ассемблера
предоставляет мнемонические коды операций для всех машинных инструкций
в наборе инструкций. Инструкции определены и объяснены
в Справочном руководстве по программированию (для моделей 6805, 6808, 6811
или 6812), который можно найти в каталоге pdf на компакт-диске. Печатные копии этих руководств Motorola
можно получить в Литературном центре Motorola.Краткий обзор
каждой инструкции и примера использования можно найти, выполнив поиск
Contents страница механизма справки, включенного в приложение TExaS . Ассемблер также содержит мнемонические директивы.
которые определяют вспомогательные действия, которые должен выполнить Ассемблер.
Эти директивы не всегда переводятся на машинный язык.
Этот ассемблер поддерживает многие псевдооперационные коды.

Ассемблер TExaS — это двухпроходный ассемблер.Во время первого прохода
Исходная программа читается для разработки таблицы символов. В течение
на втором проходе объектный файл создается (собирается) с использованием
таблица символов, разработанная на первом этапе. Это во время второго прохода
что также создается листинг исходной программы. Таблица символов
воссоздается во втором проходе. Ошибка фазирования возникает, если значения таблицы символов, рассчитанные за два прохода
разные.
Ошибки, возникающие в процессе сборки (например, undefined
символ, недопустимый код операции, слишком далеко место назначения ветвления и т. д.) находятся
объяснено в файле со списком (я сократил комментарии, чтобы каждый
строка поместится на странице).
Copyright 1999-2000 Test EXecute And Simulate — Версия 1.00
; ****** lock.rtf *******
; активировать соленоид (PA7 = 1)
; bounceislessthan 10ms
$ 0000PORTAequ $ 0000
$ 0002DDRAequ $ 0002
$ 0800org $ 0800
$ 0800cntrmb2
$ F000org $ F000
$ F000CF0C00 [2] (0) {OP} mainlds # $ 0C00
$ F003180B800002 [4] (2) {OPwP} movb # 80 $, DDRA
$ F0084D0080 [4] (6) {rPOw} offbclrPORTA, # $ 80
$ F00BCC115C [2] (10) {OP} lookldd # 4444
$ F00E7C0800 [3] (12) {WOP} stdcnt
$ F0119600 [3] (15) {rfP} loopldaaPORTA
$ F013847F [1] (18) {P} anda # $ 7F
$ F015B1F028 [3] (19) {rOP} cmpakey
$ F01826EE [3] (22) {PPP / P} bneoff
$ F01AFE0800 [3 ] (25) {ROP} ldxcnt
$ F01D09 [1] (28) {O} dex
$ F01E7E0800 [3] (29) {WOP} stxcnt
$ F02126EE [3] (32) {PPP / P} bneloop
; 7switchesmatchkeycode
$ F0234C0080 [4] (35) {rPOw} bsetPORTA, # 80 $
$ F02620E3 [3] (39) {PPP} bralook
$ F02823keyfcb% 00100011
$ FFFEorg * FainFFE
db $ FFFEF ********** Таблица символов *********************
DDRA $ 0002
PORTA $ 0000
cnt $ 0800
ключ $ F028
look $ F00B
loop $ F011
main $ F000
off $ F008
Сборка прошла успешно

Исходный код — это файл символов ASCII, который обычно создается
с редактором.Каждый исходный оператор обрабатывается полностью
до того, как будет прочитан следующий исходный оператор. Поскольку каждое утверждение
обработано, Ассемблер проверяет метку, код операции и
поля операндов. Таблица кодов операций сканируется на соответствие
с известным кодом операции. Во время обработки стандартной операции
мнемоника кода, стандартный машинный код вставляется в
объектный файл. Если директива ассемблера обрабатывается,
предпринимаются надлежащие действия.
Любые ошибки, обнаруженные Ассемблером, отображаются после фактического
печатается строка, содержащая ошибку.Если исходный список не
сообщения об ошибках по-прежнему отображаются в TheLog.RTF
чтобы указать, что процесс сборки не прошел нормально.
Объектный код — это двоичные значения (инструкции и данные), которые,
при исполнении на компьютере выполнять намеченную функцию.
Файл листинга содержит адрес, объектный код и копию
исходного кода. При желании вы можете указать количество циклов
для выполнения инструкции, текущего количества циклов и явного
подробные сведения о типах циклов, необходимых для выполнения инструкции.Файл листинга также содержит таблицу символов, описывающую, где
в память будут загружены программа и данные. Таблица символов
это список всех имен, используемых в программе вместе с
значения. Символ создается, когда вы помещаете метку, начиная с столбца
1. Примеры этого типа: DATA, CNT, Start, Rep, Inp, NAME.
и РАЗМЕР. Значение таблицы символов для этого типа — абсолютное
адрес памяти, по которому будет выполняться инструкция, переменная или константа.
хранятся в памяти. Метка второго типа создается равной
псевдооперация.Значение для этого типа символа — это просто число
указанный в поле операнда. Когда ассемблер обрабатывает
инструкция с символом в ней, она просто заменяет фиксированный
значение вместо символа. Поэтому мы будем использовать символы для
уточнить (облегчить понимание) наши программы. Символ
таблица для этого примера —
*************** Таблица символов *********************
DDRA $ 0002
PORTA $ 0000
cnt $ 0800
key $ F033
loop $ F011
main $ F000
off $ F028

Компилятор преобразует исходный код языка высокого уровня в
объектный код.Кросс-компилятор также преобразует исходный код в объект.
code и создает файл листинга, за исключением того, что объектный код
создан для целевой машины, которая отличается от машины
запуск кросс-компилятора. TExaS — кросс-ассемблер, потому что он работает на компьютере Intel, но
создает код объекта 6805/6808/6811/6812. Hiware и ImageCraft’s
ICC11 и ICC12 включают кросс-ассемблер и кросс-компиляторы.
потому что они запускаются на ПК и создают объект 6805/6808/6811/6812
код.Для получения общей информации о разработке программного обеспечения на языке C
см. главу 2 из Встроенные микрокомпьютерные системы: интерфейс в реальном времени Джонатана В. Вальвано.

Motorola использует S-записи для хранения объектного кода. 16-битный
значения сохраняются в памяти с первым старшим байтом.
Для получения дополнительной информации о конкретном формате S-записи
см. записи S19 содержат объектный код. S-запись для приведенного выше примера выглядит как
следует. Фактический объектный код окрашен в синий цвет.

S121F000CF0C00180B8000024D008018030FA008009600847FB1F033260EFE0800091F
S119F01E7E080026EE4C008020E918030FA008004D008020DE23A9
S105FFFEF0000D
S

————————————————- ————————————-
Запуск на оценочной плате
Когда отладка включает управление реальное оборудование, мы должны использовать
настоящий микрокомпьютер, работающий в режиме реального времени. Потому что программы
хранятся в ПЗУ или EEPROM на однокристальном микрокомпьютере.
трудно отлаживать.Для решения этой проблемы существуют оценки
платы, которые запускают микрокомпьютер в расширенном режиме и включают
отладчик. В этой системе разработки мы также используем кросс-ассемблер.
или кросс-компилятор для преобразования наших исходных программ в листинг
файл и объектный файл. Объектный файл передается по серийному
порт на оценочную плату и загружается в оперативную память. Простой отладчик
(например, 6811 EVB с использованием отладчика BUFFALO) позволяет нам тестировать
наше программное обеспечение. Типичные функции отладчика включают:
запускать выполнение по определенному адресу
устанавливать точку останова по определенному адресу
читать / изменять регистры микрокомпьютера
читать / изменять порты ввода-вывода микрокомпьютера
читать / изменять RAM
читать / стирать / программировать EEPROM
загружать объектный код в оперативную память, полученную с ХОЗЯЙСТВЕННОГО ПК.
Сложные среды разработки включают редактор, компилятор,
ассемблер, связь через последовательный порт и отладчик в одном
приложение, запущенное на ПК. Для получения дополнительной информации о программном обеспечении
о разработке см. главу 2 из Встроенные микрокомпьютерные системы: интерфейс в реальном времени Джонатана В. Вальвано. Для информации о разработке оборудования
платформы, см. Руководство по лабораторной работе, прилагаемое к этой книге.

————————————————- ————————————-
Моделирование
При разработке программного обеспечения с использованием симулятора мы использовать кросс-ассемблер
или кросс-компилятор, работающий на ПК, для преобразования наших исходных программ
в файл листинга и объектный файл.Затем мы «запускаем» нашу программу
на симуляторе, имитирующем микрокомпьютер с его внешним компонентом.
Моделирование в этой среде сложнее, чем на другом компьютере.
приложение из-за исполнения программного обеспечения тесно связано
к аппаратному обеспечению (чрезвычайно зависящему от него). Моделирование встроенного
система эффективна только в том случае, если все программное обеспечение, компьютер, внешние
моделируются механические и внешние электрические компоненты. Другой
Проблема усложняется тем, что внешний механический
и электрические устройства.На следующем рисунке показано программное обеспечение.
процесс разработки с использованием тренажера.

Дополнительные темы включают.
Синтаксис языка ассемблера
Поле метки
Поле операции
Поле операнда
Поле комментария
Список ошибок сборки дает результаты сборки
Список ошибок сборки типы ошибок сборки
Псевдооперации сборки — это операции времени сборки
Записи S19 содержат объект код

За подробным объяснением инструкций и их адресацией
режимах, см. справочную систему приложения TExaS .Иногда лучший способ изучить новую технику
это наблюдать за примерами. См. Файл ReadMe.txt для получения списка примеров, которые можно запустить на симуляторе.
В приложение TExaS включено множество примеров на языке ассемблера. В этот документ включены несколько простых примеров 6812.

Этот документ состоит из четырех общих частей
Обзор (этот документ)
Синтаксис (поля, псевдооперации)
Локальные переменные
Примеры

Введение в историческую эволюцию практики программирования

В первые дни вычислительной техники оборудование было дорогим, а программисты — дешевыми.На самом деле программисты были настолько дешевы, что их даже не называли «программистами», а на самом деле они обычно были математиками или инженерами-электриками. Ранние компьютеры использовались для быстрого решения сложных математических задач, поэтому математики естественно подходили для работы по «программированию».

Что такое программа?

Для начала немного предыстории. Компьютеры ничего не могут сделать сами по себе, поэтому им требуются программы, управляющие их поведением. Программы можно рассматривать как очень подробные рецепты, которые принимают ввод и производят вывод.Шаги в рецепте состоят из инструкций, которые работают с данными. Хотя это звучит сложно, вы, вероятно, знаете, как работает это утверждение:

  1 + 2 = 3  

Знак плюс — это «инструкция», а цифры 1 и 2 — это данные. Математически знак равенства указывает на то, что обе стороны уравнения «эквивалентны», однако в большинстве компьютерных языков используется некоторый вариант равенства для обозначения «присваивания». Если бы компьютер выполнял этот оператор, он бы сохранил результаты сложения («3») где-нибудь в памяти.

Компьютеры умеют вычислять числа и перемещать данные по иерархии памяти машины. Я не буду много говорить о памяти, за исключением того, что она обычно бывает двух видов: быстрая / маленькая и медленная / большая. Регистры ЦП очень быстрые, очень маленькие и действуют как блокноты. Основная память обычно очень большая и не такая быстрая, как регистровая память. Процессоры перетасовывают данные, с которыми они работают, из основной памяти в регистры и обратно во время выполнения программы.

Сборщики

Компьютеры были очень дорогими, а люди — дешевыми.Программисты проводили бесконечные часы, переводя рукописные математические вычисления в компьютерные инструкции, которые компьютер мог выполнять. У самых первых компьютеров были ужасные пользовательские интерфейсы, некоторые из которых состояли только из переключателей на передней панели. Переключатели представляют единицы и нули в одном «слове» памяти. Программист настраивал слово, указывал, где его сохранить, и фиксировал слово в памяти. Это отнимало много времени и было подвержено ошибкам.

В конце концов, инженер-электрик решил, что его время недешево, и написал программу с вводом, написанным как «рецепт», выраженный в терминах, которые люди могут прочитать, вывод в машиночитаемой версии.Это был первый «ассемблер», и это было очень спорно. Люди, которым принадлежали дорогие машины, не хотели «тратить» вычислительное время на задачу, которую люди уже выполняли; хоть и медленно и с ошибками. Со временем люди начали ценить скорость и точность ассемблера по сравнению с программой, собранной вручную, и количество «реальной работы», выполняемой с помощью компьютера, увеличилось.

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

 

01 MOV R0, 1
02 MOV R1, 2
03 ADD R0, R1, R2
04 MOV 64, R0
05 STO R2, R0

Каждая строка представляет собой компьютерную инструкцию, которая начинается с сокращенного имени инструкции, за которым следуют данные, над которыми эта инструкция работает. Эта небольшая программа сначала «переместит» значение 1 в регистр R0, а затем 2 в регистр R1. Строка 03 добавляет содержимое регистров R0 и R1 и сохраняет полученное значение в регистре R2.Наконец, строки 04 и 05 определяют, где результат должен быть сохранен в основной памяти (адрес 64). Управление местом хранения данных в памяти — одна из самых трудоемких и подверженных ошибкам частей при написании компьютерных программ.

Компиляторы

Сборка была намного лучше, чем написание компьютерных инструкций вручную; однако первые программисты стремились писать программы так, как они привыкли писать математические формулы. Это стимулировало развитие компилируемых языков более высокого уровня, некоторые из которых являются историческими сносками, а другие используются до сих пор.ALGO — одна из таких сносок, в то время как реальные проблемы продолжают решаться сегодня с помощью таких языков, как Fortran и C.

Введение этих языков «высокого уровня» позволило программистам писать свои программы в более простых терминах. На языке C наша дополнительная программа ассемблера будет написана:


 

Первый оператор описывает часть памяти, которую будет использовать программа. В этом случае размер памяти должен быть целым числом, а ее имя — x . Второй оператор — это сложение, хотя и написано «в обратном порядке».«Программист на C прочитал бы это, поскольку« X присваивается результат один плюс два ». Обратите внимание, что программисту не нужно указывать, где поместить x в память, поскольку компилятор позаботится об этом.

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

Огромным преимуществом компилируемых языков перед программами на ассемблере был перенос с одной компьютерной модели или марки на другую. В первые дни развития вычислительной техники произошел взрывной рост различных типов вычислительного оборудования таких компаний, как IBM, Digital Equipment Corporation, Texas Instruments, UNIVAC, Hewlett Packard и других. Ни у одного из этих компьютеров нет ничего общего, кроме того, что их нужно было подключать к источнику питания. Архитектуры памяти и ЦП сильно различались, и на перевод программ с одного компьютера на другой часто уходили человеко-годы.

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

Жизнь программистов стала очень хорошей. Было намного проще выразить проблемы, которые они хотели решить, с помощью языков высокого уровня. Стоимость компьютерного оборудования резко падала из-за достижений в области полупроводников и изобретения интегральных микросхем.Компьютеры становились быстрее и мощнее, а также становились намного дешевле. В какой-то момент, возможно, в конце 80-х, произошла инверсия, и программисты стали дороже, чем оборудование, которое они использовали.

Переводчики

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

В качестве примечания: когда люди говорят «интерпретируемые программы работают медленно», это основной источник предполагаемой недостаточной производительности. Современные компьютеры настолько удивительны, что большинство людей не могут отличить скомпилированные и интерпретируемые программы.

Интерпретируемые программы, иногда называемые «скриптами», еще проще перенести на различные аппаратные платформы. Поскольку сценарий не содержит никаких машинно-зависимых инструкций, одна версия программы может работать на многих разных компьютерах без изменений. Загвоздка, конечно же, в том, что интерпретатор должен быть перенесен на новую машину, чтобы это стало возможным.

Одним из примеров очень популярного интерпретируемого языка является perl. Полное выражение нашей проблемы сложения на языке Perl:

  $ х = 1 + 2  

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

Виртуальные машины

Последнее повальное увлечение моделями программирования — виртуальные машины, часто сокращенно ВМ. Есть две разновидности виртуальной машины; системные виртуальные машины и виртуальные машины процессов. Оба типа виртуальных машин обеспечивают некоторый уровень абстракции от «реального» вычислительного оборудования, хотя и имеют разные области действия.Системная виртуальная машина — это программное обеспечение, которое предлагает замену физическому оборудованию, в то время как виртуальная машина процесса предназначена для выполнения программы независимо от системы. Таким образом, в этом случае виртуальная машина процесса (далее виртуальная машина) похожа по объему на интерпретатор в том, что программа сначала компилируется в промежуточную форму, прежде чем виртуальная машина ее выполнит.

Основное различие между интерпретатором и виртуальной машиной заключается в том, что виртуальная машина реализует идеализированный ЦП, доступ к которому осуществляется через его виртуальный набор команд.Эта абстракция позволяет писать инструменты интерфейса пользователя, которые компилируют программы, написанные на разных языках, и нацелены на виртуальную машину. Вероятно, самая популярная и известная виртуальная машина — это виртуальная машина Java (JVM). Первоначально JVM предназначалась только для языка программирования Java еще в 1990-х годах, но теперь она поддерживает многие популярные компьютерные языки: Scala, Jython, JRuby, Clojure и Kotlin, чтобы перечислить лишь некоторые из них. Есть и другие примеры, которые могут быть неизвестны. Я только недавно узнал, что мой любимый язык Python — это не интерпретируемый язык, а язык, размещенный на виртуальной машине!

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

Это обертка

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


Эта статья изначально была опубликована на PyBites и перепечатывается с разрешения.

образование — Зачем нужен ассемблер?

Зачем нужен ассемблер?

Что ж, на самом деле есть только один язык, который нам когда-либо понадобится , который называется «машинный язык» или «машинный код».Выглядит это так:

  0010000100100011
  

Это единственный язык, на котором ваш компьютер может разговаривать напрямую. Это язык, на котором говорит ЦП (и технически разные типы ЦП говорят на разных версиях). Также отстойно смотреть и пытаться понять.

К счастью, каждый раздел двоичного файла соответствует определенному значению. Он разделен на несколько разделов:

  0010 | 0001 | 0010 | 0011

тип операции исходный регистр другой исходный целевой регистр
0010 0001 0010 0011
  

Эти значения соответствуют:

  тип операции 0010 = сложение
исходный регистр 0001 = регистр 1
другой источник 0010 = регистр 2
регистр назначения 0011 = регистр 3
  

Таким образом, эта операция складывает числа в регистрах 1 и 2 и помещает это значение в регистр 3.Если вы буквально поместите эти значения в ЦП и скажете ему «идти», он добавит вам два числа. Операция «вычитание» может быть 0011 или что-то в этом роде, вместо 0010 здесь. Какое бы значение ни заставлял ЦП делать вычитание.

Итак, программа может выглядеть так (не пытайтесь понять ее, поскольку я придумал эту конкретную версию машинного кода для объяснения вещей):

  инструкция 1: 0010000100100011
инструкция 2: 0011000110100100
инструкция 3: 0101001100010111
инструкция 4: 0010001001100000
  

Отстойно читать? Определенно. Но он нам нужен для ЦП . Что ж, если каждый машинный код соответствует определенному действию, давайте просто сделаем простое «английское» сокращение, а затем, когда мы поймем, что делает программа, преобразуем его в настоящий двоичный машинный код и передадим его ЦП для выполнения.

Итак, наша оригинальная инструкция сверху могла выглядеть так:

  (значение) тип операции регистр источника другой регистр назначения источника
(машинный код) 0010 0001 0010 0011
("Английский") добавить r1 r2 r3
  

Обратите внимание, что эта английская версия имеет точное соответствие машинному коду .Поэтому, когда мы пишем строчку на этом «английском», мы на самом деле просто пишем более дружелюбный и понятный машинный код.

Ну, это ассемблер. Вот почему он существует и был создан изначально.

Чтобы понять, зачем нам это нужно сейчас, прочтите приведенные выше ответы, но ключ к пониманию заключается в следующем: языки высокого уровня не имеют единственного представления — это машинный код. Например. на C или Python, или что-то еще:

  г = х + у
  

Это звучит так же, как наше добавление сверху, при условии, что x находится в регистре 1, y находится в регистре 2, а z должно оказаться в регистре 3.Но как насчет этой линии?

  г = х * 2 + (у / 6) * р + д - г
  

Попробуйте представить эту строку в 16-битном двоичном формате и сказать ЦП «идти». Вы не можете. В машинном коде нет единой инструкции операции для выполнения сложения, вычитания и всего остального с 4 или 5 переменными одновременно. Поэтому сначала его нужно преобразовать в последовательность машинного кода . Это то, что вы делаете, когда «компилируете» или «интерпретируете» язык высокого уровня.

Ну, у нас есть программы для этого, зачем нам сейчас сборка? Допустим, ваша программа работает медленнее, чем вы ожидаете, и вы хотите знать, почему.Глядя на «вывод» этой строки на машинном языке, он может выглядеть так:

  1010010010001001
0010001000010000
0110010000100100
0010001011000010
0010100001000001
0100010100000001
0010010101000100
0010101010100000
0000100111000010
  

Просто чтобы закончить эту одну строчку Python. Так вы действительно хотите отладить это?!?!?! НЕТ . Вместо этого вы просите свой компилятор любезно предоставить вам вывод в форме, которую вы действительно легко понимаете, а именно версию на языке ассемблера, соответствующую точно этому машинному коду.Затем вы можете выяснить, делает ли ваш компилятор что-то глупое, и попытаться исправить это.

(Дополнительное примечание к совету @ Raphael: вы могли бы построить ЦП, который работает с вещами, отличными от двоичных кодов, такими как троичные (основание 3) или десятичные коды, или даже ASCII. Однако для практических целей мы действительно придерживаемся двоичного кода.)

Ассемблеры и язык ассемблера

Стр. 1 из 3

Инструкции, которые распознают большинство компьютеров, слишком просты, чтобы люди могли беспокоиться о них — и поэтому мы изобрели язык ассемблера.Узнайте, как это работает и как с него началось все движение по абстрагированию от аппаратного обеспечения компьютера.

Что знают программисты

Содержание
  1. Компьютер — в чем главная идея? *
  2. Принцип памяти — Компьютерная память и голубятни *
  3. Принципы исполнения — ЦП
  4. Суть программирования
  5. переменных — объем, время жизни и многое другое *
  6. Двоичная арифметика
  7. Шестнадцатеричный *
  8. Двоичный — отрицательные числа
  9. Числа с плавающей запятой
  10. Внутри компьютера — адресация
  11. Функция Mod
  12. Рекурсия
  13. Утерянное искусство карты памяти *
  14. Хеширование — величайшая идея в программировании
  15. Расширенное хеширование
  16. XOR — Волшебный своп
  17. Введение в XML для программистов
  18. От данных к объектам *
  19. Что такое функция первого класса — и почему вам это должно быть небезразлично *
  20. Стеки и деревья
  21. Стек LIFO — краткое руководство *
  22. Структуры данных — деревья
  23. Внутренние случайные числа
  24. Метод Монте-Карло
  25. Кэш-память и принцип кеширования
  26. Сжатие данных The Dictionary Way
  27. Сложные даты *
  28. Последовательное хранение *
  29. Магия слияния *
  30. Мощность операторов
  31. Сердце компилятора *
  32. Основы указателей
  33. Функциональное и дисфункциональное программирование *

* Недавно отредактированный

Мы уже рассмотрели, что такое компьютерная программа, но полностью проигнорировали вопрос о том, как в первую очередь создается компьютерная программа.Естественный язык большинства компьютеров — двоичный, потому что это то, на чем основано их оборудование.

Когда вы пишете инструкцию в двоичном формате, это больше, чем просто абстрактная идея о том, что что-то должно быть сделано, и то, как машина таинственным образом «понимает» и выполняет то, что ей говорят. Отдельные биты в инструкции управляют тем, что делает оборудование. Один бит может выбирать конкретный регистр, другой устанавливать добавляемое арифметическое устройство, а третий может очищать бит состояния. Двоичный код инструкции — это набор «рычагов», которые заставляют машину делать то, что вы хотите.

Вы можете запрограммировать компьютер в двоичном формате, если хотите. Однако программирование в двоичном формате — неинтересное и не очень продуктивное занятие. Вы должны помнить, что делает каждый бит и как собрать их вместе, чтобы получить нужные вам инструкции. Ошибка в одном бите, и инструкция означает другое. Чтение двоичного кода также является сложной задачей для обычного человека. Даже в этом случае в первые дни именно так программировали компьютеры — часто с использованием блоков переключателей и ламп на лицевых панелях.

Быстро изобрели лучший способ выражения вещей.

Мнемонические коды

Первым шагом от чистого двоичного или машинного кода было использование символов для инструкций.

Например, в машинном коде x86 инструкция для сохранения восьмибитового значения xxxxxxxx, где x равно 0 или 1, в регистре BL:

10110000xxxxxxxx

Например, если данные — 01010101, полная команда будет:

1011000001010101

Это немного легче читать, если он записан в шестнадцатеричном формате как B055, который загружает 55 шестнадцатеричных значений в регистр BL.(Обратите внимание, что буква B в начале инструкции — просто совпадение и не имеет ничего общего с именем регистра BL.)

Hex может быть лучше двоичного, но он все равно не запоминается большинству нормальных людей. Решение — использовать легко запоминающиеся символы или мнемонику.

Например:

MOV BL, 055H

естественно читается как «переместить 55 шестнадцатеричных чисел в регистр BL». Это представление также скрывает тот факт, что все различные инструкции «перемещения» имеют разные двоичные машинные коды.Например, чтобы переместить 16-битное значение в регистр BX, вы должны использовать:

  10111000  

, за которыми следуют 16 бит, которые вы хотите загрузить, но в качестве мнемоники это будет:

MOV BX, 01234H

, который больше похож на инструкцию первого хода.

Другими словами, использование мнемоники не только упрощает чтение, но и унифицирует структуру машинного кода, группируя все различные инструкции перемещения в виде MOV, за которым следует то, что следует переместить из источника в место назначения, т.е.е.

MOV пункт назначения, источник

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

Давайте создадим ассемблер

Сначала эти мнемоники были просто вспомогательными средствами для запоминания.

Программист написал программу, используя мнемонику, а затем вручную перевел программу, используя список кодов, в машинный код.

Но тут какому-то умному программисту пришла в голову идея, сэкономившая много работы. В конце концов, все программисты в основном ленивы, это главная причина того, чтобы быть программистом!

Кто-то подумал,

«что, если я напишу программу для преобразования мнемоники в машинный код».

Совершенно очевидно.

Все, что нужно, — это большая таблица преобразований мнемонического кода в машинный. Результатом этой идеи стал первый «ассемблер» и первый «ассемблер».

Терминология вначале была не совсем стабильной, и вы обнаружите, что некоторые ранние языки ассемблера назывались «автокодированием» и многими другими вещами. Неважно, как вы это называете, идея ассемблера — это просто комбинация использования мнемоники для представления машинных кодов и использования программы для их преобразования в машинный код перед запуском программы.

Это начало процесса разработки, который идет от базового использования мнемоники до сложных языков программирования, которые мы используем сегодня.

Процесс сборки

Процесс сборки

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

Рассмотрим инструкцию ассемблера, подобную следующей

          JMP ПОЗЖЕ
          ...
          ...
ПОЗЖЕ:
 

Это называется прямой ссылкой. Если ассемблер обрабатывает файл по одной строке за раз,
тогда он не знает, где находится ПОЗЖЕ, когда впервые встречает инструкцию перехода.Итак, это не так
знать, является ли прыжок коротким, ближним или дальним. Есть большая разница между
эти инструкции. Они имеют длину 2, 3 и 5 байтов соответственно. Ассемблеру придется
угадайте, как далеко находится инструкция, чтобы сгенерировать правильную инструкцию. Если ассемблер
предположения ошибочны, тогда адреса всех остальных этикеток в программе будут неправильными, и
код необходимо будет регенерировать. Или ассемблер всегда мог выбрать худший вариант. Но
это будет означать неэффективность программы, так как все прыжки будут считаться далеко
jumps и будет иметь длину 5 байтов, тогда как на самом деле большинство переходов являются короткими, а их длина составляет всего 2
длина в байтах.

Soooooooo, что нужно сделать, чтобы ассемблер сгенерировал правильную инструкцию?
Ответ: просканируйте код дважды.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *