; *** Listing 3-5 *** ; ; Le timer Zen à période longue. (LZTIMER.ASM) ; Utilise le timer du 8253 et l'horloge système du BIOS pour mesurer ; les performances d'un code qui s'exécute en moins d'une heure. ; Les interruptions restent activées (afin de pouvoir tenir compte ; de l'interruption du timer),la mesure est moins précise que celle du ; timer Zen de précision, il vaut donc mieux l'utiliser seulement pour mesurer ; du code s'exécutant en plus de 54 millisecondes (code qui fait déborder ; le timer Zen de précision). La résolution est limitée par ; les interruptions du timer. ; ; Par Michael Abrash ; ; Routines appelables en externe: ; ; ZTimerOn: sauve l'horloge système du BIOS et démarre le ; timer Zen à période longue. ; ; ZTimerOff: arrête le timer Zen à période longue et sauve le compteur du timer ; et l'horloge système du BIOS. ; ; ZTimerReport: affiche le temps écoulé entre le démarrage et ; l'arrêt du timer. ; ; Note: Si la durée de la mesure dépasse une heure ou si minuit sonne entre les ; appels à ZTimerOn et à ZTimerOff, une erreur est reportée. ; Pour un code qui s'exécute en plus de quelques minutes, l'emploi de ; la commande TIME de DOS dans un fichier batch lancé avant et après ; l'exécution du code à mesurer ou l'utilisation de la fonction ; time-of-day de DOS sont plus appropriés que le timer Zen à période longue. ; ; Note: La version PS/2 est assemblée en positionnant PS2 à 1. ; PS2 doit être à 1 sur des ordinateurs PS/2 car les timers PS/2 ; ne sont pas compatibles avec la fonctionnalité de l'arrêt du timer non ; documenté du 8253; l'approche alternative pour la mesure qui doit être employée ; sur les ordinateurs PS/2 laisse une petite période de temps ; pendant laquelle le compteur du timer 0 et le timer du BIOS ; ne peuvent pas être synchronisés. Vous devrez également mettre le symbole ; de PS2 à 1 si vous obtenez des résultats incorrects. ; ; Note: Quand PS2 est à 0, le code s'appuie sur la fonctionnalité non documentée ; du 8253 pour obtenir des résultats plus fiables. Il est possible que ; le 8253 (ou n'importe quelle puce émulant le 8253) puisse être dans un ; état incorrect ou indéfini lors de l'utilisation de cette fonctionnalité. ; ; **************************************************************** ; * Si votre ordinateur présente des signes de dysfonctionnement * ; * après l'utilisation du timer Zen à période longue (si par * ; * exemple le lecteur de disquette ne fonctionne pas bien), * ; * relancez le système et positionnez PS2 à 1. * ; **************************************************************** ; ; Note: Chaque bloc du code à mesurer doit être lancé plusieurs ; fois. Au moins deux lectures identiques sont nécessaires pour ; déterminer une mesure réelle, et pour éliminer toute sorte de ; variabilité due aux interruptions. ; ; Note: Les interruptions ne doivent pas être désactivées durant plus de 54 ms ; pendant la mesure. Comme les interruptions sont activées, les ; touches du clavier, la souris et les autres périphériques qui génèrent ; des interruptions ne doivent pas être utilisés pendant la mesure. ; ; Note: Tout code supplémentaire désactivant l'interruption du timer (comme ; certains utilitaires résidant en mémoire) augmentera la valeur de la mesure ; affichée par le timer Zen. ; ; Note: Ces routines peuvent introduire des inexactitudes de quelques dixièmes ; de seconde dans l'horloge système pour chaque section de code mesurer ; Par conséquent, il est conseillé de relancer l'ordinateur ; à la fin d'une session de mesure. ; ; ; Tous les registres et les drapeaux sont préservés par toutes les routines. ; Code segment word public 'CODE' assume cs:Code, ds:nothing public ZTimerOn, ZTimerOff, ZTimerReport ; ; Positionner PS2 à 0 pour assembler sur un système totalement ; compatible avec le 8253; quand PS2 est à 0, les lectures sont plus fiables si ; l'ordinateur supporte la fonctionnalité non documentée d'arrêt du timer , ; mais peut être mal désactivé, si cette fonctionnalité n'est pas supportée. ; En fait, l'arrêt du timer peut interférer avec toutes les opérations ; de l'ordinateur en réglant le 8253 dans un état indéfini ou incorrect ; Utilisez le avec précaution!!! ; ; Positionner PS2 à 1 pour assembler l'utilisation sur un système ; incompatible au 8253, ce qui inclut les ordinateurs PS/2; quand PS2 est à 1, ; les lectures peuvent de temps à autre être désactivées toutes les 54 ms, ; mais le code fonctionnera correctement sur tous les systèmes. ; ; Positionner à 1 présente une sécurité et fonctionnera sur plus de systèmes, ; alors que 0 donne des résultats plus fiables pour des systèmes ; qui supportent la fonctionnalité arrêt du timer du ; 8253. A vous de choisir. ; PS2 equ 1 ; ; Adresse de base du timer 8253. ; BASE_8253 equ 40h ; ; Adresse des registres du timer 0 du 8253. ; TIMER_0_8253 equ BASE_8253 + 0 ; ; Adresse des registres de mode du 8253. ; MODE_8253 equ BASE_8253 + 3 ; ; Adresse de la variable compteur du BIOS dans le segment ; de données du BIOS. ; TIMER_COUNT equ 46ch ; ; Macro pour émuler une instruction POPF pour régler le bogue de certains ; 80286 qui autorisent des interruption pendant une POPF même si ; les interruptions sont désactivées. ; MPOPF macro local p1, p2 jmp short p2 p1: iret ;effectue un saut vers une adresse empilé & pop flags p2: push cs ;construit l'adresse de retour far call p1 ;de la prochaine instruction endm ; ; Macro pour donner suffisamment de temps entre les ; deux accès E/S afin que le périphérique accédé puisse répondre ; aux deux accès même sur un PC très rapide. ; DELAY macro jmp $+2 jmp $+2 jmp $+2 endm StartBIOSCountLow dw ? ;mot poids faible du compteur BIOS ;en début de période StartBIOSCountHigh dw ? ;mot poids fort du compteur BIOS ;en début de période EndBIOSCountLow dw ? ;mot poids faible du compteur BIOS ;en fin de période EndBIOSCountHigh dw ? ;mot poids fort du compteur BIOS ;en fin de période EndTimedCount dw ? ;Compteur du timer 0 ;en fin de période ReferenceCount dw ? ;nombre de mesures requises pour ;exécuter le code supplémentaire ; ; Chaine à afficher pour reporter les résultats. ; OutputStr label byte db 0dh, 0ah, 'Temps mesuré: ' TimedCountStr db 10 dup (?) db ' microsecondes', 0dh, 0ah db '$' ; ; Stockage temporaire du temps compté tel qu'il est subdivisé par des puissances ; de 10 lors de la conversion de mots longs binaires en ASCII. ; CurrentCountLow dw ? CurrentCountHigh dw ? ; ; Table de puissances de 10 employée pour faire la division par 10 lors de ; la conversion de mots longs binaires en ASCII. ; PowersOfTen label word dd 1 dd 10 dd 100 dd 1000 dd 10000 dd 100000 dd 1000000 dd 10000000 dd 100000000 dd 1000000000 PowersOfTenEnd label word ; ; Chaîne à afficher pour reporter le mot de poids fort du compteur BIOS ; modifié lors de la mesure(une heure ou minuit passé), ; La valeur est incorrecte et le test doit être refait. ; TurnOverStr label byte db 0dh, 0ah db '****************************************************' db 0dh, 0ah db '* Une heure est passée pendant ou l'horloge *' db 0dh, 0ah db '* a dépassé Minuit durant la mesure. Si tel est *' db 0dh, 0ah db '* le cas refaites le test; si une heure est passée ,*' db 0dh, 0ah db '* le test est trop long pour être mesuré par le *' db 0dh, 0ah db '* timer Zen à période longue. *' db 0dh, 0ah db '* Suggestion: utilisez la commande TIME ou *' db 0dh, 0ah db '* fonction time du DOS, ou une montre! *' db 0dh, 0ah db '****************************************************' db 0dh, 0ah db '$' ;******************************************************************** ;* Routine appelée pour commencer la mesure. * ;******************************************************************** ZTimerOn proc near ; ; Sauve le contexte du programme à mesurer. ; push ax pushf ; ; Le timer 0 du 8253 est en mode 2 (division par N), pour effectuer ; un compte linéaire et non par deux. Arrête aussi ; le timer 0 jusqu'au chargement du compte, excepté sur les ordinateurs PS/2. ; mov al,00110100b ;mode 2 out MODE_8253,al ; ; Le compteur du timer est à 0, nous n'aurons donc pas d'autre ; interruption du timer. ; Note: ce qui introduit une inexactitude de tout au plus de 54 ms dans ; l'horloge système à chaque mesure. ; DELAY sub al,al out TIMER_0_8253,al ;lsb DELAY out TIMER_0_8253,al ;msb ; ; Si les interruptions sont désactivées, les réactiver le temps d'effectuer ; l'interruption générée par le passage du mode 3 au mode 2. ; Les interruptions doivent être autorisées au moins 210 ns pour donner le temps ; à l'interruption de se produire. 10 sauts sont ici effectués ; pour donner un délai suffisant sur un PC très rapide. ; pushf sti rept 10 jmp $+2 endm MPOPF ; ; Stocke le début du comptage du BIOS. ; (Comme le compteur du timer est à 0, le compteur du BIOS ; restera tel quel pendant 54 ms, nous n'avons donc pas besoin de désactiver ; les interruptions.) ; push ds sub ax,ax mov ds,ax mov ax,ds:[TIMER_COUNT+2] mov cs:[StartBIOSCountHigh],ax mov ax,ds:[TIMER_COUNT] mov cs:[StartBIOSCountLow],ax pop ds ; ; Le compteur du timer est à 0 pour définir le début de l'intervalle. ; mov al,00110100b ;prépare le chargement out MODE_8253,al ;de la valeur initiale du timer DELAY sub al,al out TIMER_0_8253,al ;chargement de lsb DELAY out TIMER_0_8253,al ;chargement de msb ; ; Restaure le contexte du programme à mesurer et y retourne. ; MPOPF pop ax ret ZTimerOn endp ;******************************************************************** ;* Routine appelée pour arrêter le décompte et le lire. * ;******************************************************************** ZTimerOff proc near ; ; Sauve le contexte du programme à mesurer. ; pushf push ax push cx ; ; Si les interruptions sont désactivées, les réactiver le temps de prendre en ; compte les interruptions en attente. Les interruptions doivent être autorisées ; ; au moins 210 ns pour les laisser se produire. ; 10 sauts sont ici effectués pour donner suffisamment de temps sur les PC très ; rapides. ; sti rept 10 jmp $+2 endm ; ; Verrouille le compteur du timer. ; if PS2 mov al,00000000b out MODE_8253,al ;Verrouille le compteur du timer 0 else ; ; Le timer 0 est en mode 2 (division par N), en attendant le chargement ; d'une valeur sur 2 octets, qui arrête le timer 0jusqu'au chargement du ; compteur. (Fonctionne seulement sur des puces totalement compatibles ; 8253.) ; mov al,00110100b ;mode 2 out MODE_8253,al DELAY mov al,00000000b ;Verrouille le compteur du timer 0 out MODE_8253,al endif cli ;arrête le compteur du BIOS ; ; Lit le compteur du BIOS. (Comme les interruptions sont désactivées, le ; compteur du BIOS ne changera pas.) ; push ds sub ax,ax mov ds,ax mov ax,ds:[TIMER_COUNT+2] mov cs:[EndBIOSCountHigh],ax mov ax,ds:[TIMER_COUNT] mov cs:[EndBIOSCountLow],ax pop ds ; ; Lit le compteur du timer et le sauve. ; in al,TIMER_0_8253 ;lsb DELAY mov ah,al in al,TIMER_0_8253 ;msb xchg ah,al neg ax ;Convertit le décompte ;restant en temps écoulé mov cs:[EndTimedCount],ax ; ; Relance le timer à 0, qui attend toujours le chargement d'une valeur initiale. ; ife PS2 DELAY mov al,00110100b ;mode 2, attend le chargement ;d'une valeur sur 2 octets out MODE_8253,al DELAY sub al,al out TIMER_0_8253,al ;lsb DELAY mov al,ah out TIMER_0_8253,al ;msb DELAY endif sti ;laisse le compteur BIOS continuer ; ;Mesure d'un fragment de code de longueur nulle, pour référencer ;le surplus de cette routine. 16 mesures sont faites et une moyenne, ;dans un souci de précision arrondit le résultat. ; mov cs:[ReferenceCount],0 mov cx,16 cli ;les interruptions sont désactivées ; dans un souci de précision RefLoop: call ReferenceZTimerOn call ReferenceZTimerOff loop RefLoop sti add cs:[ReferenceCount],8 ;total + (0.5 * 16) mov cl,4 shr cs:[ReferenceCount],cl ;(total) / 16 + 0.5 ; ; Restaure le contexte du programme à mesurer et y retourne. ; pop cx pop ax MPOPF ret ZTimerOff endp ; ; Appelé par ZTimerOff pour que le timer commence à mesurer le surplus. ; ReferenceZTimerOn proc near ; ; Sauve le contextedu programme à mesurer. ; push ax pushf ; ; Le timer 0 du 8253 est en mode 2 (division par N), pour effectuer ; un compte linéaire et non par deux. ; mov al,00110100b. ;mode 2 out MODE_8253,al ; ; Le compteur du timer est à 0. ; DELAY sub al,al out TIMER_0_8253,al ;lsb DELAY out TIMER_0_8253,al ;msb ; ; Restaure le contexte du programme à mesurer et y retourne. ; MPOPF Pop ax ret ReferenceZTimerOn endp ; ; Appelé par ZTimerOff pour arrêter le timer et ajoute le résultat à ; ReferenceCount pour mesurer le surplus. N'a pas besoin de vérifier ; le compteur du BIOS car ce fragment de code ne peut pas dépasser plus de ; 54 ms. ; ReferenceZTimerOff proc near ; ; Sauve le contexte du programme à mesurer. ; pushf push ax push cx ; ; Correspondance au délai d'interruption de ZTimerOff. ; sti rept 10 jmp $+2 endm mov al,00000000b out MODE_8253,al ;Verrouille le timer ; ; Lit le compteur et le sauve. ; DELAY in al,TIMER_0_8253 ;lsb DELAY mov ah,al in al,TIMER_0_8253 ;msb xchg ah,al neg ax ;convertit le décompte ; restant en temps écoulé add cs:[ReferenceCount],ax ; ; Restaure le contexte et retourne. ; pop cx pop ax MPOPF ret ReferenceZTimerOff endp ;******************************************************************** ;* Routine appelée pour reporter les résultats. * ;******************************************************************** ZTimerReport proc near pushf push ax push bx push cx push dx push si push di push ds ; push cs ; les fonctions DOS demandent que DS pointe pop ds ; sur le texte à afficher à l'écran assume ds:Code ; ; Vérifie si minuit ou une heure ne sont pas passés. Si oui, ; le notifie à l'utilisateur. ; mov ax,[StartBIOSCountHigh] cmp ax,[EndBIOSCountHigh] jz CalcBIOSTime ;l'heure n'a pas changé, ;tout va bien inc ax cmp ax,[EndBIOSCountHigh] jnz TestTooLong ;Changement d'heure ou minuit ;détecté, les ;résultats sont incorrects mov ax,[EndBIOSCountLow] cmp ax,[StartBIOSCountLow] jb CalcBIOSTime ;changement d'heure ;ce qui est acceptable, si le total ;ne dépasse pas une heure ; Plus d'une heure ou minuit passé, résultats incorrects ; Le notifier à l'utilisateur. Ce test ne tient pas compte ; du passage d'un multiple de 24 heures, mais nous faisons confiance ; à la perspicacité de l'utilisateur pour y remédier. ; TestTooLong: mov ah,9 mov dx,offset TurnOverStr int 21h jmp short ZTimerReportDone ; ; Convertit le compte du BIOS en microsecondes. ; CalcBIOSTime: mov ax,[EndBIOSCountLow] sub ax,[StartBIOSCountLow] mov dx,54925 ;nombre de microsecondes pour ;chaque compte du BIOS mul dx mov bx,ax ;stocke le temps du BIOS en mov cx,dx ;microsecondes ; ; Convertit le compte du timer en microsecondes. ; mov ax,[EndTimedCount] mov si,8381 mul si mov si,10000 div si ;* .8381 = * 8381 / 10000 ; ; Additionne les comptes du timer et du BIOS pour obtenir le total en ; microsecondes. ; add bx,ax adc cx,0 ; ; Soustrait le surplus du timer et sauve le résultat. ; mov ax,[ReferenceCount] mov si,8381 ;convertit le compte mul si ;en microsecondes mov si,10000 div si ;*.8381 = * 8381 / 10000 sub bx,ax sbb cx,0 mov [CurrentCountLow],bx mov [CurrentCountHigh],cx ; ; Convertit le résultat en chaîne ASCII par soustractions successives ; de puissances de 10. ; mov di,offset PowersOfTenEnd - offset PowersOfTen - 4 mov si,offset TimedCountStr CTSNextDigit: mov bl,'0' CTSLoop: mov ax,[CurrentCountLow] mov dx,[CurrentCountHigh] sub ax,PowersOfTen[di] sbb dx,PowersOfTen[di+2] jc CTSNextPowerDown inc bl mov [CurrentCountLow],ax mov [CurrentCountHigh],dx jmp CTSLoop CTSNextPowerDown: mov [si],bl inc si sub di,4 jns CTSNextDigit ; ; ; Affiche les résultats. ; mov ah,9 mov dx,offset OutputStr int 21h ; ZTimerReportDone: pop ds pop di pop si pop dx pop cx pop bx pop ax MPOPF ret ZTimerReport endp Code ends end