Aspectos Avanzados de Arquitectura de

Anuncio
Aspectos Avanzados de Arquitectura de Computadoras
Aspectos Avanzados de Arquitectura de Computadoras (AAAC)
2012
Obligatorio 2
Facultad de Ingeniería
Instituto de Computación
Departamento de Arquitectura de Sistemas
Objetivo
Familiarizarse con la arquitectura IA-32 de Intel.
Herramientas
Este obligatorio se desarrollará en un entorno linux. Se utilizará la misma máquina virtual
(imagen vmware) que en el Obligatorio 1, donde se encuentran las herramientas necesarias:
•
nasm (Netwide Assembler http://nasm.sourceforge.net/)
•
gcc (compilador)
•
ld (linker)
•
objdump (object files dump)
También se necesita el bochs (máquina virtual versión debug). En la página del curso se
encuentra disponible un paquete de instalación y un instructivo de cómo instalarlo.
Se pide
El obligatorio consta de resolver los problemas planteados a continuación, documentando
tanto la solución como los obstáculos encontrados, utilizando las herramientas dadas para trabajar
con la arquitectura. Recomendamos inspeccionar el formato solicitado para la entrega para evitar
confusiones.
Parte A
Se debe implementar un boot loader, cuyo cometido es arrancar una PC, inicialmente en
modo real, cargar de un disquete booteable a memoria un micro-kernel, luego pasar a modo
protegido, para finalmente ejecutar el micro-kernel.
El micro-kernel debe borrar la pantalla, luego mostrar el mensaje "OBLIGATORIO 2- AAAC",
y finalmente correr en un loop infinito que no realiza tarea alguna.
Boot Loader
Al arrancar el procesador, comienza ejecutando la BIOS en modo real, la cual configura el
hardware básico y carga el primer sector del disquete de booteo en la dirección 0x7C00 de memoria
RAM. En este primer sector del disquete debe residir el código del boot loader, de forma tal que se
ejecute cuando la BIOS transfiere el control a la posición antes mencionada.
La primera labor del boot loader es cargar desde el segundo sector del disquete de booteo a
memoria RAM el micro-kernel. Para esto se debe usar la interrupción 0x13 de la BIOS (ver [1,
Página 1 de 9
Aspectos Avanzados de Arquitectura de Computadoras
Sección 13.2.5]), para resetear y luego cargar a memoria los sectores de disco donde se encuentra su
implementación del micro-kernel. Tener en cuenta que las interrupciones de la BIOS sólo pueden
ser usadas mientras el procesador esté en modo real.
La segunda labor del boot loader es pasar a modo protegido; una forma de hacer esto se
detalla en el siguiente fragmento de código, extraído de [2, 3]1
[BITS 16]
; We need 16-bit intructions for Real mode
[ORG 0x7C00]
; The BIOS loads the boot sector into memory location 0x7C00
cli
; Disable interrupts, we want to be alone
xor ax, ax
mov ds, ax
; Set DS-register to 0 - used by lgdt
lgdt [gdt_desc]
; Load the GDT descriptor
mov eax, cr0
or eax, 1
mov cr0, eax
; Copy the contents of CR0 into EAX
; Set bit 0
; Copy the contents of EAX into CR0
jmp 08h:clear_pipe
; Jump to code segment, offset clear_pipe
; Index 1, TI 0, RPL 00: 1000b = 08h
[BITS 32]
clear_pipe:
mov ax, 10h
mov ds, ax
; We now need 32-bit instructions
; Save data segment identifyer
; Move a valid data segment into the data
; segment register
; Move a valid data segment into the stack
; segment register
; Move the stack pointer to 06000h
mov ss, ax
mov esp, 06000h
Donde gdt_desc es una etiqueta de memoria que referencia al GDT descriptor [4]. A
continuación se presenta una definición posible de la GDT, en la cual se definen dos segmentos
(además del segmento nulo que es obligatorio), uno de código y otro de datos, solapados, que
abarcan toda la memoria:
gdt:
; Address for the GDT
gdt_null:
dd 0
dd 0
; Null Segment
gdt_code:
dw
dw
db
db
db
db
;
;
;
;
;
;
;
0FFFFh
0
0
10011010b
11001111b
0
gdt_data:
dw 0FFFFh
dw 0
Code segment, read/execute, nonconforming
Límit 0FFFFXh
Base |0FFFFh = 0
Base >> 8 |0ffh = 0
present, max priv.,1,exec, non conf, read, 0
gran=4k, 32bit, 00,X=0fh
Base 0
; Data segment, read/write
1 En [2, 3] se encuentra una guía paso a paso del proceso de boot.
Página 2 de 9
Aspectos Avanzados de Arquitectura de Computadoras
db
db
db
db
0
10010010b
11001111b
0
gdt_end:
; Used to calculate the size of the GDT
gdt_desc:
dw gdt_end - gdt - 1
dd gdt
; The GDT descriptor
; Limit (size)
; Address of the GDT
Finalmente se debe saltar al lugar de memoria donde se encuentra en memoria el microkernel.
En los dos últimos bytes del sector que contiene el código ensamblado del boot loader, debe
colocarse la palabra 0x0AA55, firma que identifica al sector como booteable para la BIOS:
times 510-($-$$) db 0
dw 0AA55h
; Fill up the file with zeros
; Boot sector identifyer
Micro-Kernel
La principal labor de esta parte del obligatorio, consiste en borrar y luego escribir un mensaje
en una pantalla en modo texto. En este modo la pantalla se encuentra mapeada a memoria como una
matriz de 80 columnas y 25 filas a partir de la dirección 0xB8000. La matriz se organiza por filas y
cada elemento ocupa dos bytes: el primero corresponde al código ASCII del carácter desplegado en
pantalla y el segundo a sus atributos de formato (como el color y el color de fondo). Consultar [1,
Capítulo 23] por una descripción más detallada del modo de funcionamiento de una pantalla en
modo texto.
El micro-kernel puede implementarse tanto en lenguaje ensamblador como en lenguaje C. En
el primer caso, tanto el boot loader como el micro-kernel se ensamblan usando el programa nasm
(pueden implementarse como dos módulos separado o como un único módulo, a elección). Si se
utiliza C, el código del micro-kernel se debe compilar (utilizando gcc), linkeditarlo, y luego,
mediante el comando objcopy, convertirlo a formato binario "crudo" (sin ciertas secciones sólo
útiles para un sistema operativo). A continuación se muestra un ejemplo de un script de ensamblado,
compilado y linkeditado de esta parte del obligatorio:
#!/bin/sh
nasm -f bin bootloader.asm -o bootloader.bin -l bootloader.list
gcc -c main.c
ld -e main -Ttext 0x7E00 -o kernel.o main.o
objcopy -R .note -R .comment -S -O binary kernel.o kernel.bin
El parámetro “-Ttext 0x7E00”, indica al linker dónde va a residir en memoria RAM el
código ejecutable, que debe coincidir con la dirección en la cual efectivamente es cargado por
el boot loader. Ver más detalles en el Apéndice A.
Mediante el comando objdump se puede observar el código ensamblador generado y la
ubicación de las etiquetas en el código del micro-kernel.
objdump -D kernel.o
Página 3 de 9
Aspectos Avanzados de Arquitectura de Computadoras
De la misma forma, para observar el código generado por el ensamblador para el boot loader
se puede revisar el archivo bootloader.list generado por el script previo.
Armado del disco booteable
Se muestra a continuación un ejemplo de un script para generar la imagen del disquete
booteable que será utilizado en la máquina virtual bochs:
rm boot.img
dd if=/dev/zero of=boot.img bs=512 count=18
dd if=bootloader.bin of=boot.img bs=512 count=1 conv=sync,notrunc
dd if=kernel.bin of=boot.img bs=512 count=14 seek=1 conv=sync,notrunc
Este script está armado para un kernel.bin cuyo tamaño es menor a 14*512 bytes (14
sectores). El número de sectores que tenga su kernel.bin debe ser tomado en cuenta en el boot
loader, de forma de cargar el número de sectores del micro-kernel necesarios a memoria.
Parte B
En esta parte se debe extender el boot loader y kernel de la PARTE A para crear un entorno
de ejecución con dos tareas que ejecutan concurrentemente. Cada tarea, que llamaremos Tarea 1 y
Tarea 2, consiste de un ciclo infinito que en cada iteración incrementa y muestra en pantalla el valor
de un contador entero (se puede incluir en cada iteración un ciclo anidado que repita código vacío a
los efectos de enlentecer la ejecución y poder visualizar el avance del contador en pantalla).
El kernel alternará la asignación de la CPU a cada una de las tareas en períodos regulares de
tiempo. El usuario puede suspender / reanudar la ejecución de la Tarea 1 oprimiendo la tecla '1' y
análogamente puede suspender / reanudar la ejecución de la Tarea 2 oprimiendo la tecla '2'.
Mientras una tarea está suspendida, el kernel no le asigna tiempo de CPU para su ejecución. Las dos
tareas no pueden estar suspendidas simultáneamente; se ignorará cualquier intento de suspender una
tarea mientras la otra se encuentra suspendida.
Para implementar el kernel se deberán programar manejadores de interrupción para el timer y
el teclado (ambas en modo protegido). Para una descripción del manejo de teclado puede consultar
[1, Capítulo 20].
Recomendamos leer [1, Sección 17.4.1], donde se trata el manejo del controlador
programable de interrupciones 8259A, módulo externo al CPU utilizado para el manejo de las
diversas interrupciones de hardware. En particular es útil consultar los mecanismos para:
•
indicar qué interrupciones de hardware pueden interrumpir y cuáles no,
•
comunicar al controlador 8259A (desde el manejador de interrupciones implementado) que
la interrupción fue atendida y por ende puede volver a interrumpir por ese canal.
Las tareas no tendrán acceso a la memoria de video. Por esta razón, el kernel debe
implementar un servicio de impresión en pantalla para desplegar una cadena especificada por
parámetro en una posición de pantalla también especificada por parámetro.
El entorno de ejecución debe cumplir con las siguientes características:
•
Cada tarea cuenta con sus propios segmentos de código, datos y stack, que deben estar
definidos con nivel de privilegio 3 (el menos privilegiado).
•
Cada tarea cuenta con su propia LDT, en la cual están definidos todos los segmentos a los
que accede.
Página 4 de 9
Aspectos Avanzados de Arquitectura de Computadoras
•
El código del kernel ejecuta con nivel de protección 0 (el más privilegiado). En general
todos los segmentos salvo los que son específicos para las tareas deben definirse con nivel
de privilegio 0.
•
Las rutinas de interrupción deben ser atendidas en el mismo contexto de la tarea
interrumpida (una interrupción no genera un cambio de tarea).
•
Todos los segmentos de código deben ser nonconforming.
•
El servicio de impresión en pantalla se accede a través de un call gate descriptor definido en
la LDT de cada tarea y los parámetros deben pasarse por stack.
•
La disposición en memoria de las distintas partes de la tarea debe ser la siguiente:
...
...
1000h-1FFFh
Espacio reservado para segmentos de código y datos de la Tarea 1.
2000h-2FFFh
Stack para nivel de privilegio 3 de la Tarea 1.
3000h-3FFFh
Stack para nivel de privilegio 0 de la Tarea 1.
4000h-4FFFh
Espacio reservado para segmentos de código y datos de la Tarea 2.
5000h-5FFFh
Stack para nivel de privilegio 3 de la Tarea 2.
6000h-6FFFh
Stack para nivel de privilegio 0 de la Tarea 2.
...
...
7C00h - XXXX Boot loader y kernel.
...
...
B8000h
Memoria de video
El código de la Tarea 1 y la Tarea 2 debe escribirse en lenguaje C con ensamblador embebido.
El kernel puede incluir código C pero no es un requisito. El kernel (eventualmente junto con el boot
loader) y cada una de las tareas se compilan como tres módulos independientes que se almacenan
cada uno en una región específica del disquete (a libre elección). El boot loader debe cargar desde el
disquete a memoria tanto el kernel como las tareas.
Por documentación sobre cómo escribir código ensamblador embebido en C consultar [5,
Sección 6.41]. Tener en cuenta que el lenguaje utilizado por gcc para compilar código ensamblador
embebido en C sigue la sintaxis de AT&T, que es diferente a la sintaxis de Intel utilizada por Nasm.
En el Apéndice B se incluye un resumen de las principales diferencias. Una referencia completa de
la sintaxis utilizada por gcc se encuentra en [6].
Descripción de la entrega
Formato de la entrega
Se debe entregar un único archivo llamado obligatorio2.tar.gz, en el formato utilizado por los
comandos tar y gzip de Unix, con el siguiente contenido:
1. Los archivos correspondientes al código fuente de su solución, repartidos en dos
directorios parteA y parteB, que deben contener los fuentes correspondientes a cada
parte. A su vez, para cada parte se debe entregar:
Página 5 de 9
Aspectos Avanzados de Arquitectura de Computadoras
◦
Un script con nombre make.sh, que arme la imagen del disquete booteable con su
solución.
◦
Archivo bochsrc de configuración del bochs.
2. La documentación del laboratorio en formato PDF con denominación
obligatorio2.pdf, conteniendo una descripción clara de todos los aspectos relevantes
de su solución.
Es muy importante respetar:
1. El nombre de directorios y archivos debe ser exactamente el pedido, respetando
mayúsculas y (falta de) espacios.
2. El archivo entregado debe tener el formato gzip-tarball.
3. El formato del archivo de documentación debe ser pdf.
La entrega se realizará a través del sitio Web del curso.
Fecha de entrega
La fecha límite para realizar la entrega es el domingo 27 de mayo a las 23:30 horas. El
sistema soporta múltiples entregas. Se recomienda que se realice una entrega con tiempo para
verificar que el sistema le permite entregar correctamente. Se recomienda especialmente que no
deje para último momento su única entrega y que realice una con al menos contenidos
parciales de forma de asegurar que no quede sin entregar.
Observaciones.
No se aceptará bajo ninguna circunstancia una entrega realizada pasada la fecha límite
estipulada. Esto incluye (pero no se restringe) a: entregas por email y entregas en formatos físicos.
En caso de que una entrega no cumpla los criterios pautados en cuanto a los formatos y
mecanismos establecidos, la misma puede no ser considerada como tal y descartada.
Observación sobre instancias de no individualidad.
Está prohibido utilizar código de otros estudiantes, de otros años, de cualquier índole, o hacer
público código, corto o largo, general o específico, a través de cualquier medio (news, correo,
papeles sobre la mesa, etc.). Los estudiantes que a juicio de los docentes no hayan cumplido con
esta norma perderán el curso. Además todos los casos serán enviados a los órganos competentes de
la Facultad, lo cual puede acarrear sanciones de otro carácter y gravedad.
Página 6 de 9
Aspectos Avanzados de Arquitectura de Computadoras
Apéndice A
Descripción de áreas de un ejecutable
Cuando se escribe código C que será cargado en una posición prefijada de memoria, puede ser
necesario, al momento de linkeditar, explicitar la ubicación que tendrán diferentes secciones que
forman un ejecutable. Existen seis secciones (ordenadas según posición estándar en memoria):
•
.text: En esta área se encuentra definido el código ejecutable. En arquitectura x86,
normalmente es señalado por el segmento CS
•
.rodata: Datos de solo lectura, variables declaradas como const y constantes de
cadena (por ejemplo el texto “ OBLIGATORIO 2-AAAC” ). Señalado por los
segmentos DS y ES.
•
.data: Esta área contiene variables globales y estáticas, inicializadas al ser definidas.
Señalado por los segmentos DS y ES.
•
.bss: Contiene las variables globales y estáticas, sin inicializar. Señalado por los
segmentos DS y ES.
•
Heap: En esta área se guardan las regiones de memoria creadas en forma dinámica.
Por ejemplo, al ejecutar un new o un malloc. No tiene un tamaño fijo y crece hacia
zonas más altas de la memoria. Señalado por los segmentos DS y ES.
•
Stack: En esta área se guardan los registros de activación de funciones y
procedimientos, que guardan entre otras cosas, la dirección de retorno y las variables
locales. Inicialmente se pone en la posición más alta disponible de la memoria. No
tiene un tamaño fijo y crece hacia las zonas más bajas de memoria. Es referenciado
por el segmento SS.
Un ejemplo en código:
int i; // Variable global no inicializada=> .bss
char * cadena; // Variable global no inicializada=> .bss
char a='a'; // Variable global inicializada=> .data ; el valor 'a' se guarda en .rodata
int main(){
int j; //Variable local => registro de activación de la función main
static int estático; // Variable estatica no inicializada => .bss
static int orig=5; // Variable estatica inicializada => .data
cadena= malloc(sizeof(char)*4);//pide 4 caracteres en el HEAP
printf("Esto es j: %d", j); //"Esto es j: %d" se guarda en .rodata
}
Página 7 de 9
Aspectos Avanzados de Arquitectura de Computadoras
Apéndice B
Principales diferencias entre la sintaxis de AT&T y la de Intel.
•
Los valores inmediatos se preceden por el símbolo $ en la sintaxis de AT&T
•
Los nombres de los registros se preceden por el símbolo % en la sintaxis de AT&T
•
Los operandos, en general, se colocan en orden inverso en las dos sintaxis.
•
El tamaño de los operandos en la sintaxis de AT&T se determina por el último carácter del
nombre de la instrucción: b – byte (8 bits), w – word (16 bits), l – long (32 bits). Por
ejemplo, la instrucción
addl $1, %eax,
suma uno al registro eax.
•
Saltos/llamadas far se codifican en AT&T como ljmp/lcall $segmento, $desplazamiento.
Página 8 de 9
Aspectos Avanzados de Arquitectura de Computadoras
Referencias
[1]R. Hyde, “The art of assembly language”, [en línea], 1996, [citado Abril de 2012], disponible en
Web: http://courses.ece.uiuc.edu/ece390/books/artofasm/artofasm.html.
[2] G. Brunmar, “The booting process”, [en línea], Julio, 2003, [citado Abril de 2012], disponible en
Web: http://www.osdever.net/tutorials/view/the-booting-process.
[3] G. Brunmar, “The world of Protected mode”, [en línea], Julio, 2003, [citado Abril de 2012],
disponible en Web: http://www.osdever.net/tutorials/view/the-world-of-protected-mode.
[4] Intel Corporation, “Intel® 64 and IA-32 Architectures Software Developer's Manual”, Volume
3A: System Programming Guide, Part 1, [en línea], Marzo, 2012, [citado Abril de 2012],
disponible en Web: http://www.intel.com/design/processor/manuals/253668.pdf.
[5] GNU Project, “Using the GNU Compiler Collection (GCC)”, [en línea], [citado Abril de 2012],
disponible en Web: http://gcc.gnu.org/onlinedocs/gcc/index.html
[6] GNU Project, “Using as”, [en línea], [citado Abril de 2012], disponible en Web:
http://sourceware.org/binutils/docs/as/
Página 9 de 9
Descargar