Студопедия

КАТЕГОРИИ:


Архитектура-(3434)Астрономия-(809)Биология-(7483)Биотехнологии-(1457)Военное дело-(14632)Высокие технологии-(1363)География-(913)Геология-(1438)Государство-(451)Демография-(1065)Дом-(47672)Журналистика и СМИ-(912)Изобретательство-(14524)Иностранные языки-(4268)Информатика-(17799)Искусство-(1338)История-(13644)Компьютеры-(11121)Косметика-(55)Кулинария-(373)Культура-(8427)Лингвистика-(374)Литература-(1642)Маркетинг-(23702)Математика-(16968)Машиностроение-(1700)Медицина-(12668)Менеджмент-(24684)Механика-(15423)Науковедение-(506)Образование-(11852)Охрана труда-(3308)Педагогика-(5571)Полиграфия-(1312)Политика-(7869)Право-(5454)Приборостроение-(1369)Программирование-(2801)Производство-(97182)Промышленность-(8706)Психология-(18388)Религия-(3217)Связь-(10668)Сельское хозяйство-(299)Социология-(6455)Спорт-(42831)Строительство-(4793)Торговля-(5050)Транспорт-(2929)Туризм-(1568)Физика-(3942)Философия-(17015)Финансы-(26596)Химия-(22929)Экология-(12095)Экономика-(9961)Электроника-(8441)Электротехника-(4623)Энергетика-(12629)Юриспруденция-(1492)Ядерная техника-(1748)

LOOP – сложная команда, простая запись цикла




Цикл со счетчиком с помощью конструкций IF и GOTO

Давайте попробуем написать цикл с пустым телом (то есть внутри цикла не будут выполняться никакие команды). Первое, с чем нужно разобраться — это где разместить переменную управления циклом, то есть счетчик. Счетчик нужен для того, чтобы цикл выполнялся не бесконечно, а определенное количество раз. Команда сравнения позволяет хранить счетчик либо в памяти, либо в каком-то регистре общего назначения. Запишем нашу схему на языке ассемблера. Мы уже знаем, как реализовать на языке ассемблера конструкции IF и GOTO, из которых можно построить цикл FOR. В качестве счетчика (переменной I) мы будем использовать регистр ЕСХ:

for_start:

mov ecx,0; инициализируем счетчик ЕСХ = 0

for_loop:; метка для перехода назад

...; тело цикла

...

inc есх; увеличиваем ЕСХ на 1

сmр есх,10; сравниваем ЕСХ с 10

jnz for_loop; если не равно, переход на for_loop

for_finish:; если ЕСХ = 10, выходим

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

for_start:

mov dword [i],0; переменная типа dword i = 0

for_loop:; метка для перехода назад

...; тело цикла

...

inc dword [i]; увеличиваем i на 1

cmp dword [i],10; сравниваем i с 10

jnz for_loop; если не равно, переход на for_loop

for_finish:; если равно, выходим

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

Pассмотрим еще одну версию цикла, использующую команду DEC и команду проверки флага ZF вместо команды сравнения СМР. Принцип работы следующий: устанавливаем счетчик (ЕСХ=10), выполняем тело цикла, уменьшаем счетчик на 1. Если ZF установлен, значит, в ЕСХ находится 0 и нам нужно прекратить выполнение цикла:

for_start:

mov есх,10; ЕСХ =10

for_loop:; метка для перехода назад

...; тело цикла

...

dec есх; уменьшаем ЕСХ на 1

jnz for_loop; если не 0, переходим на for_loop

for_finish:; если 0, выходим из цикла

Приведенные выше программы на языке ассемблера реализуют следующую конструкцию языка С:

for (i=0; i<10; i++) {}

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

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

LOOP метка

Подобно команде MUL, команда LOOP работает с двумя операндами. Первый операнд фиксирован, и мы не можем его указать явно. Это значение регистра ЕСХ (или СХ). Второй – это адрес целевой метки цикла. Инструкция LOOP уменьшает значение регистра ЕСХ (СХ) на единицу и, если результат не равен 0, то она переходит на указанную метку. Метка должна быть в пределах 128 байтов (короткий тип перехода).

Рассмотрим простой цикл FOR с использованием команды LOOP:

for_start:

mov eсx, 10; CX = 10 – 10 итераций

for_loop:;метка для возврата назад

...; тело цикла

loop for_loop; уменьшаем СХ, если не 0, переходим к

; for_loop

for_finish:; выход из цикла

Таким образом, код стал еще более компактным.

 

 

Лекция 9. Команды работы со стеком. Организация

подпрограмм и прерываний

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

9.1. Что такое стек и как он работает?

Стек – особая область памяти для хранения временных данных. В PC-совместимых компьютерах нет аппаратного стека, поэтому данные стека хранятся в памяти. Стек работает по принципу LIFO (Last In – First Out) – последним пришел, первым вышел. Данные, помещенные в стек последними, будут первыми «вытолкнуты» из стека. С точки зрения обычного человека это какая-то неправильная очередь, но в жизни мы сталкиваемся с ней часто. Например, мы собираемся куда-то поехать и укладываем вещи. Когда мы откроем сумку, вверху окажутся вещи, которые мы положили последними.

Вершина стека представлена парой SS:SP (SS:ESP) – сегмент стека (Stack Segment) и указатель вершины стека (Stack Pointer). Стек растет в памяти «вниз», то есть новая порция данных записывается по меньшему адресу. Впрочем, точный адрес данных внутри стека не имеет для нас значения, потому что любая операция над стеком имеет дело с его вершиной, на которую всегда указывает регистр SP (ESP). Стек может содержать 16- или 32-битные данные.

Микропроцессор имеет две команды для работы со стеком – PUSH и POP.

Команды PUSH и POP: втолкнуть и вытолкнуть

Команда PUSH позволяет поместить в стек содержимое любого 16- или 32-битного регистра или ячейки памяти. Формат команды следующий:

PUSH o1

Пример использования:

push eax; поместить ЕАХ в стек

Мы можем сами реализовать команду PUSH с помощью следующей пары команд:

sub esp,4;уменьшаем ESP на 4 (ЕАХ - 4-байтный регистр)

mov [ss:esp],eax; сохраняем ЕАХ в стеке

В общем виде (с использованием оператора sizeof, «позаимствованного» из языков высокого уровня) команда PUSH o1 может быть записана на псевдоязыке так:

(E)SP=(E)SP-sizeof(o1)

о1 –> SS:[(E)SP]

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

Команду POP можно реализовать с помощью команд MOV и ADD:

mov eax,[ss:esp]; помещаем в ЕАХ вершину стека

add esp,4; "удаляем" последнее значение типа dword в стеке

Рассмотрим несколько примеров:

push eax; сохранить значение регистра ЕАХ в стеке

push esi; сохранить значение регистра ESI в стеке

pop eax; извлечь данные из стека в ЕАХ

pop esi; извлечь данные из стека в ESI

В результате выполнения этих команд мы поменяем местами значение регистров ЕАХ и ESI: сначала помещаем в стек значение ЕАХ, затем – ESI, после этого извлекаем из стека последнее сохраненное значение (бывшее значение регистра ESI) в регистр ЕАХ, после этого в стеке останется бывшее значение ЕАХ, которое мы записываем в ESI.

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

mov ах,0x1234; АХ = 0x1234

mov bx,0x5678; ВХ = 0x5678

push ax; сохранить значение регистра АХ в стеке

push bx; сохранить значение регистра ВХ в стеке

...; изменяем значения регистров

pop bx; извлекаем вершину стека в ВХ

Необходимо помнить, что 8-битные регистры сохранять в стеке нельзя. Нельзя и поместить в стек регистр IP (EIP) непосредственно, при помощи команд PUSH/POP: это делается по-другому.

Команды PUSHA/POPA и PUSHAD/POPAD

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

Поскольку команды PUSHA и РОРА разрабатывались для предшественника процессора 80386, они не могут сохранять значений 32-битных регистров (они просто не подозревают об их существовании). Для сохранения и восстановления значений расширенных регистров служат команды PUSHAD и POPAD.

Регистры помещаются в стек в следующем порядке (сверху вниз):

(Е)АХ, (Е)СХ, (E)DX, (E)BX, (E)SP, (E)BP, (E)SI, (E)DI

Рассмотрим небольшой пример:

pusha; поместить в стек все регистры общего назначения

....; некоторые действия, модифицирующие

...; значения регистров

pора; восстанавливаем все регистры

Команды PUSHF/POPF и PUSHFD/POPFD

Рассмотренные четыре команды не заботились о помещении в стек регистра признаков. В 16-битных процессорах и регистр признаков был 16-битным, поэтому для помещения в стек флагов и восстановления из него использовались команды PUSHF и POPF. Для новых процессоров, где регистр признаков 32-битный, нужно использовать 32-битные версии этих команд – PUSHFD и POPFD.

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

cmp ax,bx; сравниваем АХ и ВХ

pushf; помещаем результат сравнения в стек

...; выполняем операции, изменяющие флаги

add di,4; например, сложение

popf; восстанавливаем флаги

jz equal; если АХ = ВХ, переходим на "equal"

9.2. Организация подпрограмм на языке Ассемблера

Ни одна серьезная программа не обходится без подпрограмм. Основное назначение подпрограмм – сокращение кода основной программы: одни и те же инструкции не нужно писать несколько раз – их можно объединить в подпрограммы и вызывать по мере необходимости.

Команды CALL и RET

Для вызова подпрограммы используется команда CALL, а для возврата из подпрограммы в основную программу – RET. Формат обеих команд таков:

CALL [тип_вызова] операнды

RET

Команде CALL нужно передать всего один операнд – адрес начала подпрограммы. Это может быть непосредственное значение, содержимое регистра, памяти или метка. В отличие от JMP, при выполнении команды CALL первым делом сохраняется в стеке значение регистра IP (EIP). Передача управления на указанный адрес называется вызовом подпрограммы.

Как и команде JMP, команде CALL можно указать «размер шага». По умолчанию используется near. Когда происходит вызов типа far, сегментный регистр CS также сохраняется в стеке вместе с IP (EIP). Возврат из подпрограммы выполняется с помощью команды RET, которая выталкивает из стека его вершину в IP (EIP). После этого процессор продолжит выполнение инструкций, находящихся в основной программе после команды CALL.

Если подпрограмма вызывалась по команде CALL far, то для возврата из нее нужно восстановить не только IP (EIP), но и CS: следует использовать команду RETF, а не RET.

Для примера напишем подпрограмму, которая складывает значения ЕАХ и ЕВХ, а результат помещает в ЕСХ, не обращая внимания на переполнение. Значения ЕАХ и ЕВХ после возвращения из подпрограммы должны сохраниться неизменными.

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

add_it:

push eax; сохраняем значение ЕАХ в стек

add eax,ebx; EAX = ЕАХ + ЕВХ

mov ecx,eax; копируем значение из ЕАХ в ЕСХ

pop eax;восстанавливаем оригинальное значение ЕАХ

ret; возвращаемся

Теперь вызовем нашу подпрограмму add_it с аргументами 4 и 8:

mov eax,4; ЕАХ = 4

mov ebx,8; ЕВХ = 8

call add it; вызываем add_it

; результат будет в регистре ЕСХ

Что если мы забудем восстановить оригинальное значение ЕАХ (забудем написать команду pop eax)? Команда RET попыталась бы передать управление адресу, заданному оригинальным значением ЕАХ, что могло бы вызвать сбой нашей программы, а то и всей операционной системы. То же самое произойдет, если мы забудем написать команду RET: процессор продолжит выполнение с того места, где заканчивается наша подпрограмма, и рано или поздно совершит недопустимое действие.

9.3. Организация прерываний на языке Ассемблера

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

Команды INT и IRET

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

Команде INT нужно передать всего один 8-битный операнд, который задает номер нужного прерывания.

INT op

Аппаратные прерывания вызываются аппаратными средствами компьютера, подключенными к общей шине (ISA или PCI). Устройство, запрашивающее прерывание, генерирует так называемый запрос на прерывание (IRQ, interrupt requests). Всего существует 16 аппаратных запросов на прерывание, поскольку только 16 проводников в шине ISA выделено для этой цели.

Запрос на прерывание направляется контроллеру прерываний, который, в свою очередь, запрашивает микропроцессор. Вместе с запросом он передает процессору номер прерывания. После запуска компьютера и загрузки операционной системы DOS, IRQ 0 (системный таймер) соответствует прерыванию 8 (часы). Когда процессор получает номер прерывания, он помещает в стек контекст выполняемой в данный момент программы, подобно тому, как человек кладет в книгу закладку, чтобы не забыть, с какого места продолжить чтение книги. Роль закладки играют значения CS, (Е)IР и регистр флагов.

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

Возврат из обработчика прерывания осуществляется с помощью команды IRET, которая восстанавливает исходные значения (E)IP, CS и флагов из стека. Формат команды:

IRET

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

 

 




Поделиться с друзьями:


Дата добавления: 2014-12-07; Просмотров: 1131; Нарушение авторских прав?; Мы поможем в написании вашей работы!


Нам важно ваше мнение! Был ли полезен опубликованный материал? Да | Нет



studopedia.su - Студопедия (2013 - 2024) год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! Последнее добавление




Генерация страницы за: 0.055 сек.