Раздел 2 - Входим в защищенный режим

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

2.1. Подготовка к переключению в защищённый режим

2.2. Переключение в защищённый режим

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

2.4. Пример простой программы переключения режима

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

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

2.1. Подготовка к переключению в защищённый режим

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

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

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

После выполнения сброса (или после отключения) процессор переходит в реальный режим и управление передаётся в BIOS. BIOS анализирует содержимое ячейки CMOS-памяти с адресом 0Fh - байта состояния отключения. Дальнейшие действия определяются содержимым этой ячейки.

Байт состояния отключения 0Fh используется BIOS для определения способа возврата из защищённого режима в реальный после аппаратного сброса. В таблице 3 перечислены возможные значения для байта состояния отключения.

Таблица 3. Значения байта состояния отключения.

ЗначениеПричина отключения
0Программный сброс при нажатии комбинации клавиш CTRL-ALT-DEL или неожиданный сброс. Выполняется обычный перезапуск системы, но процедуры тестирования при включении питания не выполняются.

  • 1
  • Сброс после определения объёма памяти.
  • 2
  • Сброс после тестирования памяти.
  • 3
  • Сброс после обнаружения ошибки в памяти (контроль чётности).
  • 4
  • Сброс с запросом перезагрузки.
  • 5
  • После сброса перезапускается контроллер прерываний, затем управление передаётся по адресу, который находится в области данных BIOS 0040h:0067h.
  • 6,7,8Сброс после выполнения теста работы процессора в защищённом режиме.
    9Сброс после выполнения пересылки блока памяти из основной памяти в расширенную.
    0AhПосле сброса управление немедленно передаётся по адресу, взятому из области данных BIOS 0040h:0067h.

    Для обеспечения возврата в реальный режим после сброса по адресу, записанному в области данных BIOS 0040h:0067h можно использовать байты 5 и 0Ah.

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

    Если же вы не используете прерывания и, соответственно, не перепрограммируете контроллер прерываний, можно использовать значение 0Ah, при этом после сброса управление будет сразу передано по адресу, взятому из области данных BIOS 0040h:0067h. В этом случае затраченное на возврат в реальный режим время будет меньше.

    В следующем фрагменте программы мы записываем в ячейку CMOS-памяти с адресом 0Fh значение 5.

    Напомним, что для записи числа в ячейку CMOS-памяти необходимо вначале в порт с адресом 70h записать номер нужной ячейки, а затем в порт 71h - записываемые данные.

    Не удивляйтесь, что в этом фрагменте программы вместо ячейки 0Fh указано значение 8Fh - это не ошибка. Напомним, что единственный способ замаскировать немаскируемые прерывания в компьютере IBM AT - это записать в порт 70h байт, в котором старший бит установлен в 1. Поэтому наш фрагмент программы не только записывает байт состояния отключения, но и маскирует немаскируемые прерывания (!). Нам необходимо также замаскировать обычные прерывания, поэтому мы выдаём команду CLI.

            cli
            mov     al,8f
            out     CMOS_PORT,al
            jmp     next1           ; небольшая задержка
    next1:
            mov     al,5
            out     CMOS_PORT+1,al
    
    
    
    
    

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

    Приведём процедуру, которую можно использовать для этой цели:

    ; ------------------------------------------------------------
    ; Процедура открывает адресную линию 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_ON клавиатурному процессору 8042, к которому подключены схемы управления адресной линией A20. После начального сброса линия A20 закрыта, и расширенная память за границами первого мегабайта недоступна.

    Следующий этап - запоминание содержимого сегментных регистров, которые будут нужны при возврате в реальный режим. Это сегментные регистры SS и ES:

            mov     [real_ss],ss    ; запоминаем указатель стека
            mov     [real_es],es    ; для реального режима
    
    
    
    

    На последнем перед переключением в защищённый режим этапе мы загружаем регистр GDTR адресом подготовленной заранее GDT:

    lgdt [QWORD gdt_gdt]
    
    
    
    

    Всё! Можно переключаться в защищённый режим!

    2.2. Переключение в защищённый режим

    Это самый простой этап. Для перевода процессора i80286 из реального режима в защищённый можно использовать специальную команду LMSW, загружающую регистр состояния процессора (Mashine Status Word). Младший бит этого регистра указывает режим работы процессора. Значение, равное 0, соответствует реальному режиму работы, а значение 1 - защищённому.

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

            mov     ax, 1
            lmsw    ax
    
    
    
    

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

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

    Для того, чтобы вернуть процессор 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
    
    
    
    

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

    2.4. Пример простой программы переключения режима

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

    Программа подготовлена с помощью транслятора Borland Turbo Assembler и использует режим IDEAL. Для её трансляции был использован следующий командный файл:

    tasm %1.asm /l /zi
    tlink %1.obj /v
    
    
    
    

    Вы можете запускать эту программу на любой машине, совместимой с IBM AT и оборудованной процессорами i80286, i80386, i80486. Но вы не должны запускать эту программу, если у вас компьютер на базе процессоров i80386 или i80486 и активны драйверы QEMM, EMM386 - отключите эти драйверы. Кроме того, эту программу нельзя запускать на виртуальной машине в среде Microsoft WINDOWS в режиме Enchanced Mode или на виртуальной машине в среде OS/2 версии 2.0.

    Это связано с тем, что в перечисленных выше случаях процессор работает не в реальном режиме, а в так называемом режиме виртуального процессора 8086. Этот режим возможен только для процессоров i80386 или i80486.

    Кроме того, не следует запускать эту программу на компьютерах серии PS/2, так как в них используется другой способ управления линией A20 и другой способ сброса процессора для возврата в реальный режим.

    Вы можете использовать эту программу для первых экспериментов с защищённым режимом.

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

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

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

    Если ваша программа составлена на языках высокого уровня (Си или Паскаль), ей недоступна практически вся библиотека стандартных функций. Особенно это касается функций ввода/вывода и управления памятью. Причина очевидна - большинство функций стандартных библиотек трансляторов вызывает те или иные прерывания BIOS или DOS.

    Это ограничение связано с механизмом преобразования логического адреса в физический.

    Итак, наша первая программа для защищённого режима процессора 80286:

    Листинг 1. Демонстрация переключения в защищённый режим
    и возврата обратно в реальный режим

    -----------------------------------------------------------
    
    
    IDEAL
    RADIX   16
    P286
    
    ; Используем модель памяти LARGE, при этом мы организуем
    ; несколько отдельных сегментов и для каждого сегмента
    ; создадим дескриптор в таблице GDT.
    
    MODEL   LARGE
    
    ; ------------------------------------------------------------
    ; Определения структур данных и констант
    ; ------------------------------------------------------------
    
    STRUC   desc_struc              ; структура дескриптора
            limit   dw      0       ; предел
            base_l  dw      0       ; мл. слово физического адреса
            base_h  db      0       ; ст. байт физического адреса
            access  db      0       ; байт доступа
            rsrv    dw      0       ; зарезервировано
    ENDS    desc_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 ; разрешена запись
    
    ; Типы сегментов
    
    ; сегмент данных
    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
    
    
    ; Константы
    
    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     ; ст. байт физического адреса
                                    ;  сегмента видеопамяти
    
    ; Селекторы, определённые в таблице 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)
    
    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     ; порт для маскирования прерываний
    
    STACK   STACK_SIZE      ; сегмент стека
    
    DATASEG                 ; начало сегмента данных
    
    DSEG_BEG        =       THIS WORD
    
    ; Память для хранения регистров SS, SP, ES. Содержимое
    ; этих регистров будет записано здесь перед входом в
    ; защищённый режим и восстановлено отсюда после возврата
    ; из защищённого режима в реальный.
    
            real_ss dw      ?
            real_sp dw      ?
            real_es dw      ?
    
    ; Глобальная таблица дескрипторов GDT,
    ; содержит следующие дескрипторы:
    ;
    ;       gdt_0   - дескриптор для пустого селектора
    ;       gdt_gdt - дескриптор для GDT
    ;       gdt_ds  - дескриптор для сегмента, адресуемого DS
    ;       gdt_cs  - дескриптор для сегмента кода
    ;       gdt_ss  - дескриптор для сегмента стека
    ;       gdt_bio         - дескриптор для области данных BIOS
    ;       gdt_crt         - дескриптор для видеопамяти цветного дисплея
    ;       gdt_mda         - дескриптор для видеопамяти монохромного дисплея
    
    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_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) ; размер таблицы дескрипторов
    
    CODESEG         ; сегмент кода
    
    PROC    start
    
    ; Инициализируем регистр сегмента данных
    ; для реального режима
    
            mov     ax,DGROUP
            mov     ds,ax
    
    ; Определяем базовый адрес видеопамяти
    
            call    set_crt_base
    
    ; Стираем экран дисплея (устанавливаем серый фон)
    
            mov     bh, 77h
            call    clrscr
    
    ; Выполняем все подготовительные действия для перехода
    ; в защищённый режим и обеспечения возможности возврата
    ; в реальный режим
    
            call    init_protected_mode
    
    ; Переключаемся в защищённый режим
    
            call    set_protected_mode
    
    ; --------- * Программа работает в защищённом режиме! * ---------
    
            call    write_hello_msg ; выводим сообщение на экран
            call    pause           ; ждём некоторое время
    
    ; Возвращаемся в реальный режим
    
            call    set_real_mode
    
    ; --------- * Программа работает в реальном режиме! * ---------
    
    ; Стираем экран и возвращаемся в DOS
            mov     bh, 07h
            call    clrscr
            mov     ah,4Ch
            int     21h
    
    ENDP    start
    
    
    ; ------------------------------------------------------------
    ; Макрокоманда для записи в дескриптор 24-битового
    ; базового адреса сегмента
    ; ------------------------------------------------------------
    
    MACRO setgdtentry
            mov     [(desc_struc bx).base_l],ax
            mov     [(desc_struc bx).base_h],dl
    ENDM
    
    ; ------------------------------------------------------------
    ; Процедура подготовки процессора к переходу в защищённый
    ; режим с последующим возвратом в реальный режим
    ; ------------------------------------------------------------
    
    PROC    init_protected_mode     NEAR
    
    ; Заполняем глобальную таблицу дескрипторов GDT
    
    ; Вычисляем 24-битовый базовый адрес сегмента данных
    
            mov     ax,DGROUP
            mov     dl,ah
            shr     dl,4
            shl     ax,4
    
    ; Регистры dl:ax содержат базовый адрес, сохраняем его в di:si
    
            mov     si,ax
            mov     di,dx
    
    ; Подготавливаем дескриптор для GDT
    
            add     ax,OFFSET gdtr
            adc     dl,0
            mov     bx,OFFSET gdt_gdt
            setgdtentry
    
    ; Подготавливаем дескриптор для сегмента ds
    
            mov     bx,OFFSET gdt_ds
            mov     ax,si
            mov     dx,di
            setgdtentry
    
    ; Подготавливаем дескриптор для сегмента cs
    
            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
    
    ; Записываем адрес возврата в реальный режим в область
    ; данных BIOS по адресу 0040h:0067h
    
            push    ds
            mov     ax,40
            mov     ds,ax
            mov     [WORD 67],OFFSET shutdown_return
            mov     [WORD 69],cs
            pop     ds
    
    ; Маскируем все прерывания, в том числе немаскируемые.
    ; Записываем в CMOS-память в ячейку 0Fh код 5,
    ; этот код обеспечит после выполнения сброса процессора
    ; передачу управления по адресу, подготовленному нами
    ; в области данных BIOS по адресу 0040h:0067h.
    ; Для того, чтобы немаскируемые прерывания были запрещены,
    ; устанавливаем в 1 старший бит при определении ячейки CMOS.
    
            cli
            mov     al,8f
            out     CMOS_PORT,al
            jmp     next1           ; небольшая задержка
    next1:
    
            mov     al,5
            out     CMOS_PORT+1,al  ; код возврата
    
            ret
    
    ENDP    init_protected_mode
    
    ; ------------------------------------------------------------
    ; Процедура переключает процессор в защищённый режим
    ; ------------------------------------------------------------
    
    PROC    set_protected_mode      NEAR
    
            mov     ax,[rl_crt]     ; записываем в es сегментный
            mov     es,ax           ; адрес видеопамяти
    
            call    enable_a20      ; открываем адресную линию A20
    
            mov     [real_ss],ss    ; запоминаем указатель стека
            mov     [real_es],es    ; для реального режима
    
    ; Загружаем регистр GDTR
    
            lgdt    [QWORD gdt_gdt]
    
    ; Устанавливаем защищённый режим работы процессора
    
            mov     ax,VIRTUAL_MODE
            lmsw    ax
    
    ; Мы находимся в защищённом режиме
    
    ; Очищаем внутреннюю очередь команд процессора
    ; Выполняем команду межсегментного прерхода,
    ; в качестве селектора указываем селектор текущего
    ; сегмента кода, в качестве смещения - метку flush
    
    ;       jmp     far flush
            db      0ea
            dw      OFFSET flush
            dw      CS_DESCR
    
    LABEL   flush   FAR
    
    ; Загружаем сегментные регистры SS и DS селекторами
    
            mov     ax,SS_DESCR
            mov     ss,ax
            mov     ax,DS_DESCR
            mov     ds,ax
            ret
    
    ENDP    set_protected_mode
    
    ; ------------------------------------------------------------
    ; Процедура возвращает процессор в реальный режим
    ; ------------------------------------------------------------
    
    PROC    set_real_mode   NEAR
    
    ; Запоминаем содержимое указателя стека, так как после
    ; сброса процессора оно будет потеряно
    
            mov     [real_sp],sp
    
    ; Выполняем сброс процессора
    
            mov     al,SHUT_DOWN
            out     STATUS_PORT,al
    
    ; Ожидаем сброса процессора
    
    wait_reset:
            hlt
            jmp     wait_reset
    
    ; ------->> В это место мы попадём после сброса процессора,
    ; теперь мы снова в реальном режиме
    
    LABEL   shutdown_return FAR
    
    ; Инициализируем ds адресом сегмента данных
    
            mov     ax,DGROUP
            mov     ds,ax
            assume  ds:DGROUP
    
    ; Восстанавливаем указатель стека
    
            mov     ss,[real_ss]
            mov     sp,[real_sp]
    
    ; Восстанавливаем содержимое регистра es
    
            mov     es,[real_es]
    
    ; Закрываем адресную линию A20
    
            call    disable_a20
    
    ; Разрешаем все прерывания
    
            mov     ax,000dh        ; разрешаем немаскируемые прерывания
            out     CMOS_PORT,al
    
            in      al,INT_MASK_PORT ; разрешаем маскируемые прерывания
            and     al,0
            out     INT_MASK_PORT,al
            sti
    
            ret
    ENDP    set_real_mode
    
    ; ------------------------------------------------------------
    ; Процедура открывает адресную линию 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
    
    ; ------------------------------------------------------------
    ; Процедура выполняет небольшую временную задержку
    ; ------------------------------------------------------------
    
    PROC    pause           NEAR
            push    cx
            mov     cx,50
    ploop0:
            push    cx
            xor     cx,cx
    ploop1:
            loop    ploop1
            pop     cx
            loop    ploop0
    
            pop     cx
            ret
    ENDP    pause
    
    ; ------------------------------------------------------------
    ; Сегмент данных для процедур обслуживания видеоадаптера
    ; ------------------------------------------------------------
    
    DATASEG
            columns db      80d     ; количество столбцов на экране
            rows    db      25d     ; количество строк на экране
    
            rl_crt  dw      COLOR_SEG       ; сегментный адрес видеобуфера
            vir_crt dw      CRT_DESCR       ; селектор видеобуфера
    
            curr_line       dw      0d      ; номер текущей строки
    
    CODESEG
    
    ; ------------------------------------------------------------
    ; Определение базового адреса видеобуфера
    ; ------------------------------------------------------------
    
    PROC    set_crt_base    NEAR
    
    ; Определяем количество столбцов на экране и записываем 
    ; в переменную columns
    
            mov     ax,40
            mov     es,ax
            mov     bx,[WORD es:4a]
            mov     [columns],bl
    
    ; То же для количества строк, записываем в переменную rows
    
            mov     bl,[BYTE es:84]
            inc     bl
            mov     [rows],bl
    
    ; Для того чтобы определить тип видеоконтроллера (цветной
    ; или монохромный), считываем адрес микросхемы 6845
    
            mov     bx,[WORD es:PORT_6845]
            cmp     bx,COLOR_PORT
            je      set_crt_exit
    
    ; Если видеоконтроллер монохромный, изменяем адрес сегмента
    ; и селектор, заданные по умолчанию
    
            mov     [rl_crt],MONO_SEG
            mov     [vir_crt],MDA_DESCR
    
    set_crt_exit:
            ret
    ENDP    set_crt_base
    
    ; ------------------------------------------------------------
    ; Вывод строки на экран
    ; Параметры:
    ;       (ax, bx) - координаты (x, y) выводимой строки
    ;       ds:si   - адрес выводимой строки
    ;       cx      - длина выводимой строки
    ;       dh      - атрибут выводимой строки
    ;       es      - сегмент или селектор видеопамяти
    ; ------------------------------------------------------------
    
    PROC    writexy         NEAR
            push    si
            push    di
    
    ; Вычисляем смещение в видеобуфере для записи строки,
    ; используем формулу ((y * columns) + x) * 2
    
            mov     dl,[columns]
            mul     dl
            add     ax,bx
            shl     ax,1
            mov     di,ax
            mov     ah,dh   ; записываем в ah байт атрибута
    
    ; Выполняем запись в видеобуфер
    
    wxy_write:
            lodsb   ; очередной символ в al
            stosw   ; записываем его в видеопамять
            loop    wxy_write       ; цикл до конца строки
    
            pop     di
            pop     si
            ret
    ENDP    writexy
    
    ; ------------------------------------------------------------
    ; Процедура стирания экрана
    ;       Параметр: bh - атрибут для заполнения экрана
    ; ------------------------------------------------------------
    
    
    PROC    clrscr          NEAR
            xor     cx,cx
            mov     dl,[columns]
            mov     dh,[rows]
            mov     ax,0600h        
            int     10h
            ret
    ENDP    clrscr
    
    DATASEG
    
    hello_msg db " Protected mode monitor *TINY/OS*, 
                    v.1.0 for CPU 80286  ¦ © Frolov A.V., 1992 "
    
    CODESEG
    
    ; ------------------------------------------------------------
    ; Процедура выводит сообщение в защищённом режиме
    ; ------------------------------------------------------------
    
    PROC    write_hello_msg NEAR
    
            mov     ax,[vir_crt]    ; загружаем селектор видеопамяти 
            mov     es,ax           ; в регистр es
    
    ; Выводим сообщение в верхний левый угол экрана (x=y=0)
    
            mov     bx,0            ;(X,Y) = (AX,BX)
            mov     ax,[curr_line]
            inc     [curr_line]     ; увеличиваем номер текущей строки
    
    ; Загружаем адрес выводимой строки и её длину
    
            mov     si,OFFSET hello_msg
            mov     cx,SIZE hello_msg
    
            mov     dh,30h  ; аттрибут - черный текст на голубом фоне
    
            call    writexy ; выводим строку
    
            ret
    ENDP    write_hello_msg
    
    
    CSEG_SIZE       = ($ - start) ; размер сегмента кода
    
    DATASEG
    
    DSEG_SIZE       = ($ - DSEG_BEG) ; размер сегмента данных
    
            END     start