Защищенный режим процессоров Intel

         

Прочитать данные из CLIPBOARD


Регистры на входе AX 1705h DX Формат данных, читаемых из CLIPBOARD: 01h текст; 02h графика в формате bitmap; 03h графика в формате metafile picture; 04h SYLK; 05h DIF; 06h графика в формате TIFF; 07h текст в кодировке OEM. ES:BX Указатель на буфер для читаемых данных Регистры на выходе: AX 0, если при выполнении операции произошла ошибка;

не равно 0, если операция успешно выполнена.



Прочитать содержимое отладочных регистров


Регистры на входе: AX 0DE08h ES:DI Адрес буфера размером 8 двойных слов. Регистры на выходе: AH 00h EBX Значение системного регистра CR0.

В буфере располагается содержимое отледочных регистров в следующем порядке: DR0, DR1, DR3, DR4, DR5, DR6, DR7. Регистры DR4 и DR5 зарезервированы и в процессоре i80386 не используются.



Регистры на входе: AX 0DE07h


Регистры на входе: AX 0DE07h Регистры на выходе: AH 00h EBX Значение системного регистра CR0.


Программа, которая работает с прерываниями


Следующая программа, которую мы вам представим, выполняет все необходимые действия, связанные с обработкой прерываний и исключений в защищённом режиме.

Наша первая программа (листинг 1) работала в защищённом режиме с запрещёнными прерываниями. Она как бы пролетала через неизведанное с завязанными глазами и сразу же возвращалась в хорошо освоенный реальный режим.

Теперь же программа имеет возможность "осмотреться" и может реагировать на такие события, как прерывания от клавиатуры и таймера. Кроме того, в случае возникновения исключений вам будет выдана некоторая диагностика, включающая код исключения, код ошибки и содержимое регистров процессора на момент возникновения исключения.

Таблица IDT содержит дескрипторы для обработчиков исключений и аппаратных прерываний. Вентили исключений содержат ссылки на программы с именами exc_00...exc_1F. Вслед за вентилями исключений в таблице IDT следуют вентили аппаратных прерываний. Из них задействованы только IRQ0 и IRQ1 - прерывания от таймера и клавиатуры.

Те аппаратные прерывания, которые нас не интересуют, приводят к выдаче в контроллеры прерывания команды конца прерывания. Для таких прерываний предусмотрены заглушки - процедуры с именами dummy_iret0 и dummy_iret1. Первая заглушка относится к первому контроллеру прерывания, вторая - ко второму.

Вслед за вентилями аппаратных прерываний мы поместили в таблицу IDT вентиль программного прерывания int 30h, предназначенного для организации взаимодействия с клавиатурой на манер прерывания BIOS INT 16h. При выдаче прерывания int 30h вызывается процедура с именем Int_30h_Entry.

После запуска программа переходит в защищённый режим и размаскирует прерывания от таймера и клавиатуры. Далее она вызывает в цикле прерывание int 30h (ввод символа с клавиатуры), и выводит на экран скан-код нажатой клавиши и состояние переключающих клавиш (таких, как CapsLock, Ins, и т.д.).



Если окажется нажатой клавиша ESC, программа выходит из цикла. Далее в тексте программы следует ряд команд, закрытых символом комментария.
null_idt idt_struc <> CODESEG PROC set_rmode NEAR mov [real_sp],sp ; Переводим процессор в состояние отключения, ; это эквивалентно аппаратному сбросу, но ; выполняется быстрее. ; Сначала мы загружаем IDTR нулями, затем ; выдаём команду прерывания. lidt [FWORD null_idt] int 3 rwait: hlt jmp rwait LABEL shutdown_return FAR Регистр IDTR загружается нулями, следовательно, предел дескрипторной таблицы прерываний равен нулю. После этого мы выдаём команду программного прерывания. При обработке прерывания возникает исключение, так как регистр IDTR инициализирован неправильно. Но это исключение не может быть обработано по той же причине, что вызывает новое исключение. Теперь процессор переходит уже в состояние отключения и выполняет рестарт в реальном режиме. Что нам и требовалось получить!

Для удобства мы вынесли в отдельный файл tiny-os.inc все необходимые определения структур данных и констант (листинг 2). Файл tiny-os.asm содержит саму программу, текст которой приведён в листинге 3.

Листинг 2. Структуры данных и константы. ----------------------------------------------------------- ; ------------------------------------------------------------ ; Определения структур данных и констант ; ------------------------------------------------------------ STRUC desc_struc ; структура дескриптора limit dw 0 ; предел base_l dw 0 ; мл. слово физического адреса base_h db 0 ; ст. байт физического адреса access db 0 ; байт доступа rsrv dw 0 ; зарезервировано ENDS desc_struc STRUC idt_struc ; вентиль прерывания destoff dw 0 ; смещение обработчика destsel dw 0 ; селектор обработчика nparams db 0 ; кол-во параметров assess db 0 ; байт доступа rsrv dw 0 ; зарезервировано ENDS idt_struc STRUC idtr_struc ; регистр IDTR idt_lim dw 0 ; предел IDT idt_l dw 0 ; мл. слово физического адреса idt_h db 0 ; ст. байт физического адреса rsrv db 0 ; зарезервировано ENDS idtr_struc ; --------------------------------------------------------------- ; Биты байта доступа ACC_PRESENT EQU 10000000b ; сегмент есть в памяти ACC_CSEG EQU 00011000b ; сегмент кода ACC_DSEG EQU 00010000b ; сегмент данных ACC_EXPDOWN EQU 00000100b ; сегмент расширяется вниз ACC_CONFORM EQU 00000100b ; согласованный сегмент ACC_DATAWR EQU 00000010b ; разрешена запись ACC_INT_GATE EQU 00000110b ; вентиль прерывания ACC_TRAP_GATE EQU 00000111b ; вентиль исключения ; ------------------------------------------------------------ ; Типы сегментов ; сегмент данных DATA_ACC = ACC_PRESENT OR ACC_DSEG OR ACC_DATAWR ; сегмент кода CODE_ACC = ACC_PRESENT OR ACC_CSEG OR ACC_CONFORM ; сегмент стека STACK_ACC = ACC_PRESENT OR ACC_DSEG OR ACC_DATAWR OR ACC_EXPDOWN ; байт доступа сегмента таблицы IDT IDT_ACC = DATA_ACC ; байт доступа вентиля прерывания INT_ACC = ACC_PRESENT OR ACC_INT_GATE ; байт доступа вентиля исключения TRAP_ACC = ACC_PRESENT OR ACC_TRAP_GATE ; ------------------------------------------------------------ ; Константы STACK_SIZE EQU 0400 ; размер стека B_DATA_SIZE EQU 0300 ; размер области данных BIOS B_DATA_ADDR EQU 0400 ; адрес области данных BIOS MONO_SEG EQU 0b000 ; сегмент видеопамяти ; монохромного видеоадаптера COLOR_SEG EQU 0b800 ; сегмент видеопамяти ; цветного видеоадаптера CRT_SIZE EQU 4000 ; размер сегмента видеопамяти ; цветного видеоадаптера MONO_SIZE EQU 1000 ; размер сегмента видеопамяти ; монохромного видеоадаптера CRT_LOW EQU 8000 ; мл.


байт физического адреса ; сегмента видеопамяти ; цветного видеоадаптера MONO_LOW EQU 0000 ; мл. байт физического адреса ; сегмента видеопамяти ; монохромного видеоадаптера CRT_SEG EQU 0Bh ; ст. байт физического адреса ; сегмента видеопамяти CMOS_PORT EQU 70h ; порт для доступа к CMOS-памяти PORT_6845 EQU 0063h ; адрес области данных BIOS, ; где записано значение адреса ; порта контроллера 6845 COLOR_PORT EQU 03d4h ; порт цветного видеоконтроллера MONO_PORT EQU 03b4h ; порт монохромного видеоконтроллера STATUS_PORT EQU 64h ; порт состояния клавиатуры SHUT_DOWN EQU 0feh ; команда сброса процессора VIRTUAL_MODE EQU 0001h ; бит перехода в защищённый режим A20_PORT EQU 0d1h ; команда управления линией A20 A20_ON EQU 0dfh ; открыть A20 A20_OFF EQU 0ddh ; закрыть A20 KBD_PORT_A EQU 60h ; адреса клавиатурных KBD_PORT_B EQU 61h ; портов INT_MASK_PORT EQU 21h ; порт для маскирования прерываний EOI EQU 20 ; команда конца прерывания MASTER8259A EQU 20 ; первый контроллер прерываний SLAVE8259A EQU 0a0 ; второй контроллер прерываний ; ------------------------------------------------------------ ; Селекторы, определённые в таблице GDT DS_DESCR = (gdt_ds - gdt_0) CS_DESCR = (gdt_cs - gdt_0) SS_DESCR = (gdt_ss - gdt_0) BIOS_DESCR = (gdt_bio - gdt_0) CRT_DESCR = (gdt_crt - gdt_0) MDA_DESCR = (gdt_mda - gdt_0) ; ------------------------------------------------------------ ; Маски и инверсные маски для клавиш L_SHIFT equ 0000000000000001b NL_SHIFT equ 1111111111111110b R_SHIFT equ 0000000000000010b NR_SHIFT equ 1111111111111101b L_CTRL equ 0000000000000100b NL_CTRL equ 1111111111111011b R_CTRL equ 0000000000001000b NR_CTRL equ 1111111111110111b L_ALT equ 0000000000010000b NL_ALT equ 1111111111101111b R_ALT equ 0000000000100000b NR_ALT equ 1111111111011111b CAPS_LOCK equ 0000000001000000b SCR_LOCK equ 0000000010000000b NUM_LOCK equ 0000000100000000b INSERT equ 0000001000000000b Листинг 3. Демонстрация обработки прерываний и исключений в защищённом режиме для процессора 80286 ----------------------------------------------------------- IDEAL RADIX 16 P286 MODEL LARGE include 'tiny-os.inc' STACK STACK_SIZE DATASEG DSEG_BEG = THIS WORD real_ss dw ? real_sp dw ? real_es dw ? GDT_BEG = $ LABEL gdtr WORD gdt_0 desc_struc <0,0,0,0,0> gdt_gdt desc_struc <GDT_SIZE-1,,,DATA_ACC,0> gdt_idt desc_struc <IDT_SIZE-1,,,IDT_ACC,0> gdt_ds desc_struc <DSEG_SIZE-1,,,DATA_ACC,0> gdt_cs desc_struc <CSEG_SIZE-1,,,CODE_ACC,0> gdt_ss desc_struc <STACK_SIZE-1,,,DATA_ACC,0> gdt_bio desc_struc <B_DATA_SIZE-1,B_DATA_ADDR,0,DATA_ACC,0> gdt_crt desc_struc <CRT_SIZE-1,CRT_LOW,CRT_SEG,DATA_ACC,0> gdt_mda desc_struc <MONO_SIZE-1,MONO_LOW,CRT_SEG,DATA_ACC,0> GDT_SIZE = ($ - GDT_BEG) ; Область памяти для загрузки регистра IDTR idtr idtr_struc <IDT_SIZE,,,0> ; Таблица дескрипторов прерываний IDT_BEG = $ ; ---------------------- Вентили исключений -------------------- idt idt_struc <OFFSET exc_00,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_01,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_02,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_03,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_04,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_05,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_06,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_07,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_08,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_09,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_0A,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_0B,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_0C,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_0D,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_0E,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_0F,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_10,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_11,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_12,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_13,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_14,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_15,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_16,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_17,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_18,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_19,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_1A,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_1B,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_1C,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_1D,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_1E,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_1F,CS_DESCR,0,TRAP_ACC,0> ; --------------- Вентили аппаратных прерываний --------------- ; int 20h-IRQ0 idt_struc <OFFSET Timer_int,CS_DESCR,0,INT_ACC,0> ; int 21h-IRQ1 idt_struc <OFFSET Keyb_int,CS_DESCR,0,INT_ACC,0> ; int 22h, 23h, 24h, 25h, 26h, 27h-IRQ2-IRQ7 idt_struc 6 dup (<OFFSET dummy_iret0,CS_DESCR,0,INT_ACC,0>) ; int 28h, 29h, 2ah, 2bh, 2ch, 2dh, 2eh, 2fh-IRQ8-IRQ15 idt_struc 8 dup (<OFFSET dummy_iret1,CS_DESCR,0,INT_ACC,0>) ; -------------------- Вентиль прерывания -------------------- ; int 30h idt_struc <OFFSET Int_30h_Entry,CS_DESCR,0,INT_ACC,0> IDT_SIZE = ($ - IDT_BEG) CODESEG PROC start mov ax,DGROUP mov ds,ax call set_crt_base mov bh, 77h call clrscr ; Устанавливаем защищённый режим call set_pmode call write_hello_msg ; Размаскируем прерывания от таймера и клавиатуры in al,INT_MASK_PORT and al,0fch out INT_MASK_PORT,al ; Ожидаем нажатия на клавишу <ESC> charin: int 30h ; ожидаем нажатия на клавишу ; AX - скан-код клавиши, ; BX - состояние переключающих клавиш cmp al, 1 ; если <ESC> - выход из цикла jz continue push bx ; выводим скан-код на экран mov bx, 0301h ; координаты вывода call Print_Word pop bx mov ax, bx ; выводим состояние push bx ; переключающих клавиш mov bx, 0306h call Print_Word pop bx jmp charin ; Следующий байт находится в сегменте кода. ; Он используется нами для демонстрации возникновения ; исключения при попытке записи в сегмент кода.


wrong1 db ? continue: ; После нажатия на клавишу <ESC> выходим в это место ; программы. Следующие несколько строк демонстрируют ; команды, которые вызывают исключение. Вы можете ; попробовать их, если уберёте символ комментария ; из соответствующей строки. ; Попытка записи за конец сегмента данных. Метка wrong ; находится в самом конце программы. mov [wrong], al ; Попытка записи в сегмент кода. ; mov [wrong1], al ; Попытка извлечения из пустого стека. ; pop ax ; Загрузка в сегментный регистр неправильного селектора. ; mov ax, 1280h ; mov ds, ax ; Прямой вызов исключения при помощи команды прерывания. ; int 1 call set_rmode ; установка реального режима mov bh, 07h ; стираем экран и call clrscr ; выходим в DOS mov ah,4c int 21h ENDP start MACRO setgdtentry mov [(desc_struc bx).base_l],ax mov [(desc_struc bx).base_h],dl ENDM ; ------------------------------------------- ; Установка защищённого режима ; ------------------------------------------- PROC set_pmode NEAR mov ax,DGROUP mov dl,ah shr dl,4 shl ax,4 mov si,ax mov di,dx add ax,OFFSET gdtr adc dl,0 mov bx,OFFSET gdt_gdt setgdtentry ; Заполняем дескриптор в GDT, указывающий на ; дескрипторную таблицу прерываний mov ax,si ; загружаем 24-битовый адрес сегмента mov dx,di ; данных add ax,OFFSET idt ; адрес дескриптора для IDT adc dl,0 mov bx,OFFSET gdt_idt setgdtentry ; Заполняем структуру для загрузки регистра IDTR mov bx,OFFSET idtr mov [(idtr_struc bx).idt_l],ax mov [(idtr_struc bx).idt_h],dl mov bx,OFFSET gdt_ds mov ax,si mov dx,di setgdtentry mov bx,OFFSET gdt_cs mov ax,cs mov dl,ah shr dl,4 shl ax,4 setgdtentry mov bx,OFFSET gdt_ss mov ax,ss mov dl,ah shr dl,4 shl ax,4 setgdtentry ; готовим возврат в реальный режим push ds mov ax,40 mov ds,ax mov [WORD 67],OFFSET shutdown_return mov [WORD 69],cs pop ds cli mov al,8f out CMOS_PORT,al jmp del1 del1: mov al,5 out CMOS_PORT+1,al mov ax,[rl_crt] ; сегмент видеопамяти mov es,ax call enable_a20 ; открываем линию A20 mov [real_ss],ss ; сохраняем сегментные mov [real_es],es ; регистры ; -------- Перепрограммируем контроллер прерываний -------- ; Устанавливаем для IRQ0-IRQ7 номера прерываний 20h-27h mov dx,MASTER8259A mov ah,20h call set_int_ctrlr ; Устанавливаем для IRQ8-IRQ15 номера прерываний 28h-2Fh mov dx,SLAVE8259A mov ah,28h call set_int_ctrlr ; Загружаем регистры IDTR и GDTR lidt [FWORD idtr] lgdt [QWORD gdt_gdt] ; Переключаемся в защищённый режим mov ax,VIRTUAL_MODE lmsw ax ; jmp far flush db 0ea dw OFFSET flush dw CS_DESCR LABEL flush FAR ; Загружаем селекторы в сегментные регистры mov ax,SS_DESCR mov ss,ax mov ax,DS_DESCR mov ds,ax ; Разрешаем прерывания sti ret ENDP set_pmode ; -------------------------------- ; Возврат в реальный режим ; -------------------------------- DATASEG ; Пустой дескриптор для выполнения возврата ; процессора в реальный режим через перевод ; его в состояние отключения.


null_idt idt_struc <> CODESEG PROC set_rmode NEAR mov [real_sp],sp ; Переводим процессор в состояние отключения, ; это эквивалентно аппаратному сбросу, но ; выполняется быстрее. ; Сначала мы загружаем IDTR нулями, затем ; выдаём команду прерывания. lidt [FWORD null_idt] int 3 ; Это старый способ сброса процессора через ; контроллер клавиатуры. ; mov al,SHUT_DOWN ; out STATUS_PORT,al rwait: hlt jmp rwait LABEL shutdown_return FAR in al,INT_MASK_PORT and al,0 out INT_MASK_PORT,al mov ax,DGROUP mov ds,ax assume ds:DGROUP cli mov ss,[real_ss] mov sp,[real_sp] mov ax,000dh out CMOS_PORT,al sti mov es,[real_es] call disable_a20 ret ENDP set_rmode ; ------------------------------------------------- ; Обработка исключений ; ------------------------------------------------- ; Обработчики исключений. Записываем в AX номер ; исключения и передаём управление процедуре ; shutdown LABEL exc_00 WORD mov ax,0 jmp shutdown LABEL exc_01 WORD mov ax,1 jmp shutdown LABEL exc_02 WORD mov ax,2 jmp shutdown LABEL exc_03 WORD mov ax,3 jmp shutdown LABEL exc_04 WORD mov ax,4 jmp shutdown LABEL exc_05 WORD mov ax,5 jmp shutdown LABEL exc_06 WORD mov ax,6 jmp shutdown LABEL exc_07 WORD mov ax,7 jmp shutdown LABEL exc_08 WORD mov ax,8 jmp shutdown LABEL exc_09 WORD mov ax,9 jmp shutdown LABEL exc_0A WORD mov ax,0ah jmp shutdown LABEL exc_0B WORD mov ax,0bh jmp shutdown LABEL exc_0C WORD mov ax,0ch jmp shutdown LABEL exc_0D WORD mov ax,0dh jmp shutdown LABEL exc_0E WORD mov ax,0eh jmp shutdown LABEL exc_0F WORD mov ax,0fh jmp shutdown LABEL exc_10 WORD mov ax,10h jmp shutdown LABEL exc_11 WORD mov ax,11h jmp shutdown LABEL exc_12 WORD mov ax,12h jmp shutdown LABEL exc_13 WORD mov ax,13h jmp shutdown LABEL exc_14 WORD mov ax,14h jmp shutdown LABEL exc_15 WORD mov ax,15h jmp shutdown LABEL exc_16 WORD mov ax,16h jmp shutdown LABEL exc_17 WORD mov ax,17h jmp shutdown LABEL exc_18 WORD mov ax,18h jmp shutdown LABEL exc_19 WORD mov ax,19h jmp shutdown LABEL exc_1A WORD mov ax,1ah jmp shutdown LABEL exc_1B WORD mov ax,1bh jmp shutdown LABEL exc_1C WORD mov ax,1ch jmp shutdown LABEL exc_1D WORD mov ax,1dh jmp shutdown LABEL exc_1E WORD mov ax,1eh jmp shutdown LABEL exc_1F WORD mov ax,1fh jmp shutdown DATASEG exc_msg db "Exception ...., ....:....


code ..... Press any key... " CODESEG ; ----------------------------------------------- ; Вывод на экран номера исключения, кода ошибки, ; дампа регистров и возврат в реальный режим. ; ----------------------------------------------- PROC shutdown NEAR call rdump ; дамп регистров процессора push ax call beep ; звуковой сигнал ; Выводим сообщение об исключении mov ax,[vir_crt] mov es,ax mov bx,1d mov ax,4 mov si,OFFSET exc_msg mov dh,74h mov cx, SIZE exc_msg call writexy pop ax mov bx, 040bh ; номер исключения call Print_Word pop ax mov bx, 0420h ; код ошибки call Print_Word pop ax mov bx, 0416h ; смещение call Print_Word pop ax mov bx, 0411h ; селектор call Print_Word call set_rmode ; возвращаемся в реальный режим mov ax, 0 ; ожидаем нажатия на клавишу int 16h mov bh, 07h call clrscr mov ah,4Ch int 21h ENDP shutdown ; ------------------------------------------------- ; Перепрограммирование контроллера прерываний ; На входе: DX - порт контроллера прерывания ; AH - начальный номер прерывания ; ------------------------------------------------- PROC set_int_ctrlr NEAR mov al,11 out dx,al jmp SHORT $+2 mov al,ah inc dx out dx,al jmp SHORT $+2 mov al,4 out dx,al jmp SHORT $+2 mov al,1 out dx,al jmp SHORT $+2 mov al,0ff out dx,al dec dx ret ENDP set_int_ctrlr ; ------------------------------- ; Разрешение линии A20 ; ------------------------------- PROC enable_a20 NEAR mov al,A20_PORT out STATUS_PORT,al mov al,A20_ON out KBD_PORT_A,al ret ENDP enable_a20 ; ------------------------------- ; Запрещение линии A20 ; ------------------------------- PROC disable_a20 NEAR mov al,A20_PORT out STATUS_PORT,al mov al,A20_OFF out KBD_PORT_A,al ret ENDP disable_a20 ; ---------- Обработчик аппаратных прерываний IRQ2-IRQ7 PROC dummy_iret0 NEAR push ax ; Посылаем сигнал конца прерывания в первый контроллер 8259A mov al,EOI out MASTER8259A,al pop ax iret ENDP dummy_iret0 ; ---------- Обработчик аппаратных прерываний IRQ8-IRQ15 PROC dummy_iret1 NEAR push ax ; Посылаем сигнал конца прерывания в первый ; и второй контроллеры 8259A mov al,EOI out MASTER8259A,al out SLAVE8259A,al pop ax iret ENDP dummy_iret1 ; ------------------------------------------ ; Процедура выдаёт короткий звуковой сигнал ; ------------------------------------------ PROC beep NEAR push ax bx cx in al,KBD_PORT_B push ax mov cx,80 beep0: push cx and al,11111100b out KBD_PORT_B,al mov cx,60 idle1: loop idle1 or al,00000010b out KBD_PORT_B,al mov cx,60 idle2: loop idle2 pop cx loop beep0 pop ax out KBD_PORT_B,al pop cx bx ax ret ENDP beep ; ------------------------------------------------ ; Процедура задерживает выполнение программы ; на некоторое время, зависящее от быстродействия ; процессора. ; ------------------------------------------------ PROC pause NEAR push cx mov cx,10 ploop0: push cx xor cx,cx ploop1: loop ploop1 pop cx loop ploop0 pop cx ret ENDP pause ; ------------------------------------------ ; Процедуры для работы с клавиатурой ; ------------------------------------------ DATASEG key_flag db 0 key_code dw 0 ext_scan db 0 keyb_status dw 0 CODESEG ; ---------------------------------------------- ; Обработчик аппаратного прерывания клавиатуры ; ---------------------------------------------- PROC Keyb_int NEAR call beep ; выдаём звуковой сигнал push ax mov al, [ext_scan] ; расширенный скан-код cmp al, 0 ; или обычный ? jz normal_scan1 ; --------- обработка расширенного скан-кода ------------- cmp al, 0e1h ; это клавиша <Pause>? jz pause_key in al, 60h ; вводим скан-код cmp al, 2ah ; игнорируем префикс 2Ah jz intkeyb_exit_1 cmp al, 0aah ; игнорируем отпускание jz intkeyb_exit_1 ; клавиш mov ah, [ext_scan] ; записываем скан-код и call Keyb_PutQ ; расширенный скан-код ; в "очередь", состоящую ; из одного слова mov al, 0 ; сбрасываем признак mov [ext_scan], al ; получения расширенного jmp intkeyb_exit ; скан-кода pause_key: ; обработка клавиши <Pause> in al, 60h ; вводим скан-код cmp al, 0c5h ; если это код <Pause>, jz pause_key1 ; записываем его в очередь, cmp al, 45h ; иначе игнорируем jz pause_key1 jmp intkeyb_exit pause_key1: mov ah, [ext_scan] ; запись в очередь call Keyb_PutQ ; кода клавиши <Pause> mov al, 0 ; сбрасываем признак mov [ext_scan], al ; получения расширенного jmp intkeyb_exit ; скан-кода ; --------- обработка обычного скан-кода ------------- normal_scan1: in al, 60h ; вводим скан-код cmp al, 0feh ; игнорируем FEh jz intkeyb_exit cmp al, 0e1h ; расширенный скан-код? jz ext_key ; если да, то на обработку ; расширенного скан-кода cmp al, 0e0h jnz normal_scan ext_key: mov [ext_scan], al ; устанавливаем признак jmp intkeyb_exit ; расширенного скан-кода ; Сброс признака расширенного скан-кода и выход intkeyb_exit_1: mov al, 0 mov [ext_scan], al jmp intkeyb_exit ; Запись нормального скан-кода в очередь и выход normal_scan: mov ah, 0 call Keyb_PutQ intkeyb_exit: in al, 61h ; разблокируем клавиатуру mov ah, al or al, 80h out 61h, al xchg ah, al out 61h, al mov al,EOI ; посылаем сигнал конца out MASTER8259A,al ; прерывания pop ax sti iret ENDP Keyb_int ; --------------------------------------------------- ; Запись скан-кода и расширенного скан-кода в ; "буфер", состоящий из одного слова. ; --------------------------------------------------- PROC Keyb_PutQ NEAR push ax mov [key_code], ax ; записываемый код ; ------- Обрабатываем переключающие клавиши --------- cmp ax, 002ah ; L_SHIFT down jnz @@kb1 mov ax, [keyb_status] or ax, L_SHIFT mov [keyb_status], ax jmp keyb_putq_exit @@kb1: cmp ax, 00aah ; L_SHIFT up jnz @@kb2 mov ax, [keyb_status] and ax, NL_SHIFT mov [keyb_status], ax jmp keyb_putq_exit @@kb2: cmp ax, 0036h ; R_SHIFT down jnz @@kb3 mov ax, [keyb_status] or ax, R_SHIFT mov [keyb_status], ax jmp keyb_putq_exit @@kb3: cmp ax, 00b6h ; R_SHIFT up jnz @@kb4 mov ax, [keyb_status] and ax, NR_SHIFT mov [keyb_status], ax jmp keyb_putq_exit @@kb4: cmp ax, 001dh ; L_CTRL down jnz @@kb5 mov ax, [keyb_status] or ax, L_CTRL mov [keyb_status], ax jmp keyb_putq_exit @@kb5: cmp ax, 009dh ; L_CTRL up jnz @@kb6 mov ax, [keyb_status] and ax, NL_CTRL mov [keyb_status], ax jmp keyb_putq_exit @@kb6: cmp ax, 0e01dh ; R_CTRL down jnz @@kb7 mov ax, [keyb_status] or ax, R_CTRL mov [keyb_status], ax jmp keyb_putq_exit @@kb7: cmp ax, 0e09dh ; R_CTRL up jnz @@kb8 mov ax, [keyb_status] and ax, NR_CTRL mov [keyb_status], ax jmp keyb_putq_exit @@kb8: cmp ax, 0038h ; L_ALT down jnz @@kb9 mov ax, [keyb_status] or ax, L_ALT mov [keyb_status], ax jmp keyb_putq_exit @@kb9: cmp ax, 00b8h ; L_ALT up jnz @@kb10 mov ax, [keyb_status] and ax, NL_ALT mov [keyb_status], ax jmp keyb_putq_exit @@kb10: cmp ax, 0e038h ; R_ALT down jnz @@kb11 mov ax, [keyb_status] or ax, R_ALT mov [keyb_status], ax jmp keyb_putq_exit @@kb11: cmp ax, 0e0b8h ; R_ALT up jnz @@kb12 mov ax, [keyb_status] and ax, NR_ALT mov [keyb_status], ax jmp keyb_putq_exit @@kb12: cmp ax, 003ah ; CAPS_LOCK up jnz @@kb13 mov ax, [keyb_status] xor ax, CAPS_LOCK mov [keyb_status], ax jmp keyb_putq_exit @@kb13: cmp ax, 00bah ; CAPS_LOCK down jnz @@kb14 jmp keyb_putq_exit @@kb14: cmp ax, 0046h ; SCR_LOCK up jnz @@kb15 mov ax, [keyb_status] xor ax, SCR_LOCK mov [keyb_status], ax jmp keyb_putq_exit @@kb15: cmp ax, 00c6h ; SCR_LOCK down jnz @@kb16 jmp keyb_putq_exit @@kb16: cmp ax, 0045h ; NUM_LOCK up jnz @@kb17 mov ax, [keyb_status] xor ax, NUM_LOCK mov [keyb_status], ax jmp keyb_putq_exit @@kb17: cmp ax, 00c5h ; NUM_LOCK down jnz @@kb18 jmp keyb_putq_exit @@kb18: cmp ax, 0e052h ; INSERT up jnz @@kb19 mov ax, [keyb_status] xor ax, INSERT mov [keyb_status], ax jmp keyb_putq_exit @@kb19: cmp ax, 0e0d2h ; INSERT down jnz @@kb20 jmp keyb_putq_exit @@kb20: test ax, 0080h ; фильтруем отжатия клавиш jnz keyb_putq_exit mov al, 0ffh ; устанавиваем признак mov [key_flag], al ; готовности для чтения ; символа из "буфера" keyb_putq_exit: pop ax ret ENDP Keyb_PutQ ; ----------------------------------------------------- ; Программное прерывание, предназначенное для чтения ; символа из буфера клавиатуры.


По своим функциям ; напоминает прерывание INT 16h реального режима. ; В AX возвращается скан-код нажатой клавиши, ; в BX - состояние переключающих клавиш. ; ----------------------------------------------------- PROC Int_30h_Entry NEAR push dx ; запрещаем прерывания и cli ; сбрасываем признак mov al, 0 ; готовности скан-кода mov [key_flag], al ; в буфере клавиатуры ; Ожидаем прихода прерывания от клавиатуры. ; Процедура клавиатурного прерывания установит ; признак в переменной key_flag. keyb_int_wait: sti ; разрешаем прерывания nop ; ждём прерывание nop cli ; запрещаем прерывания mov al, [key_flag] ; и опрашиваем флаг cmp al, 0 ; готовности скан-кода jz keyb_int_wait mov al, 0 ; сбрасываем флаг mov [key_flag], al ; готовности mov ax, [key_code] ; записываем скан-код mov bx, [keyb_status] ; и состояние переключающих ; клавиш sti ; разрешаем прерывания pop dx iret ENDP Int_30h_Entry ; ------------------------------------------- ; TIMER section ; ------------------------------------------- DATASEG timer_cnt dw 0 CODESEG PROC Timer_int NEAR cli push ax ; Увеличиваем содержимое счётчика времени mov ax, [timer_cnt] inc ax mov [timer_cnt], ax ; Примерно раз в секунду выдаём звуковой сигнал test ax, 0fh jnz timer_exit call beep timer_exit: ; Посылаем команду конца прерывания mov al,EOI out MASTER8259A,al pop ax sti iret ENDP Timer_int ; -------------------------------------------------- ; Процедуры обслуживания видеоконтроллера ; -------------------------------------------------- DATASEG columns db 80d rows db 25d rl_crt dw COLOR_SEG vir_crt dw CRT_DESCR curr_line dw 0d text_buf db " " CODESEG ; ----------------------------------------- ; Определение адреса видеопамяти ; ----------------------------------------- PROC set_crt_base NEAR mov ax,40 mov es,ax mov bx,[WORD es:4a] mov [columns],bl mov bl,[BYTE es:84] inc bl mov [rows],bl mov bx,[WORD es:PORT_6845] cmp bx,COLOR_PORT je color_crt mov [rl_crt],MONO_SEG mov [vir_crt],MDA_DESCR color_crt: ret ENDP set_crt_base ; ------------------------------------- ; Запись строки в видеопамять ; ------------------------------------- PROC writexy NEAR push si push di mov dl,[columns] mul dl add ax,bx shl ax,1 mov di,ax mov ah,dh write_loop: lodsb stosw loop write_loop pop di pop si ret ENDP writexy ; --------------------------------------- ; Стирание экрана (в реальном режиме) ; --------------------------------------- PROC clrscr NEAR xor cx,cx mov dl,[columns] mov dh,[rows] mov ax,0600 int 10 ret ENDP clrscr DATASEG hello_msg db " Protected mode monitor *TINY/OS*, v.1.1 for CPU 80286 ¦ © Frolov A.V., 1992 " CODESEG ; ------------------------------------ ; Вывод начального сообщения ; в защищённом режиме ; ------------------------------------ PROC write_hello_msg NEAR mov ax,[vir_crt] mov es,ax mov si,OFFSET hello_msg mov bx,0 mov ax,[curr_line] inc [curr_line] mov cx,SIZE hello_msg mov dh,30h call writexy call beep ret ENDP write_hello_msg ; ---------------------------------------------- ; Процедура выводит на экран содержимое AX ; (x,y) = (bh, bl) ; ---------------------------------------------- PROC Print_Word near push ax push bx push dx push ax mov cl,8 rol ax,cl call Byte_to_hex mov [text_buf], dh mov [text_buf+1], dl pop ax call Byte_to_hex mov [text_buf+2], dh mov [text_buf+3], dl mov si, OFFSET text_buf mov dh, 70h mov cx, 4 mov al, bh mov ah, 0 mov bh, 0 call writexy pop dx pop bx pop ax ret ENDP Print_Word DATASEG tabl db '0123456789ABCDEF' CODESEG ; ----------------------------------------- ; Преобразование байта в шестнадцатеричный ; символьный формат ; al - входной байт ; dx - выходное слово ; ----------------------------------------- PROC Byte_to_hex near push cx push bx mov bx, OFFSET tabl push ax and al,0fh xlat mov dl,al pop ax mov cl,4 shr al,cl xlat mov dh,al pop bx pop cx ret ENDP Byte_to_hex DATASEG reg_title db " CS IP AX BX CX DX SP BP SI DI " ; .... .... .... .... .... .... .... ....


sreg_title db " DS ES SS FLAGS " ; .... .... .... .... .... .... .... .... CODESEG ; ------------------------------------------------ ; Вывод на экран содержимого регистров процессора ; ------------------------------------------------ PROC rdump NEAR pushf pusha mov di, es mov ax,[vir_crt] mov es,ax mov si,OFFSET reg_title mov bx,1 ; (X,Y) = (AX,BX) mov ax,6 mov cx,SIZE reg_title mov dh,1fh ; чёрный на голубом фоне call writexy ; Выводим содержимое всех регистров mov ax,cs ; cs mov bx, 0702h call Print_Word mov bp, sp mov ax, [bp+18d] ; ip mov bx, 0708h call Print_Word mov bx, 070eh mov ax,[bp+14d] ; ax call Print_Word mov bx, 0714h mov ax,[bp+8d] ; bx call Print_Word mov bx, 071ah mov ax,[bp+12d] ; cx call Print_Word mov bx, 0720h mov ax,[bp+10d] ; dx call Print_Word mov ax,bp add ax,20d ; sp mov bx, 0726h call Print_Word mov ax,[bp+4d] ; bp mov bx, 072ch call Print_Word mov bx, 0732h mov ax,[bp+2] ; si call Print_Word mov bx, 0738h mov ax, [bp] ; di call Print_Word mov si,OFFSET sreg_title mov bx,1 mov ax,8 mov cx,SIZE sreg_title mov dh,1fh call writexy mov bx, 0902h mov ax, ds ; ds call Print_Word mov bx, 0908h mov ax, di ; es call Print_Word mov bx, 090eh mov ax,ss ; ss call Print_Word mov bx, 0914h mov ax, [bp+16d] ; flags call Print_Word ; Восстанавливаем содержимое регистров popa popf ret ENDP rdump CSEG_SIZE = ($ - start) DATASEG DSEG_SIZE = ($ - DSEG_BEG) wrong db ? END start

Проверка наличия в системе интерфейса VCPI


Регистры на входе: AX 0DE00h

Регистры на выходе: AH равен 00h - если VCPI установлен, не равен 00h - если VCPI не установлен. BH Верхний (major) номер версии VCPI. BL Нижний (minor) номер версии VCPI.



Расфиксирование блока памяти


Эта функция должна быть использована для расфиксирования блока памяти, зафиксированного предыдущей функцией.

Регистры на входе AX 0601h BX:CX Начальный линейный адрес расфиксируемого участка памяти. SI:DI Размер расфиксируемого блока памяти в байтах. Регистры на выходе: CARRY 0, если функция выполнилась без ошибки, 1, если произошла ошибка.

Разрешить виртуальные прерывания.


Функция устанавливает флаг виртуальных прерываний, разрешая виртуальные прерывания, и возвращает предыдущее сосотояние этого флага.

Регистры на входе AX 0901h Регистры на выходе: CARRY 0 AL 0, если виртуальные прерывания были запрещены, 1, если виртуальные прерывания были разрешены.

Регистр EFLAGS


В этой таблице описан формат регистра флагов для процессоров i80386 и i80486 (регистр флагов процессора i80286 называется FLAGS и представляет собой младшее 16-разрядное слово регистра EFLAGS):

Номер бита Назначение
0 - CF Флаг переноса
1 - 1 Зарезервировано и равно 1
2 - PF Флаг чётности
3 - 0 Зарезервировано и равно 0
4 - AF Флаг вспомогательного переноса
5 - 0 Зарезервировано и равно 0
6 - ZF Флаг нуля
7 - SF Флаг знака
8 - TF Флаг ловушки
9 - IF Флаг разрешения прерываний
10 - DF Флаг направления
11 - OF Флаг переполнения
12-13 - IOPL Уровень привилегий ввода/вывода
14 - NT Флаг вложенной задачи
15 - 0 Зарезервировано и равно 0
16 - RF Флаг возобновления (только i80386 и i80486)
17 - VM Флаг режима виртуального процессора 8086 (только i80386 и i80486)
18 - AC Флаг проверки выравнивания (только i80486)
19-31 - 0 Зарезервировано и равно 0


Процессор i80386 содержит средства для


Процессор i80386 содержит средства для работы в так называемом режиме виртуального процессора i8086, называемого также для краткости режимом V86 или просто виртуальным режимом. Заметим, что до разработки фирмой Intel процессора i80836 термин "виртуальный режим" иногда использовался в литературе для обозначения защищённого режима работы процессора i80286.

В режим V86 процессор может перейти из защищённого режима, если установить в регистре флагов EFLAGS бит виртуального режима (VM-бит). Номер бита VM в регистре EFLAGS - 17.

Когда процессор i80386 находится в виртуальном режиме, его поведение во многом напоминает поведение хорошо знакомого нам процессора i8086. В частности, для адресации памяти используется схема <сегмент:смещение>, размер сегмента составляет 64 килобайта, а размер адресуемой в этом режиме памяти - 1 мегабайт.

Виртуальный режим предназначен для работы программ, ориентированных на процессор i8086 (или i8088). Но виртуальный режим - это не реальный режим процессора i8086, имеются существенные отличия. Процессор фактически продолжает использовать схему преобразования адресов памяти и средства мультизадачности защищённого режима.

В виртуальном режиме используется трансляция страниц памяти. Это позволяет в мультизадачной операционной системе создавать несколько задач, работающих в виртуальном режиме. Каждая из этих задач может иметь собственное адресное пространство, каждое размером в 1 мегабайт.

Все задачи виртуального режима обычно выполняются в третьем, наименее привилегированном кольце защиты. Когда в такой задаче возникает прерывание, процессор автоматически переключается из виртуального режима в защищённый. Поэтому все прерывания отображаются в операционную систему, работающую в защищённом режиме.

Обработчики прерываний защищённого режима могут моделировать функции соответствующих прерываний реального режима, что необходимо для правильной работы программ, ориентированных на реальный режим операционной системы MS-DOS.


Сброс точки останова


Функция сбрасывает состояние для заданной отладочной точки останова.

Регистры на входе AX 0B03h BX Индекс отладочной точки останова Регистры на выходе: CARRY 0, если функция выполнилась без ошибки, 1, если произошла ошибка.

SGDT Запись в память содержимого регистра GDTR


Команда позволяет узнать текущее содержимое регистра глобальной дескрипторной таблицы GDTR, обычное её используют в системных отладчиках.



SIDT Записать в память содержимое регистра IDTR


Команда позволяет узнать текущее содержимое регистра глобальной дескрипторной таблицы прерываний IDTR, используется в системных отладчиках.



Синхронизация задач и семафоры


Прежде чем мы приведём конкретный пример простейшей мультизадачной системы, расскажем немного о применении семафоров как средств синхронизации задач.

Как правило, любая мультизадачная операционная система содержит более или менее развитые средства синхронизации и взаимодействия задач. Семафоры как средство синхронизации задач предназначены для управления коллективным доступом со стороны задач к какому либо ресурсу. Под ресурсом мы будем понимать не только физические ресурсы компьютера (диски, клавиатуру и т.д.), но и логические ресурсы - ячейки памяти, буфера и т.п.

Программа, владеющая ресурсом или создавшая ресурс может создать семафор для управления этим ресурсом. Физически семафор реализуется в оперативной памяти и представляет из себя ячейку памяти (достаточно одного байта или иногда даже одного бита памяти).

В простейшем случае для семафора определяются операции: создание семафора; уничтожение семафора; сброс семафора; сброс семафора; ожидание, пока семафор не будет установлен.

Эти операции выполняются операционной системой по запросам прикладных программ. В примере мультизадачной программы, приведённой ниже в этой главе, мы должны сами реализовать все семафорные операции.

Что касается двух первых операций, то мы выбрали самое простое решение. Семафоры создаются статически на этапе трансляции программы и представляют из себы простые статические переменные. Операция уничтожения семафора в этом случае не используется, т.к. мы создаём все нужные нам семафоры при трансляции программы.

Операции сброса и установки семафора заключаются в записи, соответственно, нуля и единицы в ячейки памяти, распределённые семафорам. Единственная особенность выполнения этих операций заключается в том, что они должны быть непрерываемыми, то есть на время выполнения этих операций необходимо запретить переключение задач. Так как в нашем случае задачи переключаются по прерываниям таймера, мы на время выполнения операций сброса и установки семафора запрещаем все прерывания при помощи команды CLI.

Операция ожидания установки семафора, напротив, должна выполняться с разрешёнными прерываниями, так как семафор ожидает одна задача, а его установку выполняет другая задача. Однако в процессе ожидания необходимо считать содержимое семафора и проверить его состояние, и вот эта операция должна выполняться с запрещёнными прерываниями.



Системные команды предназначены для использования,


Системные команды предназначены для использования, главным образом, в модулях операционных систем (в модулях ядра операционной системы, в драйверах и т.д.). Некоторые из перечисленных ниже команд полезны и при разработке прикладных программ, работающих в защищённом режиме. Мы приведём только краткий перечень основных системных команд, подробности вы можете узнать из справочных руководств по процессорам (см. список литературы).

Как правило, системные команды могут использовать только те программы, которые выполняются в нулевом привилегированном кольце.


SLDT Записать в память содержимое регистра LDTR


Команда позволяет узнать текущее содержимое регистра локальной дескрипторной таблицы LDTR, используется в системных отладчиках.



SMSW Записать слова состояния процессора


Команда записывает в память или 16-битовый регистр младшее слово регистра CR0 и может быть использована в системных отладчиках.



Создание алиасного дескриптора для сегмента кода


Так как в защищённом режиме модификация сегмента кода с использованием селектора CS невозможна, для решения такой задачи необходимо создать дескриптор сегмента данных, который бы указывал на сегмент кода (т.е. имел бы такой же базовый адрес и предел). Такой дескриптор является алиасом дескриптору сегмента кода и может быть создан при помощи функции 000Ah.

Если алиас больше не нужен, он может быть уничтожен при помощи функции 0001h.

Регистры на входе AX 000Ah BX Селектор сегмента кода, для которого требуется создать алиас. Регистры на выходе: CARRY 0, если функция выполнилась без ошибки, 1, если произошла ошибка.

Создать дескриптор в таблице LDT


С помощью этой функции программа может создать один или несколько дескрипторов в LDT, принадлежащей задаче, в рамках которой работает программа, т.е. для своей задачи.

Вся работа по инициализации полученных дескрипторов должна выполняться самой программой, для чего в интерфейсе DPMI имеются соответствующие функции.

Регистры на входе: AX 0000h CX Количество создаваемых дескрипторов. Регистры на выходе: CARRY 0, если функция выполнилась без ошибки, 1, если произошла ошибка. AX Базовый селектор.

Если программа заказывала несколько селекторов, AX содержит первый селектор из созданного массива. Для получения остальных селекторов необходимо воспользоваться функцией 00003h.



STR Запись регистра задачи


Команда записывает текущее содержимое регистра задачи TR в 16-разрядную ячейку памяти или 16-разрядный регистр. Может использоваться в системных отладчиках.



Связь с WINDOWS CLIPBOARD


Операционная система Microsoft WINDOWS имеет чрезвычайно удобное средство обмена информацией между программами - CLIPBOARD. Это средство предназначено для обмена как текстовой, так и графической информацией. Что имеется в виду под обменом информацией?

Например, вы подготавливаете рекламный проспект при помощи текстового редактора Microsoft Word for WINDOWS. В проспект необходимо поместить фотографию рекламируемого изделия. Используя сканер, вы считываете фотографию и записываете изображение в файл. Далее полученное изображение может быть отредактировано любым графическим редактором. Выделив в графическом редакторе прямоугольный участок изображения, вы можете скопировать его в CLIBBOARD (как во временную память). Затем, переключившись на текстовый редактор, вы можете вставить в любое место текста изображение, взятое из CLIPBOARD.

Вы можете также выделить часть текста и скопировать её в CLIPBOARD. Затем этот текст может быть вставлен в другое место того же текста или вообще в другой текст, редактируемый другим редактором.

Если WINDOWS работает в расширенном режиме, запустив обычную DOS-программу в окне, вы можете выделить любую часть экрана и скопировать её в CLIPBOARD. Затем содержимое CLIPBOARD можно вставить в другую DOS-программу, если она ожидает ввода с клавиатуры. Таким образом организуется перенос текстовой информации из одной DOS-программы в другую DOS-программу. Заметьте, что обе эти программы могут не знать о том, что они работают в среде WINDOWS.

Однако DOS-программа, работающая в среде WINDOWS, может и сама работать с CLIPBOARD. При этом возможен обмен информацией (текстовой или графической) между DOS-программами и приложениями WINDOWS.

Зная интерфейс DOS-программы с WINDOWS CLIPBOARD, вы легко сможете создавать DOS-программы, обменивающиеся информацией с приложениями WINDOWS. Расскажем о некоторых, наиболее полезных функциях, которые могут быть использованы для работы с WINDOWS CLIPBOARD.



Таблица прерываний защищённого режима


Обработка прерываний и исключений в защищённом режиме по аналогии с реальным режимом базируется на таблице прерываний. Но таблица прерываний защищённого режима является таблицей дескрипторов, которая содержит так называемые вентили прерываний, вентили исключений и вентили задач.

Таблица прерываний защищённого режима называется дескрипторной таблицей прерываний IDT (Interrupt Descriptor Table). Также как и таблицы GDT и LDT, таблица IDT содержит 8-байтовые дескрипторы. Причём это системные дескрипторы - вентили прерываний, исключений и задач. Поле TYPE вентиля прерывания содержит значение 6, а вентиля исключения - значение 7.

Формат элементов дескрипторной таблицы прерываний IDT показан на рис. 12.

Рис. 12. Формат элементов дескрипторной таблицы прерываний IDT.

Где располагается дескрипторная таблица прерываний IDT?

Её расположение определяется содержимым 5-байтового внутреннего регистра процессора IDTR. Формат регистра IDTR полностью аналогичен формату регистра GDTR, для его загрузки используется команда LIDT. Так же, как регистр GDTR содержит 24-битовый физический адрес таблицы GDT и её предел, так и регистр IDTR содержит 24-битовый физический адрес дескрипторной таблицы прерываний IDT и её предел.

Регистр IDTR обычно загружают перед переходом в защищённый режим. Разумеется, это можно сделать и потом, находясь в защищённом режиме. Однако для этого программа должна работать в привилегированном нулевом кольце.



Тип сегментов


Поле TYPE дескриптора определяет способ, которым можно использовать тот или иной сегмент. Разделение сегментов на типы позволяет защититься от случайного или преднамеренного использования сегментов не по назначению.

Сегмент кода может быть закрыт для чтения установкой бита R в байте доступа. Такие сегменты можно только выполнять, но нельзя читать. В сегмент кода нельзя записывать какие-либо данные. В регистр CS можно загружать только такие селекторы, которые относятся к сегментам кода.

Сегменты данных могут быть закрыты для записи установкой бита W в байте доступа. Сегменту данных нельзя передать управление, загрузив его селектор в регистр CS.

Дескрипторы некоторых типов, например, описывающих расположение таблицы LDT или сегмента состояния задачи, о котором мы будем говорить позже, нельзя использовать для чтения или записи, даже если программа выполняется в нулевом приоритетном кольце. В случае такой необходимости следует создать дополнительные (алиасные) дескрипторы, в которых эти же сегменты описаны как сегменты данных.



Линейный адрес отказа страницы


Регистр Назначение
CR0 Регистр состояния процессора
CR1 Зарезервирован
CR2 Линейный адрес отказа страницы
CR3 Базовый адрес каталога страницы


Установить адрес обработчика исключения с заданным номером


Функция может быть использована для установки собственного обработчика исключений.

Регистры на входе AX 0203h BL Номер исключения (00h - 1Fh). CX:(E)DX Адрес обработчика исключения в формате <селектор:смещение>. Регистры на выходе: CARRY 0, если функция выполнилась без ошибки, 1, если произошла ошибка.

Установить базовый адрес сегмента


Эта функция устанавливает базовый адрес сегмента в дескрипторе, соответствующем заданному селектору. Вы можете модифицировать только те дескрипторы, которые создала ваша программа.

Регистры на входе AX 0007h BX Селектор сегмента, для которого требуется установить базовый адрес. CX:DX Устанавливаемый 32-разрядный линейный базовый адрес сегмента. Регистры на выходе: CARRY 0, если функция выполнилась без ошибки, 1, если произошла ошибка.

Установить дескриптор


Функция выполняет действия, обратные предыдущей - копирует содержимое 8-байтового буфера в дескриптор, заданный селектором.

Регистры на входе AX 000Ch BX Селектор сегмента, для которого требуется установить дескриптор. ES:(E)DI Указатель на буфер размером 8 байт, из которого будет взята информация для установки дескриптора. Для 32-разрядных программ необходимо использовать регистр EDI. Регистры на выходе: CARRY 0, если функция выполнилась без ошибки, 1, если произошла ошибка.

Программа может модифицировать только дескрипторы из своей локальной таблицы LDT, созданные там с помощью функции 0000h.



Установить отладочные регистры


Регистры на входе: AX 0DE09h ES:DI Адрес буфера размером 8 двойных слов, содержащего новые значения для отладочных регистров. Регистры на выходе: AH 00h EBX Значение системного регистра CR0.

Значения, подготовленные для зарезервированных регистров DR4 и DR5, игнорируются.



Регистры на входе: AX 0DE0Bh


Регистры на входе: AX 0DE0Bh BX Вектор прерывания, используемый для IRQ0. CX Вектор прерывания, используемый для IRQ8. Регистры на выходе: AH равен 00h - успешное выполнение функции, не равен 00h - ошибка. После выполнения этой функции прерывания запрещены. Перед завершением своей работы программа должна установить прежнее отображение векторов для контроллеров прерываний.


Установить поле прав доступа в дескрипторе


При помощи этой функции вы можете модифицироать поле прав доступа в дескрипторе, созданного вашей программой.

Регистры на входе AX 0009h BX Селектор сегмента, для которого требуется установить новые права доступа. CL Байт прав доступа. CH Расширение байта прав доступа для процессора i80386/i80486, используется только 32-разрядными реализациями DPMI. Регистры на выходе: CARRY 0, если функция выполнилась без ошибки, 1, если произошла ошибка.

В расширении байта прав доступа старший бит (бит 7) - бит гранулярности, который определяет интерпретацию поля предела (0 - предел в байтах, 1 - предел в страницах). Бит 6 указывает режим работы процессора (0 - 16-разрядный, 1 - 32-разрядный). Бит 5 и 2-3 должны быть равны 0. Бит 4 может содержать любое значение.



Установить предел сегмента


Эта функция устанавливает предел сегмента в дескрипторе, соответствующем заданному селектору.

Регистры на входе AX 0008h BX Селектор сегмента, для которого требуется установить новое значение предела. CX:DX Устанавливаемое 32-разрядное значение предела сегмента. Регистры на выходе: CARRY 0, если функция выполнилась без ошибки, 1, если произошла ошибка.

Установить размер данных, записанных в CLIPBOARD


После записи данных в CLIPBOARD программист может ограничить размер CLIPBOARD:

Регистры на входе AX 1709h SI:CX Размер данных в байтах Регистры на выходе: DX:AX Размер максимального доступного участка памяти

Установить точку останова для отладки


Функция позволяет установить отладочную точку останова по заданному линейному адресу.

Регистры на входе AX 0B00h BX:CX Линейный адрес точки останова. DL Размер используемого для точки останова операнда (1, 2, или 4 байта). DH Тип точки останова 0 = Выполнение 1 = Запись 2 = Чтение/запись Регистры на выходе: CARRY 0, если функция выполнилась без ошибки, 1, если произошла ошибка. BX Индекс для доступа к отладочной точке останова

Установить вектор прерывания реального режима


Функция позволяет задать для любого вектора прерывания адрес обработчика в формате реального режима.

Регистры на входе AX 0201h BL Номер прерывания. CX:DX Адрес обработчика прерывания в формате <сегмент:смещение>. Регистры на выходе: CARRY 0, если функция выполнилась без ошибки, 1, если произошла ошибка.

Установить вектор прерывания защищённого режима


С помощью этой функции программа может установить собственный обработчик защищённого режима для любого прерывания.

Регистры на входе AX 0205h BL Номер прерывания. CX:(E)DX Адрес обработчика прерывания в формате <селектор:смещение>. Регистры на выходе: CARRY 0, если функция выполнилась без ошибки, 1, если произошла ошибка.

Установить защищённый режим работы процессора


Функция переключает процессор из реального режима в защищённый. Кроме этого, она производит перепрограммирование контроллеров прерываний, необходимое из-за конфликта используемых в реальном режиме векторов аппаратных прерываний с зарезервированными прерываниями защищённого режима.

Регистры на входе: AH 89h BH Номер прерывания для IRQ0, используется для перепрограммирования первого контроллера прерывания. Этот номер должен быть кратен 8. BL Номер прерывания для IRQ8, используется для перепрограммирования второго контроллера прерывания. Этот номер также должен быть кратен 8. ES:SI Адрес таблицы GDT, подготовленной специальным образом. Регистры на выходе: CARRY = 0 Функция выполнилась без ошибки. AH 00h CS, DS, ES, SS В эти регистры заносятся значения в соответствии с подготовленной перед вызовом функции таблицей GDT, адрес которой задаётся в регистрах ES:SI. В случае ошибки: CARRY = 1 Произошла ошибка при входе в защищённый режим. AH FF

Подготовленная перед вызовом функции 89h таблица GDT должна состоять из восьми дескрипторов:

Таблица 7. GDT для перехода в защищённый режим средствами BIOS.

0 Пустой дескриптор, содержит нули во всех полях.
1 Дескриптор, описывающий таблицу GDT.
2 Дескриптор, описывающий таблицу IDT.
3 Дескриптор для сегмента данных, сответствует селектору, который будет загружен в регистр DS.
4 Дескриптор дополнительного сегмента данных (регистр ES).
5 Дескриптор сегмента стека (регистр SS).
6 Дескриптор сегмента кода (регистр CS).
7 Этот дескриптор инициализировать не надо, он будет использоваться функцией 89h для адресации сегмента данных BIOS.

В рамках прерывания INT15h нет функции для возврата из защищённого режима в реальный. Почему?

Потому, что во-первых, в защищённом режиме прерывание 15h зарезервировано фирмой Intel, во-вторых, для работы в защищённом режиме вами подготавливается таблица IDT и определяются заново все обработчики прерываний. Обработчики прерываний BIOS рассчитаны на работу в реальном режиме и после перехода в защищённый режим становятся недоступны.



Утилита MEMOSCOP


Для определения активных интерфейсов с защищённым режимом можно использовать предлагаемую утилиту MEMOSCOP. Эта утилита проверяет присутствие всех уровней поддержки программ, работающих в защищённом режиме или с расширенной памятью - от BIOS до DPMI.

*MEMOSCOP*, © Frolov A.V., 1992 -------------------------------------------------------- Файл memoscop.c #include <stdio.h> #include <stdlib.h> #include <dos.h> void main(void) { extern int getcpu(void); unsigned cpu_type, ver; unsigned err; char ver_hi, ver_lo, verems; unsigned hostdata_seg, hostdata_size, dpmi_flags; void (far *pm_entry)(); union REGS regs; struct SREGS segregs; printf("\n*MemoScop* v 1.0, © Frolov A.V., 1992\n" "---------------------------------------\n"); // Определяем тип центрального процессора. Если программа // работает на процессоре i8086, завершаем выполнение, // так как в этом случае интерфейсы с защищенным режимом // недоступны. printf("Тип процессора: 80%d\n", (cpu_type = getcpu())); if(cpu_type == 86) { printf("\nНа этом процессоре нам работать не интересно..."); exit(0); } // Определяем размер доступной через прерывание INT 15h // расширенной памяти. printf("\n------------------------ Уровень BIOS -------------------------\n"); regs.h.ah = 0x88; int86(0x15, &regs, &regs); printf("Размер расширенной памяти, доступной через INT 15:" " \t%d Кбайт\n", regs.x.ax); // Проверяем, установлен ли драйвер HIMEM.SYS, // если установлен, выводим его версию. printf("\n------------------------ Уровень XMM --------------------------\n"); if (XMM_Installed()) { printf("Установлен драйвер HIMEM.SYS"); ver = XMM_Version(); printf(", версия: %4X, изменения: %4X\n", (short)ver, (short)(ver >> 16)); printf("Размер свободной расширенной памяти, доступной через XMM:" " %ld Кбайт\n", (long)XMM_QueryLargestFree()); printf("Общий размер расширенной памяти, доступной через XMM:" " \t %ld Кбайт\n", (long)XMM_QueryTotalFree()); } else printf("\nДрайвер HIMEM.SYS не установлен."); printf("\n------------------------ Уровень EMS/VCPI ----------------------\n"); // Проверяем наличие драйвера EMS/VCPI if(ems_init()) printf("Драйвер EMS/VCPI не загружен\n"); else { printf("Драйвер EMS/VCPI загружен, "); // Выводим номер версии драйвера if((err = ems_ver(&verems)) != 0) { printf("\nОшибка %02.2X при определении версии EMM", err); exit(-1); } printf("версия EMM: %02.2X", verems); // Определяем присутствие VCPI и его версию if(vcpi_ver(&ver_hi, &ver_lo) != 0) { printf("\nДрайвер EMM не поддерживает VCPI\n"); exit(-1); } printf("\nВерсия VCPI: %02.2X.%02.2X\n", ver_hi, ver_lo); } printf("\n------------------------ Уровень DPMI --------------------------"); // Проверяем доступность и параметры сервера DPMI regs.x.ax = 0x1687; int86x(0x2F, &regs, &regs, &segregs); if(regs.x.ax != 0) { printf("\nСервер DPMI не активен"); exit(-1); } // Определяем версию сервера DPMI printf("\nВерсия сервера DPMI: \t\t\t%d.%d\n", regs.h.dh, regs.h.dl); // Определяем тип процессора printf("Тип процессора:\t\t\t\t"); if(regs.h.cl == 2) printf("80286"); else if(regs.h.cl == 3) printf("80386"); else if(regs.h.cl == 4) printf("80486"); // Определяем возможность работы с 32-разрядными // программами dpmi_flags = regs.x.bx; printf("\nПоддержка 32-разрядных программ:\t"); if(dpmi_flags && 1) printf("ПРИСУТСТВУЕТ"); else printf("ОТСУТСТВУЕТ"); // Определяем размер области памяти для сервера DPMI hostdata_size = regs.x.si; printf("\nРазмер памяти для сервера DPMI:\t\t%d байт", hostdata_size * 16); // Определяем адрес точки входа в защищённый режим FP_SEG(pm_entry) = segregs.es; FP_OFF(pm_entry) = regs.x.di; printf("\nАдрес точки входа в защищённый режим: \t%Fp\n", pm_entry); getch(); } /** *.Name ems_init *.Title Функция проверяет установку драйвера EMS * *.Descr Эта функция проверяет наличие драйвера EMS * *.Proto int ems_init(void); * *.Params Не используются * *.Return 0 - драйвер EMS установлен; * 1 - драйвер EMS не установлен. * *.Sample ems_test.c **/ int ems_init(void) { void (_interrupt _far *EMS_driver_adr)(void); char _far *EMS_driver_name; char test_name[8]; int i; EMS_driver_adr = _dos_getvect(0x67); FP_SEG(EMS_driver_name) = FP_SEG (EMS_driver_adr); FP_OFF(EMS_driver_name) = 10; for(i=0; i<8; i++) test_name[i] = EMS_driver_name[i]; if(strncmp(test_name, "EMMXXXX0", 8) == 0) return(0); else return(1); } /** *.Name ems_ver *.Title Определение версии драйвера EMS * *.Descr Эта функция возвращает номер версии * драйвера EMS в двоично-десятичном формате. * *.Proto int ems_ver(char *ver); * *.Params char *ver - указатель на байт, в который * будет записан номер версии. * *.Return Номер версии драйвера EMS в формате BCD * *.Sample ems_test.c **/ int ems_ver(char *ver) { union REGS reg; reg.x.ax = 0x4600; int86(0x67, &reg, &reg); *ver = reg.h.al; return(reg.h.ah); } int vcpi_ver(char *ver_hi, char *ver_lo) { union REGS reg; reg.x.ax = 0xDE00; int86(0x67, &reg, &reg); *ver_hi = reg.h.bh; *ver_lo = reg.h.bl; return(reg.h.ah); }

Исходные тексты функций, вызываемых утилитой MEMOSCOP приведены ниже:


*MEMOSCOP*, © Frolov A.V., 1992 -------------------------------------------------------- Файл cpu.asm DEAL MODEL SMALL P386 PUBLIC _getcpu CODESEG PROC _getcpu NEAR ; -------------------------------------------------------- ; Пытаемся установить старшую тетраду регистра флагов в 0. ; Если программа работает на процессоре 8086, в этом ; байте все биты будут установлены в 1 ; -------------------------------------------------------- mov dx, 86 ; Тип процессора - 8086 pushf pop bx and bh,0Fh push bx popf pushf pop ax and ah,0F0h cmp ah,0F0h je cpu_end ; -------------------------------------------------------- ; Для теста на процессор 80286 пытаемся установить старшую ; тетраду регистра флагов в единицы. Если программа ; выполняется на процессоре 80286, эти биты останутся ; равными нулю ; -------------------------------------------------------- mov dx, 286 ; Тип процессора - 80286 or bh,0F0h push bx popf pushf pop ax test ah,0F0h jz cpu_end ; -------------------------------------------------------- ; Для того, чтобы отличить процессор 80386 от процессора ; 80486, используем бит 18 регистра флагов EFLAGS. ; Если программа может изменить состояние этого бита, ; она выполняется на процессоре 80486. В противном случае ; используется процессор 80386. ; -------------------------------------------------------- ; Созраняем указатель стека mov edx,esp ; Выравниваем указатель стека для предотвращения ; исключения при установке флага AC and esp,not 3 ; Копируем регистр EFLAGS в регистр EAX pushfd pop eax ; Сохраняем начальное значение регистра EFLAGS mov ecx,eax ; Переключаем флаг AC xor eax,40000H ; Пытаемся записать измененное значение обратно в регистр ; EFLAGS push eax popfd ; Копируем регистр EFLAGS в регисрр EAX pushfd pop eax ; Сравниваем старое и новое значения бита AC xor eax,ecx shr eax,18 and eax,1 push ecx ; Восстанавливаем регситр EFLAGS popfd ; Восстанавливаем указатель стека mov esp,edx ; Теперь если программа выполняется на процессоре 80386, ; регистр AX содержит значение 0, а если на процессоре ; 80486 - значение 1.


mov dx,386 ; Тип процессора - 80386 test ax,ax jz cpu_end mov ax,486 ; Тип процессора - 80486 cpu_end: mov ax, dx ret ENDP _getcpu END Для работы с функциями драйвера HIMEM.SYS используется интерфейс, описанный нами в томе 2 "Библиотеки системного программсита":

*MEMOSCOP*, © Frolov A.V., 1992 -------------------------------------------------------- Файл xmmc.asm ; Это интерфейсный модуль для вызова функций ; XMS из Си. Текст программы рассчитан на ; модель памяти Small. .model small,c .DATA ; В этом месте будет храниться адрес ; управляющей функции XMM XMM_Control dd ? .CODE ; Макроопределения для выполнения соглашения об ; использовании регистров в процедурах Си c_begin macro push bp mov bp,sp push si push di endm c_end macro pop di pop si mov sp,bp pop bp ret endm ; Все процедуры должны быть public public XMM_Installed public XMM_Version public XMM_RequestHMA public XMM_ReleaseHMA public XMM_GlobalEnableA20 public XMM_GlobalDisableA20 public XMM_EnableA20 public XMM_DisableA20 public XMM_QueryA20 public XMM_QueryLargestFree public XMM_QueryTotalFree public XMM_AllocateExtended public XMM_FreeExtended public XMM_MoveExtended public XMM_LockExtended public XMM_UnLockExtended public XMM_GetHandleLength public XMM_GetHandleInfo public XMM_ReallocateExtended public XMM_RequestUMB public XMM_ReleaseUMB ;** ;.Name XMM_Installed ;.Title Получение адреса управляющей функции ; ;.Descr Эта функция проверяет наличие драйвера ; HIMEM.SYS и в случае его присуствия ; запоминает адрес управляющей функции. ; ;.Proto unsigned XMM_Installed(void); ; ;.Params Не используются ; ;.Return 0 - драйвер HIMEM.SYS не установлен; ; 1 - драйвер HIMEM.SYS установлен. ; ;.Sample xms_test.c ;** XMM_Installed proc near c_begin mov ax, 4300h int 2fh cmp al, 80h jne NotInstalled mov ax, 4310h int 2fh mov word ptr [XMM_Control], bx mov word ptr [XMM_Control+2], es mov ax,1 jmp Installed NotInstalled: mov ax, 0 Installed: c_end XMM_Installed endp ;** ;.Name XMM_Version ;.Title Определение версии драйвера HIMEM.SYS ; ;.Descr Эта функция определяет версию драйвера ; HIMEM.SYS ; ;.Proto long XMM_Version(void); ; ;.Params Не используются ; ;.Return Номер версии в младших 16 битах, ; номер изменений - в старших 16 битах ; возвращаемого значения ; ;.Sample xms_test.c ;** XMM_Version proc near push si push di xor ah,ah call [XMM_Control] mov dx, bx pop di pop si ret XMM_Version endp ;** ;.Name XMM_RequestHMA ;.Title Запросить область HMA ; ;.Descr Эта функция пытается зарезервировать для ; программы область HMA ; ;.Proto long XMM_RequestHMA(unsigned space); ; ;.Params space - размер требуемой области для ; TSR-программы или драйвера, ; 0xffff для прикладной программы; ; ;.Return < 0 - область HMA не назначена программе, ; код ошибки находится в старшем байте. ; 0L - область HMA назначена программе. ; ;.Sample xms_test.c ;** XMM_RequestHMA proc near c_begin mov ah, 1 mov dx, [bp+4] call [XMM_Control] xor dx, dx dec ax jz @success mov dh, bl @success: c_end XMM_RequestHMA endp ;** ;.Name XMM_ReleaseHMA ;.Title Освободить область HMA ; ;.Descr Эта функция пытается освободить ; область HMA ; ;.Proto long XMM_ReleaseHMA(void); ; ;.Params Не используются ; ;.Return < 0 - область HMA не освобождена, ; код ошибки находится в старшем байте. ; 0L - область HMA освобождена. ; ;.Sample xms_test.c ;** XMM_ReleaseHMA proc near c_begin mov ah, 2 call [XMM_Control] xor dx, dx dec ax jz @success1 mov dh, bl @success1: c_end XMM_ReleaseHMA endp ;** ;.Name XMM_GlobalEnableA20 ;.Title Глобальное разрешение линии A20 ; ;.Descr Эта функция разрешает программе, получившей ; доступ к области HMA использовать линию A20 ; ;.Proto long XMM_GlobalEnableA20(void); ; ;.Params Не используются ; ;.Return < 0 - линия A20 не включена, ; код ошибки находится в старшем байте. ; 0L - линия A20 включена. ; ;.Sample xms_test.c ;** XMM_GlobalEnableA20 proc near c_begin mov ah, 3 call [XMM_Control] xor dx, dx dec ax jz @success2 mov dh, bl @success2: c_end XMM_GlobalEnableA20 endp ;** ;.Name XMM_GlobalDisableA20 ;.Title Глобальное запрещение линии A20 ; ;.Descr Эта функция запрещает программе, получившей ; доступ к области HMA использовать линию A20 ; ;.Proto long XMM_GlobalDisableA20(void); ; ;.Params Не используются ; ;.Return < 0 - линия A20 не выключена, ; код ошибки находится в старшем байте. ; 0L - линия A20 выключена. ; ;.Sample xms_test.c ;** XMM_GlobalDisableA20 proc near c_begin mov ah, 4 call [XMM_Control] xor dx, dx dec ax jz @success3 mov dh, bl @success3: c_end XMM_GlobalDisableA20 endp ;** ;.Name XMM_EnableA20 ;.Title Локальное разрешение линии A20 ; ;.Descr Эта функция разрешает программе управлять ; областью расширенной памяти. ; ;.Proto long XMM_EnableA20(void); ; ;.Params Не используются ; ;.Return < 0 - линия A20 не включена, ; код ошибки находится в старшем байте. ; 0L - линия A20 включена. ; ;.Sample xms_test.c ;** XMM_EnableA20 proc near c_begin mov ah, 5 call [XMM_Control] xor dx, dx dec ax jz @success4 mov dh, bl @success4: c_end XMM_EnableA20 endp ;** ;.Name XMM_DisableA20 ;.Title Локальное запрещение линии A20 ; ;.Descr Эта функция запрещает программе управлять ; областью расширенной памяти. ; ;.Proto long XMM_DisableA20(void); ; ;.Params Не используются ; ;.Return < 0 - линия A20 не выключена, ; код ошибки находится в старшем байте. ; 0L - линия A20 выключена. ; ;.Sample xms_test.c ;** XMM_DisableA20 proc near c_begin mov ah, 6 call [XMM_Control] xor dx, dx dec ax jz @success5 mov dh, bl @success5: c_end XMM_DisableA20 endp ;** ;.Name XMM_QueryA20 ;.Title Проверить состояние линии A20 ; ;.Descr Эта функция проверяет доступность ; линии A20 ; ;.Proto long XMM_QueryA20(void); ; ;.Params Не используются ; ;.Return < 0 - ошибка, ; код ошибки находится в старшем байте. ; 0L - линия A20 выключена, ; 1L - линия A20 включена. ; ;.Sample xms_test.c ;** XMM_QueryA20 proc near c_begin mov ah, 7 call [XMM_Control] xor dx, dx or ax, ax jnz @success6 mov dh, bl @success6: c_end XMM_QueryA20 endp ;** ;.Name XMM_QueryLargestFree ;.Title Определить максимальный размер блока ; ;.Descr Эта функция возвращает размер максимального ; непрерывного блока расширенной памяти, ; который доступен программе. ; ;.Proto long XMM_QueryLargestFree(void); ; ;.Params Не используются ; ;.Return < 0 - ошибка, ; код ошибки находится в старшем байте. ; >= 0 - размер блока. ; ;.Sample xms_test.c ;** XMM_QueryLargestFree proc near c_begin mov ah, 8 call [XMM_Control] xor dx, dx or ax, ax jnz @success7 mov dh, bl @success7: c_end XMM_QueryLargestFree endp ;** ;.Name XMM_QueryTotalFree ;.Title Определить размер расширенной памяти ; ;.Descr Эта функция возвращает размер ; всей имеющейся расширенной памяти. ; ;.Proto long XMM_QueryTotalFree(void); ; ;.Params Не используются ; ;.Return < 0 - ошибка, ; код ошибки находится в старшем байте. ; >= 0 - размер расширенной памяти. ; ;.Sample xms_test.c ;** XMM_QueryTotalFree proc near c_begin mov ah, 8 call [XMM_Control] or ax, ax mov ax, dx mov dx, 0 jnz @success8 mov dh, bl @success8: c_end XMM_QueryTotalFree endp ;** ;.Name XMM_AllocateExtended ;.Title Запросить блок расширенной памяти ; ;.Descr Эта функция выделяет программе блок ; расширенной памяти, в случае успеха ; возвращает индекс полученного блока. ; ;.Proto long XMM_AllocateExtended(unsigned space); ; ;.Params space - размер требуемого блока памяти ; в килобайтах; ; ;.Return < 0 - блок не распределен, ; код ошибки находится в старшем байте. ; > 0L - младший байт содержит индекс ; полученного блока памяти. ; ;.Sample xms_test.c ;** XMM_AllocateExtended proc near c_begin mov ah, 9 mov dx, [bp+4] call [XMM_Control] or ax, ax mov ax, dx mov dx, 0 jnz @success9 mov dh, bl @success9: c_end XMM_AllocateExtended endp ;** ;.Name XMM_FreeExtended ;.Title Освободить блок расширенной памяти ; ;.Descr Эта функция освобождает блок ; расширенной памяти, полученный функцией ; XMM_AllocateExtended(). ; ;.Proto long XMM_FreeExtended(unsigned handle); ; ;.Params handle - индекс освобождаемого блока памяти; ; ;.Return < 0 - блок не распределен, ; код ошибки находится в старшем байте. ; 0L - блок освобожден. ; ;.Sample xms_test.c ;** XMM_FreeExtended proc near c_begin mov ah, 0Ah mov dx, [bp+4] call [XMM_Control] xor dx, dx dec ax jz @successA mov dh, bl @successA: c_end XMM_FreeExtended endp ;** ;.Name XMM_MoveExtended ;.Title Копировать блок расширенной памяти ; ;.Descr Эта функция копирует блок ; расширенной памяти, используя структуру ; struct XMM_Move: ; ; struct XMM_Move { ; unsigned long Length; ; unsigned short SourceHandle; ; unsigned long SourceOffset; ; unsigned short DestHandle; ; unsigned long DestOffset; ; }; ; ;.Proto long XMM_MoveExtended(struct ; XMM_Move *move_descr); ; ;.Params struct XMM_Move *move_descr - ; указатель на структуру, описывающую ; что, откуда и куда надо копировать. ; ;.Return < 0 - ошибка при копировании, ; код ошибки находится в старшем байте. ; 0L - блок скопирован успешно. ; ;.Sample xms_test.c ;** XMM_MoveExtended proc near c_begin mov ah, 0Bh mov si, [bp+4]; call [XMM_Control] xor dx, dx dec ax jz @successB mov dh, bl @successB: c_end XMM_MoveExtended endp ;** ;.Name XMM_LockExtended ;.Title Заблокировать блок расширенной памяти ; ;.Descr Эта функция блокирует блок расширенной ; памяти и возвращает 31 разряд его ; физического адреса. ; ;.Proto long XMM_LockExtended(unsigned handle); ; ;.Params handle - индекс блокируемого блока памяти; ; ;.Return < 0 - блок не заблокирован, ; код ошибки находится в старшем байте. ; > 0L - блок заблокирован, функция ; возвращает физический адрес блока ; памяти. ; ;.Sample xms_test.c ;** XMM_LockExtended proc near c_begin mov ah, 0Ch mov dx, [bp+4] call [XMM_Control] xchg ax, bx dec bx jz XMML_Success mov dh, al XMML_Success: c_end XMM_LockExtended endp ;** ;.Name XMM_UnLockExtended ;.Title Разблокировать блок расширенной памяти ; ;.Descr Эта функция разблокирует блок расширенной ; памяти. ; ;.Proto long XMM_UnLockExtended(unsigned handle); ; ;.Params handle - индекс блока памяти; ; ;.Return < 0 - блок не разблокирован, ; код ошибки находится в старшем байте. ; 0L - блок разблокирован. ; ;.Sample xms_test.c ;** XMM_UnLockExtended proc near c_begin mov ah, 0Dh mov dx, [bp+4] call [XMM_Control] xor dx, dx dec ax jz @successC mov dh, bl @successC: c_end XMM_UnLockExtended endp ;** ;.Name XMM_GetHandleLength ;.Title Получить длину блока расширенной памяти ; ;.Descr Эта функция возвращает длину блока ; расширенной памяти по его индексу. ; ;.Proto long XMM_GetHandleLength(unsigned handle); ; ;.Params handle - индекс блока памяти; ; ;.Return < 0 - произошла ошибка, ; код ошибки находится в старшем байте. ; > 0L - длина блока в килобайтах. ; ;.Sample xms_test.c ;** XMM_GetHandleLength proc near c_begin mov ah, 0Eh mov dx, [bp+4] call [XMM_Control] or ax, ax mov ax, dx mov dx, 0 jnz @successD mov dh, bl @successD: c_end XMM_GetHandleLength endp ;** ;.Name XMM_GetHandleInfo ;.Title Получить информацию о блоке расширенной памяти ; ;.Descr Эта функция возвращает общее ; количество индексов в системе и ; содержимое счетчика блокирования для ; заданного индекса. ; ;.Proto long XMM_GetHandleInfo(unsigned handle); ; ;.Params handle - индекс блока памяти; ; ;.Return < 0 - произошла ошибка, ; код ошибки находится в старшем байте. ; > 0L - младший байт - общее количество ; индексов в системе; ; старший байт - счетчик блокирования. ; ;.Sample xms_test.c ;** XMM_GetHandleInfo proc near c_begin mov ah, 0Eh mov dx, [bp+4] call [XMM_Control] mov dx, bx or ax, ax mov ax, dx mov dx, 0 jnz @successE mov dh, bl @successE: c_end XMM_GetHandleInfo endp ;** ;.Name XMM_ReallocateExtended ;.Title Изменить размер блока расширенной памяти ; ;.Descr Эта функция изменяет размер выделенного ; блока расширенной памяти. ; ;.Proto long XMM_ReallocateExtended(unsigned handle, ; unsigned new_size); ; ;.Params handle - индекс блока памяти; ; new_size - новый размер блока памяти ; в килобайтах; ; ;.Return < 0 - блок не распределен, ; код ошибки находится в старшем байте. ; > 0L - младший байт содержит индекс ; полученного блока памяти. ; ;.Sample xms_test.c ;** XMM_ReallocateExtended proc near c_begin mov ah, 0Fh mov dx, [bp+4] mov bx, [bp+6] call [XMM_Control] xor dx, dx dec ax jz @successF mov dh, bl @successF: c_end XMM_ReallocateExtended endp ;** ;.Name XMM_RequestUMB ;.Title Запросить область UMB ; ;.Descr Эта функция пытается зарезервировать для ; программы область UMB ; ;.Proto long XMM_RequestUMB(unsigned space); ; ;.Params space - размер требуемой области ; в параграфах; ; ;.Return < 0 - область UMB не назначена программе, ; код ошибки находится в старшем байте; ; максимальный размер доступного блока ; в младшем слове (16 разрядов); ; > 0L - область UMB назначена программе, ; младшее слово содержит сегмент блока ; UMB, старший - размер выделенного ; блока UMB. ; ;.Sample xms_test.c ;** XMM_RequestUMB proc near c_begin mov ah, 10h mov dx, [bp+4] call [XMM_Control] xchg bx, ax dec bx jz RUMB_Success xchg ax, dx mov dh, dl RUMB_Success: c_end XMM_RequestUMB endp ;** ;.Name XMM_ReleaseUMB ;.Title Освободить область UMB ; ;.Descr Эта функция пытается освободить ; область UMB ; ;.Proto long XMM_ReleaseUMB(unsigned segment); ; ;.Params segment - сегмент освобождаемого блока UMB* ; ;.Return < 0 - область UMB не освобождена, ; код ошибки находится в старшем байте. ; 0L - область UMB освобождена. ; ;.Sample xms_test.c ;** XMM_ReleaseUMB proc near c_begin mov ah, 11h mov dx, [bp+4] call [XMM_Control] xor dx, dx dec ax jz @success10 mov dh, bl @success10: c_end XMM_ReleaseUMB endp END

VERW Проверить сегмент на возможность записи


С помощью этих двух команд можно проверить доступность выбранного селектором сегмента на чтение и запись, соответственно. Если операция чтения или записи доступна, флаг нуля ZF устанавливается в единицу, в противном случае он сбрасывается в ноль.

Основное назначение этой команды - предотвратить возникновение исключения по защите памяти при попытке обращения к сегменту. Прежде чем выполнять обращение, программа может проверить доступность сегмента и сделать соответствующие выводы.



Вход в защищённый режим


После того, как программа получила адрес процедуры для входа в защищённый режим, она может вызвать эту процедуру при помощи команды CALL. Перед вызовом необходимо загрузить регистры:

Регистры на входе: AX Разрядность программы. Если программа является 32-разрядной, в регистре AX необходимо установить бит 0 в единицу. ES В этот регистр необходимо загрузить сегментный адрес буфера, который будет использован сервером DPMI. Размер буфера должен быть определён при помощи предыдущей функции (регистр SI).

После загрузки регистров необходимо выполнить вызов процедуры с адресом, который был получен в регистрах ES:DI после вызова предыдущей функции.

Если функция выполнилась успешно, флаг CARRY сброшен и программа выполняется в защищённом режиме.

Регистры на выходе CS Селектор, которому соответствует базовый адрес сегмента кода программы и предел 64 килобайта. SS Селектор для сегмента стека, базовый адрес соответствует стеку реального режима, предел - 64 килобайта. DS Селектор, соответствующий сегменту данных реального режима, предел - 64 килобайта. ES Селектор, указывающий на PSP программы с пределом 100h байт. FS, GS 0 (если программа работает на процессоре i80386 или i80486). ESP Если программа работает в 32-разрядном режиме, старшее слово регистра ESP будет равно 0.

Остальные регистры не изменяются.

Если функция выполнилась с ошибкой, флаг CARRY устанавливается в единицу и программа продолжает выполнение в реальном режиме.

После входа в защищённый режим вам становится доступен интерфейс DPMI через функции прерывания INT31h.

Для завершения своей работы программа, использующая DPMI, должна выдать прерывание INT 21h (функция 4Ch) - это обычный способ завершения программ, работающих в среде MS-DOS.

Приведём фрагмент программы, использующей DPMI. На примере этого фрагмента мы покажем, как убедиться в том, что в системе имеется интерфейс DPMI, как войти в защищённый режим и завершить работу программы.

; Получаем адрес точки входа в защищённый режим mov ax, 1687h int 2Fh test ax, ax jnz Cant_Enter_PMode ; не можем войти в защищённый режим mov [PMode_Entry_Seg], es mov [PMode_Entry_Off], di ; Заказываем память для сервера DPMI (если это требуется) test si, si jz Enter_PMode_Now mov bx, si mov ah, 48h int 21h jc Cant_Enter_PMode mov es, ax ; Устанавливаем защищённый режим Enter_PMode_Now: xor ax, ax call DWORD PTR [PMode_Entry_Off] jc Cant_Enter_PMode ; Программа работает в защиённом режиме. ; Здесь располагаются строки ; вашей программы. ; Завершение программы и возврат в DOS mov ax, 4C00h int 21h

ВХОДИМ В ЗАЩИЩЁННЫЙ РЕЖИМ


2.1.

2.2.

2.3.

2.4.

Задача второй главы - показать на простом примере, как составить программу для MS-DOS, переключающую процессор из реального режима в защищённый и возвращающую его обратно в реальный режим. Сам процесс описан во многих книгах, посвящённых i80286, однако при этом обычно опускаются многие технические детали, связанные с аппаратным обеспечением компьютера. Знание этих деталей совершенно необходимо для успешного использования защищенного режима на реальных компьютерах.

Сейчас мы не будем рассматривать процесс обработки прерываний в защищённом режиме - это материал следующей главы. Там же будет приведён и соответствующий пример программы. В программе, которую представим в этой главе, мы запретили прерывания на всё то время, пока процессор находится в защищённом режиме.



Виртуальная машина WINDOWS


Операционная система WINDOWS позволяет разделять ресурсы персонального компьютера между несколькими параллельно работающими программами. При этом в среде WINDOWS могут выполняться три типа программ: Программы, созданные специально для работы под управлением WINDOWS - приложения WINDOWS. Они не могут работать вне WINDOWS, так как для своей работы используют модули, находящиеся в ядре WINDOWS. Приложения WINDOWS всегда начинают своё выполнение и выполняются в защищённом режиме, им доступен интерфейс DPMI. Программы, созданные для работы в среде MS-DOS и ничего не знающие о WINDOWS. Это хорошо знакомые вам программы реального режима. Если WINDOWS работает в стандартном режиме (Standart Mode) на компьютере с процессором i80286, выполнение этих программ происходит в реальном режиме. Если WINDOWS работает в расширенном режиме (Enhanced Mode) на процессорах i80386 или i80486, такие программы выполняются в режиме виртуального процессора 8086. Программы, начинающие свою работу в режиме виртуального процессора 8086 (как обычные программы реального режима) и переключающиеся затем в защищённый режим. Эти программы используют интерфейс DPMI, который доступен им только в том случае, если WINDOWS работает в расширенном режиме на процессорах i80386 или i80486.

Программы первого типа - тема для отдельной книги объёмом в несколько сотен страниц (см. список литературы). Программы второго типа - это обычные программы MS-DOS, о которых мы говорили в предыдущих томах "Библиотеки системного программиста".

Мы займёмся программами последнего типа. Для составления этих программ вы можете использовать обычную технику, применяемую для программ реального режима MS-DOS (за исключением отладки - как и все программы, приведённые в этой книге, программы третьего типа не поддаются отладке стандартными для реального режима средствами).

Наша следующая глава - о WINDOWS и о тех возможностях, которые операционная система WINDOWS предоставляет программам, составленным в старом "стиле" MS-DOS.



Процессор i80286 может непосредственно адресовать


Процессор i80286 может непосредственно адресовать до 16 мегабайт физической памяти, однако реально компьютеры редко имеют оперативную память такого размера. Обычный размер оперативной памяти для IBM AT составляет 2-4 мегабайта (здесь имеется в виду расширенная память - Extended Memory).

Механизм виртуальной памяти, реализованный в процессоре i80286, позволяет организовать память большого размера (например, 16 мегабайт) с использованием относительно небольшой физической оперативной памяти и дисковой памяти.

Основная идея виртуальной памяти заключается в том, чтобы хранить (и обновлять) содержимое большой виртуальной памяти на диске, подкачивая отдельные участки виртуальной памяти в реальную оперативную память по необходимости.

Можно, например, хранить все используемые программой сегменты на диске, а в физическую память записывать только те сегменты, которые необходимы для выполнения программы в настоящий момент. Процесс "подкачки" сегментов с диска в память и их выгрузки после изменения на диск называется свопингом.

Операционная система должна вести учёт сегментов и знать, какие сегменты находятся в памяти, а какие - на диске. Процессор i80286 может оказать ей в этом существенную помощь. Последние два поля дескриптора, которые мы ещё не рассматривали - бит присутствия сегмента в памяти P и бит обращения к сегменту памяти A - предназначены для аппаратной реализации учёта сегментов.

Бит P должен быть установлен в 1 для тех сегментов, которые находятся в физической памяти. Сегменты, временно отсутствующие в памяти и находящиеся на диске, помечаются в дескрипторе битом P, сброшенным в 0.

Установкой и сбросом бита P занимается операционная система. А вот проверка этого бита - работа для процессора. Когда программа обращается к отсутствующему в физической памяти сегменту (загрузкой селектора в сегментный регистр), выполнение программы прерывается и управление передаётся операционной системе. Та, в свою очередь, подкачивает нужный сегмент в оперативную память и устанавливает для него бит P в 1, после чего работа программы возобновляется.
Время от времени операционная система должна находить сегменты, к которым было обращение, и в случае их изменения сбрасывать на диск. Кроме того, если для закачки нового сегмента в физической памяти недостаточно свободного места, можно выгрузить самые старые сегменты из физической памяти на диск и на их место загрузить новый сегмент.

Процессор может оказать помощь операционной системе в определении тех сегментов, к которым было обращение. Для этих сегментов бит обращения A устанавливается процессором в 1. Сбросить бит обращения можно только из программы, поэтому такая работа возлагается на саму операционную систему.

Реально описанная выше схема реализации виртуальной памяти со свопингом сегментов используется в операционной системе OS/2 версий от 1.0 до 1.3 включительно.

Однако у этой схемы есть один серьёзный недостаток. Так как все сегменты имеют разные размеры, и все они подкачиваются по очереди (и по несколько штук) в одну область физической памяти, возможно возникновение фрагментации физической памяти. Например, был удалён сегмент размером в 10 килобайт, и образовался свободный участок физической памяти такого же размера. Новый сегмент, который требуется закачать в память, имеет размер 15 килобайт, и он не поместится на место старого.

Операционная система может выполнить перемещение сегментов в физической памяти, изменив соответствующим образом 24-битовые базовые адреса сегментов в таблицах дескрипторов. После перемещения сегментов можно объединить все свободные участки памяти в один и использовать этот участок для загрузки нового сегмента. Так как программы защищённого режима не знают физических адресов памяти (они работают только с селекторами), перемещение сегментов никак не отразится на их работе.

Но на перемещение сегментов требуется время, и кроме того, часто нужен не весь сегмент, а только его небольшой участок. Процессор i80286 не предлагает никакого решения проблемы фрагментации физической памяти при свопинге сегментов, и не позволяет загружать сегмент по частям.

Все эти возможности имеются только в процессорах i80386 и i80486. Только эти процессоры позволяют эффективно реализовать механизм виртуальной памяти.


Виртуальные машины


Обычно мультизадачные операционные системы предоставляют для выполнения i8086-программ "виртуальные машины i8086". Эти виртуальные машины реализуются при помощи задач виртуального режима.

Операционная система может виртуализовать ресурсы компьютера - память, порты ввода/вывода, систему обработки прерываний. При этом программа, ориентированная на реальный режим MS-DOS получает в своё распоряжение ресурсы, которые она воспринимает как физические.

Виртуализация памяти заключается в преобразовании адреса формата реального режима <сегмент:смещение> в линейный, а затем и физический с использованием схемы преобразования адресов защищённого режима. В этом случае программе предоставляется 1 мегабайт памяти, который может быть расположен в любом месте физической памяти компьютера и может подвергаться свопингу на диск для реализации виртуальной оперативной памяти. С точки зрения программы, работающей на виртуальной машине, адреса отображаются на этот 1 мегебайт памяти так, как будто бы это была физическая память компьютера.

Виртуализация ввода/вывода основана на том, что такие команды, как IN, OUT, INS, OUTS, CLI, STI чувствительны к текущему уровню IOPL. Так как обычно виртуальная машина работает в непривилегированном третьем кольце защиты, выдача этих команд программой в виртуальном режиме приводит к исключению. Операционная система может эмулировать действие этих команд безопасным для себя образом, что однако может привести к существенному замедлению работы виртуальной машины.

Виртуализация прерываний основана на выходе процессора из виртуального режима и возврате в защищённый при возникновении прерываний. При возникновении прерывания управление получает обработчик защищённого режима, установленный операционной системой. Операционная система может эмулировать функции соответствующего обработчика реального режима, например, функции обработчика прерывания INT 21h MS-DOS или BIOS.



Возврат в реальный режим


Для того, чтобы вернуть процессор 80286 из защищённого режима в реальный, необходимо выполнить аппаратный сброс (отключение) процессора. Это можно сделать следующим образом:

mov ax, 0FEh ; команда отключения out 64h, ax

Перед выдачей команды отключения необходимо запомнить содержимое регистра SP, так как после передачи управления по адресу, записанному в области данных BIOS 0040h:0067h, регистры SS:SP будет указывать на стек BIOS.

После выдачи команды отключения надо подождать, когда произойдёт сброс процессора. Это можно сделать, выдавая в цикле команду HLT.

Вот фрагмент программы, возвращающий процессор в реальный режим:

; Запоминаем содержимое указателя стека, так как после ; сброса процессора оно будет потеряно mov [real_sp],sp ; Выполняем сброс процессора mov al,SHUT_DOWN out STATUS_PORT,al ; Ожидаем сброса процессора wait_reset: hlt jmp wait_reset

Далее необходимо восстановить содержимое сегментных регистров, записанное в оперативную память на этапе подготовки к переключению в защищённый режим, закрыть адресную линию A20 и размаскировать прерывания.

Для закрытия линии A20 можно воспользоваться следующей процедурой:

; ------------------------------------------------------------ ; Процедура закрывает адресную линию A20 ; ------------------------------------------------------------ PROC disable_a20 NEAR mov al,A20_PORT out STATUS_PORT,al mov al,A20_OFF out KBD_PORT_A,al ret ENDP disable_a20

Следующая последовательность команд размаскирует все прерывания:

mov ax,000dh ; разрешаем немаскируемые прерывания out CMOS_PORT,al in al,INT_MASK_PORT ; разрешаем маскируемые прерывания and al,0 out INT_MASK_PORT,al sti

Теперь, когда мы знаем всё, что нужно для переключения процессора в защищённый режим и возврата в реальный режим, можно приступить к практической работе в защищённом режиме.



Книга представляет собой простое введение


Книга представляет собой простое введение в программирование для защищённого режима широко распространённых процессоров Intel 80286/80386/80486. В отличие от зарубежных авторов аналогичных книг, переведённых на русский язык, мы сконцентрировали своё внимание на практической стороне использования защищённого режима работы процессоров. В книге приведено большое количество программ, составленных на языках Си и ассемблера, поэтому вы сразу сможете проверить полученные вами знания на практике. Все описанные программы записаны на дискете, которую вы можете купить вместе с книгой. Программы подготовлены в среде Borland C 3.0, хотя можно воспользоваться и Borland C 2.0 или Borland C 3.1. Приведённые примеры нетрудно адаптировать для транслятора Microsoft Quick C.
Наша книга не похожа на справочник по процессорам i80286 и i80386 - подобной литературы издано уже достаточно много и она легко доступна. К тому же для составления работоспособных программ защищённого режима недостаточно владеть только информацией о работе процессора, нужны ещё знания некоторых аппаратных особенностей персонального компьютера и особенностей используемых операционных систем. Поэтому мы не стремились описать тонкости работы всех команд процессора в защищённом режиме - всё это есть в справочниках. Вместо этого мы приведём сведения, которые позволят вам сразу приступить к составлению собственных программ, работающих в защищённом режиме, в частности, драйверов аппаратуры и мультизадачных мониторов, программ, активно работающих с большими массивами данных. За основу вы можете взять примеры программ, приведённых в книге.
Для тех, кто ещё не знаком с защищённым режимом работы, скажем, что этот режим является основным и естественным режимом работы процессоров i80286, i80386 и i80486. Только в защищённом режиме полностью реализуются все возможности, заложенные в архитектуру процессоров. Защищённый режим работы используется во многих программных продуктах, таких как операционные системы UNIX, XENIX, OS/2, PC-MOS, QNX, Desk View, сетевых операционных системах NOVELL, в операционной системе Microsoft WINDOWS, в драйверах расширенной памяти EMM386 и QEMM, в СУБД ORACLE.
Хорошо известная система разработки программного обеспечения Borland C также может работать в защищённом режиме, при этом её производительность заметно возрастает. Перспективная операционная система Microsoft WINDOWS NT также использует защищённый режим работы процессора. Новая версия 6.0 операционной системы MS-DOS будет активно использовать защищённый режим работы процессора.
В настоящее время в области программного обеспечения отчётливо прослеживается тенденция ориентации на оболочку Microsoft WINDOWS, пользующейся огромной популярностью во всём мире. Однако не все знают, что Microsoft WINDOWS - это не только прекрасная графика, продуманный дизайн и удобство в работе. Все программы, разрабатываемые специально для WINDOWS, работают в защищённом режиме и они используют всю мощь современных процессоров и все возможности компьютера (в отличии от программ, ориентированных только на MS-DOS).
Поэтому если вы планируете составлять программы для WINDOWS, вам совершенно необходимо знать особенности работы процессора в защищённом режиме. Особенно, если вы будете разрабатывать драйверы или другие программы, работающие с аппаратурой, либо если ваши программы будут обратаывать большие массивы данных (порядка нескольких мегабайт или даже несколько десятков мегабайт). Наша книга может стать для вас первым шагом к программированию для мультизадачных операционных систем, таких как WINDOWS, OS/2 или UNIX.
Наиболее очевидны преимущества защищённого режима для решения задач, связанных с обработкой больших массивов данных. Например, для обработки графических данных, полученных со сканера при количестве градаций серого, равном 256, может потребоваться несколько десятков мегабайт оперативной памяти. Механизм так называемой виртуальной памяти, реализованный в процессорах i80386 и i80486 (и работающий только в защищённом режиме) позволяет предоставить программам практически неограниченный объём виртуальной оперативной памяти, реализованной с использованием магнитного диска.
Даже если вы составляете программы, ориентированные только на MS-DOS, вам всё равно придётся столкнуться с защищённым режимом работы процессора.


Это связано с тем, что для увеличения доступного программам объёма оперативной памяти часто используются драйверы расширенной памяти EMM386 или QEMM. Если установлен один из этих драйверов, процессор работает уже не в реальном режиме, а в так называемом режиме "виртуального процессора 8086" (в который он попадает из защищённого режима). Если ваша программа (предназначенная для работы в MS-DOS) запущена в среде WINDOWS в режиме "386 Enhanced Mode", она также будет выполняться процессором в режиме "виртуального процессора 8086".
Если при разработке программы, ориентированной на MS-DOS, не принимать во внимание возможность работы на "виртуальном процессоре", она может оказаться несовместимой с WINDOWS или драйверами EMM386 или QEMM.
Таким образом, разрабатывая программу для MS-DOS, вам следует продумать вопросы совместимости с драйверами расширенной памяти и оболочкой WINDOWS. А для этого вы должны знать особенности защищённого режима и режима виртуального процессора 8086.
Кроме обеспечения корректной работы в виртуальном режиме процессора, знание особенностей защищённого режима позволит вам наиболее полно и эффективно использовать такой ресурс компьютера, как расширенная оперативная память.
О содержании книги.
В первой главе книги мы приводим основные теоретические сведения о работе процессора в защищённом режиме. При этом мы не претендуем на полноту описания возможностей процессора, так как на этом этапе наша цель - дать минимум знаний, необходимых для того, чтобы приступить к составлению первых программ, работающих в защищённом режиме. В списке литературы есть ссылки на справочники и другие книги, в которых описана работа всех команд в защищённом режиме и приведены форматы всех регистров. Мы же сконцентрируем внимание на практическом использовании защищённого режима в компьютере IBM AT и других компьютерах, выполненных на базе процессоров i80286, i80386, i80486.
Вторая глава книги содержит простой пример программы, переводящей процессор i80286 в защищённый режим и возвращающий его обратно.


На примере этой программы вы сможете получить представление о том, как выполняются процедуры входа процессора в защищённый режим работы и возврата в реальный режим работы.
Третья глава посвящена прерываниям и исключениям в защищённом режиме. Вы поймете разницу между механизмами прерываний реального и защищённого режимов, на конкретном примере программы научитесь обрабатывать аппаратные и программные прерывания, а также исключения в защищённом режиме. Будут рассмотрены вопросы инициализации контроллера прерываний для работы в защищённом режиме.
Четвёртая глава содержит сведения об использовании мультизадачных возможностей, реализованных в процессоре i80286 аппаратно. Мы приведём пример простого мультизадачного монитора и на его примере покажем, как можно организовать разделение процессорного времени между одновременно выполняющимися задачами. Кроме того, мы рассмотрим вопросы синхронизации выполнения задач и использование семафоров.
Пятая глава посвящена особенностям процессоров i80386 и i80486. Мы расскажем об уникальной схеме преобразования адресов, позволяющей программам адресовать практически неограниченный объём памяти. Будут рассмотрены особенности обработки прерываний и мультизадачности, а также режим виртуального процессора 8086.
В шестой главе описана иерархия средств, доступных обычным DOS-программам для работы в защищённом режиме.
В этой главе описаны средства BIOS, которые программа может использовать для переключения в защищённый режим и для доступа к расширенной памяти, интерфейсы VCPI и DPMI, а также расширители DOS для работы в защищённом режиме - DOS-экстендеры.
Интерфейс VCPI (Virtual Control Programm Interface), облегчает составление программ, использующих защищённый режим работы процессоров. Этот интерфейс доступен в тех случаях, когда в компьютере установлен процессор i80386 или i80486 и используется драйвер расширенной памяти, аналогичный EMM386 или QEMM.
Интерфейс DPMI представляет собой интерфейс более высокого уровня, чем VCPI. Этот интерфейс доступен в среде WINDOWS версий 3.0 или 3.1, работающей в режиме "386 Enhanced Mode".
Седьмая глава посвящена операционной системе Microsoft WINDOWS. Эта операционная система позволяет использовать не только программы, разработанные специально для неё, но и обычные DOS-программы. При работе WINDOWS в расширенном режиме "386 Enhanced Mode" DOS-программы получают дополнительные возможности. Они могут переключаться в защищённый режим и обратно (в режим виртуального процессора 8086), могут обмениваться информацией с программами WINDOWS с помощью механизма CLIPBOARD (стандартного для программ WINDOWS механизма обена информацией).
Мы рассмотрим также некоторые вопросы совместной работы резидентных программ DOS и драйверов DOS с операционной системой WINDOWS.
Авторы выражают благодарность Максиму Синёву за ценные рекомендации по содержанию книги и за существенную помощь, оказанную при работе над разделом, посвящённым DOS-экстендерам. Мы также благодарим всех сотрудников АО Диалог-МИФИ, принимавших участие в подготовке книги к изданию.

Вызов процедуры реального режима, заканчивающейся командой RET FAR


Эта процедура позволяет выполнить вызов процедуры, предназначенной для работы в реальном режиме и возвращающей управление при помощи команды RET FAR.

Регистры на входе AX 0301h BH Байт флагов. Если установлен в единицу бит 0, выполняется сброс контроллера и линии A20. Остальные биты зарезервированы и должны быть сброшены в нуль. CX Количество слов, которые должны быть скопированы из стека защищённого режима в стек реального режима. ES:(E)DI Адрес управляющей структуры для вызова процедуры реального режима. Регистры на выходе: CARRY 0, если функция выполнилась без ошибки, 1, если произошла ошибка. ES:(E)DI Адрес модифицированной (в результате выполнения процедуры реального режима) управляющей структуры в формате защищённого режима.

Эта процедура позволяет выполнить вызов процедуры, предназначенной для работы в реальном режиме и возвращающей управление при помощи команды IRET.

Регистры на входе AX 0302h BH Байт флагов. Если установлен в единицу бит 0, выполняется сброс контроллера и линии A20. Остальные биты зарезервированы и должны быть сброшены в нуль. CX Количество слов, которые должны быть скопированы из стека защищённого режима в стек реального режима. ES:(E)DI Адрес управляющей структуры для вызова процедуры реального режима. Регистры на выходе: CARRY 0, если функция выполнилась без ошибки, 1, если произошла ошибка. ES:(E)DI Адрес модифицированной (в результате выполнения процедуры реального режима) управляющей структуры в формате защищённого режима.

Задача и сегмент состояния задачи


До сих пор мы говорили о параллельной работе программ. На самом деле каждая отдельная программа может состоять из нескольких частей, работающих параллельно. Например, текстовый процессор может содержать программные модули, которые параллельно с редактированием текста выполняют нумерацию страниц, печать текста или автоматическое сохранение его на диске. Каждую такую часть программы мы будем называть задачей.

Исходя из этой терминологии в мультизадачной среде одновременно выполняется много задач, принадлежащих разным программам. Причём количество задач больше или равно количеству выполняющихся программ.

Как правило, квантование времени процессора выполняется на уровне задач, а не на уровне программ. По прерыванию таймера процессор переключается от одной задачи к другой, и таким образом осуществляется параллельное выполнение программ.

Для хранения контекста неактивной в настоящей момент задачи процессор i80286 использует специальную область памяти, называемую сегментом состояния задачи TSS (Task State Segment). Формат TSS представлен на рис. 14.

Рис. 14. Формат сегмента состояния задачи TSS.

Сегмент TSS адресуется процессором при помощи 16-битного регистра TR (Task Register), содержащего селектор дескриптора TSS, находящегося в глобальной таблице дескрипторов GDT (рис. 15).

Рис. 15. Дескриптор сегмента состояния задачи TSS.

Поле доступа содержит бит B - бит занятости. Если задача активна, этот бит устанавливается процессором в 1.

Операционная система для каждой задачи создаёт свой TSS. Перед тем как переключиться на выполнение новой задачи, процессор сохраняет контекст старой задачи в её сегменте TSS.

Что же конкретно записывается в TSS при переключении задачи?

Записывается содержимое регистров общего назначения AX, BX, CX, DX, регистров SP, BP, SI, DI, сегментных регистров ES, CS, SS, DS, содержимое указателя команд IP и регистра флажков FLAGS. Кроме того, сохраняется содержимое регистра LDTR, определяющего локальное адресное пространство задачи.

Дополнительно при переключении задачи в область TSS со смещением 44 операционная система может записать любую информацию, которая относится к данной задаче.
Эта область процессором не считывается и никак не модифицируется.
Поле Link представляет собой поле обратной связи и используется для организации вложенных вызовов задач. Это поле мы рассмотрим в следующем разделе.
Поля Stack 0, Stack 1, Stack 2 хранят логические адреса (селектор:смещение) отдельных для каждого кольца защиты стеков. Эти поля используются при межсегментных вызовах через вентили вызова.
Для обеспечения защиты данных процессор назначает отдельные стеки для каждого кольца защиты. Когда задача вызывает подпрограмму из другого кольца через вентиль вызова, процессор вначале загружает указатель стека SS:SP адресом нового стека, взятого из соответствующего поля TSS.
Затем в новый стек копируется содержимое регистров SS:SP задачи (т.е. адрес вершины старого стека задачи). После этого в новый стек копируются параметры, количество которых задано в вентиле вызова и адрес возврата.
Таким образом, при вызове привилегированного модуля через вентиль вызова менее привилегированная программа не может передать в стеке больше параметров, чем это определено операционной системой для данного модуля.
Включение адресов стеков в TSS позволяет разделить стеки задач и обеспечивает их автоматическое переключение при переключении задач.

Зафиксировать линейную область памяти


Эта функция фиксирует в памяти область, задаваемую линейным адресом. Для зафиксированной области памяти не выполняется свопинг страниц.

Регистры на входе AX 0600h BX:CX Начальный линейный адрес фиксируемого участка памяти. SI:DI Размер фиксируемого блока памяти в байтах. Регистры на выходе: CARRY 0, если функция выполнилась без ошибки, 1, если произошла ошибка.

Закрыть CLIPBOARD


После выполнения записи необходимо закрыть CLIPBOARD (точно также, как вы закрываете файл). Для того, чтобы закрыть CLIPBOARD, вы можете использовать следующую функцию:

Регистры на входе AX 1708h Регистры на выходе: AX 0, если произошла ошибка; не равно 0, если операция успешно выполнена.

Записать данные в CLIPBOARD


С помощью этой функции DOS-программа может выполнить запись данных в WINDOWS CLIPBOARD.

Регистры на входе AX 1703h DX Формат данных, записываемых в CLIPBOARD: 01h текст; 02h графика в формате bitmap; 03h графика в формате metafile picture; 04h SYLK; 05h DIF; 06h графика в формате TIFF; 07h текст в кодировке OEM. ES:BX Указатель на записываемые данные SI:CX Длина записываемых данных Регистры на выходе: AX 0, если при выполнении операции произошла ошибка; не равно 0, если операция успешно выполнена.

С помощью этой функции можно записывать как текстовые, так и графические данные.

Операционная система WINDOWS использует отличную от принятой в DOS кодировку символов. Кодировка WINDOWS называется ANSI-кодировкой, кодировка DOS - OEM-кодировкой. Если при записи текстовых данных в CLIPBOARD вы зададите кодировку OEM (записав в регистр DX значение 7), одновременно с записью данных будет автоматически выполняться перекодировка из OEM в ANSI.

Пользуясь следующей таблицей, вы можете записывать в CLIPBOARD графические данные в формате bitmap:

Таблица 15. Формат CLIPBOARD для BITMAP-файлов.

Смещение, размер Описание
00h (2) тип(0000h)
02h (2) ширина bitmap в пикселах
04h (2) высота bitmap в пикселах
06h (2) количество байт на строку
08h (1) количество цветовых планов
09h (1) количество цветовых битов в пикселе
0Ah (4) указатель на начало данных
0Eh (2) ширина в 0.1 mm
10h (2) высота в 0.1 mm
12h графические данные

Исчерпывающую информацию о форматах графических файлов WINDOWS вы можете получить из документации, поставляемой Microsoft для разработчиков приложений.



Запретить виртуальные прерывания


Функция сбрасывает флаг виртуального прерывания и возвращает предыдущее состояние этого флага.

Регистры на входе AX 0900h Регистры на выходе: CARRY 0 AL 0, если виртуальные прерывания были запрещены, 1, если виртуальные прерывания были разрешены.

Зарезервированные функции


Функции 0004h и 0005h в спецификации DPMI версии 0.9 зарезервированы и не должны вызываться из вашей программы.


Функции 0700h и 0701h зарезервированы и не должны вызываться вашей программой.



Защита программ от отладки


Если вы разрабатываете программное обеспечение, защищённое от несанкционированного копирования, вам необходимо позаботиться о том, чтобы потенциальные взломщики ("кракеры" и "хакеры") не смогли выполнить программу инсталляции под управлением отладчика. Если взломщик сможет "подглядеть" за работой вашей программы, он рано или поздно разгадает ваш замысел и сведёт на нет все ваши усилия по защите программы от копирования.

В третьей книге первого тома "Библиотеки системного программиста" мы излагали некоторые соображения по организации защиты программ от несанкционированного копирования. Мы, в частности, рассказали о некоторых методах защиты программ от отладки - использование таймера, внутренней очереди команд процессора и другие.

Защищённый режим работы процессора открывает перед вами новую возможность. Возьмите любую программу, приведённую в этой книге и попытайтесь запустить её под управлением какого-либо отладчика (например, попробуйте Turbo Debugger или Code View). Всё будет хорошо до тех пор, пока ваша программа не попытается загрузить регистр IDTR при помощи команды LIDT. После выполнения этой команды отладчик зависает и единственное средство вновь оживить компьютер - нажать на кнопку сброса, расположенную на системном блоке.

Причина очевидна - изменились расположение и формат дескрипторной таблицы прерываний. Она подготовлена для работы в защищённом режиме, но отладчик работает в реальном режиме. Поэтому обработка всех прерываний, в том числе и от клавиатуры, невозможна.

Идея использования защищённого режима работы процессора при создании программ, защищённых от несанкционированного копирования, очевидна. Используя примеры программ, приведённые в книге, вы сможете во время работы программы инсталляции перевести процессор в защищённый режим и выполнить часть работы по инсталляции в защищённом режиме.

Например, перед переключением в защищённый режим вы можете подготовить в памяти массив контрольной информации. Расшифровка и проверка этого массива, а также запись данных в нестандартные сектора инсталляционной дискеты могут выполняться в защищённом режиме.
При этом, пользуясь обычными отладчиками, невозможно определить действия, выполняемые в защищённом режиме. Особенно, если участок программы, работающий в защищённом режиме, зашифрован.

Далее процессор можно вернуть в реальный режим и продолжить процесс инсталляции.

Находясь в защищённом режиме, вы можете читать и писать сектора дискеты только используя уровень портов ввода/вывода контроллера флоппи-диска. Программирование контроллера флоппи-диска описано в третьей книге первого тома "Библиотеки системного программиста". Обрабатывать прерывания в защищённом режиме вы уже умеете.

Очевидный недостаток применения защищённого режима при организации защиты от копирования заключается в необходимости использования процессоров i80286, i80386 или i80486. Это означает, что указанный метод непригоден для компьютеров IBM PC/XT, использующих процессор i8086 или i8088.

Однако в последнее время большинство программных комплексов требует наличия в компьютере по крайней мере процессора i80286, поэтому указанным недостатком можно пренебречь. К тому же следует учесть резкое увеличение стойкости инсталлятора, работающего в защищённом режиме, к попыткам взлома.


Мы уже говорили вам, что


Мы уже говорили вам, что операционная система MS-DOS, использующая реальный режим работы процессора, не защищена от прикладных программ. Подсистема управления памятью MS-DOS основана на разбиении всей памяти на участки, в начале которых помещаются блоки управления памятью MCB (Memory Control Block). При запуске программы MS-DOS выделяет ей необходимое количество блоков и загружает соответствующим образом сегментные регистры.

Если программа не изменяет содержимое сегментных регистров, она будет работать только с теми сегментами памяти, которые выделены ей операционной системой. Однако ничто не может помешать любой программе загрузить в сегментные регистры любое значение, в том числе адреса сегментов самой операционной системы.

Поэтому, строго говоря, MS-DOS не имеет сколько-нибудь надёжного механизма управления памятью - всё основано на выполнении программами "джентльменского соглашения" о неразрушении операционной системы.

Для повышения надёжности работы всей системы в целом необходимо запретить обычным пользовательским программам модифицировать области памяти, принадлежащие операционной системе. Если система работает одновременно с несколькими пользователями (мультипользовательская операционная система), необходимо также закрыть программам пользователя доступ к памяти, распределённой другим пользователям. Иначе говоря, необходимо разделить адресные пространства операционной системы от пользовательских программ с одной стороны, и адресные пространства отдельных пользователей друг от друга с другой стороны.

Такие операционные системы, как WINDOWS и OS/2, являются однопользовательскими, но многозадачными системами. Пользователь может запустить одновременно несколько программ. В этом случае было бы желательно разделить адресные пространства этих программ с целью исключения их влияния друг на друга (и, разумеется, на операционную систему). Аварийное завершение одной запущенной задачи не должно вызывать аварийного завершения остальных задач или, тем более, всей операционной системы.



Кроме того, необходимо разрешить модулям операционной системы доступ к памяти всех запущенных программ для выполнения эффективного управления работой всей системы.

Отсюда с одной стороны, вытекает идея наделения программ операционной системы некоторыми привилегиями по сравнению с пользовательскими программами, с другой стороны - идея наделения отдельных участков памяти средствами защиты от доступа к ним со стороны непривилегированных программ.

Многие мини-ЭВМ и большие ЭВМ реализуют схему "супервизор-пользователь". Операционная система работает в режиме "супервизор" и ей доступны все ресурсы компьютера - вся память, любые команды процессора и т.д. Запущенные программы работают в режиме "пользователь" и имеют доступ только к тем участкам памяти, которые выделены им операционной системой. Кроме того, эти программы ограничены в использовании некоторых команд процессора (например, команд ввода/вывода).

Для предотвращения доступа программам, работающим в режиме "пользователь" к чужим блокам памяти используются так называемые "ключи" (это просто целые числа). Отдельным блокам памяти операционная система назначает свои "ключи". Кроме того, запущенные программы также снабжаются "ключами". Программа может иметь доступ только к таким блокам памяти, к которым подходит "ключ", имеющийся в распоряжении программы.

Такая схема не обеспечивает полного изолирования адресных пространств отдельных задач. Адресное пространство здесь одно, но для обычных программ разрешён доступ только к своему участку памяти в соответствии с имеющимся ключом. Однако в системе может работать несколько программ с одинаковым ключом памяти!