[Назад] [Далее]

5.11.1. Символьные устройства

Драйвер символьного устройства должен содержать в поле атрибутов драйвера (смещение 04 в заголовке) единицу в самом старшем бите. Тогда остальные биты трактуются следующим образом:

IOCTL — это большой набор функций (свыше пятидесяти), доступных как различные подфункции INT 21h АН = 44h и предназначенных для прямого взаимодействия с драйверами. Но о IOCTL — чуть позже, а сейчас познакомимся с тем, как устроен, возможно, самый простой из реально полезных драйверов.

В качестве первого примера рассмотрим драйвер, который вообще не обслуживает никакое устройство, реальное или виртуальное, а просто увеличивает размер буфера клавиатуры BIOS до 256 (или больше) символов. Этого можно было бы добиться обычной резидентной программой,но BIOS хранит в своей области данных только ближние адреса для этого буфера, то есть смещения относительно сегментного адреса 0040h. Так как драйверы загружаются в память первыми, еще до командного интерпретатора, они обычно попадают в область линейных адресов 00400h – 10400h, в то время как с резидентными программами это может не получиться.

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

Процедура инициализации может пользоваться функциями DOS 01h – 0Ch, 25h, 30h и 35h.

; kbdext.asm
; драйвер символьного устройства, увеличивающий буфер клавиатуры до BUF_SIZE
; (256 по умолчанию) символов
;
BUF_SIZE           equ    256           ; новый размер буфера

        .model     tiny
        .186                            ; для сдвигов и push 0040h
        .code
        org        0                    ; драйвер начинается с CS:0000
start:                                  ; заголовок драйвера
        dd         -1                   ; адрес следующего драйвера - 
                                        ; FFFFh:FFFFh для последнего
        dw         8000h                ; атрибуты: символьное устройство,
                                        ; ничего не поддерживает
        dw         offset strategy      ; адрес процедуры стратегии
        dw         offset interrupt     ; адрес процедуры прерывания
        db         "$$KBDEXT"           ; имя устройства (не должно совпадать
                                        ; с каким-нибудь именем файла)
request dd         ?                    ; здесь процедура стратегии сохраняет адрес
                                        ; буфера запроса
buffer  db         BUF_SIZE*2 dup (?)   ; а это - наш новый буфер
                                        ; клавиатуры размером BUF_SIZE символов
                                        ; (два байта на символ)
; процедура стратегии
; на входе ES:BX = адрес буфера запроса
strategy           рroc    far
        mov        cs:word ptr request,bx   ; сохранить этот адрес для
        mov        cs:word ptr request+2,es ; процедуры прерывания
        ret
strategy           endp

; процедура прерывания
interrupt          proc    far
        push       ds                   ; сохранить регистры
        push       bx
        push       ax
        lds        bx,dword ptr cs:request ; DS:BX - адрес запроса
        mov        ah,byte ptr [bx+2]   ; прочитать номер команды,
        or         ah,ah                ; если команда 00h (инициализация),
        jnz        exit
        call       init                 ; обслужить ее,
                                        ; иначе:
exit:   mov        ax,100h              ; установить бит 8 (команда обслужена)
        mov        word ptr [bx+3],ax   ; в слове состояния драйвера
        pop        ах                   ; и восстановить регистры
        pop        bx
        pop        ds
        ret
interrupt          endp

; процедура инициализации
; вызывается только раз при загрузке драйвера
init    proc       near
        push       сx
        push       dx

        mov        ax,offset buffer
        mov        cx,cs                ; CX:AX - адрес нашего буфера клавиатуры
        cmp        cx,1000h             ; если СХ слишком велик,
        jnc        too_big              ; не надо загружаться,
        shl        cx,4                 ; иначе: умножить сегментный адрес на 16,
        add        cx,ax                ; добавить смещение - получился
                                        ; линейный адрес,
        sub        cx,400h              ; вычесть линейный адрес начала данных BIOS
        push       0040h
        pop        ds
        mov        bx,1Ah               ; DS:BX = 0040h:001Ah - адрес головы
        mov        word ptr [bx],cx     ; записать новый адрес головой буфера
        mov        word ptr [bx+2],cx   ; он же новый адрес хвоста
        mov        bl,80h               ; DS:BX = 0040h:
                                        ; 0080h - адрес начала буфера
        mov        word ptr [bx],cx     ; записать новый адрес начала,
        add        cx,BUF_SIZE*2        ; добавить размер
        mov        word ptr [bx+2],cx   ; и записать новый адрес конца

        mov        ah,9                 ; функция DOS 09h
        mov        dx,offset succ_msg   ; DS:DX - адрес строки
        push       cs                   ; с сообщением об успешной установке
        pop        ds
        int        21h                  ; вывод строки на экран
        lds        bx,dword ptr cs:request ; DS:BX - адрес запроса

        mov        ax,offset init
        mov        word ptr [bx+0Eh],ax ; CS:AX - следующий байт после
        mov        word ptr [bx+10h],cs ; конца резидентной части
        jmp        short done           ; конец процедуры инициализации

; сюда передается управление, если мы загружены слишком низко в памяти
too_big:
        mov        ah,9                 ; функция DOS 09h
        mov        dx,offset fail_msg   ; DS:DX - адрес строки
        push       cs                   ; с сообщением о неуспешной
        pop        ds                   ; установке
        int        21h                  ; вывод строки на экран
        lds        bx,dword ptr cs:request ; DS:BX - адрес запроса
        mov        word ptr [bx+0Eh],0  ; записать адрес начала драйвера
        mov        word ptr [bx+10h],cs ; в поле "адрес первого
                                        ; освобождаемого байта"
done:   pop        dx
        pop        cx
        ret
init    endp
; сообщение об успешной установке (на английском, потому что в этот момент
; русские шрифты еще не загружены)
succ_msg           db    "Keyboard extender loaded",0Dh,0Ah,'$'
; сообщение о неуспешной установке
fail_msg           db    "Too many drivers in memory - "
                   db    "put kbdext.sys first "
                   db    "in config.sys",0Dh,0Ah,'$'
        end        start

Теперь более подробно рассмотрим функции, которые должен поддерживать драйвер символьного устройства на примере драйвера устройства ROT 13. ROT 13 — это метод простой модификации английского текста, который применяется в электронной почте, чтобы текст нельзя было прочитать сразу. ROT 13 состоит в сдвиге каждой буквы латинского алфавита на 13 позиций (в любую сторону, так как всего 26 букв). Раскодирование, очевидно, выполняется такой же операцией. Когда наш драйвер загружен, команда DOS

    сору encrypt.txt rot13

приведет к тому, что текст из encrypt.txt будет выведен на экран, зашифрованный или расшифрованный ROT 13, в зависимости от того, был ли он зашифрован до этого.

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

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

Еще одно отличие этого примера — в нем показано, как совместить в одной программе обычный исполнимый файл типа ЕХЕ и драйвер устройства. Если такую программу запустить обычным образом, она будет выполняться, начиная со своей точки входа (метка start в нашем примере), а если ее загрузить из CONFIG.SYS, DOS будет считать драйвером участок программы, начинающийся со смещения 0:

; rot13.asm
; Драйвер символьного устройства, выводящий посылаемые ему символы на экран
; после выполнения над ними преобразования ROT13
; (каждая буква английского алфавита смещается на 13 позиций).
; Реализованы только функции записи в устройство
;
; Пример использования:
; сору encrypted.txt $rot13
; загрузка - из CONFIG.SYS
; DEVICE=c:\rot13.exe,
; если rot13.exe находится в директории С:\
;
        .model     small                ; модель для ЕХЕ-файла
        .code
        .186                            ; для pusha/popa
        org        0                    ; код драйвера начинается с CS:0000
        dd         -1                   ; адрес следующего драйвера
        dw         0A800h               ; атрибуты нашего устройства
        dw         offset strategy      ; адрес процедуры стратегии
        dw         offset interrupt     ; адрес процедуры прерывания
        db         "$ROT13",20h,20h     ; имя устройства, дополненное
                                        ; пробелами до восьми символов

request dd         ?                    ; сюда процедура стратегии будет писать
                                        ; адрес буфера запроса

; таблица адресов обработчиков для всех команд
command_table      dw    offset init                 ; 00h
                   dw    3 dup(offset unsupported)   ; 01, 02, 03
                   dw    2 dup(offset read)          ; 04, 05
                   dw    2 dup(offset unsupported)   ; 06, 07
                   dw    2 dup(offset write)         ; 08h, 09h
                   dw    6 dup(offset unsupported)   ; 0Ah, 0Bh, 0Ch,
                                                     ; 0Dh, 0Eh, 0Fh
                   dw    offset write                ; 10h
                   dw    2 dup(offset invalid)       ; 11h, 12h
                   dw    offset unsupported          ; 13h
                   dw    3 dup(offset invalid)       ; 14h, 15h, 16h
                   dw    3 dup(offset unsupported)   ; 17h, 18h, 19h

; процедура стратегии - одна и та же для всех драйверов
strategy           proc    far
        mov        word ptr cs:request,bx
        mov        word ptr cs:request+2,es
        ret
strategy           endp

; процедура прерывания
interrupt          proc    far
        pushf
        pusha                           ; сохранить регистры
        push       ds                   ; и на всякий случай флаги
        push       es

        push       cs
        pop        ds                   ; DS = наш сегментный адрес
        les        si,dword ptr request ; ES:SI = адрес буфера запроса
        xor        bx,bx
        mov        bl,byte ptr es:[si+2] ; BX = номер функции
        cmp        bl,19h               ; проверить, что команда 
        jbe        command_ok           ; в пределах 00 - 19h,
        call       invalid              ; если нет - выйти с ошибкой
        jmp        short interrupt_end
command_ok:                     ; если команда находится в пределах 00 - 19h,
        shl        bx,1         ; умножить ее на 2, чтобы получить смещение
                                ; в таблице слов command_table,
        call       word ptr command_table[bx] ; и вызвать обработчик
interrupt_end:
        cmp        al,0         ; AL = 0, если не было ошибок,
        je         no_error
        or         ah,80h       ; если была ошибка - установить бит 15 в АХ,
no_error:
        or         ah,01h               ; в любом случае установить бит 8
        mov        word ptr es:[si+3],ax ; и записать слово состояния
        pop        es
        pop        ds
        рора
        popf
        ret
interrupt          endp

; обработчик команд, предназначенных для блочных устройств
unsupported        proc    near
        xor        ax,ax                ; не возвращать никаких ошибок
        ret
unsupported        endp

; обработчик команд чтения
read    proc       near
        mov        al,0Bh               ; общая ошибка чтения
        ret
read    endp

; обработчик несуществующих команд
invalid proc       near
        mov        ax,03h               ; ошибка "неизвестная команда"
        ret
invalid endp

; обработчик функций записи
write   proc       near
        push       si
        mov        cx,word ptr es:[si+12h]  ; длина буфера в СХ,
        jcxz       write_finished           ; если это 0 - нам делать нечего
        lds        si,dword ptr es:[si+0Eh] ; адрес буфера в DS:SI

; выполнить ВОТ13-преобразование над буфером
        cld
rot13_loop:                             ; цикл по всем символам буфера
        lodsb                           ; AL = следующий символ из буфера в ES:SI
        cmp        al,'А'               ; если он меньше "А",
        jl         rot13_done           ; это не буква,
        cmp        al,'Z'               ; если он больше "Z",
        jg         rot13_low            ; может быть, это маленькая буква,
        cmp        al,('A'+13)          ; иначе: если он больше "А" + 13,
        jge        rot13_dec            ; вычесть из него 13,
        jmp        short rot13_inc      ; а иначе - добавлять
rot13_low:
        cmp        al,'а'               ; если символ меньше "а",
        jl         rot13_done           ; это не буква,
        cmp        al,'z'               ; если символ больше "z",
        jg         rot13_done           ; то же самое,
        cmp        al,('a'+13)          ; иначе: если он больше "а" + 13,
        jge        rot13_dec            ; вычесть из него 13, иначе:
rot13_inc:
        add        al,13                ; добавить 13 к коду символа,
        jmp        short rot13_done
rot13_dec:
        sub        al,13                ; вычесть 13 из кода символа,
rot13_done:
        int        29h                  ; вывести символ на экран
        loop       rot13_loop           ; и повторить для всех символов
write_finished:
        xor        ах,ах                ; сообщить, что ошибок не было
        pop        si
        ret
write   endp

; процедура инициализации драйвера
init    proc       near
        mov        ah,9                 ; функция DOS 09h
        mov        dx,offset load_msg   ; DS:DX - сообщение об установке
        int        21h                  ; вывод строки на экран
        mov        word ptr es:[si+0Eh],offset init ; записать адрес
        mov        word ptr es:[si+10h],cs ; конца резидентной части
        xor        ах,ах                ; ошибок не произошло
        ret
init   endp

; сообщение об установке драйвера
load_msg           db    "ROT13 device driver loaded",0Dh,0Ah,'$'

start:                                  ; точка входа ЕХЕ-программы
        push       cs
        pop        ds
        mov        dx,offset exe_msg    ; DS:DX - адрес строки
        mov        ah,9                 ; функция DOS
        int        21h                  ; вывод строки на экран
        mov        ah,4Ch               ; функция DOS 4Ch
        int        21h                  ; завершение ЕХЕ-программы

; строка, которая выводится при запуске не из CONFIG.SYS:
exe_msg db    "Эту программу надо загружать как драйвер устройства из"
        db    "CONFIG.SYS",0Dh,0Ah,'$'

        .stack

        end        start

п»ї
"target=_blank><\/a>") //-->