2009-12-07 10 views
10

Estoy intentando crear un procedimiento de suspensión/demora en el ensamblaje MASM de 16 bits x86 que, por ejemplo, imprimirá un personaje en la pantalla cada 500 ms. A partir de la investigación que he realizado, parece que hay tres métodos para lograr esto: me gustaría usar uno que use marcas de reloj de la CPU.¿Cómo puedo crear una función de suspensión en ensamblaje MASM de 16 bits x86?

Tenga en cuenta que estoy ejecutando Windows XP a través de VMWare Fusion en Mac OS X Snow Leopard - No estoy seguro de si eso afecta algo.

¿Podría alguien señalarme en la dirección correcta, o proporcionar un código de trabajo que pueda modificar? ¡Gracias!

El código que he encontrado debe imprimir 'A' en la pantalla cada segundo, pero no funciona (me gustaría usar milisegundos de todos modos).

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: Siguiendo el consejo de GJ, aquí hay un procedimiento de trabajo. Sólo tiene que llamar que

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

¿Está ejecutando su código en el espacio de usuario? En Windows? – nico

+0

Sí. Lo estoy ejecutando en Windows a través de una máquina virtual, como se indica en mi publicación :) –

+1

Tenga cuidado a la medianoche que puede tener un problema. Tal vez es mejor idea leer directamente la ubicación de la memoria de marcación en 0x0040: 0x0070. Lea también: http://www.merlyn.demon.co.uk/pas-time.htm#RDT –

Respuesta

3

Actualmente se puede utilizar la interrupción de ROM BIOS 1Ah función 00h, 'Leer contador de reloj actual'. O puede leer dword en la dirección $ 40: $ 6C pero debe asegurarse de lectura atómica. Se incrementa en MS-DOS a aproximadamente 18.2 Hz.

Para más información lea: The DOS Clock

+3

'El enlace del Reloj DOS' me lleva a una página en blanco. –

9

Esto no se puede hacer de pura MASM. Todos los viejos trucos para establecer un retraso fijo operan bajo la suposición de que usted tiene el control total de la máquina y es el único hilo que se ejecuta en una CPU, de modo que si espera 500 millones de ciclos, habrá transcurrido exactamente 500,000,000/f segundos (para una CPU a la frecuencia f); eso sería 500ms para un procesador de 1GHz.

Dado que se está ejecutando en un sistema operativo moderno, está compartiendo la CPU con muchos otros hilos (entre ellos, el kernel, haga lo que haga, no puede tener prioridad sobre el kernel), esperando 500 millones de ciclos en solo su hilo significará que más de 500 millones de ciclos transcurrirán en el mundo real. Este problema no puede resolverse solo con el código de espacio de usuario; vas a necesitar la cooperación del kernel.

La forma correcta de resolver esto es buscar qué función Win32 API suspenderá su hilo durante un número específico de milisegundos, luego simplemente llame a esa función. Debería poder hacer esto directamente desde el ensamblado, posiblemente con argumentos adicionales para su enlazador. O bien, puede haber una llamada al sistema NT kernel para realizar esta función (tengo muy poca experiencia con las llamadas al sistema NT, y sinceramente no tengo idea de cómo se ve la tabla llamada del sistema NT, pero una función sleep es el tipo de cosa que podría esperar ver). Si hay una llamada al sistema disponible, la emisión de una llamada directa al sistema desde el ensamblado es probablemente la forma más rápida de hacer lo que desea; también es el menos portátil (¡pero entonces estás escribiendo el ensamblaje!).

Editar: En cuanto a the NT kernel system call table, allí no parece ser ninguna llamada relacionados con el dormir o conseguir la fecha y la hora (como utiliza su código original), pero hay varias llamadas al sistema para configurar y temporizadores de consulta . Girar mientras espera que un temporizador alcance el retraso deseado es una solución efectiva, aunque poco elegante.

+0

primero, muchas gracias por la respuesta elaborada. Déjame ampliar las opciones, entonces. ¿Cómo hago una llamada cada cantidad de tiempo fija que puede pasar de una máquina a otra (es decir, no me importa que se ejecute cada 500 ms en una máquina y cada 150 ms en otra). –

+1

Creo que te refieres a "ciclos/f" y no a "f/ciclos". –

+0

kigurai: Reparado. Suspiro. yuval: Honestamente, no estoy lo suficientemente familiarizado con las API de NT para saber cómo puedes hacerlo en la cabeza; Solo puedo sugerir lo que siempre hago en esta situación: ¡mire la tabla de llamadas del sistema y vea qué puede construir con las herramientas que tiene! – kquinn

2

Bueno, entonces. Un estilo antiguo, no constante, el consumo de potencia de bucle de retardo que hará que otros subprocesos que se ejecutan desaceleración se vería así:

 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 

El retraso es cuadrática para la constante. Pero si usas este ciclo de retraso, en algún lugar del mundo morirá un gatito inocente.

1

no he probado este código, pero el concepto es necesario hacer ... Guardar/restaurar ES registro es opcional! ¡Verifique el código cuidadosamente!

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

.. El problema con todos los ejemplos de código anteriores es que usan operaciones sin bloqueo. Si examina el uso de la CPU durante un período de espera relativamente largo, verá que se ejecuta alrededor del 50%. Lo que queremos es usar alguna función de DOS o BIOS que bloquee la ejecución para que el uso de la CPU sea cercano al 0%.

.. De mano, me viene a la mente la función BIOS INT 16h, AH = 1. Es posible que pueda diseñar una rutina que llame a esa función, luego inserte una tecla en el búfer de teclado cuando haya expirado el tiempo. Existen numerosos problemas con esa idea;), pero puede ser algo para pensar. Es probable que esté escribiendo algún tipo de manejador de interrupciones.

.. En la API de Windows de 32 bits, hay una función de "suspensión". Supongo que podrías decir eso.

4

uso 15h INT, la función 86h:

llamada Con: AH = 86h CX: DX = intervalo en nosotros

+0

Esta es la mejor respuesta. – clearlight

Cuestiones relacionadas