ТА_Методички / Lec_6
.pdfНаприклад, адреса 8C65:12A4h у нормалізованій формі записується так: 8D8F:0004h. Нормалізація забезпечує однозначність запису кожної фізичної адреси й коректність усіх операцій порівняння та адресної арифметики, в тому числі пов'язаних із міжсегментними переходами.
Тому для безпомилкової роботи з блоками даних, обсяги яких перевищують обсяг сегмента, треба використовувати huge-вказівники, хоча при цьому дещо збільшується час виконання програми через додаткові операції нормалізації.
Наступна функція застосовує huge-вказівник для визначення суми всіх машиних слів адресного простору BIOS, починаючи з адреси F000:0000h до FFFF:000Fh включно; сума є унікальною для кожної серії персональних комп'ютерів і може бути використана в програмах захисту інформації.
/* Функція обчислення суми слів BIOS-області. Варіант 1 */ unsigned long Summa_BIOS (void)
{ |
|
|
unsigned long sum=0; |
|
|
unsigned huge* ph =(unsigned huge*)0xf0000000; |
/* початок області */ |
|
while (ph) |
/* остання адреса - ffff:000fh */ |
|
sum += *ph++; |
|
|
return sum; |
|
|
} |
|
|
21/37
cs-, ds-, _ssта _es-вказівники
Ефективність безпосереднього програмування оперативної пам'яті зростає у разі застосування спеціальних коротких вказівників, що оголошуються через модифікатори _cs, _ds, _ss та _es (ці специфікатори теж є службовими словами Borland C).
Короткі вказівники зберігають тільки зміщення фізичних адрес, а сегментна частина адреси береться з відповідного системного регістра: CS, DS, SS чи ES.
Для запису в сегментні регістри необхідної адреси початку сегмента використовують спеціальні псевдорегістрові змінні: _CS, _DS, _SS та _ES. Нижче подано альтернативний варіант функції обчислення суми машинних слів адресного простору BIOS, в якому для звертання до даних використано спеціальний короткий вказівник _es.
Час виконання даного варіанта функції істотно менший, ніж попереднього, де використовувався huge-вказівник.
22/37
/* Функція обчислення суми слів BIOS-області. Варіант 2 */
unsigned long BIOS_summa(void) |
|
{ |
|
unsigned _es * p_es = (unsigned _es*)0 |
/* початкове зміщення */ |
unsigned long sum; |
|
_ES=0xf000; |
/* сегмент адреси BIOS-області */ |
for (sum=*p_es++; *p_es; p_es++) |
|
sum += *p_es; |
/* сума даних усього сегмента */ |
return sum; |
|
} |
|
Застосування спеціальних near-вказівників та відповідних псевдорегістрових змінних вимагає особливої обережності, оскільки в процесі виконання програми значення сегментних регістрів можуть змінюватись.
У цьому випадку, щоб уникнути формування помилкових фізичних адрес, перед наступним звертанням до даних через near-вказівники з модифікаторами _cs, _ds, _es або _ss необхідно відновити потрібне значення відповідного сегментного регістра. Нижче наведено ще два приклади застосування _es- вказівників у функціях зміни та копіювання текстових відеозображень.
23/37
4. Безпосереднє програмування відеопам'яті
Безпосереднє звертання до відеопам'яті комп'ютера є не тільки найшвидшим способом виведення інформації на екран у текстових режимах роботи відеосистеми, а й надає програмістові додаткові можливості для формування та опрацювання текстових повідомлень. Зокрема, саме таким способом (або через BIOS-переривання) можна:
зчитати символ і/або його атрибути із заданої позиції екрана;
змінити виведені на екран символи і/або їх атрибути;
вивести символ у правій нижній позиції екрана або текстового вікна, уникнувши скролінга;
записати і/або зчитати текстову інформацію з ненульової сторінки відеопам'яті.
Стандартним текстовим режимом, який автоматично встановлюється для відеоадаптерів VGA та SVGA, є режим номер 3 (25 рядків по 80 символів, 16кольорів). Вся інформація, що виводиться на екран, зберігається у спеціальній області оперативної пам'яті, яка називається відеопам'яттю. Кожен символ займає у відеопам'яті два байти: у молодшому байті зберігається код, а в старшому - атрибути даного символа (колір фону, колір і яскравість символа, прапорець блимання). Байт коду символа завжди записується у відеопам'ять за парною адресою, а байт атрибутів - за непарною (рис. 3).
24/37
ASCII-код Атрибути символа символа
парний байт непарний байт Адреси відеопам'яті
Рис. 3. Розташування символів у відеопам'яті
Відеопам'ять текстових режимів входить у загальний адресний простір центрального мікропроцесора. Початок відеопам'яті для всіх кольорових відеоадаптерів має однакову адресу B800:0000h, а для монохромних відеоадаптерів - B000:0000h.
Всю відеопам'ять текстових режимів поділено на 8 сторінок, які пронумеровано від 0 до 7. У режимі 25x80 символів обсяг відеосторінки становить 4 Кбайт (4096 байтів). У кожен момент часу на екрані може відображатись тільки одна сторінка, стандартно активною є нульова сторінка відеопам'яті.
Оскільки сторінки відеопам'яті мають фіксовані адреси, то можна безпосередньо звертатись до кожного байта екранного зображення. Зміна значень байтів, що потрапляють у межі активної відеосторінки, відразу ж відображається у відповідній зміні символів і/або атрибутів текстових повідомлень на екрані.
25/37
Для звертання до відеопам'яті використовують або far-вказівники (як це було зроблено в функції PutLastScrSmb (), наведеній у попередньому пункті), або спеціальні короткі _es-вказівники (приклади подамо далі). За сегментний компонент адреси здебільшого приймають адресу початку відеопам'яті (BSOOh для кольорових відеоадаптерів), а зміщення обчислюють за координатами символа на екрані з урахуванням номера відеосторінки:
зміщення = ((рядок -1)*80 + (позиція -1) )*2 + сторінка* 4096
Першою запишемо функцію, яка повертає код і атрибути символа, що висвітлюється у позиції col екранного рядка row і зберігається на нульовій сторінці відеопам'яті.
/* Функція зчитування символа зі заданої позиції екрана */
#include <dos.h> |
|
|
unsigned |
GetScrSymbol (int row, int col) |
|
{ |
|
|
unsigned |
far * addr = (unsigned far*)MK_FP(Oxb800, 0); |
|
addr += (row-1) *80+(col-1); |
/* адреса символа у відеопам'яті */ |
|
return (*addr); |
/* повертає код і атрибути символа */ |
|
} |
|
|
26/37
Для звертання до двобайтових слів відеопам'яті у функції GetScrSymbol () використано довгий вказівник addr. Початково addr отримує адресу першого байта відеопам'яті - ця адреса формується за допомогою бібліотечного макроса MK_FP (), а потім встановлюється на задане знакомісце.
Значенням виразу *addr є числове дане з типом unsigned, у молодшому байті якого записано ASCII-код символа, а в старшому -його атрибути.
Байти коду та атрибутів символа можна легко виділити зі значення, яке повертає функція, використовуючи порозрядні операції. Наприклад:
unsigned int scr_symb, |
code, attr; |
scr_symb=GetScrSymbol(symy, symx); |
|
code = scr_symb & 0xff; |
/* код символа */ |
attr = scr_symb >> 8; |
/* атрибути символа */ |
Вищої швидкодії звертання до відеопам'яті можна досягти, застосовуючи спеціальні near-вказівники: char _es* та unsigned _es*. Перший з них використовують для побайтової роботи з відеопам'яттю, а другий - у разі запису/зчитування цілих слів.
Нагадаємо, що ці вказівники зберігають тільки зміщення адреси символа, а сегментну частину адреси перед звертанням до відеопам'яті треба занести в системний регістр ES.
27/37
Записана далі функція Invert () візуально виділяє задану частину рядка екрана, інвертуючи двійкові коди атрибутів символів. Параметри х1 та х2 задають номери початкового та кінцевого символів ділянки інвертування у рядку r. Просування по байтах атрибутів у функції Invert () здійснюється через вказівник patr, що має тип char _es * і зберігає зміщення адрес.
/* Функція інвертування атрибутів символів рядка */ void Invert (int r, int x1, int x2)
{
char _es *patr=(char _es*) (((r-l)*80+x1-l)*2+l) ;
_es = 0xb800; |
/* сегментна частина адрес */ |
while (x1++ <= x2) { |
|
*patr = ~ *patr; |
/* інвертування байта атрибутів */ |
patr += 2; |
|
} |
|
} |
|
28/37
Наступна функція ScreenCopy () виконує копіювання цілої відеосторінки (точніше, видимої на екрані частини). Сторінка, номер якої задається параметром from_page, переписується на іншу відеосторінку, що має номер to_page. Фактично функція копіює масив обсягом 25x80x2 байт, тому для неї питання швидкодії є особливо важливими.
Оскільки сегментна частина адрес двобайтового слова, що копіюється, та ділянки, в яку воно записується, однакова (це адреса сегмента відеопам'яті), то найбільш ефективним способом адресації даних буде використання двох коротких _es-вказівників pfrom та pto.
/* Функція копіювання відеосторінки */
void ScreenCopy(int from_page, int to_page)
{
unsigned _es *pfrom = (unsigned _es *)(from_page*4096); unsigned _es *pto = (unsigned _es *)(to_page*4096);
int n; |
/* номер символа, що копіюється */ |
_ES = 0xbS00; |
/* сегмент адрес */ |
for (n=0; n< 25*80; |
n++, pfrom++, pto++) |
*pto = *pfrom; |
/* копіювання символів з атрибутами */ |
} |
|
29/37
За допомогою функції ScreenCopy () можна швидко зберегти поточне екранне зображення на вільній відеосторінці, а потім відновити його на екрані, активізувавши дану відеосторінку або повторно скопіювавши її на нульову сторінку відеопам'яті.
30/37
