Appearance
B2-A1. Arquitectura del procesador y compilación de un programa en C
Objetivos
- Identificar la arquitectura y las características del procesador utilizando la terminal de Linux.
- Crear un programa sencillo en lenguaje C utilizando Visual Studio Code.
- Compilar el código fuente para generar un programa ejecutable.
- Ejecutar el programa y comprobar su funcionamiento.
- Examinar el código ensamblador y el código máquina generado.
- Comprender la evolución desde el código máquina hasta los lenguajes de alto nivel.
1. Identificar la arquitectura del procesador
Abre una terminal de Linux y ejecuta:
bash
uname -mEste comando muestra la arquitectura de la máquina sobre la que se está ejecutando Linux.
Algunos resultados habituales son:
x86_64: Arquitectura x86 de 64 bits, utilizada por procesadores Intel y AMD.aarch64: Arquitectura ARM de 64 bits.
Ejecuta también:
bash
lscpuEste comando muestra información detallada sobre el procesador.
Localiza los siguientes datos:
- Arquitectura del procesador.
- Fabricante y modelo del procesador.
- Número de CPU lógicas.
- Número de sockets.
- Número de núcleos por socket.
- Número de hilos por núcleo.
Ten en cuenta que, si estás trabajando en una máquina virtual, algunos datos pueden corresponder a la configuración de la máquina virtual y no al procesador físico completo.
Núcleos e hilos
A partir de los resultados de lscpu, comprueba la siguiente relación:
CPU lógicas = sockets × núcleos por socket × hilos por núcleo
Realiza el cálculo utilizando los valores obtenidos y comprueba si coincide con el número de CPU lógicas que muestra el sistema.
Ten en cuenta que esta relación corresponde a configuraciones habituales y puede variar en sistemas con CPU deshabilitadas o configuraciones especiales.
Documenta: Incluye capturas de ambos comandos, identifica los datos solicitados y muestra el cálculo realizado para comprobar el número de CPU lógicas. Si algún dato no aparece o el resultado no coincide, indícalo.
2. Preparar el entorno de trabajo
En primer lugar, comprueba si tienes instalado el compilador GCC:
bash
gcc --versionSi aparece su versión, el compilador está disponible. Si no está instalado, consulta con el profesor antes de continuar.
Comprueba también que están disponibles las herramientas que utilizaremos posteriormente:
bash
objdump --versionbash
objcopy --versionbash
xxd -vLos comandos objdump y objcopy forman parte de las herramientas GNU Binutils. El comando xxd permite visualizar archivos binarios en hexadecimal.
Si alguna herramienta no está disponible, consulta con el profesor.
Desde la terminal, sitúate en tu directorio de trabajo y crea un directorio llamado b2-a1:
bash
mkdir b2-a1
cd b2-a1Abre este directorio con Visual Studio Code:
bash
code .El punto (.) indica que queremos abrir el directorio actual.
Documenta: Incluye capturas de la comprobación de GCC y del directorio abierto en Visual Studio Code.
3. Crear un programa en C
Desde Visual Studio Code, crea un archivo llamado main.c.
Introduce el siguiente código:
c
#include <stdio.h>
int main(void) {
int num1;
int num2;
int suma;
printf("Introduce el primer numero: ");
scanf("%d", &num1);
printf("Introduce el segundo numero: ");
scanf("%d", &num2);
suma = num1 + num2;
printf("La suma es: %d\n", suma);
return 0;
}El programa solicita dos números enteros, calcula su suma y muestra el resultado.
Explicación del código
#include <stdio.h>: Incluye las declaraciones necesarias para utilizar funciones de entrada y salida.int main(void): Define la función principal, donde comienza la ejecución del programa.int: Permite declarar variables de tipo entero.printf(): Muestra información por pantalla.scanf(): Lee los números introducidos por el usuario y los almacena en las variables indicadas.%d: Especifica que se trabaja con números enteros.&num1y&num2: Indican las direcciones de memoria donde se almacenarán los valores introducidos.suma = num1 + num2: Suma los dos números y guarda el resultado.return 0: Finaliza el programa indicando que ha terminado correctamente.
Guarda el archivo.
Documenta: Incluye una captura del código fuente completo en Visual Studio Code.
4. Compilar el programa
Abre la terminal integrada de Visual Studio Code mediante el menú Terminal → New Terminal.
Comprueba que te encuentras en el directorio b2-a1.
Ejecuta:
bash
gcc -O0 main.c -o sumaEn este comando:
gcc: Ejecuta el compilador de C.-O0: Desactiva las optimizaciones habituales del compilador, facilitando el análisis posterior del código generado.main.c: Es el archivo que contiene el código fuente.-o: Permite indicar el nombre del archivo ejecutable que se generará.suma: Es el nombre elegido para el ejecutable.
Si no aparecen errores, comprueba que se ha creado el archivo:
bash
ls -lAhora deberías tener dos archivos:
main.c: Código fuente del programa.suma: Programa ejecutable generado por el compilador.
Documenta: Incluye capturas de la compilación y de los archivos existentes en el directorio.
5. Ejecutar el programa
Desde la misma terminal, ejecuta:
bash
./sumaEl prefijo ./ indica que queremos ejecutar un archivo situado en el directorio actual.
Introduce los dos números que solicite el programa y comprueba que muestra correctamente su suma.
Realiza al menos tres ejecuciones con parejas de números diferentes, incluyendo una con números negativos.
Documenta: Incluye capturas de las tres ejecuciones y sus resultados.
6. Identificar el ejecutable generado
Ejecuta:
bash
file sumaEl comando file permite identificar el tipo de archivo y, en este caso, obtener información sobre el ejecutable generado.
Comprueba si aparece la arquitectura identificada en el primer apartado.
Ejecuta también:
bash
file main.cObserva la diferencia entre la información que muestra file para el código fuente y para el ejecutable.
Finalmente, consulta el tamaño de ambos archivos:
bash
ls -lh main.c sumaDocumenta: Incluye capturas de los resultados e identifica el tipo, la arquitectura (cuando aparezca) y el tamaño de cada archivo.
7. Examinar el código ensamblador
El compilador GCC transforma el código fuente en instrucciones máquina que puede ejecutar el procesador.
El lenguaje ensamblador permite representar esas instrucciones mediante nombres como mov, add o jmp, en lugar de utilizar directamente sus valores binarios.
Los ejemplos de instrucciones que utilizaremos corresponden a la arquitectura x86-64. Si estás trabajando con otra arquitectura, los nombres y operandos pueden ser diferentes.
7.1. Obtener el código ensamblador
Desde la terminal de Visual Studio Code, ejecuta:
bash
objdump -d -M intel --no-show-raw-insn --disassemble=main suma > assembly.txtEste comando obtiene el desensamblado de la función main y guarda el resultado en assembly.txt.
objdump: Herramienta que permite examinar archivos ejecutables.-d: Desensambla las instrucciones del ejecutable.-M intel: Utiliza la sintaxis Intel para mostrar las instrucciones x86-64.--no-show-raw-insn: Oculta los bytes del código máquina.--disassemble=main: Limita el desensamblado a la funciónmain.>: Redirige la salida del comando a un archivo, creándolo o sobrescribiéndolo.
Abre assembly.txt desde el explorador de archivos de Visual Studio Code.
7.2. Analizar las instrucciones
Observa que el archivo contiene direcciones e instrucciones en ensamblador.
Localiza los siguientes elementos:
- Una instrucción
mov, utilizada para mover datos. - Una instrucción
add, utilizada para realizar una suma. - Una instrucción
call, utilizada para llamar a una función.
Para cada instrucción, identifica:
- Su dirección.
- El nombre de la instrucción.
- Los operandos que utiliza.
Ten en cuenta que el compilador puede generar distintas instrucciones dependiendo de su versión y de las opciones de compilación. Si no encuentras alguna de las anteriores, selecciona otra instrucción y consulta su significado.
Documenta: Incluye una captura de assembly.txt abierto en Visual Studio Code. Identifica tres instrucciones y explica su función y sus operandos.
8. Examinar el código máquina
El procesador ejecuta código máquina, formado por bytes que representan las instrucciones y sus operandos.
8.1. Extraer el código máquina
Ejecuta:
bash
objcopy --dump-section .text=machine-code.bin sumaEste comando extrae los bytes de la sección .text del ejecutable y los guarda en machine-code.bin.
La sección .text contiene código ejecutable, incluyendo otras funciones además de main.
El archivo generado es binario, por lo que no resulta adecuado abrirlo directamente como texto.
8.2. Convertir los bytes a hexadecimal
Ejecuta:
bash
xxd -g 1 machine-code.bin > machine-code.txtEste comando crea un archivo de texto que contiene los bytes en hexadecimal.
xxd: Muestra los bytes de un archivo en hexadecimal.-g 1: Agrupa los valores byte a byte.>: Guarda el resultado en el archivo indicado.
Abre machine-code.txt desde Visual Studio Code.
Observa las tres columnas:
- Desplazamiento: Posición de los bytes dentro del archivo.
- Hexadecimal: Representación de los bytes del código máquina.
- Texto: Interpretación de los bytes como caracteres, cuando es posible.
8.3. Relacionar el ensamblador con el código máquina
Ejecuta:
bash
objdump -d -M intel --disassemble=main suma > machine-code-and-assembly.txtAbre el archivo generado en Visual Studio Code.
En este caso aparecen las instrucciones en ensamblador junto a los bytes que las representan en código máquina.
Localiza una instrucción mov o add y observa sus bytes correspondientes.
Por ejemplo, una posible representación en x86 es:
asm
83 c0 03 add eax, 3Los bytes 83 c0 03 representan la instrucción add eax, 3 en código máquina x86.
Documenta: Incluye capturas de machine-code.txt y machine-code-and-assembly.txt. Selecciona una instrucción e identifica su representación hexadecimal.
9. Evolución de los lenguajes de programación
Vamos a comprobar cómo una misma operación puede representarse mediante código máquina, lenguaje ensamblador y lenguaje C.
9.1. Código máquina
En los primeros ordenadores era habitual introducir directamente instrucciones mediante códigos numéricos, interruptores, tarjetas perforadas u otros mecanismos.
Por ejemplo, la instrucción x86:
asm
mov eax, 5Puede representarse mediante estos bytes en hexadecimal:
text
B8 05 00 00 00Y en binario:
text
10111000 00000101 00000000 00000000 00000000Cada grupo de ocho bits representa un byte.
Imagina tener que escribir un programa completo utilizando únicamente estos valores numéricos.
9.2. Lenguaje ensamblador
El lenguaje ensamblador permitió sustituir los códigos numéricos por nombres más fáciles de recordar.
Por ejemplo:
asm
mov eax, 5
add eax, 3Un programa llamado ensamblador transforma estas instrucciones en código máquina.
Esto facilitó considerablemente la programación, aunque seguía siendo necesario conocer los registros y las instrucciones específicas del procesador.
9.3. Lenguaje C
Los lenguajes de alto nivel, como C, permiten expresar las operaciones mediante instrucciones más próximas al razonamiento humano.
Por ejemplo:
c
int suma = 5 + 3;El programador ya no necesita indicar qué registros debe utilizar el procesador ni qué instrucciones máquina deben ejecutarse.
El compilador se encarga de generar el código máquina correspondiente.
9.4. Comprobar las diferencias
Actividad opcional
Este apartado es voluntario y permite profundizar en la relación entre el código C, el lenguaje ensamblador y el código máquina. No es necesario realizarlo para completar la actividad.
Vamos a modificar nuestro programa para comprobar cómo un cambio en el código C produce instrucciones diferentes en ensamblador y código máquina.
Paso 1. Modificar el programa
Abre main.c y localiza la operación:
c
suma = num1 + num2;Sustitúyela por:
c
suma = num1 * num2;Modifica también el mensaje de printf() para indicar que ahora se calcula una multiplicación.
Guarda el archivo.
Paso 2. Compilar y ejecutar
Para conservar el ejecutable original, genera uno nuevo:
bash
gcc -O0 main.c -o multiplicacionEjecuta el programa:
bash
./multiplicacionIntroduce dos números y comprueba que muestra correctamente su producto.
Paso 3. Obtener el ensamblador
Ejecuta:
bash
objdump -d -M intel --no-show-raw-insn --disassemble=main multiplicacion > multiplication-assembly.txtAbre en Visual Studio Code los archivos:
assembly.txt: Ensamblador del programa original de suma.multiplication-assembly.txt: Ensamblador del programa de multiplicación.
Utiliza la búsqueda de Visual Studio Code (Ctrl + F) para localizar:
- La instrucción
adden el programa original. - La instrucción
imulen el programa modificado.
En x86-64, add realiza una suma e imul permite realizar una multiplicación de enteros con signo.
Paso 4. Identificar el código máquina
Ejecuta:
bash
objdump -d -M intel --disassemble=main multiplicacion > multiplication-machine-code.txtAbre en Visual Studio Code:
machine-code-and-assembly.txt: Código máquina y ensamblador de la suma.multiplication-machine-code.txt: Código máquina y ensamblador de la multiplicación.
Localiza las instrucciones add e imul y observa los bytes en hexadecimal que aparecen junto a ellas.
Paso 5. Comparar los resultados
Completa una tabla similar a la siguiente, utilizando las instrucciones reales que hayas encontrado:
| Operación en C | Instrucción en ensamblador | Operandos | Código máquina (hexadecimal) |
|---|---|---|---|
num1 + num2 | add | ... | ... |
num1 * num2 | imul | ... | ... |
No es necesario analizar todas las instrucciones generadas por el compilador. Nos centraremos únicamente en las que realizan las operaciones aritméticas.
Documenta:
- Incluye una captura del código C modificado y de su ejecución.
- Muestra las instrucciones
addeimullocalizadas en los archivos de ensamblador. - Identifica los bytes de código máquina correspondientes a ambas instrucciones.
- Completa la tabla comparativa.
- Explica brevemente qué ha cambiado en el código C, en el ensamblador y en el código máquina.
9.5. Comparación final
| Característica | Código máquina | Ensamblador | Lenguaje C |
|---|---|---|---|
| Representación | Bytes binarios | Instrucciones simbólicas | Expresiones y estructuras de alto nivel |
| Facilidad de lectura | Muy baja | Media | Alta |
| Dependencia de la arquitectura | Directa | Directa | Menor |
| Herramienta de traducción | No necesita | Ensamblador | Compilador |
| Control del hardware | Muy directo | Muy directo | Más abstracto |
Documenta: Explica brevemente qué avance supuso la aparición del ensamblador respecto al código máquina y qué ventajas aportaron posteriormente los lenguajes de alto nivel como C.
Entrega
La actividad se documentará en Markdown, dentro del repositorio de Sistemas Informáticos, incluyendo las capturas y explicaciones solicitadas en cada apartado.
Una vez finalizada:
- Incorpora la documentación y las imágenes al repositorio de Sistemas Informáticos.
- Realiza los correspondientes
commitypushpara actualizar el repositorio remoto de GitHub. - Comprueba que la actividad se ha publicado correctamente en GitHub Pages y que puede consultarse desde el sitio web de Sistemas Informáticos.
- Genera un documento PDF a partir del archivo Markdown utilizando la extensión Markdown PDF de Visual Studio Code.
- Entrega en AULES el PDF generado.
El PDF debe contener toda la documentación de la actividad, incluidas las capturas. Además, la actividad debe estar correctamente publicada y accesible desde GitHub Pages.