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');
}




    Source: geocities.com/gusdelact/cib5122003

               ( geocities.com/gusdelact)