[imagen obtenida de:http://www.elclarin.cl/web/images/stories/2012/marzo/hardware.jpg]
¿Qué es un registro?
En el hardware de las computadoras los registros son memorias que están cerca del procesador, los cuales son la memoria mas rápida de acceder por este, por tanto son muy valiosos para operaciones matemáticas y es muy común que operaciones en ensamblador reciban parámetros dentro de estos espacios de memoria.
Una de las desventajas de estos es que son bastante caros y esto hace que los registros dentro de nuestros ordenadores tengan una capacidad de almacenamiento bastante limitada.
En ensamblador podemos trabajar directamente con los valores de los registros, cosa que no pasa en lenguajes de capas superiores, utilizados de la manera correcta estos pueden ser una herramienta muy útil para generar programas potentes.
Principales registros ensamblador
Dentro de la familia de procesadores x86 existen 8 registros generales cada uno con características diferentes y usado para objetivos particulares, cada registro tiene una capacidad de 32 bits, pero en mis ejemplos quizás trabajaré con instrucciones de 64 bits puesto que la maquina a la que tengo acceso tiene una arquitectura de 64 bits y el compilador "cc" me lo permite.
Los registros generales son los siguientes:
- AX - Accumulator Register
- BX - Base Register
- CX - Counter Register
- DX - Data Register
- SI - Source Index
- DI - Destination Index
- BP - Base Pointer
- SP - Stack Pointer
Se puede trabajar solamente con partes del registro por ejemplo AX representa una parte de 8 bits de tamaño de Accumulator Register, pero si quisieramos trabajar con los 32 bits del registro agregamos la letra "E" al inicio del nombre("EAX") y para registros de 64 bits se utilizaría la letra "R"("RAX")
[imagen obtenida de: http://www.cs.virginia.edu/~evans/cs216/guides/x86-registers.png]
Accumulator Register(EAX)
Este registro sirve para realizar operaciones como por ejemplo sumar o restar. En algunas arquitecturas este tipo de operaciones solo podría llevarse a cabo en un registro especifico para esto, pero en la arquitectura x86 se puede usar cualquier registro, pero es mas recomendable usar EAX puesto que este ya esta optimizado para ser utilizado de esta manera.
Counter Register(ECX)
Este registro es bastante obvio es un registro contador, normalmente se utiliza dentro de ciclos para saber cuando detenerse por lo tanto esto lo hace recomendable para comparaciones.
Data Register(EDX)
Este registro esta íntimamente ligado con el registro "EAX" este se utiliza cuando los datos con los que trabajamos son muy grandes y no caben en "EAX" entonces la parte mas significativa de los bits del dato se guarda en "EAX" y los menos significativos en el registro "EAX"
Source Index(ESI)
Este se utiliza cuando se requiere trasladar datos de un bloque del programa a otro por ejemplo pasar datos de una función a otra.
Destination Index(EDI)
Este se utiliza cuando se requiere trasladar datos de un bloque del programa a otro por ejemplo asegurar datos en un ciclo para no tener que moverlos a la memoria principal. La diferencia con el registro ESI es que este es orientado a la escritura y el otro a la lectura.
Base Pointer(EBP) y Stack Pointer(ESP)
Este registro esta ligado a la pila de memoria de ensamblador, este registro y el registro "ESP" son la base para la utilización de funciones en el lenguaje ensamblador x86. Se usa para llevar control la abstracción a la cual se le llama función, la memoria principal se utiliza para llevar control sobre en que parte del programa se esta(función) y para saber que bloques de memoria serán utilizados por esta función. El registro "EBP" contiene un puntero al primer bloque de la memoria del rango de bloques para la función y "ESP" representa el ultimo bloque de memoria de una función.
Base Register(EBX)
Este registro es de uso general y no tiene ningún proposito en especifico.
Bloques de código ensamblador
Debido a que una computadora tiene normalmente una gran cantidad de procesos ejecutandose al mismo tiempo(por turnos) es necesario dividir nuestros programas en pequeños bloques de instrucciones dentro de las funciones para que los procesos que se ejecutan al mismo tiempo se turnen ejecutando sus bloques. Para dividir estos bloques se utilizan etiquetas con el siguiente formato ".nombre_de_la_etiqueta:" además estos bloques son utilizados para generar iteraciones en el lenguaje ensamblador.
A manera de ejemplo pongo el siguiente código donde se utilizan bloques de código y comparaciones para generar una iteración donde se imprimen los números del 0 al 9 utilizando un contador.
.globl main
main:
.LFB1:
pushq %rbp
movq %rsp, %rbp
subq $16, %rsp
movl $0, %ecx ; Mueve el valor incial cero ala variable que servira de contador
jmp .L3 ; De aqui salta al bloque de codigo .L3
.L4:
movl %ecx, %edi ;Mueve el valor de %ecx al registro %edi para mover el valor a la funcion que se va a ejecutar
call print ;Esta funcion yo la defino recibe un numero entero y lo imprime con un formato especifico con la función printf
addl $1, %ecx ;Suma uno al valor del registro
.L3:
cmpl $9, %ecx ;Ahora compara el valor de %exc si es mayor que 9 (%ecx > 9)
jle .L4 ;En caso de que no se cumpla esa condición salta al bloque .L4
leave
ret
Al correrlo me genera la siguiente salida:
El programa no imprime los valores correctos en la terminal debido a que los registros son pocos y tienen una capacidad bastante pequeña, entonces de un bloque a otro, otro proceso podría modificar los valores dentro de los registros por tanto cuando se compara la primera vez tal vez haya conservado el valor 0 pero cuando vuelve a compararse pudo haber sido modificado por otro proceso por ejemplo la función printf puedo haberlo utilizado y haber puesto un valor mayor a 9 en el registro.
En estos casos resultaría útil utilizar la memoria principal para conservar los valores, pues dado que esta es mas grande se puede definir una cantidad de memoria a utilizar por el programa y de esta una mas para la función, lo cual hace los datos más fácilmente respetados por otros procesos. Entonces para corregir este código lo que hice fue:
.globl main
main:
.LFB1:
pushq %rbp
movq %rsp, %rbp
subq $16, %rsp
movl $0, -4(%rbp) ;Mueve el valor 0 a la memoría principal
jmp .L3
.L4:
movl -4(%rbp), %edi ;Mueve el valor principal al registro %edi
call print
movl -4(%rbp), %eax ;Recupera el valor de la memoria
addl $1, %eax ;Suma 1 al valor de %eax
movl %eax, -4(%rbp) ;Devuelve el valor a la memoria principal
.L3:
movl -4(%rbp), %ecx ;Recupera el contador de la memoria principal
cmpl $9, %ecx ;Compara si es menor que 9 valor en el registro
jle .L4 ;Salta al bloque .L4 si la condición no se cumplio
leave
ret
Y el resultado es el siguiente:
Con esto podemos comprobar que a pesar de que los registros son de muy rápido acceso, son muy limitantes en el sentido de que son escasos y tienen que se repartidos entre procesos en el sistema. A pesar de ello los registros son esenciales en las arquitecturas de computadoras y mezclados con otros medios de almacenamiento y los algoritmos correctos esto los hace muy necesarios y útiles.
Referencias
http://www.swansontec.com/sregisters.html
http://www.cs.virginia.edu/~evans/cs216/guides/x86.html

Muy bien; 10 pts para el lab de integrados
ResponderBorrar