No se obtiene la salida esperada utilizando cmpxchg8b durante mucho tiempo sin firmar

Estoy intentando escribir un código de ensamblaje en línea de comparación e intercambio simple. Aqui esta mi codigo

#include  #include  #include  static inline unsigned long cas(volatile unsigned long* ptr, unsigned long old, unsigned long _new) { unsigned long prev=0; asm volatile("lock cmpxchg8b %0;" : "=m"(prev) : "m"(*ptr),"a"(old),"c"(_new) ); return prev; } int main() { unsigned long *a; unsigned long b=5,c; a=&b; c=cas(a,b,6); printf("%lu\n",c); return 0; } 

Idealmente, este código debería imprimir 5, pero está imprimiendo 0. ¿Qué está mal en mi código? Por favor, ayuda.

Permítanme comenzar diciendo “Usar inline asm es una mala idea”. Y permítame repetir que “usar inline asm es una mala idea”. Podría escribir una entrada de wiki completa sobre por qué usar inline asm es una mala idea. Por favor, considere usar builtins (como __sync_bool_compare_and_swap de gcc) o bibliotecas como en su lugar.

Si está escribiendo software de producción, es casi seguro que los riesgos del uso de inline asm son mayores que cualquier beneficio. Si estás escribiendo para propósitos educativos, sigue leyendo.

(Para una ilustración más detallada de por qué no debe usar asm en línea, espere a que Michael o Peter aparezcan y señale todas las cosas incorrectas con este código. Es muy difícil, incluso para las personas que saben esto, hacerlo bien. .)

Aquí hay un código que muestra cómo usar cmpxchg8b . Es simple, pero debería ser suficiente para dar una idea general.

 #include  // Simple struct to break up the 8 byte value into 32bit chunks. typedef union { struct { unsigned int lower; unsigned int upper; }; unsigned long long int f; } moo; unsigned char cas(moo *ptr, moo *oldval, const moo *newval) { unsigned char result; #ifndef __GCC_ASM_FLAG_OUTPUTS__ asm ("lock cmpxchg8b %[ptr]\n\t" "setz %[result]" : [result] "=q" (result), [ptr] "+m" (*ptr), "+d" (oldval->upper), "+a" (oldval->lower) : "c" (newval->upper), "b" (newval->lower) : "cc", "memory"); #else asm ("lock cmpxchg8b %[ptr]" : [result] "=@ccz" (result), [ptr] "+m" (*ptr), "+d" (oldval->upper), "+a" (oldval->lower) : "c" (newval->upper), "b" (newval->lower) : "memory"); #endif return result; } int main() { moo oldval, newval, curval; unsigned char ret; // Will not change 'curval' since 'oldval' doesn't match. curval.f = -1; oldval.f = 0; newval.f = 1; printf("If curval(%u:%u) == oldval(%u:%u) " "then write newval(%u:%u)\n", curval.upper, curval.lower, oldval.upper, oldval.lower, newval.upper, newval.lower); ret = cas(&curval, &oldval, &newval); if (ret) printf("Replace succeeded: curval(%u:%u)\n", curval.upper, curval.lower); else printf("Replace failed because curval(%u:%u) " "needed to be (%u:%u) (which cas has placed in oldval).\n", curval.upper, curval.lower, oldval.upper, oldval.lower); printf("\n"); // Now that 'curval' equals 'oldval', newval will get written. curval.lower = 1234; curval.upper = 4321; oldval.lower = 1234; oldval.upper = 4321; newval.f = 1; printf("If curval(%u:%u) == oldval(%u:%u) " "then write newval(%u:%u)\n", curval.upper, curval.lower, oldval.upper, oldval.lower, newval.upper, newval.lower); ret = cas(&curval, &oldval, &newval); if (ret) printf("Replace succeeded: curval(%u:%u)\n", curval.upper, curval.lower); else printf("Replace failed because curval(%u:%u) " "needed to be (%u:%u) (which cas has placed in oldval).\n", curval.upper, curval.lower, oldval.upper, oldval.lower); } 

Algunos puntos:

  • Si falla el cas (porque los valores no coinciden), el valor de retorno de la función es 0, y el valor que necesita usar se devuelve en el valor antiguo. Esto hace que intentarlo de nuevo sea simple. Tenga en cuenta que si ejecuta subprocesos múltiples (lo que debe ser o no usaría el lock cmpxchg8b ), un segundo bash también podría fallar, ya que el “otro” hilo podría haberlo superado nuevamente.
  • El __GCC_ASM_FLAG_OUTPUTS__ define está disponible en versiones más recientes de gcc (6.x +). Te permite saltar haciendo setz y usar las banderas directamente. Ver los documentos de gcc para más detalles.

En cuanto a cómo funciona:

Cuando llamamos a cmpxchg8b , le pasamos un puntero a la memoria. Va a comparar el valor (8 bytes) que se encuentra en esa ubicación de memoria con los 8 bytes en edx: eax. Si coinciden, escribirá los 8 bytes en ecx: ebx en la ubicación de la memoria y se establecerá el indicador zero . Si no coinciden, el valor actual se devolverá en edx: eax y se borrará el indicador zero .

Entonces, compare eso con el código:

  asm ("lock cmpxchg8b %[ptr]" 

Aquí estamos pasando el puntero a los 8 bytes a cmpxchg8b .

  "setz %[result]" 

Aquí estamos almacenando el contenido del indicador de zero establecido por cmpxchg8b en (resultado).

  : [result] "=q" (result), [ptr] "+m" (*ptr), 

Especifique que (resultado) es una salida (=) y que debe ser un registro de bytes (q). Además, el puntero de memoria es una entrada + salida (+) ya que lo leeremos y le escribiremos.

  "+d" (oldval->upper), "+a"(oldval->lower) 

Los signos + nuevamente indican que estos valores están dentro + fuera. Esto es necesario ya que si la comparación falla, edx: eax se sobrescribirá con el valor actual de ptr.

  : "c" (newval->upper), "b"(newval->lower) 

Estos valores son de entrada solamente. El cmpxchg8b no va a cambiar sus valores, por lo que los colocamos después del segundo colon.

  : "cc", "memory"); 

Ya que estamos cambiando las banderas, necesitamos informar al comstackdor a través de “cc”. La restricción de “memoria” puede no ser necesaria, dependiendo de para qué se está usando cas. Es posible que el subproceso 1 notifique al subproceso 2 que algo está listo para procesarse. En ese caso, debe asegurarse de que gcc no tenga ningún valor en los registros que planea escribir en la memoria más adelante. Es absolutamente necesario que los descargue en la memoria antes de ejecutar el cmpxchg8b .

Los documentos de gcc describen en detalle el funcionamiento de la statement asm extendida. Si partes de esta explicación aún no están claras, algunas lecturas podrían ayudar.

Por cierto, en caso de que olvidara mencionar, escribir inline asm es una mala idea …

Lo siento por no responder a tu pregunta directamente, pero mi pregunta es: ¿por qué no usar C11 o C ++ 11 ? Es mucho menos propenso a errores que escribir sus propias funciones y tiene la ventaja de que no está apuntando a una architecture de hardware o comstackdor específico.

En su caso, debe utilizar atomic_compare_exchange_weak() o atomic_compare_exchange_strong() .