вівторок, 30 квітня 2019 р.

0xB800

BD.COM із запущеним вірусом die385.
Випадково зіткнувся із авторами симпатичного дебаггера епохи 90-х, BD.COM. Є навіть надія, що його джерельні тексти на github потраплять. Та й згадав в процесі тих розмов про магічне число, B800/B8000. Виявляється, про нього зараз пам'ятає менше людей, ніж я очікував. :-)

Колись давно (1976) вирішила Intel на колінці зробити мікропроцесор,  щоб «закрити» ринок, поки розроблять щось більш достойне. Вийшов він в 1978 і називався 8086. Процесор був 16-бітний, однак міг адресувати цілий мегабайт пам’яті (на що, як ми знаємо, потрібно 20 біт), за допомогою хитрого трюка – адресу ділили на дві частини, по 16 біт. Для отримання «справжньої», фізичної адреси, яку процесор може використати для звертатиметься до пам’яті, першу частину зсували на 4 біти вліво (це те саме, що множити на 16) та додавали другу.

Першу частину, наслідуючи «дорослі» машини, які підтримували сегментацію – можливість ділити пам’ять на ізольовані блоки, сегменти, назвали сегментом. Ніякого розділення, звичайно, не було – воно «приросло» в 80286 (1982), але слово гарне, все таке. Другу – зміщення (відносно початку сегменту). Тобто, адреса складалася із пари segment:offset. Для адреси сегменту було виділено спеціальні сегментні регістри, не змінюючи яких, можна було звертатися до 64Кб, маніпулюючи 16-бітним зміщенням.


Трішки пізніше IBM задумалася, що не зле б і їм вийти на ринок персональних комп’ютерів, який якраз набирав обертів. (Ймовірно, не підозрюючи, що через десятиліття революцію «персоналок» пов’язуватимуть саме із ними). Для свого персонального комп’ютера (IBM PC) вони обрали 8088 варіант згаданого 8086 із 8-бітною шиною даних, для здешевлення і сумісності із вже присутньою на ринку 8-бітною периферією.

Хоча потім все почало швидко змінюватися, перший IBM PC (модель 5150), був машиною дуже скромною, в стандартну комплектацію входило (монітор і клавіатуру не рахуємо):
  • Від 16 до 64 Кб RAM, із можливістю розширити до захмарних тоді 256 Кб (після заміни BIOS).
  • Один або два 5.25” дисководи для дисків на 160Кб (швидко зрозуміли, що вони "можуть" і 180Кб).
  • Кольорову відеокарту CGA із підтримкою кольорів та графічного режиму,  із 16Кб відеопам’яті. Текстові режими -- 40x25 та 80x25 символів.
  • Опціонально можна було купити MDA – чисто текстову, монохромну, із 4Кб пам’яті.
  • Коштувало все це щастя, в комплектації із 64Кб + CGA + один дисковод + дисплей 5153, згідно вікіпедії, $3005  -- $8281 станом на 2018. (Див. також додаток А). 
Тут нас цікавитимуть саме відеокарти, точніше -- а як  взаємодіяти із ними? Відповідей є декілька. Рухатимемося по абстракціях зверху вниз.

1. Базовою операційною системою для IBM PC був PC DOS (він же – MS DOS), хоча постачалися й інші (UCSD_Pascal, CP/M-86). DOS надавав API для текстового (телетайп-style) виводу. По перше, це було достатньо повільно. По друге – лише зліва направо, зверху вниз, скроллінг автоматичний. Потім з’явився драйвер ANSI.SYS, який підтримував escape-послідовності, що дозволяли побавитися із  кольором та іншими атрибутами. Він був екстремально повільним -- навіть на фоні попереднього API. Плюсом цих способів було те, що вони працювали всюди, де був DOS, навіть якщо апаратура була інша.

2. Більш просунутим, але й більш низькорівневим, способом було спілкуватися із BIOS – вбудованим firmware комп’ютера, напряму. IBM PC-сумісні системи використовували для роботи із відео-сервісом int 10h. Виводити текст так було швидше, можна було робити багато чого ще – переключати кольори, керувати атрибутами, виводити графіку (попіксельно) тощо. Це все ще було досить повільно, але достатньо портабельно – навіть якщо апаратура була не до кінця IBM PC сумісною (а таких комп’ютерів було багато), BIOS ховав це.

3. Найбільш просунутим підходом було взаємодіяти із апаратурою безпосередньо. Для цього згадані 8086/8088 надають окремий адресний простір – 64К портів вводу-виводу. І CGA чи MDA карточки ними користувалися – для вибору відеорежиму та інших налаштувань (порти 03D0h-03DFh для CGA, 03B0h-03BFh, для MDA). Однак, для передачі даних, що виводитимуться на екран, використовувалася інша технологія – memory mapped input/output, ввід-вивід, відображений на пам’ять. Процесор «думає», що пише в пам’ять, але відеокарта слухає шину та перехоплює звертання до певних адрес.

Карта пам'яті оригінального IBM PC. Взято тут: "IBM 51xx PC Family Computers".

Як тоді видавалося, адресований 8086/8088 адресний простір -- це дуже багато. Тому під RAM виділили перших 640Кб, решта -- під memory mapped I/O, BIOS і т.д. А потім кілька десятиліть об ту межу 640, шпорталися... (Див. також ""Extending DOS" by Ray Duncan et. al", деякі подробиці -- тут.)
Так ось, пам’ять карточки CGA в IBM PC знаходилася за фізичною адресою 0xB8000, MDA -- 0B0000h.

Найшвидшим способом виводу було блоком передати цілий екран, або його частину. Якщо залучити DMA, то процесор міг в той час навіть ще чимось займатися. (DMA там теж водиться!). Звичайно, працював цей спосіб лише не тих машинах, де відображення на пам'ять було ідентичним IBM PC.
Ліричний відступ: Один символ на екрані використовував два байти – перший містив ASCII-код символу, другий – атрибут, такий як колір символу, колір фону, мигання, підвищена яскравість. Таким чином, в режимі 40x25 для одного екрану потрібно 2Кб, і в 16Кб CGA-карти можна помістити аж 8 таких екранів. Переключаючи їх можна реалізувати double buffering або швидке прокручування екрану. (Контролер відеокарти підтримував можливість вибирати, з якої області відеопам'яті відображати на екран, див. тут про Start Address Register)
Згадуючи сегменти-зміщення 8086/8088, природним значенням сегмента для CGA було 0xB800 при початковому зміщенні 0.

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

Новіші відеокарти, EGA/VGA, вміли підтримувати сумісність із CGA, тому на достатньо нових комп’ютерах можна було бути впевненим – текстова пам’ять починається з адреси B8000h. Хоча у нас спостерігалося помітне відставання, в 90-х на Заході все це швидко ставало взагалі неактуальним (Windows 95 вийшов того ж року, що й остання редакція BD.COM), але й у нас MDA-картки вже майже не траплялися -- із півдесятка державних установ районного містечка, де я бував в 90-х, MDA-подібні картки зустрілася лише раз.
Кумедно, що Пітер Нортон, у своїй славнозвісній "The Peter Norton Programmers guide to the IBM PC", пише (російське видання 1991, оригінал -- 1985, сторінка 77):
Моїм першим комп'ютером був "Пошук-1", сякий-такий клон IBM XT, то він навіть текстовий режим CGA емулював:
"Пошук-1", фото взято тут.
"Для здешевлення кінцевого пристрою, було прийнято рішення не використовувати окремий контролер клавіатури та відео-контролер, а їхні функції покласти на ЦП через емуляцію цих пристроїв. Також для спрощення роботи електроніки та через деякі конструктивні особливості відсутня можливість прямого доступу до пам'яті (DMA).” Через відсутність повноцінного відео-контролера - відсутній і текстовий режим, а весь текст малюється попіксельно у графічному режимі через BIOS. Також 32 Кб відеопам'яті розташовані в оперативній пам'яті комп'ютера (у моделях із 128 Кб користувачу залишалось 96 Кб, а у моделях із 512 Кб- 490 Кб)."
От і запам'яталася, напевне, на все життя, ця магічна константа. :-)

Додаток А  



Тогочасні персоналки були, в своїй масі, 8-бітними. Хоча, часто, технологічно помітно просунутіші, чи, принаймні, вишуканіші за IBM PC. Почав перераховувати, зрозумів, що це затягнеться, тому обмежуся посиланнями:




Додаток B


Кілька сторінок із оригінальних інструкцій, "IBM 5150 Technical Reference", 1981 року:







Додаток B

В коді такі трюки виглядали наступним чином.

С (взято, із змінами, на OSDev)

void write_string( int colour, const char far *string )
{
    volatile char far *video = (volatile char far*)0xB8000;
    while( *string != 0 )
    {
        *video++ = *string++;
        *video++ = colour;
    }
}

FAR -- якраз "далекий", вказівник, пара сегмент-зміщення, 32 біти (на 16-бітній машині).


На PASCAL це могло виглядати так, із наступним безпосереднім звертанням до елементів масиву:

var screen: array[1..4000] of byte absolute $B8000:0000

type ScrType=Record
          Character: char;
          Attribute: byte;
     end;
var Screen: array [1..25, 1..80] of ScrType absolute $B8000:0000;


Або якось так:

procedure clr_video(filler: char);
var

     i: Integer;
begin

     for i := 0 to 80 * 25 - 1 do          Mem[$B8000 + i * 2] := filler;
end;


На асемблері (приклад з книги Тома Свана):

; SetVidAddr    Prepare video-RAM address
PROC    SetVidAddr
        mov     es, [vBASE]     ; Set es to video segment address
        xor     bh, bh          ; Zero upper half of bx
        mov     bl, dh          ; Assign row to bl
        shl     bx, 1           ; Multiply row (bx) times 2
        mov     di, [scRow+bx]  ; Set di to video buffer row address
        xor     dh, dh          ; Convert column to 16-bit word
        shl     dx, 1           ; Multiply column (dx) times 2
        add     di, dx          ; Add column offset to row address
        ret                     ; Return to caller
ENDP    SetVidAddr

; ScPokeStr     Poke a string into the display
PROC    ScPokeStr
        push    es              ; Save es segment address
        call    SetVidAddr      ; Prepare es:di
        mov     ah, [attribute] ; Assign attribute to ah
        cld                     ; Auto-increment si, di
@@10:
        lodsb                   ; Get next char into al
        stosw                   ; Display attribute and char
        loop    @@10            ; Loop on cx
        pop     es              ; Restore es segment address
        ret                     ; Return to caller
ENDP    ScPokeStr




1 коментар: