Директиви сегментації
У ході попереднього обговорення ми з'ясували всі основні правила запису команд і операндів у програмі на асемблері. Відкритим залишилося питання про те, як правильно оформити послідовність команд, щоб транслятор міг їх обробити, а мікропроцесор - виконати.
При розгляді архітектури мікропроцесора ми довідалися, що він має шість сегментних регістрів, за допомогою яких може одночасно працювати:
с одним сегментом коду;
с одним сегментом стека;
с одним сегментом даних;
с трьома додатковими сегментами даних.
Фізично сегмент являє собою область пам'яті, зайняту командами й (або) даними, адреси яких обчислюються щодо значення у відповідному сегментному регістрі.
Синтаксичний опис сегмента на асемблері являє собою конструкцію, зображену на рис. 4:
Рис. 4. Синтаксис опису сегмента
Важливо відзначити, що функціональне призначення сегмента трохи ширше, ніж проста розбивка програми на блоки коду, даних і стека. Сегментація є частиною більш загального механізму, пов'язаного з концепцією модульного програмування . Вона припускає уніфікацію оформлення об'єктних модулів, створюваних компілятором, у тому числі з різних мов програмування. Це дозволяє поєднувати програми, написані на різних мовах. Саме для реалізації різних варіантів такого об'єднання й призначені операнды в директиві SEGMENT . Розглянемо їх докладніше.
Атрибут вирівнювання сегмента (тип вирівнювання) повідомляє компоновщику про те, що потрібно забезпечити розміщення початку сегмента на заданій границі. Це важливо, оскільки при правильному вирівнюванні доступ до даних у процесорах i80х86 виконується швидше. Припустимі значення цього атрибута наступні:
BYTE — вирівнювання не виконується. Сегмент може починатися з будь-якої адреси пам'яті;
WORD — сегмент починається за адресою, кратному двом, тобто останній (молодший) значущий біт фізичної адреси дорівнює 0 (вирівнювання на границю слова);
DWORD — сегмент починається за адресою, кратному чотирьом, тобто два останніх (молодших) значущі біти рівні 0 (вирівнювання на границю подвійного слова);
PARA — сегмент починається за адресою, кратному 16, тобто остання шістнадцяткова цифра адреси повинна бути 0h (вирівнювання на границю параграфа);
PAGE — сегмент починається за адресою, кратному 256, тобто дві останні шістнадцяткові цифри повинні бути 00h (вирівнювання на границю 256-байтной сторінки);
За замовчуванням тип вирівнювання має значення PARA .
Атрибут комбінування сегментів (комбінаторний тип) повідомляє компоновщику, як потрібно комбінувати сегменти різних модулів, що мають те саме ім'я. Значеннями атрибута комбінування сегмента можуть бути:
PRIVATE — сегмент не буде поєднуватися з іншими сегментами з тим же ім'ям поза даним модулем;
PUBLIC — змушує компоновщик з'єднати всі сегменти з однаковими іменами. Новий об'єднаний сегмент буде цілим і безперервним. Всі адреси (зсуву) об'єктів, а це можуть бути, залежно від типу сегмента, команди й дані, будуть обчислюватися відносно початку цього нового сегмента;
COMMON — розташовує всі сегмент із тим самим ім'ям по одній адресі. Всі сегменти з даним ім'ям будуть перекриватися й спільно використовувати пам'ять. Розмір отриманого в результаті сегмента буде дорівнює розміру найбільшого сегмента;
STACK — визначення сегмента стека. Змушує компоновщик з'єднати всі однойменні сегменти й обчислювати адреси в цих сегментах щодо регістра ss. Комбінований тип STACK (стек) аналогічний комбінованому типу PUBLIC, за винятком того, що регістр ss є стандартним сегментним регістром для сегментів стека. Регістр sp установлюється на кінець об'єднаного сегмента стека. Якщо не зазначено жодного сегмента стека, компоновщик видасть попередження, що стековий сегмент не знайдений. Якщо сегмент стека створена, а комбінований тип STACK не використовується, програміст повинен явно завантажити в регістр ss адреса сегмента (подібно тому, як це робиться для регістра ds ).
За замовчуванням атрибут комбінування приймає значення PRIVATE .
Атрибут класу сегмента (тип класу) — це укладений в лапки рядок, що допомагає компоновщику визначити відповідний порядок проходження сегментів при збиранні програми із сегментів декількох модулів. Компоновщик поєднує разом у пам'яті всі сегменти з тим самим ім'ям класу (ім'я класу, у загальному випадку, може бути будь-яким, але краще, якщо воно буде відбивати функціональне призначення сегмента). Типовим прикладом використання ім'я класу є об'єднання в групу всіх сегментів коду програми (звичайно для цього використовується клас “code”). За допомогою механізму типізації класу можна групувати також сегменти ініціалізованих і неініціалізованих даних;
Атрибут розміру сегмента . Для процесорів i80386 і вище сегменти можуть бути 16 або 32-розрядними. Це впливає, насамперед, на розмір сегмента й порядок формування фізичної адреси всередині нього. Атрибут може приймати наступні значення:
USE16 — це означає, що сегмент допускає 16-розрядну адресацію. При формуванні фізичної адреси може використовуватися тільки 16-розрядний зсув. Відповідно, такий сегмент може містити до 64 Кбайт коду або даних;
USE32 — сегмент буде 32-розрядним. При формування фізичної адреси може використовуватися 32-розрядний зсув. Тому такий сегмент може містити до 4 Гбайт коду або даних.
Всі сегменти самі по собі рівноправні, тому що директиви SEGMENT і ENDS не містять інформації про функціональне призначення сегментів. Для того щоб використовувати їх як сегменти коду, даних або стека, необхідно попередньо повідомити транслятор про це, для чого використовують спеціальну директиву ASSUME , що має формат, показаний на рис. 15. Ця директива повідомляє транслятор про те, який сегмент до якого сегментного регістра прив'язаний. У свою чергу, це дозволить транслятору коректно зв'язувати символічні імена, певні в сегментах. Прив'язка сегментів до сегментних регістрів здійснюється за допомогою операндов цієї директиви, у яких ім'я_сегмента повинне бути ім'ям сегмента, певним у вихідному тексті програми директивою SEGMENT або ключовим словом nothing . Якщо в якості операнда використовується тільки ключове слово nothing , то попередні призначення сегментних регістрів анулюються, причому відразу для всіх шести сегментних регістрів. Але ключове слово nothing можна використовувати замість аргументу ім'я сегмента; у цьому випадку буде вибірково розриватися зв'язок між сегментом з ім'ям ім'я сегмента й відповідним сегментним регістром (див. рис. 5).
Рис. 5. Директива ASSUME
Для простих програм, що містять по одному сегменті для коду, даних і стека, хотілося б спростити її опис. Для цього в транслятори MASM і TASM ввели можливість використання спрощених директив сегментації . Але тут виникла проблема, пов'язана з тим, що необхідно було якось компенсувати неможливість прямо управляти розміщенням і комбінуванням сегментів. Для цього разом зі спрощеними директивами сегментації стали використовувати директиву вказівки моделі пам'яті MODEL , що частково стала управляти розміщенням сегментів і виконувати функції директиви ASSUME (тому при використанні спрощених директив сегментації директиву ASSUME можна не використовувати). Ця директива зв'язує сегменти, які у випадку використання спрощених директив сегментації мають визначені імена, із сегментними регістрами.
У лістингу 1 наведений приклад програми з використанням спрощених директив сегментації:
Лістинг 1. Використання спрощених директив сегментації ;============Prg_3_1.asm================== masm ;режим роботи TASM: ideal або masm model small ;модель пам'яті .data ;сегмент даних message db 'Уведіть дві шестнадцатеричные цифри,$' .stack ;сегмент стека db 256 dup ('?') ;сегмент стека .code ;сегмент коду main proc ;початок процедури main mov ax,@data ;заносимо адресу сегмента даних у регістр ax mov ds,ax ;ax в ds ;далі текст програми (див. сегмента коду в лістингу 3.1 книги) mov ax,4c00h ;пересилання 4c00h у регістр ax int 21h ;виклик переривання з номером 21h main endp ;кінець процедури main end main ;кінець програми із крапкою входу main
|
Синтаксис директиви MODEL показаний на рис. 6.
Рис. 6. Синтаксис директиви MODEL
Обов'язковим параметром директиви MODEL є модель пам'яті . Цей параметр визначає модель сегментації пам'яті для програмного модуля. Передбачається, що програмний модуль може мати тільки певні типи сегментів, які визначаються згаданими раніше спрощеними директивами опису сегментів . Ці директиви наведені в табл. 1.
Таблиця 1. Спрощені директиви визначення сегмента
Формат директиви (режим MASM) |
Призначення |
.CODE [ім'я] |
Початок або продовження сегмента коду |
.DATA |
Початок або продовження сегмента ініціалізованих даних. Також використовується для визначення даних типу near |
.CONST |
Початок або продовження сегмента постійних даних (констант) модуля |
.DATA? |
Початок або продовження сегмента неініціалізованих даних. Також використовується для визначення даних типу near |
.STACK [розмір] |
Початок або продовження сегмента стека модуля. Параметр [розмір] задає розмір стека |
.FARDATA [ім'я] |
Початок або продовження сегмента ініціалізованих даних типу far |
.FARDATA? [ім'я] |
Початок або продовження сегмента неініціалізованих даних типу far |
Наявність у деяких директивах параметра [ім'я] говорить про те, що можливо визначення декількох сегментів цього типу. З іншого боку, наявність декількох видів сегментів даних обумовлено вимогою забезпечити сумісність із деякими компіляторами мов високого рівня, які створюють різні сегменти даних для ініціалізованих і неініціалізованих даних, а також констант.
При використанні директиви MODEL транслятор робить доступними кілька ідентифікаторів, до яких можна звертатися під час роботи програми, для того, щоб одержати інформацію про ті або інші характеристики даної моделі пам'яті (див. табл. 1 ). Перелічимо ці ідентифікатори і їхні значення (табл. 2).
Таблиця 2. Ідентифікатори, створювані директивою MODEL
Ім'я ідентифікатора |
Значення змінної |
@code |
Фізична адреса сегмента коду |
@data |
Фізична адреса сегмента даних типу near |
@fardata |
Фізична адреса сегмента даних типу far |
@fardata? |
Фізична адреса сегмента ініціалізованих даних типу far |
@curseg |
Фізична адреса сегмента неініціалізованих даних типу far |
@stack |
Фізична адреса сегмента стека |
Якщо ви подивитеся на текст лістингу 1 , то побачите приклад використання одного із цих ідентифікаторів. Це @data ; з його допомогою ми одержали значення фізичної адреси сегмента даних нашої програми.
Операнды директиви MODEL використовують для задання моделі пам'яті, що визначає набір сегментів програми, розміри сегментів даних і коду, спосіб зв'язування сегментів і сегментних регістрів. У табл. 3 наведені деякі значення параметра модель пам'яті директиви MODEL .
Таблиця 3. Моделі пам'яті
Модель |
Тип коду |
Тип даних |
Призначення моделі |
TINY |
near |
near |
Код і дані об'єднані в одну групу з ім'ям DGROUP. Використовується для створення програм формату .com. |
SMALL |
near |
near |
Код займає один сегмент, дані об'єднані в одну групу з ім'ям DGROUP. Цю модель звичайно використовують для більшості програм на асемблері |
MEDIUM |
far |
near |
Код займає кілька сегментів, по одному на кожний поєднуваний програмний модуль. Всі посилання на передачу керування — типу far. Дані об'єднані в одній групі; всі посилання на них — типу near |
COMPACT |
near |
far |
Код в одному сегменті; посилання на дані — типу far |
LARGE |
far |
far |
Код у декількох сегментах, по одному на кожний поєднуваний програмний модуль |
Параметр модифікатор директиви MODEL дозволяє уточнити деякі особливості використання обраної моделі пам'яті (табл. 4).
Таблиця 4. Модифікатори моделі пам'яті
Значення модифікатора |
Призначення |
use16 |
Сегменти обраної моделі використовуються як 16-бітні (якщо відповідною директивою зазначений процесор i80386 або i80486) |
use32 |
Сегменти обраної моделі використовуються як 32-бітні (якщо відповідною директивою зазначений процесор i80386 або i80486) |
Необов'язкові параметри мова й модифікатор мови визначають деякі особливості виклику процедур. Необхідність у використанні цих параметрів з'являється при написанні й зв'язуванні програм на різних мовах програмування.
Описані стандартні й спрощені директиви сегментації не виключають один одного. Стандартні директиви використовуються, коли програміст бажає одержати повний контроль над розміщенням сегментів у пам'яті і їхньому комбінуванні із сегментами інших модулів.
Спрощені директиви доцільно використовувати для простих програм і програм, призначених для зв'язування із програмними модулями, написаними на мовах високого рівня. Це дозволяє компоновщику ефективно зв'язувати модулі різних мов за рахунок стандартизації зв'язків і керування.
Завдання для виконання лабораторної роботи.
Представити формат речення асемблера.
Представити формат директив асемблера.
Представити формат команд та макрокоманд асемблера.
Описати моделі пам’яті, які використовуються директивою MODEL.
Виконання лабораторної роботи.
Опрацювати теоретичні відомості.
Виконати поставлене завдання (п.4), записати результат.
Дати відповіді на контрольні питання(п.6).
Зробити висновки.
Контрольні питання.
Які ви знаєте спрощені директиви визначення сегмента?
Для чого використовується директива ASSUME?
Які ви знаєте атрибути вирівнювання сегмента?
Зміст звіту.