Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых

Аппаратное обеспечение IBM PC

© Александр Фролов, Григорий Фролов
Том 2, книга 1, М.: Диалог-МИФИ, 1992.

[Назад] [Содеожание] [Дальше]

12.5. Программирование сопроцессора

Используя языки высокого уровня, такие как Си или Паскаль, вы можете даже и не знать, что созданная вами программа использует для вычислений арифметический сопроцессор. При установке системы программирования QuickC или C 6.0 вам предоставляется возможность выброа одного из трех вариантов стандартной библиотеки:

  • библиотека эмулятора;
  • библиотека, рассчитанная на наличие сопроцессора;
  • библиотека альтернативной математики.

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

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

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

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

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

  • резидентные программы;
  • драйверы;
  • программы, предъявляющие жесткие требования к точности и скорости вычислений.

В случае с резидентными программами невозможность использования библиотеки эмулятора вызвана тем, что после оставления программы резидентной в памяти, например, функцией _dos_keep(), она теряет доступ к модулям эмуляции. Механизм вызова программ эмуляции основан на использовании прерываний с номерами 34h...3Eh. Перед тем как оставить программу резидентной, функция _dos_keep() восстанавливает содержимое этих векторов, делая невозможным доступ резидентной программе к модулям эмулятора. Да и самих этих модулей уже нет в памяти - на их место может быть загружена новая программа.

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

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

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

Какие средства можно использовать для составления программ для сопроцессора?

Обычно это или ассемблер MASM (возможно использование TASM), либо интегрированная среда разработки QuickC версии 2.01, содержащая встроенный Quick Assembler.

Приведем пример самой простой программы, подготовленный для трансляции программой Quick Assemler. Эта программа выполняет вычисления по следующей несложной формуле:

z = x + y;


Значения x и y задаются в виде констант:

                  .MODEL  TINY

                  .STACK  100h

                  .DATA

; Здесь находятся константы с одинарной
; точностью x и y

x  dd 1.0
y  dd 2.0

; Резервируем четыре байта для результата

z  dd ?

                  .CODE
                  .STARTUP

; Записываем в стек численных регистров
; значение x

                  fld    x

; Складываем содержимое верхушки стека
; с константой y

                  fadd   y

; Записываем результат в ячейку z

                  fstp   z

; Завершаем работу программы и
; возвращаем управление операционной системе

                  .EXIT   0

                  END


Как убедиться в том, что программа работает правильно?

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

Запустим отладчик CodeView, передав ему в качестве параметра имя приведенной выше программы:

cv test87.com


После того, как отладчик запустится, откройте окно регистров сопроцессора, нажав комбинацию клавиш Alt-V-7:

После этого на в нижней части экрана появится окно регистров сопроцессора:

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

Теперь вы видите содержимое регистров управления и состояния (cControl, cStatus), регистра тегов (cTag), регистров указателей команд и данных (Instr Ptr, Data Ptr), код выполняемой команды (Opcode). Отображается также содержимое стека численных регистров (Stack), но пока это поле пустое, так как все численные регистры отмечены в регистре тегов как пустые (код 11).

Нажмите еще раз клавишу F8, выполнив следующую команду программы. Эта команда запишет в стек численных регистров значение переменной x:

Теперь в области регистров стека показано содержимое регистра cST(0), причем как в двоичном виде, так и с использованием экспоненциальной (научной) нотации.

Как и следовало ожидать, регистр ST(0) содержит величину 1.0.

Выполним еще одну команду, прибавляющую к содержимому ST(0) значение 2.0 из переменной y. Теперь регистр ST(0) содержит величину 3.0:

Последняя команда выталкивает из стека хранящееся там значение (3.0) и записывает его в переменную z. Теперь стек численных регистров снова пуст:

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

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

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

         include sysp.inc

        .MODEL tiny
        .CODE

        PUBLIC ntrace87


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

ntrace87 proc near

; Сохраняем в стеке регистры,
; содержимое которых будет изменяться

     pushf
     push ax
     push bx
     push cx
     push dx
     push ds
     push bp

     mov bp,sp

          push cs
          pop ds

; Сохраняем полное состояние сопроцессора

          fsave  cs:regs_87

; Выводим сообщение об останове

          mov dx,offset cs:trace_msg
          @@out_str

; Выводим содержимое всех регистров

         mov ax,cs        ; cs
     call Print_word
        @@out_ch ':'
     mov ax,[bp]+14   ; ip
     call Print_word

        @@out_ch 13,10,13,10,'A','X','='
     mov ax,[bp]+10
     call Print_word

        @@out_ch ' ','B','X','='
     mov ax,[bp]+8
     call Print_word

        @@out_ch ' ','C','X','='
     mov ax,[bp]+6
     call Print_word

        @@out_ch ' ','D','X','='
     mov ax,[bp]+4
     call Print_word

        @@out_ch ' ','S','P','='
     mov ax,bp
        add ax,16
     call Print_word

        @@out_ch ' ','B','P','='
     mov ax,[bp]
     call Print_word

        @@out_ch ' ','S','I','='
     mov ax,si
     call Print_word

        @@out_ch ' ','D','I','='
     mov ax,di
     call Print_word

        @@out_ch 13,10,'D','S','='
     mov ax,[bp]+2
     call Print_word

        @@out_ch ' ','E','S','='
     mov ax,es
     call Print_word

        @@out_ch ' ','S','S','='
     mov ax,ss
     call Print_word

        @@out_ch ' ','F','='
     mov ax,[bp]+12
     call Print_word

; Выводим содержимое регистров сопроцессора

        lea dx,cs:r87_msg
        @@out_str

; Выводим содержимое управляющего регистра

        @@out_ch 'C','N','T','R','='

        mov ax, cs:regs_87.cr
        call Print_word

; Выводим содержимое регистра состояния

        @@out_ch ' ','S','T','A','T','E','='

        mov ax, cs:regs_87.sr
        call Print_word

; Выводим содержимое регситра тегов

        @@out_ch ' ','T','A','G','='

        mov ax, cs:regs_87.tg
        call Print_word

; Выводим содержимое указателя адреса

        @@out_ch ' ','C','M','D','A','D','R','='

        mov ax, cs:regs_87.cmdhi
        and  ah, 0f0h
        mov al, ah
        mov cl, 4
        ror al, cl
        call Print_byte
        mov ax, cs:regs_87.cmdlo
        call Print_word
        @@out_ch ' '

; Выводим содержимое указателя операнда

        @@out_ch ' ','O','P','R','A','D','R','='

        mov ax, cs:regs_87.oprhi
        and  ah, 0f0h
        mov al, ah
        mov cl, 4
        ror al, cl
        call Print_byte
        mov ax, cs:regs_87.oprlo
        call Print_word

; Выводим содержимое непустых численных регистров

        lea dx,cs:nr_msg
        @@out_str

        mov cx, 8              ; количество регистров - 8
        mov dx, 0              ; индекс текущего регистра
        mov bx, cs:regs_87.tg  ; содержимое регистра тегов

; Цикл по стеку численных регистров

nreg_loop:

; Проверяем поле регистра тегов, соответствующее
; текущему обрабатываемому численному регистру

          mov ax, bx
          and ax, 0c000h
          cmp ax, 0c000h

; Если это поле равно 11B, считаем, что данный
; численный регистр пуст, переходим к следующему

          je continue

; Выводим на экран содержимое численного регистра

          call Print_numreg

continue:

; Сдвигаем содержимое регистра тегов для
; обработки поля, соответствующего следующему
; регистру.

          rol bx, 1
          rol bx, 1
          inc dx       ; увеличиваем индекс текущего регистра

          loop nreg_loop

          lea dx,cs:hit_msg
          @@out_str

; Ожидаем нажатия на любую клавишу

        mov ax,0
     int 16h

; Восстанавливаем содержимое регистров

        frstor  cs:regs_87

     pop bp
     pop ds
     pop dx
     pop cx
     pop bx
     pop ax
     popf

     ret

trace_msg db 13,10,'>---- BREAK ----> At address ','$'
hit_msg db 13,10,'Hit any key...','$'
r87_msg db 13,10,13,10,'Coprocessor state:',13,10,'$'
nr_msg db 13,10,'Numeric Registers:',13,10,'$'

regs_87 db 94 dup(?)
ten db 10

ntrace87 endp

;==========================================
; Процедура выводит на экран содержимое
; численного регистра с номером, заданным
; в регистре al

Print_numreg proc near
          push cx
          push bx

; Выводим обозначение численного регистра

          push dx
          @@out_ch 'S','T','('
          pop dx
          mov al, dl
          call Print_byte
          push dx
          @@out_ch ')','='
          pop dx

; Выводим содержимое численного регистра в
; шестнадцатеричном формате

          mov cx, 10    ; счетчик байтов в числе с
                                                 ; расширенной точностью
          mov bp, 10    ; первоначальное смещение
                                                 ; к старшему байту числа

; Смещение к полю первого численного регистра
; в области сохранения

          mov bx, offset cs:regs_87.st0

; Вычисляем смещение старшего байта численного
; регистра, номер которого задан в регистре DX

          mov ax, dx
          imul cs:ten
          add bx, ax
          dec bx

; Выводим в цикле 10 байтов числа

pr_lp:
          push bx

          add bx, bp
          mov al, cs:[bx]
          call Print_byte
          pop bx

          dec bp
          loop pr_lp

          push dx
          @@out_ch 13,10
          pop dx

          pop bx
          pop cx
          ret

Print_numreg endp


;==========================================
; Процедура выводит на экран содержимое AL

Print_byte proc near

          push ax
          push bx
          push dx

          call Byte_to_hex
          mov bx,dx
          @@out_ch bh
          @@out_ch bl

          pop dx
          pop bx
          pop ax
          ret
Print_byte endp

;==========================================
; Процедура выводит на экран содержимое AX

Print_word proc near

        push ax
     push bx
     push dx

        push ax
        mov cl,8
        rol ax,cl
        call Byte_to_hex
        mov bx,dx
        @@out_ch bh
        @@out_ch bl

        pop ax
     call Byte_to_hex
     mov bx,dx
        @@out_ch bh
        @@out_ch bl

     pop dx
     pop bx
     pop ax
     ret
Print_word endp

Byte_to_hex proc near
;--------------------
; al - input byte
; dx - output hex
;--------------------
     push ds
     push cx
     push bx

     lea bx,tabl
     mov dx,cs
     mov ds,dx

     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
     pop ds
     ret

tabl db '0123456789ABCDEF'
Byte_to_hex endp

     end


Работа программы основана на использовании команды FSAVE, сохраняющей в памяти содержимое всех регистров сопроцессора. Область сохранения описывается следующей структурой, определенной в файле sysp.inc:

State87 struc
        cr dw ?
        sr dw ?
        tg dw ?
        cmdlo dw ?
        cmdhi dw ?
        oprlo dw ?
        oprhi dw ?
        st0 dt ?
        st1 dt ?
        st2 dt ?
        st3 dt ?
        st4 dt ?
        st5 dt ?
        st6 dt ?
        st7 dt ?
State87 ends


Для демонстрации возможностей ntrace87 мы немного изменили нашу первую программу, работающую с сопроцессором - после каждой комадны сопроцессора вставили вызов ntrace87:

                  .MODEL  tiny
                  DOSSEG

                  EXTRN ntrace87:NEAR

                  .STACK  100h

                  .DATA
x  dd 1.0
y  dd 2.0
; Резервируем четыре байта для результата

z  dd ?

                  .CODE
                  .STARTUP

                  push cs
                  pop  ds

; Записываем в стек численных регистров
; значение x
                  call ntrace87

                  fld    x
                  call ntrace87

; Складываем содержимое верхушки стека
; с константой y

                  fadd   y
                  call ntrace87

; Записываем результат в ячейку z

                  fstp   z
                  call ntrace87

; Завершаем работу программы и
; возвращаем управление операционной системе

quit:
                  .EXIT   0

                  END


В процессе работы этой программы на каждом шаге на экран выводится дамп содержимого регистров центрального процессора и сопроцессора (пустые численные регистры не отображаются):

>---- BREAK ----> At address 2314:0105

AX=0000 BX=0000 CX=00FF DX=2314 SP=FFFE BP=091C SI=0100 DI=FFFE
DS=2314 ES=2314 SS=2314 F=7202

Coprocessor state:
CNTR=037F STATE=4000 TAG=FFFF CMDADR=023256  OPRADR=02365E
Numeric Registers:

Hit any key...
>---- BREAK ----> At address 2314:010D

AX=0000 BX=0000 CX=00FF DX=2314 SP=FFFE BP=091C SI=0100 DI=FFFE
DS=2314 ES=2314 SS=2314 F=7202

Coprocessor state:
CNTR=037F STATE=7800 TAG=3FFF CMDADR=023246  OPRADR=02365E
Numeric Registers:
ST(00)=3FFF8000000000000000

Hit any key...
>---- BREAK ----> At address 2314:0115

AX=0000 BX=0000 CX=00FF DX=2314 SP=FFFE BP=091C SI=0100 DI=FFFE
DS=2314 ES=2314 SS=2314 F=7202

Coprocessor state:
CNTR=037F STATE=7800 TAG=3FFF CMDADR=02324E  OPRADR=023662
Numeric Registers:
ST(00)=4000C000000000000000

Hit any key...
>---- BREAK ----> At address 2314:011D

AX=0000 BX=0000 CX=00FF DX=2314 SP=FFFE BP=091C SI=0100 DI=FFFE
DS=2314 ES=2314 SS=2314 F=7202

Coprocessor state:
CNTR=037F STATE=4000 TAG=FFFF CMDADR=023256  OPRADR=023666
Numeric Registers:

Hit any key...


[Назад] [Содеожание] [Дальше]