Сегментна структура програм
Сегментна структура програм
Яким чином поняття сегментів пам'яті відбивається на структурі програми? Слід зауважити, що структура програми визначається, з одного боку, архітектурою процесора (якщо звернення до пам'яті можливо тільки за допомогою сегментів, то і програма, мабуть, повинна складатися з сегментів), а з іншого - особливостями тієї операційної системи, під управлінням якої ця програма буде виконуватися. Нарешті, на структуру програми впливають також і правила роботи обраного транслятора - різні транслятори пред'являють трохи розрізняються вимоги до початкового тексту програми. При підготовці цієї книги для трансляції та налагодження прикладів програм використовувався пакет TASM 5.0 корпорації Borland International; він зручний, зокрема, наявністю наочного многооконного відладчика. Питання це, проте, не є принциповим, і Новомосковсктель може для налагодження прикладів, наведених в книзі, скористатися будь-яким асемблером, ознайомившись попередньо з його описом.
Приклад 1-1.Простая програма з трьома сегментами
; Вкажемо відповідність сегментних регістрів сегментам
assume CS: code, DS: data
; Опишемо сегмент даних
data segment; Відкриємо сегмент даних
msg db "Програма працює! $ '; що виводиться рядок
data ends; Закриємо сегмент даних
; Опишемо сегмент стека
stk segment stack; Відкриємо сегмент стека
db 256 dup (?); Відводимо під стек 256 байт
stk ends; Закриємо сегмент стека
end begin; Кінець тексту з точкою входу
Слід зауважити, що при введенні початкового тексту програми з клавіатури можна використовувати як прописні, так і малі літери; транслятор сприймає, наприклад, рядки MOV AX, DATA і mov ax, data однаково. Однак за допомогою відповідних ключів можна змусити транслятор розрізняти великі та малі літери в окремих елементах пропозицій. У цій книзі в текстах програм і при описі операторів мови в основному використовуються малі літери, за винятком позначень регістрів, які для наочності виділені прописними буквами.
У програмі 1-1 описані три сегменти: сегмент команд з ім'ям code, сегмент даних з ім'ям data і сегмент стека з ім'ям stk. Опис кожного сегмента починається з ключового слова segment, яка випереджає деяким ім'ям, і закінчується ключовим словом end, перед яким вказується той же ім'я, щоб транслятор знав, який саме сегмент ми хочемо закінчити. Імена сегментів вибираються цілком довільно. Текст програми закінчується директивою асемблера end, завершальній трансляцію. В якості операнда цієї директиви вказується точка входу в програму; в нашому випадку це мітка begin.
Порядок опису сегментів в програмі, як правило, не має значення. Часто програму починають з сегмента даних, це трохи полегшує читання програми, і в деяких випадках усуває можливі неоднозначності в інтерпретації команд, що посилаються на дані, які ще не описані. Ми на початку програми розташували сегмент команд, за ним - сегмент даних і в кінці - сегмент стека; такий порядок надає деякі зручності при налагодженні програми. Важливо тільки розуміти, що в оперативну пам'ять комп'ютера сегменти потраплять в тому ж порядку, в якому вони описані в програмі (якщо спеціальними засобами асемблера не поставити інший порядок завантаження сегментів в пам'ять).
Сегменти вводяться в програму за допомогою директив асемблера segment і ends. Що таке директива асемблера? У тексті програми зустрічаються ключові слова двох типів: команди процесора (mov. Int) і директиви транслятора (в даному випадку терміни "транслятор" і "асемблер" є синонімами, позначаючи програму, що перетворює вихідний текст, написаний на мові асемблера, в коди, які будуть при виконанні програми сприйматися процесором). До директивам асемблера відносяться позначення початку і кінця сегментів segment і ends; ключові слова, що описують тип використовуваних даних (db, dup); спеціальні описатели сегментів на кшталт stack і т. д. Директиви служать для передачі транслятору службової інформації, якою він користується в процесі трансляції програми. Однак до складу здійсненним програми, що складається з машинних кодів, ці рядки не потраплять, оскільки процесору, що виконує програму, вони не потрібні. Іншими словами, оператори типу segment і ends не транслюються в машинні коди, а використовуються лише самим асемблером на етапі трансляції програми. З цим питанням ми ще зіткнемося при розгляді лістингів програм.
Ще одна директива асемблера використовується в першому реченні програми:
Призначенням програми 1-1 передбачається висновок на екран текстової рядки "Програма працює!", Описаної в сегменті даних. Наступні пропозиції програми якраз і виконують цю операцію. Робиться це не безпосередньо, а шляхом звернення до службових програм операційної системи MS-DOS, яку ми для стислості будемо надалі називати просто DOS. Справа в тому, що в складі команд процесора і, відповідно, операторів мови асемблера немає команд виведення даних на екран (як і команд введення з клавіатури, записи в файл на диску і т.д.). Висновок навіть одного символу на екран в дійсності представляє собою досить складну операцію, для виконання якої потрібно довга послідовність команд процесора. Звичайно, цю послідовність команд можна було б включити в нашу програму, проте набагато простіше звернутися за допомогою до операційної системи. До складу DOS входить велика кількість програм, які здійснюють стандартні і часто необхідні функції - висновок на екран і введення з клавіатури, запис в файл і читання з файлу, читання або установка поточного часу, виділення або звільнення пам'яті і багато інших.
Як завершити виконувану програму? Насправді, завершення програми - це досить складна послідовність операцій, в яку входить, зокрема, звільнення пам'яті, зайнятої завершилася програмою, а також виклик тієї системної програми (конкретно - командного процесора COMMAND.COM), яка виведе на екран запит DOS, і чекатиме введення наступних команд оператора. Всі ці дії виконує функція DOS з номером 4Ch. Ця функція передбачає, що в регістрі AL знаходиться код завершення нашої програми, який вона передасть DOS. Якщо програма завершилася успішно, код завершення має дорівнювати 0, тому ми в одному реченні mov AX, 4C00h завантажуємо в АН 4Ch, а в AL - 0, і викликаємо DOS вже знайомої нам командою int 21h.
Для того, щоб виконати пробний прогін приведеної програми, її необхідно спочатку оттранслировать і скомпонувати. Нехай вихідний текст програми зберігається в файлі з ім'ям P.ASM. Трансляція здійснюється викликом асемблера TASM.EXE за допомогою наступної команди DOS;
Ключ / z дозволяє висновок на екран рядків вихідного тексту програми, в яких асемблер виявив помилки (без цього ключа пошук помилок довелося б проводити з лістингу трансляції).
Ключ / zi управляє включенням в об'єктний файл інформації, що не необхідної при виконанні програми, але використовуваної отладчиком.
Ключ / n пригнічує висновок в лістинг переліку символічних позначень в програмі, від чого трохи зменшується інформативність лістингу, але скорочується його розмір.
Ті, хто стоїть далі параметри визначають імена файлів: вихідного (P.ASM), об'єктного (P.OBJ) і лістингу (P.LST). При бажанні можна в рядку виклику транслятора вказати повні імена файлів з їх розширеннями, проте необхідності в цьому немає, так як за замовчуванням транслятор використовує саме зазначені вище розширення.
Рядок виклику компоновщика має наступний вигляд:
Ключ / г пригнічує утворення лістингу компоновки, який зазвичай не потрібний.
Ключ / v передає в завантажувальний файл інформацію, використовувану отладчиком. Ті, хто стоїть далі параметри позначають імена модулів: об'єктного (Р.ОBJ) і завантажувального (Р.Ехе).
Оскільки при вивченні цієї книги вам доведеться написати й налагодити велика кількість програм, доцільно створити командний файл (з ім'ям, наприклад, А.ВАТ), що автоматизує виконання однотипних операцій трансляції та компонування. Текст командного файлу в найпростішому варіанті може бути таким (в припущенні, що шлях до каталогу з пакетом TASM був вказаний в параметрі команди PATH):
tasm / z / zi / n p, p, p
tlink / x / v p, p
Запуск підготовленої програми Р.Ехе здійснюється командою Р.Ехе
При завантаженні програми сегменти розміщуються в пам'яті, як показано на рис. 1.9.

Мал. 1.9.Образ програми в пам'яті.