→ Пошук по сайту       Увійти / Зареєструватися
Знання Асемблер Сучастні мікропроцесори

Використання засобів 32-розрядних процесорів в програмуванні

Як вже наголошувалося, при розробці 16-розрядних програм реального режиму, призначених для виконання по управлінням операційної системи MS-DOS, цілком допустиме використання ряду додаткових можливостей 32-розрядних процесорів. У реальному режимі можна використовувати:
32-розрядні операнди;
додаткові команди і розширені можливості команд МП 86;
додаткові режими адресації;
чотири сегментні регістри для адресації даних замість двох.
Для того, щоб транслятор розпізнавав всі ці засоби, необхідно почати програму з директиви .586 (або, за бажання, .486 або .386) і вказати при цьому для сегментів команд і даних описувач use 16, щоб програма залишилася 16-розрядною.

Слід відмітити, що можливості використання в програмах реального режиму додаткових засобів 32-розрядних процесорів, хоча і здаються привабливими, насправді вельми обмежені. Нові команд не так вже багато, і вони не мають принципового характеру; 32-розрядні дані використовуються в прикладних програмах відносно рідко (якщо не стосуватися обчислювальних програм, що містять дійсні числа, але такі програми рідко пишуть на мові асемблера); розширені можливості адресації повною мірою виявляються лише в 32-розрядних програмах, що не працюють в DOS. Проте в якихось випадках залучення засобів 32-розрядних процесорів може виявитися корисним і в 16-розрядних програмах, і ми приведемо декілька прикладів їх використання.

Серед системних даних DOS і BIOS є дані, що вимагають для свого розміщення 2 слів. До таких даних, зокрема, відноситься системний час, що накопичується в 4х-байтовой осередку з абсолютною адресою 46ch. Вище, в розділі 3.5, вже описувалася системна процедура відліку поточного часу. В процесі початкового завантаження комп'ютера в осередок з адресою 46ch переноситься з годинника реального часу час, минулий від початку доби, а потім вміст цього осередку збільшується на 1 кожним перериванням від системного таймера, підключеного до вектора 8. Читання осередку 46ch дозволяє визначити поточний час з погрішністю приблизно в 1/18 секунд, що дозволяє достатньо точно вимірювати інтервали часу. Арифметичні дії з системним часом зручно виконувати в розширених 32-розрядних регістрах.

Розглянемо програму, яка дозволяє встановити необхідний часовий інтервал і відпрацювати деяким чином його закінчення. Оскільки MS-DOS є однозадачною системою, єдиним способом організації паралельних процесів - виконання програми і очікування закінчення тимчасового інтервалу - є використання механізму переривань. У нашому випадку програма містить обробник переривань від системного таймера, який 18 разів в секунду читає системний час і порівнює його значення із заданою заздалегідь величиною. Досягши рівності обробник переривань або сам відпрацьовує цю подію, або встановлює прапор закінчення тимчасового інтервалу, який періодично тестується в основній програмі. Перший варіант дозволяє зміряти часовий інтервал з більшою точністю, але другою надає більше можливостей, оскільки в обробнику переривань не можна звертатися до функцій DOS, а основна програма може робити все, що їй заманеться.

Приведений нижче приклад виконаний у вигляді програми типу .сом. Така організація програми спрощує обробник переривань і полегшує його написання. Справа полягає в тому, що процесор, переходячи по апаратному перериванню на обробник переривання, модифікує тільки регістри Cs:ip (значеннями, отриманими з вектора переривань). Решта всіх регістрів, у тому числі і сегментні, зберігає ті значення, які вони мали на момент переривання. Значення ці можуть бути якими завгодно, особливо, якщо основна програма викликає функції DOS. Тому, якщо в обробнику переривань необхідно звернутися до даних, що зберігаються в основній програмі, нам необхідно набудувати який-небудь з сегментних регістрів (наприклад, DS або ES) на сегментну адресу сегменту даних основної програми. Якщо ж програма написана у форматі .сом, то її поля даних входять в той же (єдиний) сегмент, де розташовані команди, і для звернення до даних можна скористатися регістром CS, який при виклику обробника настроюється процесором.

 Приклад 4-1. Читання і порівняння системного часу по перериваннях від таймера

.586 ;Будут 32-розрядні операнди
assume CS : code, Ds:code
code segment use16 ;16-разрядное додаток
org 100h ;Формат .COM
main proc
;Сохраним початковий вектор
mov Ax,3508h
int 21h
mov word ptr old_08,bx
mov word ptr old_08+2,es
;Встановимо наш обробник
mov Ax,2508h
mov Dx,offset new_08
int 21h
;Прочитаем системний час, додамо необхідний інтервал
;і збережемо в двухсловной елементі пам'яті
mov Ax,40h ;Настройка ES на
mov Es,ax ;область даних BIOS
mov EAX, ES : 6ch ;Получаем системний час
add Eax,time_int ;Прибавить інтервал
mov time_end,eax ;Сохраним у пам'яті
;Імітація робочого циклу програми з опитом прапора
again: test flag,0ffh ;Проверка прапора готовності
jnz ok ;Если встановлений, на OK
mov Ан,02h ;B очікуванні закінчення
mov DL'.' ;временного інтервалу
int 2 In ;выводим на екран крапки
jmp again ;И знову на перевірку прапора
ok: mov Ан,09h ;Интервал завершений.
mov Dx,offset msg ;Выполним, що хотіли
int 2 In
;Завершим програму, відновивши спочатку початковий вектор
lds Dx,old_08
mov Ax,2508h
int 21h
mov Ax,4c00h
int 21n
main endp
;Наш обробник переривань від системного таймера
new_08 proc
pushf ;Запишем прапори в стек
call Cs:old_08 ;и викличемо системний обробник
push EAX ;Сохраним використовувані
push ES ;регистры
mov Ax,40h ;Настроим ES
mov Es,ax ;на область даних BIOS
mov Eax,es:6ch;Прочитаем поточний час
cmp Eax,cs:time_end ;Сравним з обчисленим
jb ex ;Если менше, на вихід
inc Cs:flag ;Интервал закінчився, встановимо прапор
ex: mov Al,20h ;Команда кінця переривання
out 20h,al ;в контроллер переривань
pop ES ;Восстановим
pop EAX ;сохраненные регістри
iret ;Выход з обробника
new_08 endp
;Поля даних програми
old_08 dd 0 ;Для зберігання початкового вектора
time_int dd 18*2 ;Требуемый інтервал (~2с)
time_end dd 0 ;Момент закінчення інтервалу
flag db 0 ;Флаг закінчення інтервалу
msg db "Час закінчився !$' ;Информационное повідомлення
code ends
end main 

Організація програмного комплексу з обробником переривань від системного таймера вже розглядалася в прикладі 3-3 в гл. 3. Установка обробника в даному прикладі виконується трохи простіше, оскільки немає необхідності настроювати регістр DS на сегмент даних - він і так вже налаштований на єдиний сегмент програми. Встановивши обробник, програма настроює регістр ES на область даних BIOS і прочитує з осередку з адресою 46ch поточний системний час командою add до нього додається заданий в осередку time_int інтервал (у прикладі - приблизно 2 з), і результат зберігається в осередку timc_cnd.

Дії з установки обробника закінчені, і програма може приступити до виконання запланованих для неї дій. У даному прикладі програма в циклі викликає функцію DOS 02h виводу на екран символу крапки; насправді вона може, наприклад, виконувати обробку і вивід на екран деяких даних. У кожному кроці циклу відбувається тестування прапора закінчення тимчасового інтервалу flag, який має бути встановлений обробником переривань після закінчення заданого тимчасового інтервалу. Поки прапор скинутий, цикл продовжується. Як тільки прапор опиниться встановлений, програма приступає до виконання дій з відробітку цієї події. У даному прикладі виконується вивід на екран інформаційного повідомлення і завершення програми з обов'язковим відновленням початкового вмісту вектора 8.

Обробник переривань new_08 перш за все виконує виклик початкового обробника, адресу якого ми зберегли в осередку old_08. Методика зчеплення обробників переривань розглядалася в гл.З (див. приклад 3-4). В даному випадку зчеплення обробників необхідне, оскільки підключення до вектора 8 нашого обробника не повинно порушити хід системного годинника.

Після повернення з системного обробника виконується збереження використовуваних регістрів, настроювання регістра ES на область даних BIOS, читання поточного часу і порівняння його із записаним в осередку time_end. Поки поточний час менше заданого, обробник просто завершується командою iret, пославши заздалегідь в контроллер переривань команду кінця переривання EOI і відновивши збережені раніше регістри. Якщо ж заданий часовий інтервал закінчився, і поточний час виявляється рівним (або великим) значенню в осередку time_end, обробник перед своїм завершенням встановлює прапор flag, ініціюючи в основній програмі заплановані для цієї події дії. Якщо такими діями має бути, наприклад, включення або виключення апаратури, підключеної до комп'ютера, це можна зробити в самому обробнику переривань. В цьому випадку прапор flag не потрібний, і дії основної програми і обробника перериванні протікають паралельно і незалежно.
Розглянуту програму неважко модифікувати так, щоб прапор flag встановлювався не після закінчення заданого інтервалу, а в заданий момент календарного часу. Це завдання дозволить нам проілюструвати прийоми виконання арифметичних операцій з 32-розрядними операндами.

Приклад 4-2 відрізняється від попереднього тільки зміною алгоритму обчислення часу і службовими полями даних. Процедури установки обробника переривань, циклу очікування установки прапора і самого обробника переривань повністю збігаються з прикладом 4-1.
Для набуття необхідного значення часу в тих же одиницях, які використовуються системою при роботі з осередком 46ch, треба спочатку обчислити час в секундах від початку доби, а потім для отримання часу в тактах таймера помножити цю величину на 18,2065 (див. розділ 3.5). Для того, щоб не привертати арифметичний співпроцесор і залишатися в рамках цілих 32-бітових чисел, множення числа секунди на 18.2065 виконується по наступній формулі:

Такти = t*18 + t/5 + t/154 

Відладжуючи на машині приклад 4-2, треба стежити за тим, щоб заданий час був більший поточного по машинному годиннику, інакше програма вічно чекатиме установки прапора. Спроба завершити її натисненням комбінації <Ctrl>/<c> приведе до зависання системи, оскільки в цьому випадку не будуть виконані рядки відновлення початкового вмісту перехопленого програмою вектора. По-справжньому в програмах, що містять обробники яких-небудь переривань, використовуваних системою, необхідно передбачати власні засоби обробки натиснення <Ctrl>/<c>, щоб аварійне завершення програми виконувалося так само коректно, як і штатне, з попереднім з відновленням векторів.

Приклад 4-2. Очікування заданого моменту часу по перериваннях від таймера

.586
assume Cs:code,ds:code
code segment use16
org 100h
main proc
;Сохраним початковий вектор
...
;Встановимо наш обробник
...
;Перетворимо необхідний календарний час в кількість
;интервалов по 55 мс
mov Eax,hour ;Возьмем годинник
mov Ebx,3600 ; Коефіцієнт перетворення в секунди
mul EBX ;Преобразуем годинник в секунди в Edx:eax
mov temp,eax ;Сохраним годинник в temp
mov Eax,min ;Возьмем хвилини
mov Ebx,60 ;Коэффициент перетворення в секунди
mul EBX ;Преобразуем хвилини в секунди в Edx:eax
add temp,eax ;Прибавим хвилини в temp
mov Eax,sec ;Возьмем секунди
add temp,eax ;Прибавим секунди в temp
mov Eax,temp ;Число секунд
mov Ebx,18 ;Будем умножати на 18
mul EBX ;Умножим на 18
mov time,eax ;Сохраним у time
xor Edx,edx ;Подготовимся до ділення
mov Eax,temp ;Будем ділити число секунд
mov Ebx,5 ;Будем ділити на 5
div EBX ;Поделим
add time,eax ;Прибавим до time
xor Edx,edx ;Подготовимся до ділення
mov Eax,temp ;Будем ділити число секунд
mov Ebx,154 ;Будем ділити на 154
div EBX ;Поделим
add time,eax ;Прибавим до time
;Імітація робочого циклу програми з опитом прапора
...
;Завершимо програму, відновивши спочатку початковий вектор
...
main endp
new_08 proc
...
new_08 endp
old_08 dd 0
hour dd 13 ;Часы
min dd 45 ;Минуты
sec dd 0 ;Секунды
time dd 0 ;Вычисленное час в тактах таймера
temp dd 0 ;Ячейка для проміжного результат
flag db 0 ;Флаг настання заданого часу
msg db "Час наступив!$'
code ends
end main 

Розглянемо деякі деталі приведеного прикладу.

Три осередки для зберігання заданого часу (годинника, хвилин і секунд) оголошено оператором dd, як подвійні слова для спрощення програми і прискорення завантаження цих значень в розширений регістр ЕАХ. Якби ми, економлячи пам'ять, що відводиться по дані, оголосили б ці осередки як байти, то завантаження, наприклад, числа годинника в регістр ЕАХ виглядала б таким чином:

xor Eax,eax

mov Al,hour 

Для перетворення годинника в секунди ми повинні число годинника помножити на 3600. Обома співмножником (3600 і максимум 23) є невеликі числа, які помістилися б в 16-розрядний регістр. Проте результат може досягти величини 82800, яка в регістр АХ вже не поміститься. Якби ми виконали множення двох 16-розрядних регістрів, наприклад, АХ на ВХ, то результат (по правилах виконання команди mul) опинився б в парі регістрів Dx:ax, і нам довелися б ці два числа об'єднувати в одне декількома операціями перенесення і зрушення:

push AX ; Зберігаємо на якийсь час АХ

mov Ax,dx ;Старшая половина твору

sal Еах,1б ;Сдвигаем у старшу половину ЕАХ

pop AX ;Младшая половина твору 

Виконуючи множення з використанням 32-розрядних регістрів, ми отримуємо результат знову ж таки в парі регістрів Edx:eax, але оскільки в нашому випадку твір ніколи не перевищить 4 Грама, все воно цілком знаходитиметься в одному регістрі ЕАХ, і ми позбавляємося від приведеної вище процедури. Результат множення зберігається в допоміжному осередку temp.

Аналогічним чином виконується переклад числа хвилин в секунди; отриманий результат додається до вмісту осередку temp.

Число секунд перетворювати не треба, воно просто додається до вмісту temp.

Отримане число секунд умножається на 18, і результат поміщається в осередок time, яка потім опитуватиметься в обробнику переривань.

До отриманого числа тактів таймера треба додати ще дві величини, що коректують, - результати ділення числа секунди на 5 і на 154. При використанні в операції ділення 32-розрядних регістрів ділиме поміщається в пару регістрів Edx:eax. У нашому випадку ділиме цілком поміщається в ЕАХ, і регістр EDX необхідно обнулити. Для цього можна було виконати команду

mov Еах,0 

але ефективніша операція

хоr Еах,еах  

яка при будь-якому вмісті ЕАХ залишає в нім 0.

При діленні Edx:eax на ЕВХ приватне поміщається в ЕАХ, залишок в EDX. Залишок нас не цікавить, а приватне (перша величина, що коректує) додається до вмісту осередку temp.

Аналогічним чином те ж число секунд з осередку tmp ділиться на 154, і результат додається до вмісту time. Перетворення закінчене.

На закінчення розглянемо приклад впорядковування масиву 32-розрядних чисел в убуваючому порядку методом бульбашкового сортування. У приведеному алгоритмі використовуються розширені можливості адресації 32-розрядних процесорів.

Приклад 4-3. Бульбашкове сортування

.586
assume Cs:code,ds:data
code segment use16
main proc
mov AX, data ;Настроим DS
mov Ds,ax ;на сегмент даних
mov Esi,offset list ;ESI-> початок масиву
mov Ecx,1000 ;Число елементів в масиві
start: mov EDX, 0 ;Индекс порівнюваної пари
sort: cmp Edx,ecx ;Индекс пари дійшов до
jge stop ;индекса масиву? До наступної пари
mov EAX[Esi+edx*4+4];Второй елемент пари
cmp [Esi+edx*4],eax ;Сравним з попереднім
jge noswap ;Если перший більше, то добре
xchg [Esi+edxm], EAX ;Первый менше. Обміняти
mov [Esi+edxm + 4],eax ;первый на другій
noswap: inc EDX ;Увеличим індекс пари
jmp sort ;И на порівняння
stop: loop start ;Цикл по всіх елементах
mov Ax,4c00h
int 21h
main endp
code ends
data segment
list label ;Имя тестового масиву
nmb=0 ;Заполним масив на етапі
rept 1000 /трансляциі числами від 0
ddnmb /до 990
nmb=nmb+10 /через 10
endm
data ends
stk segment stack
dw 128 dup (0)
stk ends
end main 

Алгоритм бульбашкового сортування передбачає виконання двох вкладених циклів. У внутрішньому циклі порівнюються пари елементів. Перший елемент береться за адресою [ESI + EDX * 4], другий - за наступною адресою [ESI + EDX * 4 + 4]. Якщо другий елемент більше першого, відбувається обмін значень цих елементів, і елемент з меншим значенням "спливає" на одне місце вище (тобто переміщається за більшою адресою). Після цього збільшується індекс пари і виконується порівняння другого елементу з наступним. Якщо виявляється, що наступний елемент більше попереднього, вони міняються місцями. В результаті елемент з найменшим значенням спливає на самий верх списку.

Внутрішній цикл, пройшовшись по всіх парах, повторюється спочатку, забезпечуючи спливання наступного по величині елементу. Кожен наступний прохід внутрішнього циклу вимагає на 1 менше кроків, чим попередній. Після завершення впорядковування елементи шикуються по зростаючих адресах в порядку зменшення їх значень.
У прикладі 4-3 тестовий масив даних утворений із зростаючих (на 10) чисел від 0 до 990. В результаті впорядковування вони повинні розташуватися в зворотному порядку, від великих до менших. У прикладі не передбачені засоби виводу на екран елементів масиву, тому його вивчення слід проводити у відладчику, спостерігаючи спливання кожного елементу.

Як вже наголошувалося, в 32-розрядних процесорах збільшено до 4 число сегментних регістрів даних. Це дає можливість спільної роботи з чотирма сегментами даних (загальним об'ємом до 256 Кбайт) без перенастроювання сегментних регістрів. Структура такого роду програми може виглядати таким чином:

.586
datal segment use16
first dw 7000h dup(')
datal ends
data2 segment use6
second dw 7000h dup (')
data2 ends
data3 segment use16
third dw 7000h dup (')
data3 ends
data4 segment use16
forth dw 7000h dup (')
data4 ends
code segment use16
assume Ds:datal,es:data2,fs:data3,gs:data4
main proc
;Настроим все 4 сегментних регістра на базові адреси
; відповідних сегментів
mov Ax,datal ;DS->datal
mov word ptr[BX],1111h ;Обращение через DS за умовчанням
;Звернення до різних сегментів з явною вказівкою
;требуемого сегментного регістра (заміна сегменту)
mov word ptr ES:[BX],2222h
mov word ptr FS:[BX],3333h
mov word ptr GS:[BX],4444h
;Звернення по іменах полів даних різних сегментів ; з урахуванням дії директиви assume
mov first,1 ;Запись у сегмент datal
mov second,2 ;Запись у сегмент data2
mov third,3 ;Запись у сегмент data3
mov fourth,4 ;Запись у сегмент data4
; Перенесення даних з сегменту в сегмент
push first
pop second+2
push third
pop fourth+2
...
main endp
code ends 

У програмі оголошено 4 сегменти даних з іменами datal, data2, data3 і data4, що містять масиви 16-розрядних даних з іменами first, second, third і fourth. Довжина кожного масиву складає 56 Кбайт, і, таким чином, загальний об'єм даних, доступних програмі у будь-який момент, складає більше 200 Кбайт. Сегменти даних описані до сегменту команд, що в даному випадку має значення. У сегменті команд за допомогою директиви assume вказана відповідність кожному з сегментів свого сегментного регістра (DS, ES, FS і GS). Це дасть нам можливість звертатися по іменах полів сегментів без явної вказівки відповідних цим сегментам сегментних регістрів.

Програма починається із звичайною практично для всіх програм процедури налаштування всіх сегментних регістрів. Варто ще раз повторити, що директива assume лише забезпечує правильну трансляцію програми, але не ініціалізував сегментні регістри. "Правильна трансляція" в даному випадку полягає в тому, що при обробці команд, в яких згадуються імена даних того або іншого сегменту, асемблер автоматично передує цим командам, префіксом заміни сегменту, вибираючи для заміни сегментний регістр, вказаний в директиві assume для даного сегменту. Так, команда

mov first, I 

перетвориться в послідовність код (по лістингу' трансляції)

С7 06 0000r 0001 

де С7 06 - це код команди mov у разі прямої адресації пам'яті і використання безпосереднього операнда, 0000г - зсув осередку, що адресується, а 0001 - безпосередній операнд (всі числа, зрозуміло, шістнадцятиричні). Тут немає префікса заміни сегменту, тому що адресується сегмент, якому відповідає регістр DS, використовуваний процесором за умовчанням. Проте команди із зверненням до інших сегментів транслюються з включенням в їх коди відповідних пре фіксів, не дивлячись на те, що в початкових пропозиціях не вказані сегментні регістри, а містяться тільки посилання на (унікальні) імена осередків тих або інших сегментів:

mov second, 2 ; Код команди 26: С7 06 0000r 0002

mov third, 3 ;Код команди 64: С7 06 0000r 0003

mov fourth, 4 ; Код команди 65: С7 06 0000r 0004 

Набудувавши сегментні регістри, ми можемо звертатися до полів даних всіх чотирьох сегментів з використанням будь-яких способів адресації. У приведеному фрагменті в регістр ВХ поміщається зсув останнього осередку будь-якого з масивів, після чого за допомогою непрямої базової адресації в останні слова всіх чотирьох масивів записуються довільні числа 1111h, 2222h, 3333h і 4444h. У всіх випадках потрібний описувач word ptr, оскільки по вигляду команди асемблер не може визначити, чи хочемо ми занести в пам'ять байт, слово або подвійне слово. При зверненні до сегментів, що адресуються не через DS, необхідна явна вказівка сегментного регістра (яке буде перетворено в код префікса заміни сегменту), тому що по вигляду команди з адресацією через регістри транслятор не може визначити, до якого сегменту відбувається звернення.

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

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

загрузка...
Сторінки, близькі за змістом