Написання резидентних програм для MS DOS
У статті описані основні кроки, які використовуються при написанні резидентних програм на асемблері для операційної системи MS DOS. В кінці статті докладно розібраний приклад резидентної програми.
Резидентна програма для MS DOS є фрагмент коду, який постійно перебуває в оперативній пам'яті комп'ютера і викликається при виникненні певних умов. Далі буде показано як написати резидентную програму на асемблері, постійно знаходиться в пам'яті і спричинюється при виникненні в системі переривань. Спочатку розглянемо визначення і основні типи переривань для процесорів x86.
Переривання для процесорів x86 являє собою деякий подія в системі, що потребує певної обробці. При виникненні переривання, за винятком одного випадку, виконання поточної програми переривається і відбувається обробка переривання. Після обробки переривання триває виконання перерваної програми.
Для процесорів x86 існують такі види переривань: апаратні, програмні та внутрішні переривання процесора. Апаратні переривання, в свою чергу, поділяються на маскіруемие і немасковані. Масковані апаратні переривання при певних умовах можуть бути проігнорірованни процесором, а немасковані переривання обробляються завжди.
Апаратне переривання можна визначити як запит від деякого периферійного пристрою (клавіатура, послідовний порт, дисковод і т. Д.) На обробку даних цього пристрою, керування ним або виникнення виняткової ситуації для цього пристрою. При виникненні такого запиту виконання поточної програми переривається (якщо це переривання не замасковано) і викликається процедура обробника переривання. Оброблювач переривання виконує необхідні дії для отримання даних від периферійного пристрою або для управління ним і повертає управління в перервану програму.
Програмні переривання є викликом будь-яких функцій або сервісів операційної системи і прикладних програм з використанням команди INT XX, де XX - номер переривання від 0 до 255. Внутрішні переривання процесора виникають при виконанні програмою будь-яких операцій, що викликають фатальні помилки (наприклад, ділення на 0, переповнення при діленні, вихід за межі сегмента і т. д.), а також при використанні режиму відладки.
У будь-якому випадку, при виникненні переривання будь-якого типу викликається обробник цього переривання, який являє собою спеціальним чином оформлену процедуру. Для апаратних переривань обробник переривання повинен крім роботи з пристроєм, що викликав переривання, виконати деякі операції з управління апаратурою механізму переривань процесора x86.
Розглянемо процес написання процедури обробника переривання на асемблері, що викликається при виникненні програмного переривання. Загальна структура і синтаксис для обробника програмного переривання:
Ідентифікатор NAME визначає ім'я процедури обробника, яке може бути будь-якою послідовністю дозволених в асемблері символів, але не повинно бути службовим або зарезервованим словом.
У секції 1 виконується збереження всіх регістрів, змінних у процедурі обробника. Це необхідно для того, щоб після повернення управління в перервану програму, вона отримала регістри в тому ж вигляді, якими вони були до виклику програмного переривання. Якщо переривання має повертати в викликала його програму деякі результати в регістрах, то зберігати значення цих регістрів не потрібно.
У секції 2 виконується ініціалізація сегментних регістрів DS, ES або SS для звернення процедури обробника переривання до своїх внутрішніх даними, стеку або деякого додаткового сегменту. Значення ініціалізіруемих регістрів повинні бути збережені в секції 1.
У секції 3, власне, виконується основний код процедури обробника переривання, виконуються необхідні дії і заносяться значення в регістри, якщо переривання має повертати в викликала його програму деякі результати в регістрах.
У секції 4 відбувається відновлення значень для змінених процедурою обробника переривання регістрів, крім тих регістрів, в яких викликала переривання програмі повертаються результати.
Команда IRET виконує повернення з процедури обробника переривання в викликала його програму.
Розглянемо докладніше які дії виконують команди INT і IRET.
У стеці зберігаються в наступній послідовності: регістр прапорів, сегментний регістр CS, регістр покажчика команд IP. Скидаються прапори IF і TF в регістрі прапорів.
Обчислюється зсув щодо початку таблиці векторів переривань: зсув = XX * 4, де XX - номер переривання.
У сегментний регістр CS по обчисленому зміщення з таблиці векторів переривань заноситься значення сегмента обробника переривання, а в регістр IP - зміщення обробника переривання.
Відбувається передача управління на обробник програмного переривання. При цьому всі регістри крім CS, IP і регістра прапорів зберігають своє значення таким, яким воно було до виклику команди INT XX.
Таким чином, при вході в обробник програмного переривання, в стеку знаходяться значення регістрів CS, IP і регістра прапорів. Ці значення перебували в даних регістрах до виклику команди INT XX. У вершині стека розташовується значення регістра IP.
При виклику команди IRET виконуються наступні дії:
З стека відновлюється значення регістра IP.
З стека відновлюється значення регістра CS.
З стека відновлюється значення регістра прапорів.
Відбувається передача управління в перервану програму, на команду, що знаходиться безпосередньо за командою програмного переривання INT XX.
Після виконання команди IRET структура стека стає такою ж, якою вона була до виклику команди INT XX.
Такі основні моменти, які використовуються при написанні обробників програмних переривань. Розглянемо тепер структуру і роботу обробників апаратних переривань.
На відміну від обробників програмних переривань, обробники апаратних переривань викликаються не командою INT, а самим процесором. Вище було сказано, що при написанні обробників апаратних переривань вони повинні виконувати ще й деякі дії з управління апаратурою механізму переривань процесора x86. У найпростішому випадку, структура такого обробника виглядає наступним чином:
Команда OUT 20h, AL виконує дії з управління апаратурою механізму переривань процесорів x86. Конкретно, вона посилає сигнал EOI (End Of Interrupt - кінець переривання) в контролер переривань, повідомляючи йому таким чином, що обробка апаратного переривання завершена.
Команда OUT 20h, AL, що викликається в кінці обробника апаратного переривання, розблокує контролер переривань, дозволяючи йому роботу з раніше заблокованими переривань.
Якщо потрібно написати обробник апаратного переривання, який повинен тільки виконувати певні дії при виникненні апаратного переривання (наприклад, видавати звуковий сигнал при натисканні на будь-яку клавішу), всю роботу по управлінню відповідною апаратурою можна покласти на системний обробник цього апаратного переривання. В такому випадку, структура обробника буде наступною:
В цьому випадку команда JMP SYS_HANDLER виконує дальній перехід на системний обробник переривання, тому в кінці нашого обробника не потрібно викликати команду IRET - вона буде викликана в системному обработчике.
Після того, як визначено, яким чином оформити процедуру обробника апаратного або програмного переривання, розглянемо дії, необхідні для того, щоб ця процедура обробника викликалася при виникненні переривання.
Розглянемо тепер необхідні операції для установки сегмента і зміщення в таблиці для обробника програмного або апаратного переривання, в якому буде викликаний системний обробник цього переривання. Для цього перед записом в таблицю нових значень сегмента і зміщення потрібно спочатку зберегти значення сегмента і зміщення системного обробника:
При установці значень сегмента і зміщення обробника апаратного переривання потрібно до цього скинути прапор IF (команда CLI), а після установки нових значень встановити прапор IF (команда STI). Це необхідно для того, щоб в процесі установки значень сегмента і зміщення не виникло апаратне переривання.
При написанні обробника апаратного переривання потрібно враховувати те, що він повинен бути завершений до виникнення чергового апаратного переривання для цього обробника. Якщо ця умова не виконана, то, в кращому випадку, який виник переривання буде втрачено. У гіршому випадку невиконання цієї умови може спричинити за собою втрату даних або зависання комп'ютера. Наприклад, при написанні апаратного обробника переривань від таймера, код обробника повинен виконуватися за часом менш 1/18 с. так як переривання від таймера за умовчанням генеруються 18.2 раз в секунду. Те ж можна сказати і про обробнику апаратних переривань від клавіатури - код обробника повинен виконуватися не довше затримки повторення символів.
Також при написанні будь-якого обробника переривання потрібно форматувати сегментний регістр DS, якщо обробник звертається до якихось внутрішніх осередків пам'яті. Замість цього можна використовувати звернення до осередків пам'яті з використанням префікса заміни сегмента (наприклад CS: [BX]), але це збільшує розмір відповідної команди. Використання префікса заміни сегмента ефективно в тому випадку, коли кількість звернень до внутрішніх елементів пам'яті обробника невелика (2 - 3).
Розглянемо тепер кошти для завершення резидентної програми і збереженні частини її коду в пам'яті для подальшого використання при виникненні переривань.
Переривання DOS INT 27h. Переривання призначене для завершення програми і збереження її резидентної в пам'яті. Для цього в регістр DX потрібно занести кількість байт для зберігається частини програми плюс один байт. Початок зберігається частини програми збігається зі зміщенням 0000h щодо кодового сегмента, тому для COM програм потрібно враховувати розмір префікса програмного сегмента (PSP - Program Segment Prefix) - 256 байт.
Переривання DOS INT 21h, функція 31h. Функція 31h переривання DOS INT 21h виконує ті ж дії, що і переривання DOS INT 27h, але в регістр DX заноситься розмір зберігається частини програми в параграфах (блоки пам'яті довжиною 16 байт). Також є можливість визначити код повернення при завершенні резидентної програми (заноситься в регістр AL).
Нижче наведено загальну структуру резидентної програми:
; дані для резидентної частини програми
HANDLER ENDP
; дані для нерезидентної частини програми
; основна частина програми (нерезидентна)
Наступний фрагмент коду дає приклад визначення основної структури резидентної програми на асемблері (програма типу COM):
Розглянемо роботу програми.
Процедура decode перетворює двійковій-десяткове число в регістрі AL в два ASCII символу, відповідних значенням годин, хвилин або секунд (це залежить від конкретного значення, записаного в регістрі AL). Процедура додає до значення розряду числа (молодший розряд знаходиться в перших чотирьох бітах регістра AL, старший - у старших чотирьох бітах) ASCII код символу "0", тим самим формуючи ASCII код для цифри молодшого або старшого розряду. Далі цей ASCII код записується в поточну позицію шаблону. Після запису в шаблон значення покажчика на поточний символ шаблону збільшується на 3 (дві цифри для годин, хвилин або секунд, плюс символ ':').