Tema I. Hardware y Lenguaje Ensamblador para Intel x86 Cualquier sistema operativo tiene que administrar los recursos de la computadora. El código que aplica dichas operaciones es dependiente de la arquitectura de la computadora y es escrito en lenguaje ensamblador. Un sistema operativo con un diseño modular, aisla las rutinas en ensamblador para que apliquen las operaciones de bajo nivel (entrada/salida, cambios de contexto, manejo de interrupciones) 1. Unidad central de procesamiento Intel, modelo de memoria y sus Registros. El centro de una computadora es el CPU. En el caso de una PC es un CPU Intel. La arquitectura de computadora de 16 bits está basada en la arquitectura 8088 y las arquitecturas como 8086,80186, 80286 y 80386 difieren en esta por aspectos de velocidad, precio y manejo de hardware. La unidades básicas son el byte (8 bits) y una palabra o word (16 bits o 2 bytes) El modelo de memoria del 8088 es un espacio de direcciones de 1 MegaByte (1 MB), donde cada byte tiene una dirección unica de 0 a 2^20-1. Las variables tipicamente ocupan una palabra y pueden estar localizados en cualquier area de memoria, pero es practica comun almacenarlo a partir de un byte par y el siguiente byte impar, es decir, se usa alineacion par de memoria. Por ejemplo, la variable que tiene valor 0x7705 en memoria se almacena como (suponiendo que esta en la direccion 0x2030 Direccion: 0x2030 0x2031 Contenido: 0x05 0x77 (la regla es que el contenido se almacena al reves de como se lee) Por ejemplo una palabra de 2 words o double word con valor 0x12243648 y a partir de la direccion 0x1040 Direccion: 0x103D 0x103E 0x103F 0x1040 0x48 0x36 0x24 0x12 El 8088 tiene 12 registros, los cuales se clasifican en 3 grupos Uso generico AX (1 word) o AH (1 byte) y AL (1 byte) BX (1 word) o BH (1 byte) y BL (1 byte) CX (1 word) o CH (1 byte) y CL (1 byte) DX (1 word) o DH (1 byte) y DL (1 byte) De apuntadores IP - siguiente instruccion a ejecutar (1 word) SP - apuntador de la pila (1 word) BP - apuntador para manejar parametros de una funcion (1 word) SI - source index (1 word) DI - data index (1 word) Los registros SI y DI son utilizados para operaciones de desplazamiento de bloques y cadenas, y de comparacion. Algunos compiladores utilizan estos registros para variables de registro, guardandolas y recolaconadolas cuando se requieran. De Banderas De segmento CS - codigo (1 word) DS - dato (1 word) SS - stack o pila (1 word) ES - (1 Word) Estos registros se pueden considerar de 20 bits, pero los cuatro de bajo orden son siempre 0x0, por lo que siempre se almacenan 16 bits. Estos registros sirven para localizar en que area de memoria está el codigo maquina que se ejecuta, los datos y la pila La siguiente figura muestra la organización en memoria de un programa (con el sistema operativo incluido) Memoria Baja 0 100K 120K 184K 210K 1MB -------------------------------------------------------- |Sistema operativo|Texto|Datos|Pila|Texto|Datos|Pila|....| -------------------------------------------------------- Programa 1 Programa 2 Basicamente en memoria de la computadora se aloja al sistema operativo y varios programas. Para que se pueda utilizar el direccionamiento de los distintos componentes de un programa se usan los registros de segmento, donde los datos se hacen relativos a los segmentos de código, datos o pila segun aplique. Por ejemplo, si el programa 1 está en ejecución, CS tiene la dirección de 100K (0x19000), pero los ultimos 4 bits son cortados, se lee como 0x1900) y todas las direcciones del programa son relativas a 0x19000, entonces un salto (JMP) a 0x0400 (1K) se interpreta como Direccion real = 0x01900*0x10 + 0x0400 = 0x19400 (101 K) En si cualquier codigo que se ejecuta es Direccion Real = CS*0x10 + Direccion Relativa dada por IP Para datos Direccion Real = DS*0x10 + Direccion Relativa de variable por ejemplo, mover el contenido de la dirección 0x2500 al registro AX, si DS es 0x1E000 (120 K), la direccion real es 0x1E000*0x10 + 0x2500 = 0x1E2500 Para pila Direccion Real = SS*0x10 + Direccion Relativa dada por SP El registro ES es un segmento extra y es usado de manera auxiliar. FLAGS (1 word) El CPU contiene un registro denominado FLAGS o palabra de condicion del programa (Program Status Word). Contiene los bits del código de condición (colocados en instrucciones de comparación), donde un bit indica si las interrupciones están activadas o desactivadas y algunos otros bits. En una maquinas con un modo de kernel y uno de usuario, un bit en la PSW indica qué modo es el actual. El 8088 tiene sólo un modo (en esencia, el modo del kernel). El PSW se usa principalmente para el procesamiento de interrupciones. 2. Estructura de un programa ensamblador Un programa ensamblador queda dividido en 3 secciones, datos, pila y código. En cada sección se indican una serie de instrucciones, con el siguiente formato IDENTIFICADOR OPERACION OPERANDO COMENTARIO El identificaro es una etiqueta La operación define datos o instrucciones ensamblador El operando son los valores con los que opera la operacion Comentario, dado con un ; La mayoria de las instrucciones tiene un código de operación de 1 byte seguido de 1 byte que especifica los modos de direccionamiento y los registros. Los modos de direccionamiento son de registro a registro, memoria a registro, indirecto e indizado. El direccionamiento directo se utiliza para variables locales. 3. Primer programa en ensamblador Solo suma una unidad al registro AX y lo almacena en BX ;primer programa en ensamblador que suma una unidad a AX y guarda el ;resultado en BX ;IDENTIFICADOR OPERACION OPERANDO COMENTARIO ;Segmento de codigo CODESEG SEGMENT PARA ;define un segmento en una direccion ;divisible entre 16 o 10H MOV AX,0 ;mover un 0 a AX INC AX ;incrementar una unidad a AX MOV BX,AX CODESEG ENDS ;fin de segmento de codigo END 4. Instrucciones básicas en ensamblador. ADD AX, var ; sumar el contenido de una palabra de la memoria var al registro AX AND BX,[BP+6] ; hacer un AND BX con la palabra en memoria en la direccion BP+6 CMP AX,1 ; comparar AX con 1 DEC [BP-4] ; decrementar una unidad a palabra en memoria en la direccion BP-4 INC SI ; incrementar una unidad al contenido del registro SI JE etiqueta ; saltar a etiqueta si la operacion de comparacion es igual (cero en FLAG:SF) JL etiqueta ; saltar a etiqueta si la comparacion anterior fue menor (JG,JNE existen) LOOP etiqueta ; restar 1 de CX; si es distinto de cero saltar a etiqueta MOV AX,var ; mover el contenido de var a AX MOVB AX,VAR ; pasar el contenido del byte de la memoria var a AX POP var ; descargar una palabra de la pila y colocarla en var PUSH BX ; meter el registro BX en la pila REP ; repetir siguiendo la instruccion hasta que CX sea 0 CLI ; desactivar interrupciones (eliminar las señales de interrupcion) STI ; activar las interrupciones (fija la señal de interrupcion) IRET ; regresa desde la interrupcion, descargando PC,CS,PSW CALL _rutina ; llamada al procedimiento _rutina RET ; retornar de la llamada al procedimiento (descarga la direccion de retorno en IP y por tanto pasa a ese sitio) 5. Invocando una rutina ensamblador desde lenguaje C. Es posible que C use rutinas en ensamblador. Solo que se debe tener en cuenta las siguientes reglas +) Se debe definir una funcion en ensamblador. Si el nombre en C es X, en ensamblador es _X +) Los registros BP, SI y DI deben ser guardados en la pila. En el caso de SI y DI se usen, se debe aplicar esta regla. BP SIEMPRE se debe salvar. Primero se debe guardar BP, luego SI y DI. Esto se debe a que los procedimientos en C asumen que BP, DI y SI no cambian. +) El registro BP es la referencia de parametros y variables locales dentro de la función. En la entrada de la función, el valor actual de BP es guardado en la pila (via un push) y el valor de SP se mueve a BP. Se conoce que BP es el apuntador al "stack frame" o "contexto de la pila" y la organización de memoria se ve de la siguiente manera (suponiendo que se guardo DI y SI) ----------- SP---> DI guardado Memoria Baja ----------- SI guardado ----------- variables locales ----------- BP guardado BP---> ----------- Dirección retorno ----------- parametros ----------- Memoria alta Como regla + La direccion de retorno esta en BP+2 (BP corrido 2 bytes) + Para accesar los parametros se debe tomar desde BP+4. C guarda los parametros en orden inverso, el ultimo parametro se apila primero y el primero hasta el final (esto permite implantar funciones de parametros variables). Es decir los parametros se toman con BP mas un corrimiento (offset) positivo + Para accesar las variables locales debe tomar bp-x,donde x queda definido segun el tipo de dato y el orden de declaracion de las variables; es decir BP mas un offset negativo + El valor de retorno se deposita en el registro AX (cuando es de 1 o 2 bytes) o en AX y DX (cuando es de 4 bytes) Suponiendo una funcion f que recibe un parametro char y otra funcion g que recibe dos parametros char. La siguiente relación muestra el manejo de la pila para el paso de parametros, suponiendo las direcciones asignadas en el texto. En main, los registros SP y BP son SP FFF6 BP FFF6 Cuando se invoca a f SP FFF2 (4 bytes, dado que en sp+2 está la direccion de retorno y en dx el parametro de entrada) BP FFF6 Se introduce sp al stack SP FFF0 (se introducen 2 bytes en la pila) BP FFF0 Se resta 4 al SP SP FFEC BP FFF0 Al iniciar la logica de la rutina SP FFEC BP FFF0 Al preparar los dos parametros para g SP FFE8 (se introducen 4 bytes en el stack, los dos parametros) BP FFF0 Al invocar a g SP FFE6 (se introducen 2 bytes donde esta la direccion de retorno BP FFF0 Se introduce sp al stack SP FFE4 BP FFE4 Se resta 2 a la pila SP FFE2 BP FFE4 El siguiente diagrama muestra la estructura de la memoria En la rutina main Memoria Alta Memoria Baja --------------------------------------------------------------- | | Datos | Texto | --------------------------------------------------------------- ^ | SP (Pila) Al invocar la funcion f Memoria Alta Memoria Baja --------------------------------------------------------------- | |par1f|retorno|BP Anterior|local1| ... | Datos | Texto | --------------------------------------------------------------- ^ | SP (Pila) Al invocar la funcion g Memoria Alta Memoria Baja -------------------------------------------------------------------------------------------------------- | |par1f|retorno|BP Anterior|local1|par2g|par1g|retorno|BP Anterior|local1|local2|... | Datos | Texto | -------------------------------------------------------------------------------------------------------- ^ | SP (Pila) Se reproduce el código C y ensamblador ifndef ??version ?debug macro endm $comm macro name,dist,size,count comm dist name:BYTE:count*size endm else $comm macro name,dist,size,count comm dist name[size]:BYTE:count endm endif ?debug S "func.c" ?debug C E916A61F2F0666756E632E63 _TEXT segment byte public 'CODE' _TEXT ends DGROUP group _DATA,_BSS assume cs:_TEXT,ds:DGROUP _DATA segment word public 'DATA' d@ label byte d@w label word _DATA ends _BSS segment word public 'BSS' b@ label byte b@w label word _BSS ends _TEXT segment byte public 'CODE' ; ; char g(char p1,char p2) ; assume cs:_TEXT _g proc near push bp mov bp,sp mov cx,word ptr [bp+2] mov bl,byte ptr [bp+4] mov bh,byte ptr [bp+6] sub sp,2 ; ; { ; ; char y = p1+p2; ; mov al,byte ptr [bp+4] add al,byte ptr [bp+6] mov byte ptr [bp-1],al ; ; return y; ; mov al,byte ptr [bp-1] jmp short @1@58 @1@58: ; ; } ; mov sp,bp pop bp ret _g endp ; ; char f(char p) ; assume cs:_TEXT _f proc near push bp mov bp,sp mov cx,word ptr [bp+2] mov bl,byte ptr [bp+4] sub sp,4 ; ; { ; char a=2+p,b=3+p ; ; mov al,byte ptr [bp+4] add al,2 mov byte ptr [bp-1],al mov al,byte ptr [bp+4] add al,3 mov byte ptr [bp-2],al ; ; char x=g(a,b); ; mov al,byte ptr [bp-2] push ax mov al,byte ptr [bp-1] push ax call near ptr _g pop cx pop cx mov byte ptr [bp-3],al ; ; return x; ; mov al,byte ptr [bp-3] jmp short @2@58 @2@58: ; ; } ; mov sp,bp pop bp ret _f endp ?debug C E9 _TEXT ends _DATA segment word public 'DATA' s@ label byte _DATA ends _TEXT segment byte public 'CODE' _TEXT ends public _f public _g _s@ equ s@ end extern int f(char); extern int g(int,int); main() { f(16); printf("\n%p %p %p\n",main,g,f); } 6. Lenguaje ensamblador para interactuar con el Hardware Además del CPU, Intel consta de hardware para controlar memoria, teclado, pantalla de despliegue, unidades de disco y otros perifericos. La CPU se comunica con dichos componentes a través de un conjunto de 62 conexiones paralelas conocidos como el bus del sistema. Algunas conexiones se utilizan para direcciones, algunos datos y otros para el control. La CPU emite comandos a los dispositivos de E/S mediante la ejecución de la instrucción ensamblador OUT, la cual produce un byte para el controlador del dispositivo. El controlador contiene la electrónica que convierte los comandos digitales de la CPU en señales de control analógicas para activar el dispositivo de E/S. Cada controlador tiene un conjunto de direcciones E/S asignado a él. El controlador del floppy, por ejemplo, tiene la dirección E/S 0x3F0 a 0x3F7, y el video tiene dirección de E/S 0x3C0 a 0x3CF. Estas direcciones de E/S pertenecen a un espacio de de dirección de E/S diferentes y no están relacionadas con las direcciones ordinarias de memoria. De esta manera, la CPU manda comandos a los distintos dispositvos. Por ejemplo OUT 3F2H ; mandar un byte a la tarjeta controladora del floppy Cuando un dispositivo de E/S termina de ejecutar su comando, éste le puede indicar a la CPU enviando una señal de interrupción por el bus. La señal de interrupción hace que la CPU introduzca tres palabras en la pila actual: primero el registro de FLAGS o PSW, despues el registro CS y por último el Program Counter (PC). Después se desactiva el bit de interrupción del registro PSW, para desactivar interrupciones adicionales y finalmente se carga un nuevo Program Counter (PC) y registro CS. Los cuatro bytes que contienen los nuevos PC y CS se denominan vectores de interrupción; y esta carga oblica a que la CPU salte a una dirección en alguna parte de dirección de código. Como cada controlador tiene su vector de interrupción, cada uno tiene la dirección del código que sirve a las interrupciones de ese controlador. El sistema PC tiene en ROM una serie de rutinas para controlar el hardware del sistema, denominado BIOS (Basic Input Output System, sistema básico de entrada y salida). Contiene un conjunto de procedimientos de lectura y escritura de bloques del disco, escritura de caracteres en la pantalla y otra E/S. Ninguno de estos procedimientos del BIOS es impulsado por interrupciones. Todos espera a que la E/S termine anstes de devolver el control al usuario. Por tanto, si un programa está esperando la entrada del teclado, todo el sistema, incluso todos los procesos de fondo, hacen un alto total. En un sistema de tiempo compartido, la detención de todo el sistema debido a que un programa necesite entrada no es óptimo. En resumen, para manejar el hardware de una computadora, se puede hacer por medio de dos filosofías, escribiendo al bus de sistema o por medio del BIOS. 7. Uso del BIOS Para poder comunicarse con el BIOS se necesita usar una interrupción de software. EXisten distintos números de interrupciones para distintos propósitos y a continuación se listan número Nombre Descripción 0x08 CLK_INT Interrupción del reloj 0x09 KBD_INT Interrupción del teclado 0x10 VID Servicio de desplegado de video 0x0E FLPY Control del disco flexible (floppy) 0x16 KBD Servicios de teclado Por cada interrupción se deben en algunos casos llenar los registros de uso genérico (AX, BX, DX) Se muestra a continuación la tabla para algunos servicios INTERRUPCION SubFuncion Registros entrada Registros de salida 16H Leer teclado AH=00H AX 10H Escribir a video AH=0EH AL=caracter 13H Estado de floppy AH=02H DL=00H AX Se muestra codigo ensamblador para leer del teclado un caracter, escribir un caracter a video y probar la existencia de un disco flexible El código ensamblador que maneja los servicios de BIOS ifndef ??version ?debug macro endm $comm macro name,dist,size,count comm dist name:BYTE:count*size endm else $comm macro name,dist,size,count comm dist name[size]:BYTE:count endm endif ?debug S "bios.c" ?debug C E97406212F0662696F732E63 _TEXT segment byte public 'CODE' _TEXT ends DGROUP group _DATA,_BSS assume cs:_TEXT,ds:DGROUP _DATA segment word public 'DATA' d@ label byte d@w label word _DATA ends _BSS segment word public 'BSS' b@ label byte b@w label word _BSS ends _TEXT segment byte public 'CODE' ; ; int kbdgetc() ; assume cs:_TEXT _kbdgetc proc near push bp ;guardar bp en la pila mov bp,sp ;poner bp relativo a sp mov ah,00H ;peticion de lectura de un caracter del teclado int 16H ;invocar la interrupcion 16 del BIOS, en AX se queda xor ah,ah ;eliminar byte para caracteres especiales pop bp ;desapilar bp ret _kbdgetc endp ; ; void ttyw(int c) ; assume cs:_TEXT _ttyw proc near push bp mov bp,sp mov al,[bp+4] ;tomar el caracter a escribir mov ah,0EH ;la subfuncion OEH es escribir a video mov bl,07H ;color de background mov bh,0 ;usar pagina de video actual int 10H ;invocar la interrupcion 10H del BIOS pop bp ret _ttyw endp ; ; int hasFloppy() ; assume cs:_TEXT _hasFloppy proc near push bp mov bp,sp mov ah,02H ;peticion para reestablecer el disco xor al,al ;limpiar al mov dl,00H ;pedir unidad de disco flexible xor dh,dh ;limipiar dh int 13H ;invocar interrupcion 13H del BIOS, disco mov al,ah xor ah,ah pop bp ret _hasFloppy endp ?debug C E9 _TEXT ends _DATA segment word public 'DATA' s@ label byte _DATA ends _TEXT segment byte public 'CODE' _TEXT ends public _hasFloppy public _ttyw public _kbdgetc _s@ equ s@ end El archivo de encabezado es /*leer un caracter del teclado usando el BIOS*/ extern int kbdgetc(void); /*desplegar un caracter en video, usando el BIOS*/ extern void ttyw(int); #define HASFLPY 0x0009 #define NOFLPY 0x0080 /*checa si la computadora tiene una unidad de disco flexible*/ extern int hasFloppy(); El programa que prueba es #include "bios.h" void main() { int ch=0; int floppy=0; /*leer un caracter*/ ch=kbdgetc(); ttyw(ch); ttyw('\n'); floppy=hasFloppy(); if (floppy==HASFLPY) ttyw('f'); else if (floppy==NOFLPY) ttyw('n'); else ttyw('?'); ttyw('\n'); }