Аппаратное обеспечение 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... |

