assembly - registers - lenguaje ensamblador x86
Instrucción ilegal en ASM: lock cmpxchg dest, src (5)
Necesita cmpxchgl %edx, (%ecx)
Esta operación no tiene sentido a menos que el destino sea un operando de memoria, sin embargo, la instrucción permite un destino de registro. La CPU fallará si la instrucción usa un modo de registro.
Lo intenté, tu código funciona con un operando de memoria. No sé si te das cuenta de esto, pero esta secuencia (con un destino de registro) tiene un nombre popular: "el error f00fc7c8" o " el error F00F ". En los días de Pentium, esta era una instrucción de "HCF" (detener y capturar incendios) o "asesinatos asesinos", ya que generaría una excepción que no sería capaz de atender porque el autobús estaba bloqueado, y era invocable por el usuario. modo. Creo que puede haber una solución de software de nivel de sistema operativo.
He estado jugando con algunos ensambles x86 ya que vienen en varias de mis clases. En particular, he querido exponer Compare-and-swap (CAS) como una función de usuario. Esto es con la intención de que pueda implementar mis propios bloqueos.
Estoy usando Linux 2.6.31 con GCC 4.1.1 en una CPU Intel.
Tengo lo siguiente:
// int cmpxchg(int *dest, int expected, int update)
.globl cmpxchg
cmpxchg:
pushl %ebp
movl %esp, %ebp
// edx holds dest
movl 8(%ebp), %edx
// eax holds expected value
movl 12(%ebp), %eax
// ecx holds the new value
movl 16(%ebp), %ecx
// cmpxchg dest_addr, exp_value
// compare to %eax is implicit
lock cmpxchgl %edx, %ecx
leave
ret
Esto está dentro de un archivo * .s, que compilo con mi programa de controlador. Cuando incluyo la línea
lock cmpxchgl %edx, %ecx
y ejecutar, recibo un error de "instrucción ilegal". Cuando reemplazo la línea con
cmpxchgl %edx, %ecx
mi código parece funcionar bien.
En primer lugar, ¿es necesario el lock
? No estoy seguro de si cmpxchgl
es naturalmente atómico, así que usé el lock
para estar seguro. Como programa de usuario, ¿incluso puedo usar el lock
?
Gracias
=============================================== ==============
Mi código final (para aquellos que pueden vagar por aquí en el futuro):
// int cmpxchg(int *dest, int expected, int update)
.globl cmpxchg
cmpxchg:
pushl %ebp
movl %esp, %ebp
// edx holds dest, use eDx for Destination ;-)
movl 8(%ebp), %edx
// eax holds expected value implicitly
movl 12(%ebp), %eax
// cmpxchg dest_add, src_value
lock cmpxchgl %edx, 16(%ebp)
leave
ret
Curioso, ¿este código final sigue siendo correcto? Por lo que puedo ver, está haciendo la comparación al revés, es decir, está comparando el valor del puntero (es decir, la dirección real a la que hace referencia el puntero) con el número entero que se usa como actualización ... además el destino se establece como el int temporal que se usa como el valor de actualización. En otras palabras, en lugar de:
lock cmpxchgl %edx, 16(%ebp)
Creo que querrías algo como:
//move the update value into ecx register
movl 0x16(%ebp), %ecx
//do the comparison between the value at the address pointed to by edx and eax,
//and if they are the same, copy ecx into the address being pointed to by edx
lock cmpxchgl %ecx, (%edx)
¿El código original realmente funcionó como estaba planeado (no solo compilado) y, de no ser así, terminaste reorganizando el código para que se parezca más al anterior?
El programa compila bien aquí (GNU como 2.20) (lo pegué en test.s y lo ejecuté como -o test.o test.s )
En cuanto a la cerradura, la documentación de Intel dice:
Esta instrucción se puede usar con un prefijo LOCK para permitir que la instrucción se ejecute atómicamente. Para simplificar la interfaz con el bus del procesador, el operando de destino recibe un ciclo de escritura sin tener en cuenta el resultado de la comparación. El operando de destino se escribe de nuevo si la comparación falla; de lo contrario, el operando fuente se escribe en el destino. (El procesador nunca produce una lectura bloqueada sin producir también una escritura bloqueada).
La respuesta de Ross ya dice la mayor parte de esto, pero intentaré aclarar un par de cosas.
- Sí, un prefijo
LOCK
es necesario si quieres atomicidad. La única excepción a esto es laXCHG
(noCMPXCHG
), que está bloqueada de manera predeterminada, como señaló asveikau. - Sí, es perfectamente legal usar
LOCK
desde el código de modo de usuario. - Sí, es perfectamente legal usar
CMPXCHG
con un operando de destino de registro.
Dicho esto, no es legal usar un LOCK CMPXCHG
junto con un operando de destino de registro. Citando el volumen 2A del manual IA-32 (página 3-538 en mi copia):
El prefijo LOCK solo se puede anteponer a las siguientes instrucciones y solo a aquellas formas de las instrucciones donde el operando de destino es un operando de memoria: ADD, ADC, AND, BTC, BTR, BTS, CMPXCHG, CMPXCHG8B, DEC, INC, NEG, NOT, OR, SBB, SUB, XOR, XADD y XCHG.
Parece poco probable que sea la fuente del problema, pero la documentación oficial también indica que la instrucción no es compatible antes que la arquitectura 486. ¿Esto ocurre en modo real, con anulaciones de modo protegido?