2009-12-07 1 views
10

J'essaye de créer une procédure de sommeil/retard dans l'Assemblée de MASM de 16 bits x86 qui dirait, par exemple, imprimer un caractère sur l'écran toutes les 500ms. D'après les recherches que j'ai faites, il semble qu'il y ait trois méthodes pour y parvenir - je voudrais utiliser celle qui utilise des ticks d'horloge CPU.Comment est-ce que je peux créer une fonction de sommeil dans le MASM Assembly x86 de 16bit?

S'il vous plaît noter que je cours Windows XP via VMWare Fusion sur Mac OS X Snow Leopard - Je ne suis pas sûr si cela affecte n'importe quoi.

Quelqu'un pourrait-il me diriger dans la bonne direction, ou fournir un morceau de code que je peux modifier? Je vous remercie!

Le code que j'ai trouvé est censé imprimer 'A' sur l'écran toutes les secondes, mais ne fonctionne pas (je voudrais utiliser des millisecondes de toute façon).

TOP: 
MOV AH,2C 
INT 21 
MOV BH,DH ; DH has current second 
GETSEC:  ; Loops until the current second is not equal to the last, in BH 
MOV AH,2C 
INT 21 
CMP BH,DH ; Here is the comparison to exit the loop and print 'A' 
JNE PRINTA 
JMP GETSEC 
PRINTA: 
MOV AH,02 
MOV DL,41 
INT 21 
JMP TOP 

EDIT: Après les conseils de GJ, voici une procédure de travail. Il suffit d'appeler ce

DELAY PROC 
TIMER: 
MOV  AH, 00H 
INT  1AH 
CMP  DX,WAIT_TIME 
JB  TIMER 
ADD  DX,3   ;1-18, where smaller is faster and 18 is close to 1 second 
MOV  WAIT_TIME,DX 
RET 
DELAY ENDP 
+0

Courez-vous votre code en userland? Sur Windows? – nico

+0

Oui. Je l'exécute sur Windows via une machine virtuelle, comme indiqué dans mon post :) –

+1

Faites attention à minuit, vous pouvez avoir un problème. Peut-être est une meilleure idée de lire directement l'emplacement de la mémoire tick à 0x0040: 0x0070. Lire aussi: http://www.merlyn.demon.co.uk/pas-time.htm#RDT –

Répondre

3

En fait, vous pouvez utiliser la fonction 1Ah d'interruption du BIOS ROM 00h, 'Read Current Clock Count'. Ou vous pouvez lire dword à l'adresse $ 40: $ 6C mais vous devez vous assurer lecture atomique. Il est incrémenté par MS-DOS à environ 18.2 Hz.

Pour plus d'informations lire: The DOS Clock

+3

Le lien 'The Clock Clock' me mène à une page blanche. –

9

Cela ne peut pas être fait dans le plus pur MASM. Toutes les anciennes astuces pour régler un retard fixe supposent que vous avez le contrôle total de la machine et que vous êtes le seul thread sur une CPU, de sorte que si vous attendez 500 millions de cycles, 500,000,000/f secondes se seront écoulées (pour un CPU à la fréquence f); ce serait 500ms pour un processeur 1GHz. Parce que vous utilisez un système d'exploitation moderne, vous partagez le CPU avec beaucoup d'autres threads (parmi eux, le noyau - peu importe ce que vous faites, vous ne pouvez pas avoir la priorité sur le noyau!), Donc 500 en attente millions de cycles en seulement votre thread signifiera que plus de 500 millions de cycles s'écoulent dans le monde réel. Ce problème ne peut pas être résolu par le code d'espace utilisateur seul; vous allez avoir besoin de la coopération du noyau.

La bonne façon de résoudre cela est de rechercher quelle fonction API Win32 va suspendre votre thread pendant un nombre spécifié de millisecondes, puis appelez simplement cette fonction. Vous devriez être capable de le faire directement à partir de l'assemblage, éventuellement avec des arguments supplémentaires à votre éditeur de liens. Ou, il peut y avoir un appel système noyau NT pour effectuer cette fonction (j'ai très peu d'expérience avec les appels système NT, et honnêtement je ne sais pas à quoi ressemble la table d'appel système NT, mais une fonction de veille s'attendre à voir). Si un appel système est disponible, l'émission d'un appel système direct à partir de l'assembly est probablement le moyen le plus rapide de faire ce que vous voulez; c'est aussi le moins portable (mais alors, vous écrivez l'assemblage!).

Modifier: En regardant the NT kernel system call table, il ne semble pas y avoir d'appels liés à dormir ou obtenir la date et l'heure (comme votre code d'origine utilise), mais il y a plusieurs appels système mis en place et des minuteries de requête . Le fait de tourner pendant que vous attendez une minuterie pour atteindre le délai désiré est une solution efficace, même si elle est inélégante.

+0

tout d'abord, merci beaucoup pour la réponse élaborée. Permettez-moi d'élargir les options, alors. Comment puis-je faire un appel chaque fois que le temps est écoulé de machine à machine (c'est-à-dire que cela ne me dérange pas de le faire fonctionner tous les 500ms sur une machine et toutes les 150ms sur une autre). –

+1

Je pense que vous voulez dire "cycles/f" et non "f/cycles". –

+0

kigurai: Fixe. Soupir. yuval: Honnêtement, je ne suis pas assez familier avec les API NT pour savoir comment vous pouvez le faire de mon mieux; Je ne peux que suggérer ce que je fais toujours dans cette situation: regardez la table d'appel système et voyez ce que vous pouvez construire avec les outils dont vous disposez! – kquinn

2

Eh bien, alors. Un style ancien, non constante, boucle de retard consommateur d'énergie qui fera d'autres threads lents en cours d'exécution vers le bas ressemblerait à ceci:

 delay equ 5000 

top: mov ax, delay 
loopa: mov bx, delay 
loopb: dec bx 
     jnc loopb 
     dec ax 
     jnc loopa 

     mov ah,2 
     mov dl,'A' 
     int 21 
     jmp top 

Le retard est du second degré à la constante. Mais si vous utilisez cette boucle de délai, quelque part dans le monde, un jeune chaton innocent mourra.

1

Je n'ai pas testé ce code, mais concept doit travailler ... Save/es restaurer registre est facultative! Vérifiez le code attentivement!

DelayProcedure: 
    push es      //Save es and load new es 
    mov ax, 0040h 
    mov es, ax 
//Pseudo atomic read of 32 bit DOS time tick variable 
PseudoAtomicRead1: 
    mov ax, es:[006ch] 
    mov dx, es:[006eh] 
    cmp ax, es:[006ch] 
    jne PseudoAtomicRead1 
//Add time delay to dx,ax where smaller is faster and 18 is close to 1 second 
    add ax, 3 
    adc dx, 0 
//1800AFh is last DOS time tick value so check day overflow 
    mov cx, ax 
    mov bx, dx 
//Do 32 bit subtract/compare 
    sub cx, 00AFh 
    sbb dx, 0018h 
    jbe DayOverflow 
//Pseudo atomic read of 32 bit DOS time tick variable 
PseudoAtomicRead2: 
    mov cx, es:[006ch] 
    mov bx, es:[006eh] 
    cmp cx, es:[006ch] 
    jne PseudoAtomicRead2 
NotZero: 
//At last do 32 bit compare 
    sub cx, ax 
    sbb bx, dx 
    jae Exit 
//Check again day overflow because task scheduler can overjumps last time ticks 
    inc bx    //If no Day Overflow then bx = 0FFh 
    jz PseudoAtomicRead2 
    jmp Exit 
DayOverflow: 
//Pseudo atomic read of 32 bit DOS time tick variable 
PseudoAtomicRead3: 
    mov ax, es:[006ch] 
    mov dx, es:[006eh] 
    cmp dx, es:[006ch] 
    jne PseudoAtomicRead3 
//At last do 32 bit compare 
    sub ax, cx 
    sbb dx, bx 
    jb PseudoAtomicRead3 
Exit: 
    pop es      //Restore es 
    ret 
0

.. Le problème avec tous les exemples de code ci-dessus est qu'ils utilisent des opérations non bloquantes. Si vous examinez l'utilisation de l'UC pendant une période d'attente relativement longue, vous verrez qu'elle tourne autour de 50%. Ce que nous voulons, c'est utiliser une fonction DOS ou BIOS qui bloque l'exécution de sorte que l'utilisation du processeur soit proche de 0%.

.. D'accord, la fonction BIOS INT 16h, AH = 1 me vient à l'esprit. Vous pouvez être en mesure de concevoir une routine qui appelle cette fonction, puis insère une séquence de touches dans le tampon du clavier lorsque le temps a expiré. Il y a de nombreux problèmes avec cette idée;), mais cela peut être matière à réflexion. Il est probable que vous écrivez une sorte de gestionnaire d'interruption.

.. Dans l'API Windows 32 bits, il existe une fonction "Sleep". Je suppose que tu pourrais t'abandonner à ça.

4

utilisation INT 15h, 86h fonction:

appel Avec: AH = 86h CX: DX = intervalle uS

+0

C'est la meilleure réponse. – clearlight

Questions connexes