Программирование сопроцессора
Используя языки высокого уровня, такие как Си или Паскаль, вы можете даже и не знать, что созданная вами программа использует для вычислений арифметический сопроцессор. При установке системы программирования 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...
Содержание раздела