¿Alguna vez te has preguntado cuál es el motivo que lleva a que un programa compilado para funcionar en las CPU x86 para PC, no funcione en una CPU ARM por ejemplo? ¿Qué es lo que hace que un programa sea compatible con una familia de procesadores concreta y con otras no? ¿Qué son los registros y cual es su función? En este artículo os explicamos lo que son los registros y las instrucciones de una CPU para que podáis comprenderlo.
Muchas veces hemos hablado de que los procesadores cuentan con un conjunto de instrucciones concreto, o de que en una nueva CPU se han añadido nuevos registros e instrucciones. Vamos a ver qué es lo que esto quiere decir.
¿Qué es una instrucción?
Una instrucción no es más que una acción que mandamos a hacer en un procesador. Las instrucciones pueden ser operaciones aritméticas con diferentes tipos de datos como coma flotante, enteros, vectorial, escalar, operaciones lógicas, operaciones de movimientos de datos, operaciones de movimiento de bits (donde un bit es cambiado de posición), operaciones de salto, etc.
Esas mismas instrucciones se dividen en otros sub-tipos según donde se encuentren los datos. Por ejemplo, algunas instrucciones permiten operar con los datos que se encuentran en los registros en ese momento, mientras que en otros casos hemos de marcar la dirección de memoria en la que se encuentra el dato (modo directo) o la dirección de la dirección de memoria (modo indirecto).
A los conjuntos de registros e instrucciones de las CPU se les llama ISA (Instruction Set Architecture) y todos bajo la misma ISA utilizan la misma codificación de las instrucciones y por tanto el mismo código binario para ellas.
Independientemente de cuál sea la CPU que esté utilizando nuestro sistema, todas ellas leen el código binario de una manera particular correspondiente a su familia. Lo que hacen es tomar una cantidad de determinada de bits del código binario que están ejecutando e interpretan su significado según la disposición de éste. Toda instrucción está codificada de la siguiente manera: los primeros dígitos corresponden al código de la instrucción y cómo se ha de ejecutar ésta, y los últimos bits son el dato mismo o dónde se encuentra el dato sobre el que queremos realizar la instrucción.
Registros y conjunto de instrucciones
Todas las familias de procesadores tienen un lenguaje ensamblador común dentro de la misma, cuyas instrucciones tienen una correlación 1:1 con el conjunto de registros e instrucciones de dicha familia de procesadores. En la tabla de arriba podéis ver la relación entre las diferentes instrucciones del lenguaje ensamblador de los x86 con su código de instrucción, el cual en la tabla se encuentra expresado en hexadecimal.
Hay que tener en cuenta que continuamente se añaden instrucciones nuevas a las ISAs, lo que lleva a que programas muy nuevos que utilicen expresamente esas nuevas instrucciones solo funcionen en procesadores que las soporten. En general, los conjuntos de instrucciones se mantienen estables en el tiempo con pocos cambios, pero de tanto en cuando se introducen instrucciones para ciertos mercados específicos que acaban formando parte del estándar o son descartadas después.
También existe el caso de nuevas instrucciones más eficientes que las ya existentes, pero en el que no se eliminan dichas instrucciones del conjunto por existir una gran cantidad de software dependiente de ellas en el mercado.
RISC vs CISC vs Post-RISC
Los procesadores RISC tienen pocas instrucciones por lo que necesitan suplir la falta de instrucciones por otras más complejas, pero a cambio consiguen una mayor velocidad a la hora de ejecutarlas debido a su ligereza. Los procesadores CISC en cambio tienen conjuntos de instrucciones mucho más complejos que requieren una construcción mas compleja del hardware, pero a cambio realizan dichas instrucciones en menos ciclos.
Dicha diferencia, aunque polémica en su día, ya no lo existe debido a que a partir de la aparición en PC de la CPU Pentium Pro en PC pasamos a lo que llamamos época Post-RISC en la que pese a que los programas utilizan un conjunto de registros e instrucciones éstos son convertidos en un microcódigo de instrucciones más simples en el proceso, permitiendo que arquitecturas CISC se puedan comportar como arquitecturas RISC y alcanzar grandes velocidades de reloj a base de descomponer instrucciones complejas en microinstrucciones.
Los registros de las CPUs x86 contemporáneas no están diseñados de cara el set de instrucciones x86 sino de cara al microcódigo en el que la unidad de control descodifica las diferentes instrucciones.
Los registros comunes en todas las CPUs
Los registros son la memoria más cercana a un procesador que existe y por tanto la más rápida en cuanto a velocidad de acceso. Se trata de memorias muy pequeñas a las que la unidad de control y la de ejecución tienen tienen acceso directo para realizar ciertas funciones concretas.
Los registros más comunes en una CPU independientemente de su ISA son:
- Para los cálculos aritméticos ejecutados en la ALU se utilizan los registros del tipo acumulador, cada set de registros e instrucciones tiene una cantidad diferente de registros del tipo acumulador.
- Los registros de acceso a la memoria se divide en dos tipos. Por un lado unos registros contienen la dirección de memoria donde se encuentran los datos. Por el otro los datos que se han de escribir o los que se han de leer.
- Los registros de propósito general no tienen una utilidad concreta y especifica, se utilizan en algunas arquitecturas para poder acceder a ellos con la menor latencia posible.
- El contador de programa en un registro que indica la dirección de memoria de la siguiente instrucción a ejecutar. Las instrucciones de salto las modifican cuando no se quiere acceder a a la siguiente instrucción sino a otra parte del programa. En cada ciclo de instrucción completo se incrementa la dirección de memoria en 1 y esta asociado al bus de direcciones del procesador.
Interpretación de una ISA a otra a tiempo real
Incluso si tuviéramos un conversor 1:1 del código de las instrucciones seguiríamos teniendo problemas porque aunque dos procesadores puedan tener una misma instrucción de suma nos podemos encontrar con que la forma de utilizar los registros y los registros que usan son distintos y que incluso haya registros que se encuentran en una familia y no en otros. Un ejemplo de estas dificultades lo encontraron tanto Microsoft como Qualcomm a la hora de adaptar Windows 10 a ARM para que todas las aplicaciones x86 funcionasen sin problemas en una CPU ARM.
No obstante, hay soluciones como el hecho de utilizar un software de traducción de instrucciones. Dicho software traslada el código binario a un código intermedio paro luego trasladarlo al código binario del procesador objetivo en que el que queremos correr la aplicación. Obviamente este proceso es mucho más lento y solo es recomendable para ejecutar software muy antiguo de familias de procesadores inexistentes en el mercado.