Capítulo 4. Arquitectura del Intel Pentium

Tabla de contenidos

4.1. El entorno de ejecución del procesador Intel Pentium
4.1.1. Espacio de direcciones
4.1.2. Registros de propósito general
4.1.3. Registro de estado y control
4.1.4. El registro contador de programa
4.1.5. Otros registros del Intel Pentium
4.1.6. Estado visible de un programa
4.2. Ciclo de ejecución de una instrucción
4.2.1. Fase de fetch
4.2.2. Fase de decodificación inicial
4.2.3. Fase de decodificación final
4.2.4. Fase de ejecución
4.2.5. Fase de escritura de resultados
4.2.6. Ejecución de una instrucción
4.2.7. Ciclo de ejecuciones en procesadores actuales
4.3. La pila
4.3.1. Instrucciones de manejo de la pila
4.3.2. El puntero de pila
4.3.3. Valores iniciales del puntero de pila
4.4. Ejercicios

La arquitectura de un procesador consiste de los elementos internos que permiten la ejecución de las instrucciones de su lenguaje máquina. La complejidad de un procesador actual es demasiado grande para poder ser estudiada en su totalidad. A modo de referencia, los modelos del procesador Pentium producidos a comienzos del año 2004 contienen alrededor de 50 millones de transistores (en diciembre de 1974, el procesador 8080 producido por la misma empresa tenía 6000 transistores). Este tipo de circuitos se diseñan a lo largo de varios años y por grandes equipos de diseñadores. La figura 4.1 muestra a la izquierda la imagen del Pentium 4 del año 2001 así como su tamaño[1]. En la parte derecha se muestra el mismo chip comparado con una moneda de un céntimo de euro (16.25 mm de diámetro).

Figura 4.1. Pentium 4 Northwood (Dic. 2001). Fuente: Intel Technology Journal, vol. 6, núm. 2.

Pentium 4 Northwood (Dic. 2001). Fuente: Intel Technology Journal, vol. 6, núm. 2.

La complejidad de diseño de estos circuitos viene acompañada por una tecnología de fabricación que permite un empaquetado en dispositivos de tamaño extremadamente reducido. La figura 4.2 muestra el aspecto de un Pentium 4 ya empaquetado y listo para ser instalado en una placa de circuito impreso.

Figura 4.2. Chip con un procesador Pentium 4 en su interior

Chip con un procesador Pentium 4 en su interior

Se explican a continuación, los elementos básicos de la arquitectura del procesador para entender el funcionamiento y la manipulación de datos sin analizarlo en su totalidad. Se aplica, por tanto, un nivel de abstracción a la arquitectura real del procesador Intel Pentium y se estudian sólo aquellos componentes necesarios para la comprensión de las instrucciones que permiten la ejecución de operaciones básicas tales como cálculo aritmético, implementación de condicionales, llamadas a subrutinas, paso de parámetros, gestión de la pila, etc.

Para el desarrollo de aplicaciones avanzadas sobre un procesador de estas características sí es preciso tener un conocimiento más profundo de la arquitectura. En tal caso, los documentos en los que se encuentra el nivel de detalle necesario para esta tarea los proveen los mismos fabricantes. En lugar de explicar en detalle un procesador en concreto, generalmente las empresas fabricantes de procesadores crean una arquitectura concreta y luego fabrican múltiples chips todos ellos compatibles con esa arquitectura pero con diferentes prestaciones. En el caso concreto del procesador Intel Pentium, la empresa fabricante ofrece tres documentos de información agrupados bajo el nombre de IA-32 Intel Architecture Software Developer's Manual que contienen todos los aspectos del funcionamiento de la arquitectura denominada IA-32. El primer volumen ofrece una visión global de la arquitectura, el formato de instrucciones, el entorno de ejecución del procesador, los tipos de datos, las técnicas utilizadas para las llamadas a procedimientos y las extensiones especiales incluidas en esta arquitectura. El segundo volumen describe todas y cada una de las instrucciones máquina contenidas en la arquitectura. El tercer volumen contiene la información necesaria para utilizar el procesador en un sistema completo, como por ejemplo, los bits de control y estado, gestión de memoria, esquema de protección, manejo de excepciones e interrupciones, gestión de múltiples procesadores, memoria cache, etc.

4.1. El entorno de ejecución del procesador Intel Pentium

En esta sección se describe el entorno de ejecución del procesador tal y como se ve desde un programa escrito en lenguaje ensamblador. Este entorno consta, además de otros componentes, de un conjunto de registros, un espacio de direcciones, un registro de condiciones y estado, y el registro contador de programa.

En adelante las diferentes unidades de información que es capaz de manipular el procesador se denominarán utilizando los términos que se muestran en la tabla 4.1. Todos ellos son utilizadas por algún componente del procesador y sus tamaños son todos múltiplos de bytes.

Tabla 4.1. Nomenclatura para los tamaños de información

Denominación Tamaño
Byte 8 bits
Word 16 bits, 2 bytes
Doubleword 32 bits, 4 bytes
Quadword 64 bits, 8 bytes
Double Quadword 128 bits, 16 bytes

La figura 4.3 muestra los tamaños relativos de estos datos así como la numeración seguida para referirse a los bytes de los que están compuestos. Nótese que los bits se comienzan a numerar por el cero el menos significativo.

Figura 4.3. Tipos de datos del procesador

Tipos de datos del procesador

4.1.1. Espacio de direcciones

El procesador Pentium permite gestionar el acceso a memoria de dos formas posibles denominadas modelo lineal y modelo segmentado.

En el modelo lineal la memoria aparece como un único espacio contiguo de tamaño máximo 232 bytes o 4 gigabytes. En él se almacenan todos los datos, código y demás información necesaria para la ejecución de los programas. Las direcciones en este modelo tienen un tamaño fijo de 32 bits.

El modelo segmentado es más complejo. El espacio de direcciones se organiza como un grupo de espacios de direcciones independientes denominados segmentos. La razón por la que se propone esta técnica es para separar código, datos e información adicional de los programas en diferentes segmentos. La dirección para acceder a un byte en este modelo consta de dos partes, un identificador de segmento y un desplazamiento dentro de ese segmento. El procesador puede utilizar hasta un total de 16.383 segmentos y cada uno de ellos de un tamaño máximo de 4 gigabytes. La ventaja de gestionar la memoria de esta forma es el incremento en la seguridad en la ejecución de programas. Mediante la colocación de código y datos en segmentos separados se puede forzar una política de acceso a datos únicamente dentro del mismo segmento y así detectar fácilmente accesos a zonas de memoria incorrectas.

En el resto de este documento se utilizará únicamente el modelo lineal de memoria. Toda dirección tiene un tamaño de 32 bits y se dispone de un espacio de hasta 4 gigabytes de información almacenados de forma contigua.

Tal y como se ha descrito en la sección 3.4.5, para obtener un mejor rendimiento en el uso de memoria, el bus de datos que conecta al procesador con la memoria tiene un tamaño de 32 bits. Esto quiere decir que el procesador es capaz de manipular 4 bytes de datos en una sola operación (lectura o escritura) siempre y cuando el acceso sea alineado, es decir, que los datos estén almacenados a partir de una posición que es múltiplo de cuatro. La figura 4.4 ilustra este mecanismo. El procesador igualmente es capaz de acceder tanto a tamaños de información más pequeños como a datos no alienados, pero dichas operaciones serán más lentas.

Figura 4.4. Acceso alineado a memoria

Acceso alineado a memoria

4.1.2. Registros de propósito general

Los registros son circuitos digitales internos del procesador que se comportan igual que las celdas de memoria, es decir, permiten las operaciones de lectura y escritura de datos pero a una velocidad mucho mayor, pues no requieren la comunicación con ningún circuito externo al procesador. Los registros que ofrece un procesador se identifican por su nombre y son susceptibles de ser utilizados al escribir programas en ensamblador.

El Intel Pentium ofrece 16 registros básicos para la ejecución de programas: 8 registros de propósito general, 6 registros de segmento, el registro de estado y control, y el registro contador de programa. Los seis registros de segmento no se describen en detalle puesto que se utilizan para acceder a memoria en el modelo segmentado que no se considera en este documento.

Los registros de propósito general son 8 con nombres %eax, %ebx, %ecx, %edx, %esi, %edi, %ebp y %ebp. Todos ellos tienen un tamaño de 32 bits y su principal cometido es almacenar datos temporales necesarios para la ejecución de programas. Mientras la mayor parte de datos e instrucciones se almacenan en la memoria principal, en estos registros se guardan temporalmente aquellos datos que necesita el procesador más a menudo, de esta forma se obtiene un mejor rendimiento en la ejecución. Por ejemplo, si un dato se utiliza varias veces seguidas, en lugar de leerlo de memoria cada vez es mejor almacenarlo al principio en un registro y referirse a esa copia cada vez que sea necesario.

El procesador permite referirse a ciertas porciones de los registros de propósito general con nombres diferentes. Así, se permite manipular únicamente los 16 bits de menos peso de los ocho registros suprimiendo del nombre la letra “e” del comienzo. Por ejemplo, el registro %ax se refiere a los dos bytes de menos peso del registro %eax. Nótese que no es un registro adicional que tenga el procesador, sino la posibilidad de utilizar la mitad menos significativa de un registro. Cuando se realiza una operación sobre una porción de un registro, el resto de bits permanece intacto.

Para los primeros cuatro registros, esto es %eax, %ebx, %ecx y %edx se permite manipular los dos bytes de menos peso de forma independiente. Los nombres se obtienen mediante la segunda letra del nombre original añadiendo el sufijo “h” para el de más peso o “l” para el de menos peso. Por tanto, el registro %eax tiene un tamaño de 32 bits, sus 16 bits de menos peso se manipulan mediante el nombre %ax, el byte de menos peso mediante el nombre %al y el segundo de menos peso con %ah. La figura 4.5 muestra los ocho registros de propósito general así como los nombres para referirse a las diferentes porciones.

Figura 4.5. Registros de propósito general

Registros de propósito general

4.1.3. Registro de estado y control

Durante la ejecución de instrucciones existen situaciones especiales que convienen ser reflejadas en un registro para su posible consulta. Por ejemplo, si el resultado de una operación aritmética ha producido acarreo, es probable que un programa tenga que tomar medidas especiales. La forma de ofrecer este tipo de funcionalidad consiste en capturar estas condiciones en un registro de estado. El número de bits y condiciones que se almacenan en este registro es diferente en cada arquitectura. Un ejemplo de funcionalidad análoga a esta es el conjunto de luces e indicadores que tiene un equipo de música. Mediante esos indicadores informan al usuario de algunas de las condiciones de funcionamiento internas (nivel de audio, filtros encendidos, etc). En el contexto de un procesador es suficiente almacenar estos valores en un registro e incluir en su lenguaje máquina instrucciones para su manipulación.

Pero aparte de las condiciones de funcionamiento, existe un conjunto de funcionalidades que es preciso activar o desactivar en ciertos momentos de la ejecución de un procesador. Continuando con la analogía del equipo de música, este ofrece un conjunto de interruptores o mandos para controlar ciertos aspectos de funcionamiento del dispositivo. Un procesador ofrece también esta posibilidad a través de los denominados bits de control y que suelen almacenarse también en el registro de estado y control. Por ejemplo, el procesador Intel Pentium permite que una instrucción sea interrumpida y se pase a ejecutar momentáneamente un conjunto de instrucciones. Mediante un bit de control se permite o prohibe que estas interrupciones se produzcan.

El registro de estado y control del procesador Intel Pentium se denomina “Eflags” y consta de 32 bits. La figura 4.6 muestra su estructura, en la que se comprueba que de los 32 bits tan sólo 18 de ellos contienen información sobre el estado y control, el resto contienen un valor fijo.

Figura 4.6. Registro de estado y control

Registro de estado y control

Las condiciones que representan los bits más importantes de este registro son:

  • Bit de acarreo (CF): Su valor es 1 si una operación aritmética con naturales ha producido acarreo. Este bit se utiliza, por tanto para detectar situaciones de desbordamiento.

  • Bit de paridad (PF): Su valor es 1 si el byte menos significativo de una operación aritmética contiene un número impar de unos.

  • Bit de ajuste (AF): Su valor es 1 si se produce acarreo en operaciones aritméticas en la codificación BCD.

  • Bit de cero (ZF): Su valor es 1 si el resultado de la última operación aritmética ha sido cero.

  • Bit de signo (SF): Su valor es idéntico al bit más significativo del resultado que corresponde con el bit de signo, cero si es positivo y 1 si es negativo.

  • Bit de desbordamiento (OF): Su valor es 1 si el entero obtenido como resultado no puede ser representado en complemento a 2 con el número de bits utilizado.

Si se pudiese ver la evolución de los valores de bits de estado durante la ejecución de un programa se podría comprobar cómo sus valores fluctúan continuamente dependiendo de los resultados aritméticos producidos. El valor de estos bits se mantiene en el registro eflags mientras no se realice otra operación aritmética. El valor de estos bits modifican el comportamiento de un subconjunto muy relevante de instrucciones del procesador, entre ellas los saltos condicionales.

4.1.4. El registro contador de programa

Desde el instante en que un procesador comienza a funcionar, esto es, cuando el circuito recibe el voltaje necesario, hasta que este voltaje desaparece, su actividad consiste en ejecutar las instrucciones máquina almacenadas en memoria. El procesador obtiene una instrucción de memoria, la interpreta, ejecuta y al terminar repite el proceso con la siguiente instrucción.

Como consecuencia, en todo momento se debe saber dónde está almacenada la siguiente instrucción a ejecutar. Es decir, mientras en el interior del procesador se interpreta la instrucción recibida, se debe almacenar la dirección de memoria a la que hay que acceder para ejecutar la siguiente instrucción. En la arquitectura IA-32, en el modelo lineal de memoria, esa dirección de memoria consta de 32 bits y se almacena en el registro con nombre %eip (extended instruction pointer). Si la instrucción que está ejecutando no indica lo contrario, el procesador continua con la instrucción que está almacenada en las siguientes posiciones de memoria. Algunas instrucciones, como por ejemplo las de salto, modifican el contenido de este registro, y por tanto modifican la secuencia de ejecución.

Todo procesador dispone de un registro de estas características y que se conoce generalmente como el “contador de programa” o PC. En el caso del Pentium, no es posible acceder a %eip de forma explícita, o sea que no se puede leer ni escribir directamente un valor. En cambio, sí se puede modificar de forma implícita mediante instrucciones como por ejemplo las de salto o las de llamadas a subrutina.

La forma que tiene el procesador de cargar la siguiente instrucción a ejecutar consiste en sacar el contenido del registro %eip al bus de direcciones de memoria y efectuar una operación de lectura tal y como ilustra la figura 4.7. Cuando dicha operación ha terminado, el procesador obtiene el conjunto de bits que codifican la siguiente instrucción a ejecutar.

Figura 4.7. Contador de programa

Contador de programa

4.1.5. Otros registros del Intel Pentium

Aparte de los descritos anteriormente, el procesador dispone de registros adicionales para efectuar operaciones especializadas, que aunque no se estudian en detalle, son muy importantes para obtener el mayor rendimiento posible en la ejecución de programas. La arquitectura los agrupa de la siguiente forma:

  • Ocho registros de 80 bits para almacenar números reales codificados en coma flotante. Por contra, el grupo de registros descritos anteriormente se utiliza para operar con números naturales, enteros y caracteres.

  • Tres registros de 16 bits que almacenan bits de estado, control y etiquetado de números en coma flotante. Se utilizan para codificar condiciones especiales de sus operaciones.

  • Un registro de 11 bits que contiene el código de operación de la última instrucción con operandos en coma flotante.

  • Dos registros de 48 bits con la dirección de memoria de la última instrucción con operandos en coma flotante y la dirección del último operando en coma flotante obtenido de memoria.

  • Ocho registros de 64 bits para la ejecución de instrucciones del tipo MMX. Estas 57 instrucciones están orientadas a la ejecución eficiente de aplicaciones multimedia y procesado de señal de audio y vídeo.

  • Ocho registros de 128 bits para la ejecución de instrucciones de tipo SIMD (Single Instruction Multiple Data).

Tanto las instrucciones de tipo MMX como las de tipo SIMD persiguen una finalidad similar. Tras analizar el tipo de programas que ejecutan estos procesadores, se han identificado ciertas instrucciones que aparecen en aplicaciones de procesado de vídeo en las que es preciso realizar una única instrucción sobre un conjunto muy grande de datos. Por ejemplo, supóngase que se debe sumar una constante a toda una tabla de números. En lugar de ejecutar esta operación con las instrucciones convencionales, es decir, realizar la suma elemento a elemento, el procesador ofrece la posibilidad de ejecutar esta instrucción sobre todos los datos a la vez. De esta posibilidad se deriva su nombre (SIMD, única instrucción, múltiples datos). Dado que el tamaño de los operandos es mayor que el de las instrucciones convencionales, se requiere un banco de registros especial para ellas.

4.1.6. Estado visible de un programa

De toda la arquitectura del procesador Intel Pentium, en adelante se considerará únicamente la parte encargada de ejecutar instrucciones con enteros, naturales y caracteres. No se estudiarán ni las instrucciones ni la arquitectura para manipular números en coma flotante ni las extensiones MMX y SIMD.

Una vez restringido el ámbito de estudio a este subconjunto, los datos que utiliza un procesador para ejecutar las instrucciones máquina están almacenados en un conjunto de dispositivos concretos. Se define como el “estado visible de un programa” al conjunto de datos imprescindibles para su ejecución. La forma de decidir qué datos forman parte de este estado es si se considera la situación en la que un programa en ejecución se detiene y se transfiere a otro procesador. ¿Qué datos deben transferirse para que la ejecución en este nuevo procesador continúe exactamente igual a como procedería en el procesador origen? El estado está contenido en los siguientes elementos:

  • La memoria RAM. Es el lugar en el que están almacenados los datos y el código de un programa por lo que su ejecución depende de ella.

  • Los registros de propósito general. En cualquier instante de la ejecución de un programa, estos registros contienen datos temporales que son resultados parciales u operandos a utilizar en el futuro. Por esta razón, estas ocho palabras de 4 bytes cada una forman parte del estado visible.

  • Los bits de estado contenidos en el registro eflags puesto que la ejecución de ciertas instrucciones varía dependiendo de estos valores.

  • El contador de programa. Indica qué instrucción está ejecutando el procesador, y por tanto es parte imprescindible de este estado.

4.2. Ciclo de ejecución de una instrucción

Se define como el ciclo de ejecución de un procesador a los pasos internos que sigue para ejecutar una instrucción. El número de pasos y duración de este ciclo varían de procesador a procesador y depende totalmente de su arquitectura. La mayor parte de las técnicas utilizadas para obtener un mayor rendimiento en la ejecución de instrucciones están orientadas a modificar la arquitectura para obtener un ciclo de ejecución más rápido.

La complejidad del ciclo de ejecución se incrementa con la complejidad de los procesadores. El procesador Intel Pentium tiene múltiples ciclos de ejecución posible dependiendo del tipo de instrucción a ejecutar. A modo de simplificación se estudia el más representativo de ellos que utilizan las operaciones que manipulan datos enteros. El ciclo de ejecución de estas instrucciones consta de cinco etapas: fetch (F), decodificación inicial (D1), decodificación final (D2), ejecución (E) y escritura de resultados (W). La figura 4.8 muestra la secuencia de fases en la ejecución de varias instrucciones.

Figura 4.8. Ciclos de ejecución de varias instrucciones

Ciclos de ejecución de varias instrucciones

A continuación se describen las tareas que se realizan en cada una de estas fases.

4.2.1. Fase de fetch

En esta fase el procesador obtiene la siguiente instrucción a ejecutar de memoria. Para ello se carga el contenido del registro contador de programa %eip en el bus de direcciones y se realiza una operación de lectura. El procesador recibe los primeros bytes de la instrucción y los almacena en el registro de instrucciones (IR) para proceder a su decodificación. Al mismo tiempo que se obtienen los primeros bytes de la instrucción se calcula el siguiente valor para el contador de programa. Este valor todavía no se almacena en %eip puesto que no la longitud exacta de la instrucción no se sabe con exactitud hasta que se termina la fase de decodificación. La figura 4.9 muestra una versión simplificada de los componentes internos del procesador que participan en esta fase.

Figura 4.9. Fase de fetch

Fase de fetch

4.2.2. Fase de decodificación inicial

El proceso de decodificación de una instrucción está dividido en dos fases debido principalmente a que el Intel Pentium tiene un formato de instrucción de longitud variable. Durante esta fase los bytes que codifican una instrucción se obtienen de forma gradual pues no se sabe de antemano su tamaño. La decodificación se realiza a partir de los datos obtenidos en la fase anterior y depositados en el registro de instrucciones y se obtiene el número de bytes que ocupa la instrucción y sus componentes básicos.

Lo primero que se obtiene es el código de operación. Dependiendo del valor recibido se procede a obtener el resto de los elementos de la instrucción con sus respectivos tamaños. Al terminar esta fase ya se sabe con exactitud la operación a realizar y la forma en que obtener sus operandos. El contador de programa ya puede ser actualizado con el valor de la dirección en la que comienza la siguiente instrucción. La figura 4.10 muestra los componentes que participan en esta fase.

Figura 4.10. Fase de decodificación inicial

Fase de decodificación inicial

Las instrucciones del Intel Pentium pueden tener hasta un máximo de dos operandos que a su vez pueden estar almacenados en múltiples lugares (registros, memoria, la propia instrucción, etc). Una vez terminada esta fase, el procesador ya sabe qué pasos seguir para obtener los operandos y ejecutar el resto de la instrucción pero todavía no ha obtenido ninguno de ellos. La razón por la que existe esta fase de decodificación previa es por la complejidad del lenguaje máquina. Al tener formato variable existen multitud de comprobaciones que se deben hacer en la información recibida de memoria para saber de qué instrucción se trata.

4.2.3. Fase de decodificación final

Esta fase se encarga de obtener los operandos que participan en la ejecución de la instrucción y que pueden estar almacenados en varios lugares: registros, memoria o incluso formar parte de la propia instrucción. En el caso de que un operando esté en memoria, esta fase necesita ejecutar una operación de lectura de memoria. Previa a esta operación el procesador debe calcular la dirección efectiva del operando, es decir, su posición en memoria. En general, los procesadores ofrecen un número elevado de posibilidades para especificar esta dirección en una instrucción máquina. La figura 4.11 ilustra lo que sucede en esta fase para una instrucción que contiene dos operandos, el primero de ellos está en un registro y el segundo en memoria. El cálculo de la dirección efectiva puede requerir operaciones aritméticas no triviales.

Figura 4.11. Fase de decodificación final

Fase de decodificación final

4.2.4. Fase de ejecución

Una vez obtenidos los operandos, en esta fase se realizan los cálculos aritméticos especificados en la instrucción. La duración de esta fase depende del tipo de operación requerida. Por ejemplo, una suma tarda un tiempo mucho más reducido que una multiplicación o división entera. La duración de esta fase se puede representar o como una fase de duración variable o como múltiples fases consecutivas de ejecución de duración fija.

Además del cálculo aritmético, es en esta fase en la que se actualizan los valores de los bits del registro de estado y de control con los valores derivados del resultado producido. La figura 4.12 muestra los componentes que participan en esta fase.

Figura 4.12. Fase de ejecución

Fase de ejecución

4.2.5. Fase de escritura de resultados

Una vez terminada la operación aritmético/lógica codificada en la instrucción, el procesador guarda el resultado obtenido en un destino que puede ser igualmente un registro interno o una posición de memoria. La figura 4.13 muestra los elementos involucrados en esta fase y las dos posibilidades de escritura.

Figura 4.13. Fase de escritura de resultado

Fase de escritura de resultado

Al terminar esta fase, el procesador tiene en el contador de programa la dirección de memoria en la que está almacenada la siguiente instrucción. La siguiente fase de fetch comienza la ejecución de una nueva instrucción.

La secuencia de fases descrita anteriormente es una de las múltiples que utiliza el procesador. Existen instrucciones que ejecutan ligeras variaciones con respecto a esta secuencia de cinco pasos. Como ejemplo de esta variedad se pueden tomar las instrucciones de coma flotante. La forma en que el procesador opera con números reales aumenta el número de fases hasta ocho. Tras las dos etapas de decodificación se produce un acceso a memoria. Tras la fase de escritura de resultados, este tipo de instrucciones tiene una fase adicional de notificación de errores. Situaciones tales como el desbordamiento por arriba o por abajo así como otras situaciones erróneas (ver el capítulo 2) son notificadas mediante excepciones y suelen detener la ejecución del programa. Tal es la importancia de estos errores que el procesador dedica una de sus fases de ejecución a estas tareas. La figura 4.14 muestra el ciclo de ejecución para las instrucciones en coma flotante.

Figura 4.14. Ciclo de ejecución de instrucciones de coma flotante

Ciclo de ejecución de instrucciones de coma flotante

En este ciclo de ejecución se puede comprobar como las dos primeras fases son idénticas a las instrucciones de aritmética entera. Las flechas en las dos fases de ejecución indican que su duración varía dependiendo de la operación y los operandos involucrados.

4.2.6. Ejecución de una instrucción

Para ilustrar el ciclo de ejecución se analiza a continuación la ejecución detallada de una instrucción concreta del procesador. Supóngase que en la posición de memoria n se encuentra la instrucción INC %eax que tiene el efecto de incrementar o sumar 1 al contenido del registro de propósito general %eax y depositar el resultado de nuevo en el mismo registro. Esta instrucción se codifica con un único byte con valor 0x40. El ciclo de ejecución de esta instrucción consta de los siguientes pasos:

  1. Fase de fetch: Se obtiene de la posición de memoria n contenida en el contador de programa el byte que codifica la instrucción. Se calcula el nuevo valor del contador, que en este caso es n + 1 pero todavía no se actualiza.

  2. Fase de decodificación inicial: Se detecta que no es preciso obtener más datos de memoria pues con un único byte es suficiente. Se identifica la operación de incremento y que tiene un único operando que es un registro de propósito general. Se actualiza el valor del contador de programa a n + 1.

  3. Fase de decodificación final: Se obtienen los operandos de la instrucción que en este caso es el valor almacenado en el registro %eax.

  4. Fase de ejecución: Utilizando la unidad aritmético/lógica se realiza la suma del valor del registro obtenido en la fase anterior y la constante 1. Se actualizan los bits de estado pertinentes en el registro de estado y de control.

  5. Fase de escritura de resultado: El resultado obtenido en la fase anterior se escribe de nuevo en el registro %eax.

4.2.7. Ciclo de ejecuciones en procesadores actuales

La descripción anterior supone una simplificación significativa de la estructura real del Intel Pentium. La arquitectura interna del procesador permite la ejecución de múltiples instrucciones de forma simultánea. La ejecución se realiza mediante una técnica denominada “segmentación” (en inglés “pipelining”) que se asemeja al esquema de cadena de producción. Al dividir la ejecución de instrucciones en fases, mientras una instrucción está en su fase de ejecución se puede estar decodificando la siguiente y haciendo el fetch de la siguiente. Al circuito que implementa este esquema de ejecución se le denomina “pipeline”.

Además de la técnica de segmentación, el procesador Intel Pentium consigue aumentar la velocidad de ejecución mediante la utilización de múltiples flujos de ejecución. Es decir, el procesador no sólo simultanea las diferentes fases del ciclo de ejecución de varias instrucciones sino que dispone de múltiples pipelines que trabajan en paralelo. A los procesadores con esta característica se les denomina “superescalares”. La consecuencia más importante de esta técnica es que el orden en que se ejecutan las instrucciones puede verse alterado por el paralelismo creado.

De este paralelismo se deriva gran parte de la complejidad de diseño de estos procesadores. A nivel de un programa en ensamblador, el paradigma de ejecución en el que se asume que las instrucciones se ejecutan una tras otra. El procesador por tanto utiliza las técnicas de segmentación y paralelismo para aumentar la velocidad de ejecución pero debe mantener en todo momento la consistencia con el esquema secuencial. En otras palabras, internamente un procesador puede reorganizar y paralelizar la ejecución de instrucciones todo lo que pueda siempre y cuando los resultados producidos concuerden con la ejecución secuencial de instrucciones.

En la actualidad, los procesadores más modernos de la arquitectura IA-32 contienen múltiples pipelines especializados en diferentes tipos de instrucciones (enteros, coma flotante, saltos, etc.) con lo que se consigue una velocidad de ejecución muy elevada.

4.3. La pila

Aparte de los componentes de la arquitectura presentados en las secciones anteriores, la mayor parte de procesadores ofrecen la infraestructura necesaria para manipular una estructura de datos organizada y almacenada en memoria que se denomina “la pila”.

La pila es una zona de la memoria sobre la que se pueden escribir y leer datos de forma convencional. Esta zona tiene una posición especial que se denomina “la cima de la pila”. El procesador contiene dos instrucciones de su lenguaje máquina para realizar las operaciones de “apilar” y “desapilar” datos de la pila. Los datos que se pueden apilar y desapilar, en el caso del Intel Pentium son siempre de tamaño 4 bytes.

4.3.1. Instrucciones de manejo de la pila

La instrucción para apilar un dato en la pila tiene el formato push dato. Es una instrucción con un único operando que deposita el dato especificado como parámetro en la cima de la pila. Supóngase que la cima de la pila está en la posición @cima. La instrucción push dato produce el siguiente efecto.

  • Se resta 4 a la dirección de la cima de la pila, se obtiene, por tanto @cima - 4.

  • Se escribe el dato de 32 bits dado como único operando en la posición de memoria indicada por @cima - 4 y la dirección de la cima se asigna a este nuevo valor. El dato que estaba previamente almacenado en esas posiciones se ha perdido.

La figura 4.15 muestra la pila antes y después de ejecutar la instrucción push dato.

Figura 4.15. Efecto de la instrucción push

Efecto de la instrucción push

De la descripción de la instrucción push se deduce que efectúa una operación de escritura en memoria RAM. Si a continuación de esta instrucción se ejecuta otra del mismo tipo, el dato se almacena a partir de la cuarta posición de memoria antes del último valor depositado en de la pila.

La instrucción pop destino ejecuta el procedimiento complementario al de push dato. Tiene un único operando que, en este caso, especifica el lugar en el que almacenar el dato que se encuentra en la cima de la pila. Supóngase que la cima de la pila está en la posición de memoria @code. La ejecución de la instrucción pop destino tiene el siguiente efecto.

  • Se lee el dato de 32 bits almacenado en la posición de memoria indicada por la dirección de la cima @cima y se almacena en el lugar especificado como operando de la instrucción.

  • Se suma 4 a la dirección de la cima de la pila, se obtiene, por tanto @cima + 4.

La figura 4.16 muestra la pila antes y después de ejecutar la instrucción pop destino.

Figura 4.16. Efecto de la instrucción pop

Efecto de la instrucción pop

Nótese que las instrucciones push y pop tienen estructura y efectos complementarios. La instrucción push recibe como operando el dato a depositar, no es preciso especificar el destino pues se deposita automáticamente en la nueva cima. La instrucción pop, por contra, recibe como parámetro el lugar en el que almacenar el dato obtenido y no es preciso indicar de dónde se obtiene pues se lee automáticamente de la cima de la pila. La instrucción push ajusta la cima restando 4 al valor actual, mientras que pop suma 4 a ese valor. Además, una instrucción realiza una operación de lectura en memoria y la otra una operación de escritura. El dato que la instrucción pop lee de la cima de la pila no desaparece de esa posición de memoria, pues lo único que se hace es leer ese valor. Sí es cierto que la cima de la pila ya no apunta a ese dato, pero este sigue almacenado en la misma posición.

Los destinos posibles que se pueden especificar en la instrucción pop dependen del lenguaje máquina del procesador, pero en el Pentium se permite especificar cualquier registro de propósito general de 32 bits como operando de esta instrucción. Por ejemplo, la instrucción pop %edx lee los cuatro bytes almacenados en la cima de la pila, los copia en el registro %edx y ajusta la dirección de la cima.

4.3.2. El puntero de pila

Del funcionamiento de las instrucciones push y pop se deduce que en algún lugar del procesador debe estar almacenada la dirección de la cima de la pila y que dicho valor es modificado por ambas instrucciones. En el caso del Intel Pentium, esta dirección de memoria está guardada por defecto en el registro de propósito general %esp. Las dos últimas letras del nombre de este registro corresponden con las iniciales de las palabras stack pointer o “apuntador de pila”. La primera consecuencia de esta característica del procesador es que, a pesar de que dichos registros están, en principio, disponibles para almacenar valores de forma temporal, el caso de %esp es especial, pues es donde las instrucciones de manipulación de la pila asumen que se encuentra la dirección de la cima. El tamaño de este registro es de 32 bits que coincide con el tamaño de toda dirección de memoria del Intel Pentium.

Si en el instante antes de ejecutar una instrucción push %esp contiene el valor v1, tras su ejecución contendrá el valor v1 - 4. De forma análoga, si antes de ejecutar la instrucción pop %esp contiene el valor v2, tras su ejecución contendrá el valor v2 + 4. La figura 4.17 muestra el efecto de la ejecución de dos instrucciones consecutivas sobre la pila tanto en memoria como en los registros de propósito general.

Figura 4.17. Ejecución de instrucciones de pila

Ejecución de instrucciones de pila

El que la dirección de la pila esté contenida en un registro de propósito general permite que su contenido sea manipulado como cualquier otro registro. Un programa, por tanto, puede leer y escribir cualquier valor de %esp, tan sólo se debe tener en cuenta que el procesador obtiene de ese registro la dirección de memoria necesaria para ejecutar las instrucciones push y pop.

Supóngase que se ha depositado un cierto dato en la pila mediante la instrucción push y que se encuentra, por tanto en la cima. La instrucción pop deposita ese valor en el lugar especificado pero, ¿es posible ejecutar la instrucción pop sin ningún operando?. En otras palabras, la operación que se quiere ejecutar no es la de copiar el dato de la cima, sino simplemente corregir el valor de la cima al igual que haría pop pero sin depositar el dato en ningún lugar. La instrucción pop, por definición, debe incluir un único operando, con lo que no se puede utilizar para hacer esta operación.

La solución se deriva del hecho de que %esp es un registro de propósito general y de que todos los datos leídos o extraídos de la pila son de tamaño 4 bytes. Para corregir el valor de la cima de la pila tal y como hace pop pero sin depositar su valor en destino alguno es suficiente con sumar 4 al valor de %esp. La instrucción ADD $4, %esp produce exactamente ese efecto. El primer operando es la constante a sumar, y el segundo es a la vez el otro sumando y el lugar donde dejar el resultado. Esta instrucción por tanto asigna a %esp su valor incrementado en cuatro unidades. El efecto que esta instrucción tiene sobre la pila es el deseado. La siguiente instrucción asume que la cima está en la nueva posición contenida en %esp.

4.3.3. Valores iniciales del puntero de pila

Todo programa en ensamblador comienza ejecutar con un valor en el registro %esp que apunta a la cima de la pila previamente preparada. Los programas, por tanto, no deben realizar operación alguna para inicializar la pila ni para reservar su espacio. Esta tarea la lleva a cabo, antes de que comience la ejecución, el sistema operativo.

El sistema operativo es un programa que se encarga de realizar las tareas de administración de todos los dispositivos y recursos disponibles en el equipo. Entre ellas se encuentra la de permitir la ejecución de programas en ensamblador. Todo programa antes de comenzar a ejecutar su primera instrucción tiene una zona de memoria reservada para la pila y su puntero a la cima correctamente inicializado.

Pero la memoria de un equipo es limitada, y por tanto, la pila ocupa un lugar en memoria también limitado. ¿Qué sucede si se intenta acceder a posiciones de memoria fuera de este límite? Esta situación puede ser provocada al menos por dos situaciones: se deposita un dato mediante la instrucción push cuando todo el espacio reservado para la pila ya está ocupado o se intenta obtener un dato de la pila cuando esta no contiene dato alguno.

Supóngase que la pila está almacenada en la zona de memoria que va desde la dirección p hasta la dirección q (ambas inclusive) y p < q. ¿Qué valores contiene el registro %esp cuando la pila está llena y cuando está vacía? Si la pila está llena entonces la cima está en la posición de memoria con valor más bajo posible, es decir cima = p. Si en estas condiciones se ejecuta una instrucción push el procesador detiene la ejecución del programa de forma abrupta.

Si la pila está vacía quiere decir que no se ha introducido dato alguno en ella y por tanto si se ejecutase la instrucción push se depositaría el primer dato. Por tanto, la cima de la pila vacía debe estar en la posición q + 1 para que el dato del primer push se almacene correctamente. La figura 4.18 muestra los valores de la cima para las dos condiciones descritas.

Figura 4.18. Valores de la cima para la pila vacía y llena

Valores de la cima para la pila vacía y llena

4.4. Ejercicios

  1. La instrucción ADD $128, %eax suma la constante 128 al contenido del registro %eax y deposita el resultado de nuevo en dicho registro. Describir los pasos que sigue el procesador en las cinco fases de ejecución de esta instrucción.

  2. ¿Cuántos registros cambian de contenido tras ejecutar la instrucción push %eax?

  3. ¿Cuántos registros, como máximo, cambian de contenido tras ejecutar la instrucción pop %eax?

  4. ¿Cuántos registros contienen datos diferentes al ejecutar la instrucción push %eax seguida de la instrucción pop %eax?

  5. ¿Cuántas posiciones de memoria han modificado su valor, como máximo, tras ejecutar las dos instrucciones de la pregunta anterior?

  6. La instrucción pop %edx no ha modificado el contenido del registro %edx ¿Cómo es esto posible?

  7. ¿Qué efecto se produce en la pila si mediante una instrucción se suma 4 al registro %esp? ¿Y si se resta 4?

  8. ¿Qué secuencia de cuatro instrucciones de pila se pueden ejecutar para intercambiar los valores de dos registros?

  9. ¿Cómo se define el estado visible de un programa?. ¿En qué cuatro elementos está contenido dicho estado en la versión simplificada del procesador?

  10. Escribir la secuencia de instrucciones en ensamblador cuya ejecución sea equivalente a ejecutar la instrucción push %eax. Explicar la solución propuesta.

  11. Escribir la secuencia de instrucciones en ensablador cuya ejecución sea equivalente a ejecutar la instrucción pop %eax. Explicar la solución propuesta.

Envío de errata



[1] Scott Thompson y otros. 130nm Logic Technology Featuring 60nm Transistors, Low-K Dielectrics, and Cu Interconnects. Intel Technology Journal, volumen 6, número 2, Mayo 2002.