Anda di halaman 1dari 34

El sistema operativo OSO

Departamento de Ingeniera y Tecnologa de Computadores Universidad de Murcia 25 de abril de 2005 ndice


1. Introduccin 2. Estructura del sistema 3. Ejecucin del sistema operativo 3.1. Iniciacin del sistema operativo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.2. Puntos de entrada al sistema operativo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4. Gestin de procesos y de memoria 4.1. La tabla de procesos . . . . . . . . . 4.2. Estado de un proceso . . . . . . . . 4.3. Contexto de un proceso . . . . . . . 4.4. Gestin de procesos . . . . . . . . . 4.4.1. Creacin de procesos . . . . 4.4.2. Planicacin de procesos . . 4.4.3. Apropiacin de la CPU . . . 4.5. Gestin de memoria . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 3 4 4 6 9 9 10 11 13 16 16 17 17 17 23 24 25 27 28 28 28 30 31 31 31 31

5. Sistema de cheros 5.1. Gestin de los dispositivos de almacenamiento . . . . . . . . . . . . . . . . . . . . . . . . . . 5.2. Gestin del sistema de cheros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6. Gestin de la E/S 6.1. Lectura de un dispositivo de almacenamiento . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.2. Salida por pantalla . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.3. Entrada por teclado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7. Llamadas al sistema 7.1. Sincronizacin de procesos dentro del ncleo 7.2. La llamada sys_crear_proceso . . . . . 7.3. La llamada sys_escribir . . . . . . . . . 7.4. La llamada sys_leer . . . . . . . . . . . . 8. Funciones auxiliares . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

1.

Introduccin

Este documento describe la estructura, diseo e implementacin del sistema operativo OSO. OSO es un sistema operativo monoltico muy pequeo con el que se pretende mostrar, de manera prctica, algunos de los conceptos fundamentales de los sistemas operativos actuales, en especial, el concepto de multitarea. Entre los objetivos que han guiado el diseo de este sistema operativo podemos destacar los siguientes: El sistema operativo debe ser lo ms pequeo posible. Por este motivo OSO slo implementa tres llamadas al sistema bsicas como son: la creacin de procesos, la lectura de teclado y la salida por pantalla. La implementacin de estas llamadas al sistema tambin ser un aspecto importante ya que muestran cmo el sistema operativo ofrece sus servicios a los procesos de usuario. El sistema operativo debe ser multitarea. La multitarea es uno de los conceptos ms importantes y es fundamental entender su funcionamiento. En concreto, se pretende mostrar el funcionamiento del cambio de contexto, el bloqueo/desbloqueo de procesos y la planicacin de los mismos. El sistema operativo debe hacer una gestin bsica de la memoria. En el caso de OSO la gestin de la memoria la vamos a basar en particiones estticas de igual tamao. El sistema operativo debe gestionar algn tipo de sistema de cheros. La importancia de ver en detalle el funcionamiento de los sistemas de cheros es que en ellos se almacenan el cdigo y los datos de los distintos programas que ejecuta el sistema operativo. Este objetivo tambin implica el uso de algn tipo de dispositivo de almacenamiento. Para simplicar estos dos objetivos de diseo se usa el disquete como dispositivo de almacenamiento y FAT12 como sistema de cheros. Por ltimo, el sistema operativo debe ser interactivo. Esta caracterstica facilita mucho el uso del sistema operativo al permitir al usuario controlar el funcionamiento del mismo. Este objetivo se va a conseguir mediante la entrada de informacin por teclado y la salida de informacin por pantalla, lo que supone una gestin bsica de la E/S por parte del sistema operativo. Esta gestin de la E/S nos va a permitir, adems, mostrar de forma natural uno de los aspectos fundamentales de la gestin de procesos como es el bloqueo de un proceso y su desbloqueo posterior. En cuanto a la implementacin son tambin varios los objetivos que hemos tenido en cuenta. Algunos de ellos son los siguientes: La implementacin debe centrarse en el sistema operativo y no en el hardware. Para cumplir este objetivo se utiliza el modo real de los procesadores actuales de Intel y compatibles. Este modo ofrece una visin del hardware muy sencilla, aunque tiene como principal problema el que no ofrece ningn mecanismo de proteccin hardware al sistema operativo. La implementacin debe hacerse en un lenguaje de alto nivel. En el caso de OSO, el lenguaje utilizado es C. El lenguaje ensamblador tambin se utiliza pero slo en aquellos casos en los que es realmente necesario (como es la implementacin del cambio de contexto). Un aspecto importante es entender cmo se relaciona el lenguaje de alto nivel con el lenguaje ensamblador. En el caso de C esta relacin es mucho ms directa que la proporcionada por otros lenguajes de alto nivel como Pascal o C++, lo que justica el uso de este lenguaje. El entorno de programacin debe ser lo ms sencillo y cmodo posible. Este objetivo se consigue de varias maneras, principalmente, utilizando el disquete como dispositivo de almacenamiento y FAT12 como sistema de cheros, al igual que hace el sistema operativo OSO. Al utilizar un sistema de cheros real se puede modicar su contenido fcilmente mediante las utilidades que existen en los sistemas operativos actuales (como Windows y Linux). As, podremos cambiar de manera sencilla el ncleo del sistema operativo cuando lo modiquemos y los programa a ejecutar en l.

En las siguientes secciones vamos a describir cmo se han conseguido estos objetivos. En primer lugar describiremos la estructura general del sistema operativo. Despus veremos cmo se han implementado cada uno de sus distintos componentes.

2.

Estructura del sistema

La gura 1 describe la estructura general del sistema en el que se ejecuta el sistema operativo OSO.

Crear proceso

Escribir en pantalla

Leer de teclado

Espacio de usuario

Llamadas al sistema Ncleo del sistema operativo Espacio del ncleo

BIOS

Hardware de teclado

PIC

Hardware

Figura 1: Estructura general de OSO Como podemos observar, la interfaz de llamadas al sistema slo proporciona tres servicios bsicos a los programas de usuario: la creacin de procesos, la lectura de caracteres de teclado y la salida por pantalla de cadenas de caracteres. Para implementar las tres llamadas al sistema, OSO necesita acceder al hardware. Esto no siempre es as, es decir, podra haber llamadas al sistema que no necesitaran acceder al nal al hardware. OSO accede al hardware, bien directamente, bien indirectamente a travs de la BIOS. Aunque el sistema operativo puede omitir la BIOS y acceder directamente a cualquier dispositivo hardware, en el caso de OSO se ha optado por hacer uso de algunos servicios de la BIOS en aras de simplicar la implementacin del sistema operativo. No obstante, es importante saber que los sistemas operativos actuales rara vez hacen uso de la BIOS ya que sta presenta algunos problemas para el sistema operativo (no es reentrante, habilita las interrupciones, etc.). Como muestra la gura, OSO hace uso de la BIOS para leer de disco y escribir en pantalla, y accede directamente al hardware para leer de teclado y para indicar al PIC (Programmable Interrupt Controller) que una interrupcin ha sido tratada. De las distintas interrupciones existentes, OSO slo va a controlar dos: la del reloj (o timer) y la del teclado. La interrupcin de reloj se va a utilizar para implementar la multitarea. As, se le va a poder quitar la CPU a un proceso cuando ste lleve demasiado tiempo en ella. La interrupcin del teclado, por su parte, se va a utilizar para desbloquear a aquellos procesos bloqueados que esperan un carcter de teclado.

Las llamadas al sistema se solicitan con una interrupcin software (ejecucin de la instruccin de salto int 22h). Esta interrupcin software y las dos interrupciones hardware (la de reloj y la de teclado, ambas iniciadas por la activacin elctrica de cables) constituyen los tres nicos puntos de entrada al sistema operativo, es decir, estas tres interrupciones son las que hacen que el procesador deje de ejecutar cdigo de los procesos de usuario para ejecutar cdigo del sistema operativo. Un aspecto a tener en cuenta es que OSO se ejecuta en el modo real de los procesadores de Intel y compatibles. Ya que este modo no ofrece ningn tipo de proteccin, los procesos de usuario podrn acceder directamente al hardware sin que el sistema operativo lo pueda evitar. Tambin podrn acceder directamente a la memoria de otros procesos o a la del propio sistema operativo e interferir en su ejecucin.

3.

Ejecucin del sistema operativo

En esta seccin vamos a describir primero qu pasos realiza el sistema operativo cuando comienza a ejecutarse y despus veremos los tres puntos de entrada al sistema operativo una vez iniciado el mismo.

3.1.

Iniciacin del sistema operativo

El siguiente chero fuente, kernel.c, contiene la funcin main del sistema operativo OSO, es decir, describe lo que hace el sistema operativo cuando comienza su ejecucin:
#include #include #include #include #include "string.h" "fat.h" "procesos.h" "sais.h" "kernel.h"

/* El sistema operativo ejecuta indenidamente el procedimiento idle * mientras espera la llegada de peticiones de servicio y mientras no * haya otros procesos listos para ejecutarse .*/ static char molinillo[ ] = {|, /, -, \\}; void idle(void) { long int i; unsigned char j; unsigned char far *pantalla = (char far *)0xb8000000; j=0; for(;;) {

10

15

20

for (i = 0; i < 2000000; i++) ; pantalla[0] = molinillo[j]; j = (j + 1) % 4;

25

struct dispositivo * disquete = NULL; void main(void) { kputs("Inicializando la tabla de dispositivos\n\r"); inicializar tabla dispositivos(); kputs("Inicializando la tabla de procesos\n\r"); inicializar tabla procesos();

30

35

kputs("Creando proceso 0 (idle)\n\r"); crearproceso0(); disquete = abrir dispositivo(0); kputs("Estableciendo SAIs\n\r"); establecersai(sai timer, 0x08); establecersai(sai teclado, 0x09); establecersai(sai llamadas, 0x22); kputs("Cargando programa SHELL.COM. . . "); if (crearproceso(disquete, "SHELL.COM") < 0) kputs("Fallo\n\r"); else kputs("Correcto\n\r"); kputs("Sistema operativo cargado\n\r"); } idle();

40

45

50

55

/* $Id: kernel.c,v 1.5 2004/04/12 17:12:56 piernas Exp $ */

60

Lo primero que hace el sistema operativo es inicializar algunas estructuras de datos internas para que tengan valores conocidos. Estas estructuras son: la tabla de dispositivos de almacenamiento, congurada por la funcin inicializar_tabla_dispositivos, y la tabla de procesos, congurada por la funcin inicializar_tabla_procesos. El siguiente paso es crear el proceso 0 mediante la funcin crearproceso0. El proceso 0 va a ser el ujo inicial de la funcin main de nuestro sistema operativo y es, por tanto, un proceso especial. Este proceso se ejecuta slo cuando no hay ningn otro proceso listo. En nuestro caso el proceso 0 va a estar ejecutando indenidamente el cdigo de la funcin idle a la que se salta al nal de la funcin main. La implementacin de la funcin idle se encuentra justo antes de la funcin main. Lo nico que hace esta funcin es ejecutar un bucle innito que dibuja un molinillo en la esquina superior izquierda de la pantalla. El molinillo se construye mostrando repetidamente y de forma indenida la secuencia de caracteres |, /, -, \. Ya que el dispositivo de almacenamiento principal de OSO es el disquete, lo siguiente que se hace, tras crear el proceso 0, es abrir dicho dispositivo de almacenamiento mediante la funcin abrir_dispositivo. La primera unidad de disquetes se identica con el nmero 0 y se es el nmero que se pasa como parmetro a la funcin. El valor devuelto por la funcin abrir_dispositivo se almacena en la variable disquete y es un puntero a una estructura de datos que contiene informacin sobre el sistema de cheros FAT del disquete. Esta informacin se utilizar cuando se quiera acceder al disquete para, por ejemplo, cargar un programa. Lo siguiente que hace el sistema operativo es establecer los tres puntos de entrada al mismo. Estos tres puntos de entrada ya los hemos descrito en la seccin 2 anterior y son: llamadas al sistema (que se solicitan mediante una interrupcin software), interrupcin de reloj e interrupcin de teclado. Para establecer un punto de entrada lo que se hace es modicar un vector de interrupcin con la direccin de inicio de la subrutina de atencin a interrupcin (SAI ) correspondiente. La interrupcin de reloj tiene asociado el vector 8, la de teclado el vector 9 y la instruccin int 22h el vector 22h. Las subrutinas que se alian, mediante la funcin establecersai, con cada uno de estos vectores son, respectivamente, sai_timer, sai_teclado y sai_llamadas, y se describirn en detalle ms tarde. Lo ltimo que hace el sistema operativo es crear, mediante la funcin crearproceso, el primer proceso que se ejecutar en el espacio de usuario. En este caso se trata del programa SHELL.COM que desempea un papel similar al del proceso init de los sistemas Unix. El programa SHELL.COM es un pequeo intrprete de rdenes que ejecuta cualquier programa cuyo nombre se introduce por teclado. Puede ver ms informacin sobre este programa en el documento titulado Desarrollo de aplicaciones en el sistema operativo OSO.

Cuando llegamos a este punto, el sistema operativo ya ha terminado de iniciar todos los componentes del sistema. Lo ltimo que hace es saltar a la funcin idle cuyo cdigo se ejecutar indenidamente como un proceso ms (el proceso 0 que hemos comentado antes). El ncleo del sistema operativo es un ejecutable COM que necesita un chero c0t especial para compilarse. A continuacin mostramos dicho chero que se llama c0t_krnl.asm en nuestro caso:
model tiny .code EXTRN main:NEAR .startup call main END ; $Id: c0t krnl.asm,v 1.3 2004/04/12 16:31:17 piernas Exp $

El chero c0t_krnl.asm lo nico que contiene es una llamada a la funcin main que acabamos de describir. No obstante, es importante darse cuenta de que el sistema operativo necesita un entorno adecuado para su ejecucin. Como podemos ver, dicho entorno no se establece dentro de este chero (aunque se podra hacer). Nosotros hemos optado por que sea el programa cargador el que establezca el entorno de ejecucin adecuado del sistema operativo antes de cederle el control de la CPU. Finalmente, mostramos el contenido del chero cabecera kernel.h en el que nicamente podemos destacar la declaracin de la variable disquete como variable externa, ya que ser utilizada en otros mdulos del programa.
#ifndef #dene KERNEL H KERNEL H
5

/* Las siguientes tres constantes se denen y usan en el cargador. Aqu se * muestran a ttulo informativo */ /* Zona de memoria ocupada por el sistema operativo. La memoria libre empieza a partir de 1000:0000 */ #dene INICIO SO ((char far *)0x00500100) #dene FIN SO ((char far *)0x0050FB00) /* Tamao mximo del sistema operativo: 8 KB */ #dene SECTORES SO 16 /* Variable de tipo dispositivo para el disquete (supone que el SO * se ejecuta desde un disquete */ extern struct dispositivo * disquete; #endif /* $Id: kernel.h,v 1.3 2004/04/12 16:31:17 piernas Exp $ */

10

15

20

3.2.

Puntos de entrada al sistema operativo

Como hemos comentado en el apartado anterior, el proceso 0, que es el ujo inicial de nuestro sistema operativo, slo se ejecuta cuando no hay ningn proceso listo. En realidad, el proceso 0 no ejecuta todo el cdigo del sistema operativo sino slo una parte muy pequea del mismo (la funcin main y las funciones que sta invoca). Cuando hay procesos listos, la CPU se reparte entre ellos. El proceso 0 no se ejecuta y el sistema operativo permanece a la espera de ser activado para realizar alguna accin.

Como ya hemos comentado en la seccin 2, el sistema operativo OSO puede ser activado por tres motivos: una llamada al sistema, una interrupcin de teclado o una interrupcin de reloj. En estos tres casos el control de la CPU pasa al sistema operativo que ejecuta parte de su cdigo para atender a la interrupcin. El cdigo de estos tres puntos de entrada al sistema operativo se encuentra en el chero sais.c que se muestra a continuacin:
#include #include #include #include "asm.h" "llamadas.h" "io.h" "procesos.h"

void establecersai (void (*sai) (void), unsigned char numvector) { unsigned long far * vector; asm {cli} vector = (long far *) ((int)numvector * 4); *vector = (long)sai; } asm {sti}

10

15

/* SAI para las llamadas al sistema */ void sai llamadas(void) { salvar contexto; if ((current>contexto>ax >> 8) > NR LLAMADAS) current>contexto>ax = ENOSYS; else current>contexto>ax = (tabla llamadas[current>contexto>ax >> 8].func)(); } restaurar contexto;

20

25

30

/* SAI para la interrupcin de teclado */ void sai teclado(void) { salvar contexto; recoger caracter(); asm { /* EOI. Decimos al PIC que la interrupcin ha sido tratada. */ mov al, 20h out 20h, al

35

40

} }

restaurar contexto;

45

/* SAI para la interrupcin del timer */ static char molinillo[ ] = {|, /, -, \\}; static unsigned char j = 0; static unsigned char far *pantalla = (char far *)0xb8000000; void sai timer(void) { salvar contexto; /* Dibujamos un molinillo usando las interrupciones del timer */ pantalla[2] = molinillo[j]; j = (j + 1) % 4;

50

55

60

/* Comprobar si se deben parar los motores de las disqueteras */ tick pararmotor(); /* Le decimos al PIC que la interrupcin ha sido tratada. */ asm { mov al, 20h out 20h, al } /* Actualizamos diversos valores, segn el estado del proceso interrumpido */ switch(current>estado) { case PROCESO SYSCALL: current>tiempoSYS++; break; case PROCESO RUN: current>tiempoUSER++; current>quantum; break; default: ; /* Situacin de error */ } /* Llamamos al planicador por si hay que cambiar de proceso */ planicador(); } restaurar contexto;
90 65

70

75

80

85

/* $Id: sais.c,v 1.3 2004/04/12 16:31:17 piernas Exp $ */

El chero cabecera asociado a sais.c es sais.h y es el siguiente:


#ifndef #dene void void void void SAIS H SAIS H
5

establecersai (void (*sai) (void), unsigned char numvector); sai llamadas(void); sai teclado(void); sai timer(void);

#endif /* $Id: sais.h,v 1.2 2004/04/12 16:31:17 piernas Exp $ */

10

Se denen tres SAIs, una para cada punto de entrada. Adems, se dene la funcin establecersai que se utiliza en la funcin main del ncleo para asociar cada una de estas SAIs con una interrupcin concreta. Como podemos observar, la estructura de las tres SAIs es similar: se salva el contexto del proceso interrumpido, se hace cierto trabajo y se restaura el contexto del proceso al que se le concede la CPU. El proceso reanudado puede ser el mismo proceso interrumpido u otro proceso distinto seleccionado por el planicador. En la seccin 4 que hay a continuacin podr encontrar una descripcin ms detallada sobre el contexto y la planicacin de procesos. La funcin sai_llamadas atiende las llamadas al sistema. Bsicamente, lo que hace es comprobar que la llamada existe (lnea 23) y ejecutar la funcin asociada a dicha llamada (lnea 27). La funcin sai_teclado atiende las interrupciones de teclado. Esta funcin llama a la funcin recoger_caracter para leer del hardware de teclado el carcter introducido. Ya que la interrupcin de teclado es una interrupcin hardware, hay que indicar al PIC que la interrupcin ha sido tratada (lneas 3943) para que as el PIC pueda atender a ms interrupciones de teclado.

La funcin sai_timer atiende las interrupciones de reloj. Esta funcin desempea tres tareas distintas: llama a la funcin tick_pararmotor (para parar el motor de la disquetera si ha transcurrido cierto tiempo), actualiza ciertos campos de tiempo que existen en la estructura PCB del proceso actual (lneas 7282) y llama a la funcin planificador para cambiar de proceso si es necesario (lo cual ocurrir cuando el proceso actual haya agotado su quantum). Al ser las interrupciones de reloj interrupciones hardware, la funcin sai_timer tambin necesita decirle al PIC que ha tratado la interrupcin (lneas 6568) para as poder recibir ms interrupciones de reloj. Como podemos ver, la funcin sai_timer dibuja otro molinillo (como la funcin idle descrita anteriormente) para poder ver visualmente cundo se produce una interrupcin de reloj. Este molinillo se dibuja en la segunda posicin de la esquina superior izquierda de la pantalla. La explicacin completa de cada una de estas SAIs se ver en las seccin correspondiente, cuando veamos procesos, entrada/salida y llamadas al sistema.

4.

Gestin de procesos y de memoria

Esta seccin describe la gestin de procesos y de memoria que realiza OSO. En especial, la seccin se centra en la gestin de procesos ya que la gestin de memoria implementada es muy sencilla y se describir de manera breve al nal de la seccin, en el apartado 4.5.

4.1.

La tabla de procesos

La gestin de procesos en OSO es bastante simple. OSO mantiene una tabla de procesos que contiene un bloque de control de proceso o PCB para cada proceso. La estructura PCB se dene en el chero cabecera procesos.h que se muestra a continuacin:
#ifndef #dene PROCESOS H PROCESOS H
5

#include "fat.h" #dene MAX PROCESOS #dene QUANTUM struct contexto { unsigned unsigned unsigned unsigned unsigned unsigned unsigned unsigned unsigned unsigned unsigned unsigned unsigned unsigned }; 10 2

int int int int int int int int int int int int int int

anteriorSS; anteriorSP; ax; bx; cx; dx; ds; es; di; si; bp; ip; cs; estado;

10

15

20

25

struct PCB { unsigned int unsigned int

pid; registroSS;

30

};

unsigned int unsigned int unsigned int int unsigned long unsigned long unsigned int struct contexto far *

registroSP; estado; quantum; prioridad; tiempoUSER; tiempoSYS; memoria; contexto;

35

extern struct PCB * current; #dene #dene #dene #dene #dene PCB LIBRE PROCESO LISTO PROCESO RUN PROCESO SYSCALL BLOQ TECLADO 0 1 2 3 4

40

45

void inicializar tabla procesos(); void crearproceso0(void); int crearproceso(struct dispositivo far * pdis, char far * programa); void desbloquearprocesos(int bloqueadosen); void planicador(void); void ceder CPU(void); #endif /* $Id: procesos.h,v 1.5 2004/04/19 19:12:07 piernas Exp $ */

50

55

Para cada proceso la estructura PCB correspondiente almacena: el identicador del proceso (pid), un puntero a la cima de la pila (registroSS y registroSP), el estado del proceso (estado), unidades de tiempo que le quedan al proceso para agotar su quantum (quantum), la prioridad del proceso (prioridad), unidades de tiempo que el proceso se ha estado ejecutando en modo usuario (tiempoUSER) y en modo ncleo (tiempoSYS), segmento de memoria asignado al proceso (memoria) y un puntero al contexto del proceso (contexto). La utilidad de cada uno de estos campos la veremos ms tarde. La tabla de procesos es una estructura de datos esttica por lo que el nmero mximo de proceso que se pueden ejecutar a la vez est limitado (en este caso, a 10, tal y como indica la constante MAX_PROCESOS denida en la lnea 6 del chero procesos.h).

4.2.

Estado de un proceso

Como podemos ver en el chero procesos.h (lneas 4145), un proceso puede estar en 4 estados posibles: PROCESO_LISTO. Indica que el proceso est esperando a que se le conceda la CPU. PROCESO_RUN. Indica que el proceso est usando la CPU. PROCESO_SYSCALL. Este estado indica que el proceso est en mitad de la ejecucin de una llamada al sistema y que no se le puede quitar la CPU. Como veremos en la seccin 7, este estado se utiliza cuando un proceso ejecuta una llamada al sistema que necesita hacer uso de la BIOS. BLOQ_TECLADO. Indica que el proceso est bloqueado esperando a que se introduzca un carcter por teclado. En este estado al proceso no se le puede dar la CPU. El quinto valor, PCB_LIBRE, no es un estado de proceso y se utiliza para indicar que una entrada de la tabla de procesos est libre.

10

4.3.

Contexto de un proceso

Antes de ver la implementacin de procesos en OSO, es importante entender qu es, cmo se guarda y cmo se restaura el contexto de un proceso. Podemos decir que el contexto de un proceso lo constituyen todos aquellos datos que es necesario guardar cuando se interrumpe un proceso para que posteriormente, cuando se reanude el proceso, ste pueda continuar su ejecucin de forma normal, como si nunca hubiera sido interrumpido. En nuestro caso, el contexto de un proceso est formado por todos los registros del procesador. En el chero asm.h se denen dos macros que permiten guardar el contexto de un proceso interrumpido y reanudar la ejecucin de un proceso. El contenido de este chero es el siguiente:
#ifndef #dene ASM H ASM H
5

/* La interrupcin ya ha guardado en la pila CS, IP y los ags. Hay que * guardar todos los dems registros. Los registros BP, SI y DI los apila * automticamente el compilador cuando ve que se usan, como ser nuestro * caso al hacer pop di y pop si en restaurar proceso * * Las intrucciones que aade el compilador son: * push bp; * push si; * push di; * * Para poder acceder a las variables dentro del ncleo, debemos poner en * DS y en ES valores correctos. Como el ncleo es un programa COM, debemos * hacer que DS y ES tengan el mismo valor que CS. Esto es lo que hacen las * tres ltimas instrucciones en ensamblador. * * La ltima parte guarda la informacin de pila del proceso interrumpido * en su PCB (guardando previamente la informacin anterior del PCB en la * propia pila). Adems, se hace que el campo contexto del PCB apunte a * la cima de la pila para poder acceder fcilmente a los registros * almacenados y que conguran el contexto. */ #dene salvar contexto \ asm { \ push es; \ push ds; \ push dx; \ push cx; \ push bx; \ push ax; \ \ mov ax, cs; \ mov ds, ax; \ mov es, ax; \ } \ AX = current>registroSS; \ asm {push ax} \ AX = current>registroSP; \ asm {push ax} \ current>registroSS = SS; \ current>registroSP = SP; \ current>contexto = (struct contexto far *) \ (((long)current>registroSS << 16) + current>registroSP); /* Restauramos el contexto para el proceso apuntado por current. * * Los tres ltimos pop los tenemos que hacer nosotros porque no llegamos

10

15

20

25

30

35

40

45

50

11

* al nal del procedimiento, donde los aade el compilador, ya que * ejecutamos un iret antes. */ #dene restaurar contexto \ SS = current>registroSS; \ SP = current>registroSP; \ asm {pop ax} \ current>registroSP = AX; \ asm {pop ax} \ current>registroSS = AX; \ current>contexto = (struct contexto far *) \ (((long)current>registroSS << 16) + current>registroSP); \ asm { \ pop ax; \ pop bx; \ pop cx; \ pop dx; \ pop ds; \ pop es; \ pop di; \ pop si; \ pop bp; \ iret; \ } \ #endif /* $Id: asm.h,v 1.4 2004/04/19 19:12:07 piernas Exp $ */

55

60

65

70

75

Como podemos ver, la macro salvar_contexto almacena en la pila el valor de todos los registros del procesador a excepcin de 3: CS, IP y el registro de estado. Esto es as porque estos 3 registros los apila automticamente el hardware cuando se produce una interrupcin, que es la nica causa por la que la ejecucin de un proceso se puede interrumpir. Por tanto, no es necesario guardarlos de nuevo. La macro salvar_contexto hace alguna cosa ms. Adems de salvar el contexto del proceso actual en la pila del propio proceso, asigna a los registros DS y ES un valor adecuado para as poder acceder a todas las variables denidas dentro del ncleo del sistema operativo (vea las lneas 3336). Es decir, establece un contexto apropiado para la ejecucin del sistema operativo. Ya que no modica el registro SS podemos decir que cuando un proceso se ejecuta en modo ncleo usa la misma pila que usa cuando se ejecuta en modo usuario. El hecho de que cada proceso tenga su propia pila cuando se ejecuta en modo ncleo es importante porque nos permite interrumpir a cualquier proceso dentro del ncleo, haciendo as que el ncleo del sistema operativo sea reentrante. Lo siguiente que se hace en salvar_contexto es guardar en la pila el valor actual de los campos registroSS y registroSP del PCB del proceso interrumpido (el apuntado por current). Estos campos apuntan en todo momento al ltimo contexto almacenado en la pila. Ya que vamos a almacenar un nuevo contexto, es importante que el valor anterior de registroSS y registroSP se guarde para no perder as la direccin del contexto anterior. Lo ltimo que hace la macro salvar_contexto es guardar la direccin de la cima de la pila (es decir, la del nuevo contexto) en los campos registroSS y registroSP, y usar el valor de dichos campos para hacer que el campo contexto del PCB del proceso interrumpido apunte a la pila. El campo contexto, que es de tipo struct contexto far *, nos permite tratar a la pila como a una estructura de datos ms y nos facilita el acceso al valor de los registros almacenados en la pila cuando sta guarda el contexto de un proceso interrumpido. Tambin nos permite modicar fcilmente el valor de uno de dichos registros para as devolver un valor a un proceso cuando ste reanude su ejecucin. La utilidad del campo contexto la veremos al estudiar la implementacin de las llamadas al sistema, en la seccin 7. Para restaurar el contexto de un proceso lo nico que hay que hacer es devolver a los registros del procesador el valor que tenan cuando se interrumpi el proceso. Ya que estos registros se salvaron en la pila del proceso basta

12

con desapilarlos de la misma para restaurarlos. Al igual que antes, no es necesario desapilar los registros CS, IP y el registro de estado puesto que esto lo hace automticamente el hardware cuando se ejecuta la instruccin iret. La restauracin del contexto se hace con la macro restaurar_contexto. Esta macro restaura el contexto del proceso apuntado por la variable current sin importar qu proceso sea ste. Quizs se est preguntando por qu es necesario el poder guardar y restaurar varios contextos. La respuesta es que un proceso ya interrumpido (y, por tanto, ejecutando cdigo del ncleo del sistema operativo) puede ser interrumpido de nuevo. Vea el apartado 7.1 para ms detalles.

4.4.

Gestin de procesos

Una vez descritos el mecanismo de cambio de contexto y la tabla de procesos, es el momento de ver cmo gestiona OSO los procesos. La gestin de procesos se implementa en el chero procesos.c, cuyo contenido es el siguiente:
#include #include #include #include "io.h" "string.h" "procesos.h" "asm.h"

struct PCB tablaprocesos[MAX PROCESOS]; struct PCB * current = NULL; /* El PCB 0 se asigna al sistema operativo, que ocupa los primeros 64 KB de memoria. El resto de PCBs quedan libres para procesos. */ void inicializar tabla procesos() { int i; unsigned int mem = 0; for(i = 0; i < MAX PROCESOS; i++) { tablaprocesos[i].estado = PCB LIBRE; tablaprocesos[i].memoria = mem; mem += 0x1000; }

10

15

20

/* El proceso 0 es un hilo dentro del propio sistema operativo */ void crearproceso0(void) { current = tablaprocesos; current>pid = 0; current>estado = PROCESO RUN; current>quantum = QUANTUM; current>prioridad = 0; current>tiempoUSER = 0; current>tiempoSYS = 0; current>memoria = 0; } /* Crea un nuevo proceso para el chero ejecutable programa del * dispositivo pdis */ int crearproceso(struct dispositivo far * pdis, char far * programa) { int i; struct PCB * proceso; struct contexto far * pila; long memoria; for(i = 0; i < MAX PROCESOS; i++)

25

30

35

40

45

13

{ }

if (tablaprocesos[i].estado == PCB LIBRE) break;


50

if (i == MAX PROCESOS) return 1; proceso = tablaprocesos + i; memoria = (long)(proceso>memoria) << 16; if (cargarCOM(pdis, programa, (char far *)((long)memoria + 0x100))) return 1; pila = (struct contexto far *)(memoria + 0xFFFE sizeof(struct contexto)); pila>ax = 0; pila>bx = 0; pila>cx = 0; pila>dx = 0; pila>di = 0; pila>si = 0; pila>bp = 0; pila>estado = 0x0200; pila>cs = (long)memoria >> 16; pila>ip = 0x100; pila>ds = pila>cs; pila>es = pila>cs; proceso>pid = i; proceso>registroSS = pila>cs; proceso>registroSP = 0xFFFE sizeof(struct contexto); proceso>prioridad = 0; proceso>tiempoUSER = 0; proceso>tiempoSYS = 0; proceso>quantum = QUANTUM; asm {cli} proceso>estado = PROCESO LISTO; asm {sti} } return i;

55

60

65

70

75

80

85

/* Desbloquea a todos los procesos que estn esperando en bloqueadosen */ void desbloquearprocesos(int bloqueadosen) { int i; for (i = 0; i < MAX PROCESOS; i++) if (tablaprocesos[i].estado == bloqueadosen) tablaprocesos[i].estado = PROCESO LISTO;

90

95

/* Selecciona, de forma circular, al siguiente proceso listo tras el * proceso apuntado por actual */ static struct PCB * round robin(struct PCB * actual) { int i = actual tablaprocesos; int n = MAX PROCESOS; while (n) { i = (i + 1) % MAX PROCESOS; if (i == 0) { n; continue;

100

105

110

14

} if (n == 0) i = 0; }

} if (tablaprocesos[i].estado == PROCESO LISTO) break; n;

115

return tablaprocesos + i;

120

/* Este es el procedimiento que selecciona el siguiente proceso a ejecutar */ void planicador(void) { /* No le podemos quitar la CPU a un proceso mientras se est atendiendo su llamada al sistema. Y hemos de cambiar de proceso si el proceso est bloqueado o si ha agotado su quantum. */ switch(current>estado) { case PROCESO SYSCALL: break; case BLOQ TECLADO: current = round robin(current); current>estado = PROCESO RUN; break; case PROCESO RUN: if (current>quantum) break; current>estado = PROCESO LISTO; current>quantum = QUANTUM; current = round robin(current); current>estado = PROCESO RUN; default: ; /* situacin de error */ } } static void nuevo proceso(void) { salvar contexto; planicador(); } restaurar contexto;

125

130

135

140

145

150

155

/* El siguiente procedimiento permiten a un proceso ceder la * CPU a otro proceso. Esto es til cuando un proceso se bloquea */ void ceder CPU(void) { /* Simulamos una interrupcin */ asm { pushf cli push cs } nuevo proceso(); } /* $Id: procesos.c,v 1.6 2004/04/12 16:31:17 piernas Exp $ */

160

165

170

Al principio del chero se denen las variables tablaprocesos y current. La primera, como su propio nombre indica, es la tabla de procesos que mantiene el sistema operativo. La segunda apunta en todo momento a la entrada de la tabla de procesos correspondiente al proceso actual, es decir, al proceso en ejecucin. Vamos a describir ahora los distintos aspectos que se implementan las funciones de este chero.

15

4.4.1.

Creacin de procesos

La primera funcin, inicializar_tabla_procesos, inicializa la tabla de procesos marcando todas las entradas como libres. Adems, inicializa el campo memoria de los distintos PCBs. Para un PCB concreto este campo indica el segmento de memoria que ocupar el proceso al que se le asigne dicho PCB. La siguiente funcin, crearproceso0, inicializa la entrada PCB del proceso que ocupa la entrada 0 de la tabla de procesos. Como hemos comentado en la seccin 3.1, este proceso 0 es el propio sistema operativo que, segn la entrada PCB, ocupa los primeros 64 KB de memoria (lo que incluye la tabla de vectores de interrupcin y la zona de datos de la BIOS). La funcin crearproceso, que se describe en las lneas 3886 del chero procesos.c, es la ms importante de todas. Esta funcin crea un nuevo proceso para ejecutar el cdigo del programa COM cuyo nombre se le pasa como argumento. El otro parmetro que se le pasa a esta funcin es el dispositivo de almacenamiento en el que se encuentra el programa a ejecutar. Lo primero que hace la funcin crearproceso es buscar una entrada libre en la tabla de procesos. Si no encuentra ninguna la funcin termina. Si encuentra una entrada libre carga en el segmento de memoria asignado a dicha entrada el programa a ejecutar, haciendo uso de la funcin cargarCOM (lneas 5458). Observe que el programa no se carga al principio del segmento sino 0x100 bytes ms adelante ya que se supone que se es el desplazamiento en el que comienza un programa COM dentro de un segmento. Si el programa se ha podido cargar en memoria entonces la funcin crearproceso inicializa su pila (lneas 6072) y su entrada PCB (lneas 7483) y termina, devolviendo el identicador o PID del nuevo proceso (que se corresponde con su nmero de entrada en la tabla de procesos). Lo ms interesante es cmo se inicializa la pila ya que debe almacenar un contexto adecuado para as poder ejecutar el nuevo proceso cuando se restaure su contexto. Todos los registros almacenados en la pila van a tener un valor 0 salvo los siguientes: CS, DS, ES y SS: todos van a tener el mismo valor, que ser el segmento de memoria asignado a la entrada PCB seleccionada para el proceso. IP: su valor inicial es 0x100 para un programa COM. registro de estado: debe tener activo el bit IF para que se habiliten las interrupciones cuando el programa comience a ejecutarse. Se le asigna el valor 0x0200. SP: apunta a la cima de la pila creada (vea las lneas 60 y 79). Ya que estamos suponiendo segmentos de 64 KB, la pila crece desde la direccin 0xFFFE hacia abajo. 4.4.2. Planicacin de procesos

La planicacin de procesos realizada por OSO consiste en una planicacin circular con un quantum de 2 unidades de tiempo. Cada unidad de tiempo equivale al periodo de los ticks de reloj. Ya que se producen 182 1 ticks por segundo, podemos decir que el quantum equivale a 2 18 2 = 0 110 segundos o 110 milisegundos. La planicacin de procesos se realiza en la funcin planificador (lnea 124 del chero procesos.c). Como vemos, slo se cambia de proceso cuando el proceso actual est bloqueado (est en el estado BLOQ_TECLADO) o cuando est en ejecucin y ha agotado su quantum (su estado es PROCESO_RUN y el campo current->quantum es 0). En estos dos casos se llama a la funcin round_robin que selecciona de forma circular al siguiente proceso listo tras el proceso actual. En cuanto a la funcin round_robin, el nico aspecto interesante que podemos comentar es que nicamente selecciona al proceso 0 (que es el propio sistema operativo) cuando no hay ningn otro proceso listo. Otra funcin relacionada con la planicacin es la funcin desbloquearprocesos (lnea 89 de procesos.c) que se utiliza para poner como listo a cualquier proceso cuyo estado coincida con el que se pasa

16

como parmetro. Esta funcin la utilizar la SAI del teclado para desbloquear a todo proceso que se encuentre bloqueado esperando un carcter de teclado (el estado de estos procesos ser BLOQ_TECLADO). Finalmente, la funcin ceder_CPU (lnea 159) la puede utilizar un proceso para ceder la CPU a otro proceso. Esto ocurrir cuando un proceso se bloquee dentro del ncleo. En ese caso, el proceso no podr continuar su ejecucin hasta que no se produzca el evento que espera, por lo que tendr que ceder la CPU para que el sistema no se quede parado. La funcin ceder_CPU, junto con la funcin nuevo_proceso, simula una interrupcin. Para ello, apila la palabra de estado del procesador (con pushf), el registro CS y el registro IP. Esto ltimo lo hace de forma indirecta llamando a la funcin nuevo_proceso que acta como una SAI. 4.4.3. Apropiacin de la CPU

Para evitar que un proceso monopolice la CPU hay que desalojar peridicamente al proceso que se encuentre en ella. Para conseguir esto vamos a hacer uso de las interrupciones de reloj que trata la funcin sai_timer descrita en el apartado 3.2. En cada interrupcin de reloj, la SAI decrementa en una unidad el quantum del proceso en ejecucin y llama al planicador. Si el quantum es 0, el planicador cambia de proceso. En caso contrario, deja la CPU al proceso interrumpido.

4.5.

Gestin de memoria

En el apartado 4.4.1 hemos visto que cuando se inicializa la tabla de procesos en la funcin inicializar_tabla_procesos se inicializa tambin el campo memoria de los distintos PCBs. Como hemos dicho, para un PCB este campo indica el segmento de memoria que ocupar un proceso al que se le asigne dicho PCB. Como podemos observar en dicha funcin, al PCB 0 se le asigna el segmento de memoria 0, al PCB 1 el segmento de memoria 0x1000, al PCB 2 el 0x2000, y as sucesivamente hasta el PCB 9, al que se le asigna el segmento de memoria 0x9000. Dicho de otra manera, al PCB 0 se le asignan los primeros 64 KB de memoria, al PCB 1 los siguientes 64 KB y as sucesivamente. En total, los 10 procesos que pueden existir a la vez ocuparn los primeros 640 KB de memoria RAM, que es la memoria disponible en el modo real para la ejecucin de programas. Como vemos, la gestin de memoria de OSO es muy sencilla y se basa en un esquema de particiones estticas del mismo tamao donde a cada proceso se le asignar la particin correspondiente al PCB que use. Esta gestin de memoria tambin permite cierta proteccin entre procesos. OSO slo es capaz de ejecutar programas COM. Estos programas se ejecutan haciendo uso de un nico segmento porque todos los registros de segmento, incluido el de pila, tienen el mismo valor. Ya que el tamao del desplazamiento dentro de un segmento es de 16 bits, un programa COM no puede direccionar ms de 64 KB y, por tanto, no puede acceder a ninguna direccin de memoria fuera del segmento de memoria asignado (siempre, claro est, que no modique el valor de ningn registro de segmento).

5.

Sistema de cheros

OSO utiliza FAT12 como sistema de cheros. Este sistema de cheros es el que utiliza Windows en los disquetes. Como hemos comentado en la introduccin, al utilizar un sistema de cheros real podemos utilizar las herramientas que ya existen en sistemas operativos como Windows o Linux. En esta seccin no pretendemos describir de forma detallada cmo funciona FAT12 ya que es un sistema de cheros ampliamente explicado y documentado. Lo que vamos a hacer es explicar aquellos aspectos de la implementacin ms interesantes.

17

El cdigo fuente que implementa el sistema de cheros en OSO aparece en los cheros fat.h y fat.c. Estos cheros se muestran a continuacin:
#ifndef #dene FAT H FAT H 512 4
5

#dene TAMA SECTOR #dene NUM DISPOSITIVOS struct dispositivo { unsigned int unsigned int unsigned int unsigned int unsigned long unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int };

unidad; cabezas; sectores por pista; bytes por sector; total sectores; tipo fat; inicio fat; entradas fat; inicio raiz; entradas raiz; inicio clusters; sectores por cluster;

10

15

20

void inicializar tabla dispositivos(void); struct dispositivo * abrir dispositivo(unsigned char unidad); int cerrar dispositivo(struct dispositivo * pdis); int cargarCOM(struct dispositivo far * pdis, char far * programa, char far * direccion); #endif /* $Id: fat.h,v 1.3 2003/07/24 15:21:38 piernas Exp $ */
25

30

#include "io.h" #include "string.h" #include "fat.h" /* Estructura de un sector de arranque en un sistema de cheros FAT */ struct boot sector { char salto[3]; /* 00h */ char identicacion[8]; /* 03h */ /* 0Bh */ unsigned int bytes por sector; unsigned char sectores por cluster; /* 0Dh */ unsigned int sectores reservados; /* 0Eh */ /* 10h */ unsigned char copias fat; unsigned int entradas raiz; /* 11h */ unsigned int total sectores; /* 13h */ unsigned char formato disco; /* 15h */ unsigned int sectores por fat; /* 16h */ /* 18h */ unsigned int sectores por pista; unsigned int cabezas; /* 1Ah */ unsigned int sectores ocultos; /* 1Ch */ unsigned int pad; unsigned long total sectores long; /* 20h */ unsigned char unidad hd; /* 24h */ char reservado; /* 25h */ char marca; /* 26h */ /* 27h */ unsigned long numero serie; char etiqueta[11]; /* 2Bh */ char reservado dos[8]; /* 36h */
5

10

15

20

25

18

};

char char

cargador[0x1BE0x3E]; /* 3Eh */ particiones[5120x1BE]; /* 1BEh */

30

/* Estructura de datos para guardar informacin de los distintos * dispositivos abiertos */ struct dispositivo tabla dispositivos[NUM DISPOSITIVOS]; /* Inicializa la tabla de dispositivos */ void inicializar tabla dispositivos(void) { int i; for (i = 0; i < NUM DISPOSITIVOS; i++) tabla dispositivos[i].cabezas = 0;

35

40

/* Abre el dispositivo especicado por unidad. Se supone que el dispositivo * tiene formato FAT12 o FAT16 */ struct dispositivo * abrir dispositivo(unsigned char unidad) { int i; struct dispositivo * pdis; struct boot sector sector; long total clusters; for (i = 0; i < NUM DISPOSITIVOS; i++) if (tabla dispositivos[i].cabezas == 0) break; if (i == NUM DISPOSITIVOS) return NULL; if (leersector((char *)&sector, 0, 0, 1, unidad)) { kputs("ERROR: leyendo el sector de arranque\n\r"); return NULL; } pdis = tabla dispositivos + i; /* Datos generales del disco */ pdis>unidad = unidad; pdis>cabezas = sector.cabezas; pdis>sectores por pista = sector.sectores por pista; pdis>bytes por sector = sector.bytes por sector; pdis>total sectores = (long) sector.total sectores; if (pdis>total sectores == 0) pdis>total sectores = sector.total sectores long; /* Directorio raiz */ pdis>inicio raiz = sector.sectores reservados + sector.sectores por fat * sector.copias fat; pdis>entradas raiz = sector.entradas raiz; /* Datos sobre clusters */ pdis>inicio clusters = pdis>inicio raiz + (pdis>entradas raiz * 32) / pdis>bytes por sector; pdis>sectores por cluster = sector.sectores por cluster; /* Datos de la FAT */ total clusters = ((unsigned)pdis>total sectores pdis>inicio clusters) / pdis>sectores por cluster; pdis>tipo fat = sector.formato disco; pdis>inicio fat = sector.sectores reservados; pdis>entradas fat = total clusters + 2; return pdis;

45

50

55

60

65

70

75

80

85

/* Lee num sectores del dispositivo pdis empezando en el sector nsec. * los sectores ledos se almacenan a partir de la direccin buffer */ static int dispositivo leersectores (struct dispositivo far * pdis, unsigned nsec,

90

19

unsigned int num, char far * buffer) { unsigned int sector, cabeza, cilindro; int i; if ((nsec + num 1) >= pdis>total sectores) { kputs("Intentando acceder mas alla del final del dispositivo\n\r"); kcadvalor("Sector: ", nsec); kcadvalor("Contador: ", num); return 1; } cilindro = nsec / (pdis>cabezas * pdis>sectores por pista); cabeza = (nsec % (pdis>cabezas * pdis>sectores por pista)) / pdis>sectores por pista; sector = (nsec % (pdis>cabezas * pdis>sectores por pista)) % pdis>sectores por pista + 1; for(i = 0; i < num; i++) { if (leersector(buffer, cilindro, cabeza, sector, pdis>unidad)) { kputs("ERROR: E/S\n\r"); return 1; } ++sector; if (sector > pdis>sectores por pista) { sector = 1; ++cabeza; if (cabeza >= pdis>cabezas) { cabeza = 0; ++cilindro; } } buffer += pdis>bytes por sector; } return 0;

95

100

105

110

115

120

125

/* Lee el cluster cluster (formado por cualquier nmero de sectores) del * dispositivo pdis y lo almacena en la direccin buffer de memoria */ static int dispositivo leercluster(struct dispositivo far * pdis, unsigned int cluster, char far * buffer) { unsigned sector cluster; sector cluster = (cluster 2) * pdis>sectores por cluster; sector cluster += pdis>inicio clusters; return dispositivo leersectores(pdis, sector cluster, pdis>sectores por cluster, buffer);

130

135

140

145

/* Cierra un dispositivo abierto previamente */ int cerrar dispositivo(struct dispositivo * pdis) { int entrada = pdis tabla dispositivos; tabla dispositivos[entrada].cabezas = 0; return 0;

150

/* Formato de la entrada de directorio en un sistema FAT */ struct entrada dir { char nombre[8]; char ext[3];

155

20

};

unsigned char r: 1, h: 1, s: 1, e: 1, d: 1, a: 1, pad: 2; } atributos; char reservado dos[10]; struct { unsigned int s: 5, m: 6, h: 5; } hora; struct { unsigned int d: 5, m: 4, a: 7; } fecha; unsigned int bloque inicio; long tama;

struct {

160

165

170

/* Devuelve el cluster inicial (cluster) y el tamao (tama) del chero * nomch del directorio raz. Si el chero existe, la funcin devuelve * 0 y -1 en caso contrario. */ static int cluster inicial(struct dispositivo far * pdis, char far * nomch, unsigned int far * cluster, long far * tama) { struct entrada dir far * dir; char bloque[TAMA SECTOR]; int i, lon; char nombre[8], ext[3], far * psubstr; int raiz; psubstr = kstrchr(nomch, .); if (psubstr) { lon = psubstr nomch; if (lon > 8) lon = 8; kstrncpy(nombre, nomch, lon); for (i = lon; i < 8; i++) nombre[i] = ; lon = kstrlen(++psubstr); if (lon > 3) lon = 3; kstrncpy(ext, psubstr, lon); for (i = lon; i < 3; i++) ext[i] = ; } else { lon = kstrlen(nomch); if (lon > 8) lon = 8; kstrncpy(nombre, nomch, lon); for (i = lon; i < 8; i++) nombre[i] = ; for (i = 0; i < 3; i++) ext[i] = ; } raiz = pdis>inicio raiz; if (dispositivo leersectores(pdis, raiz, 1, bloque)) return 1; dir = (struct entrada dir *) bloque; for (i = 0; i < pdis>entradas raiz; i++) { if (!kstrncmp(dir>nombre, nombre, 8) && !kstrncmp(dir>ext, ext, 3)) { *cluster = dir>bloque inicio; *tama = dir>tama; return 0; } ++dir; if ((char *)dir >= (bloque + pdis>bytes por sector)) { ++raiz;

175

180

185

190

195

200

205

210

215

220

225

21

if (dispositivo leersectores(pdis, raiz, 1, bloque)) return 1; dir = (struct entrada dir *) bloque;
230

/* No hemos encontrado el chero */ *cluster = 0; return 1;

235

static int siguiente cluster 12(struct dispositivo far * pdis, int cluster) { unsigned int desplazamiento, aleer, bloque1, bloque2; char bloque[2 * TAMA SECTOR]; unsigned int siguiente; desplazamiento = ((cluster << 1) + cluster) >> 1; bloque1 = desplazamiento / pdis>bytes por sector; bloque2 = (desplazamiento + 1) / pdis>bytes por sector; if (bloque1 == bloque2) aleer = 1; else aleer = 2; bloque1 += pdis>inicio fat; if (dispositivo leersectores(pdis, bloque1, aleer, bloque)) return 0; siguiente = *((unsigned int far *)(bloque + desplazamiento % pdis>bytes por sector)); if (cluster & 0x0001) siguiente >>= 4; else siguiente &= 0x0FFF; if (siguiente > 0xFF0) return 0; } return siguiente;

240

245

250

255

260

265

static int siguiente cluster 16(struct dispositivo far * pdis, int cluster) { return 0; } /* Devuelve el cluster que sigue a cluster o 0 si cluster es el ltimo * cluster de un chero o si se produce un error al leer de disco. */ static int siguiente cluster(struct dispositivo far * pdis, int cluster) { switch(pdis>tipo fat) { case 0xF0: return siguiente cluster 12(pdis, cluster); case 0xF8: return siguiente cluster 16(pdis, cluster); } return 0; } /* Carga el chero programa del directorio raz del dispositivo pdis en * direccin de memoria direccion. */ int cargarCOM(struct dispositivo far * pdis, char far * programa, char far * direccion) { unsigned int cluster; long tama; unsigned int tama cluster;

270

275

280

285

290

22

tama cluster = pdis>bytes por sector * pdis>sectores por cluster; if (cluster inicial(pdis, programa, &cluster, &tama)) return 1; while (cluster) { dispositivo leercluster(pdis, cluster, direccion); (long)direccion += tama cluster; cluster = siguiente cluster(pdis, cluster); } } return 0;
295

300

305

/* $Id: fat.c,v 1.6 2004/06/14 13:49:02 piernas Exp $ */

Antes de comentar las estructuras de datos y funciones ms importantes, conviene decir que en estos cheros se estn mezclando dos cosas: la gestin del dispositivo de almacenamiento y la gestin del sistema de cheros en s. Comentaremos las funciones que implementan cada una de estas gestiones de forma separada.

5.1.

Gestin de los dispositivos de almacenamiento

OSO est preparado para manejar hasta 4 dispositivos de almacenamiento a la vez. Cada dispositivo de almacenamiento debe tener un sistema de cheros FAT ya que se va a usar la informacin que se almacena en el sector de arranque de este sistema de cheros para conocer la geometra del disco (cilindros, cabezas y sectores por pista) y as poder averiguar la posicin exacta del sector o sectores a leer o escribir. La funcin inicializar_tabla_dispositivos (lnea 37 del chero fat.c) se invoca al inicio del sistema operativo. Esta funcin inicializa la estructura tabla_dispositivos en la que se guarda informacin para cada dispositivo abierto (es decir, en uso). Antes de acceder a un dispositivo se debe llamar a la funcin abrir_dispositivo (lnea 46). Esta funcin busca un hueco en la tabla de dispositivos para el nuevo dispositivo y rellena la entrada correspondiente con los datos que hay en el sector de arranque. En el sector de arranque encontramos datos sobre el propio dispositivo (cabezas, sectores por pista, bytes por sector, tamao total del dispositivo) y datos sobre el sistema de cheros FAT (copias de la FAT, sectores que ocupa cada copia de la FAT, tamao del directorio raz, sectores por cluster, tipo de FAT, etc.). En la funcin abrir_dispositivo tambin calculamos ciertos valores que no aparecen directamente en el sector de arranque pero que son tiles a la hora de acceder a un sistema de cheros FAT. Estos valores tambin se almacenan en la entrada correspondiente de la tabla de dispositivos. Algunos valores calculados son: sector de inicio del directorio raz, sector de inicio del primer cluster, nmero total de clusters, etc. A partir de este momento ya podemos acceder al dispositivo para leer o escribir, haciendo uso del puntero devuelto por la funcin abrir_dispositivo. En la versin de OSO que estamos describiendo slo se implementa la lectura. Para leer existen dos funciones: dispositivo_leersectores (lnea 92 de fat.c) y dispositivo_leercluster (lnea 133 de fat.c). La funcin dispositivo_leersectores lee un cierto nmero de sectores a partir de una determinada posicin y los almacena en la direccin de memoria pasada como parmetro. Esta funcin nos presenta el dispositivo como un array lineal de sectores, desde 0 hasta N 1. Por tanto, est funcin tendr que averiguar en qu cilindro, cabeza y sector dentro de una pista se encuentra cada uno de los sectores a leer, para lo que utilizar la informacin sobre la geometra del disco que se guarda en la tabla de dispositivos. La funcin dispositivo_leercluster hace uso de la funcin anterior para leer de disco el cluster cuyo nmero se le pasa como parmetro. Ya que los clusters 0 y 1 no existen en realidad, tendr que restar 2 al nmero de cluster pasado para poder calcular la posicin exacta del cluster a leer.

23

Cuando ya no se va a usar ms un dispositivo, se debe llamar a cerrar_dispositivo (lnea 149) para liberar la entrada ocupada en la tabla de dispositivos.

5.2.

Gestin del sistema de cheros

En la implementacin actual, OSO slo es capaz de gestionar sistemas de cheros FAT12, aunque el cdigo est preparado para aadir fcilmente la gestin de sistemas de cheros FAT16. Otra limitacin de OSO es que no reconoce subdirectorios. Slo trabaja con el directorio raz del sistema de cheros. Por tanto, no implementa ningn mecanismo de resolucin de rutas. La gestin del sistema de cheros que hace OSO se implementa principalmente en dos funciones: cluster_inicial y siguiente_cluster_12. La funcin cluster_inicial recibe el dispositivo y el nombre del chero a buscar en el directorio raz de dicho dispositivo y devuelve, en dos parmetros de salida, el cluster inicial y el tamao del chero. La funcin devuelve 0 si encuentra el chero y -1 en caso contrario. Para buscar el chero en el directorio raz, lo primero que hace cluster_inicial es descomponer la cadena de caracteres del nombre del chero en los campos nombre y extensin (lneas 185209 del chero fat.c). Ambos campos estn separados por el carcter .. Ya que el campo nombre debe tener 8 caracteres, si en la cadena pasada como parmetro hay ms de 8 caracteres antes del punto, se toman slo los 8 primeros caracteres, y si hay menos, se rellena con espacios hasta completar los 8 caracteres. Con el campo extensin se hace exactamente lo mismo aunque en este caso para un tamao de 3 caracteres. Una vez descompuesto el nombre del chero en los campos nombre y extensin, se busca entre las entradas del directorio raz una que tenga los mismos campos nombre y extensin que los buscados (lneas 211230). Si encontramos dicha entrada, devolvemos el nmero de cluster y el tamao almacenados en la misma. Un aspecto a tener en cuenta es que vamos a suponer que en un sistema de cheros FAT12 los nombres de chero se guardan en maysculas. Por ello, es importante que el nombre del chero a buscar ya se encuentre en maysculas cuando se llame a la funcin cluster_inicial. La funcin cluster_inicial nos da el nmero del primer cluster o bloque lgico asignado al chero. Para obtener la direccin del resto de clusters del chero se hace uso de la funcin siguiente_cluster que, a su vez, hace uso de la funcin siguiente_cluster_12. En un sistema de cheros FAT12, cada nmero de cluster ocupa 12 bits, es decir, 15 bytes. Esto hace que un cluster par y otro impar compartan los 8 bits de un byte. La distribucin de los 3 bytes ocupados por un cluster par (P) y otro impar (I) consecutivos es PP IP II, donde cada letra representa 4 bits y el byte compartido IP almacena los 4 bits ms signicativos del cluster par y los 4 bits menos signicativos del cluster impar. As pues, para leer la entrada par tendremos que leer los bytes PP IP como un entero (lnea 256 de fat.c), lo que har que se interpreten como el entero IP PP (esto es as porque en memoria los bytes se almacenan en el orden little-endian). De ese entero nos interesan los 12 bits menos signicativos, los cuales obtendremos aplicando al entero ledo un y-lgico con el valor 0x0FFF (lnea 261 de fat.c). Si la entrada es impar tendremos que leer los bytes IP II tambin como entero, lo que har que se interpreten como II IP. En este caso nos interesan los 12 bits ms signicativos los cuales obtendremos desplazando a la derecha 4 posiciones el entero ledo (lnea 259 de fat.c). Lo nico que nos falta es saber en qu posicin o desplazamiento se encuentra el primer byte del entero a leer. Podemos observar que para el cluster 0 el primer byte a leer es el 0, para el cluster 1 el byte 1, para el 2 el byte 3, para el 3 el byte 4, y as sucesivamente. Este es el dato que se calcula en la lnea 242 del chero fat.c, que equivale a la operacin 2Z+Z , es decir, a 1 5 Z donde se desprecian los decimales. 2 Finalmente, debemos de tener en cuenta que los 2 bytes a leer se pueden encontrar en dos bloques distintos, uno como ltimo byte de un bloque y el otro como primer byte del siguiente bloque (lneas 243249 de fat.c).

24

La ltima funcin, cargarCOM, hace uso de las funciones dispositivo_leercluster, cluster_inicial y siguiente_cluster para cargar en una posicin de memoria dada el contenido del chero cuyo nombre se le pasa como parmetro.

6.

Gestin de la E/S

El sistema operativo OSO maneja tres dispositivo distintos: un dispositivo de almacenamiento (habitualmente, el disquete), la pantalla y el teclado. Los dos primeros dispositivos se gestionan haciendo uso de los servicios que proporciona la BIOS mientras que el teclado se gestiona directamente accediendo al hardware correspondiente (aunque el teclado tambin se podra gestionar a travs de la BIOS, hemos preferido gestionarlo directamente para as poder bloquear/desbloquear a procesos que lean de teclado, como veremos despus). La gestin de la entrada/salida se implementa en los cheros io.h e io.c que se muestran a continuacin:
#ifndef #dene IO H IO H
5

/* Para salida por pantalla */ void escribe cad(char far * cadena, int longitud); /* Para la lectura de un sector de disco */ unsigned int leersector (unsigned char far *buffer, char pista, char cabeza, char sector, char unidad); void tick pararmotor(void); /* Para entrada por teclado */ #dene NULO \0 void recoger caracter(void); char obtener caracter(void); #endif /* $Id: io.h,v 1.6 2004/04/12 17:12:56 piernas Exp $ */

10

15

#include "io.h" #include "procesos.h" /* Escribe una cadena de caracteres por pantalla usando la funcin teletipo * de la BIOS */ void escribe cad(char far * cadena, int longitud) { asm { mov ah, 03h mov bh, 0 int 10h mov ah, 13h mov al, 1 mov bl, 07h mov cx, longitud /* Cuidado, bp se utiliza para acceder a longitud, y se * modica justo a continuacin. */ les bp, dword ptr cadena int 10h } } /* Las siguientes funciones se utilizan para leer un sector de disco y * para parar el motor de la disquetera si es necesario */ static unsigned int pararmotor = 0; unsigned int leersector (unsigned char far *buffer, char pista,
5

10

15

20

25

25

int i;

char cabeza, char sector, char unidad) {

30

for(i = 0; i < 3; i++){ asm { les bx, dword ptr buffer mov al , 1 mov ch , pista mov cl , sector mov dh , cabeza mov dl , unidad mov ah , 02 int 13h mov ax , 0 jnc noError mov ax , 1 } } noError: if (unidad == 0 | | unidad == 1) { asm {cli} pararmotor = 36; /* Unos dos segundos */ asm {sti} }

/* 1 sector */

35

/* De disco a memoria. */ /* Devolvemos 0 indicando que la operacion tuvo exito. */ /* Si no se produce error salimos del bucle. */ /* Devolvemos -1 indicando que la operacin fall. */

40

45

50

void tick pararmotor(void) { unsigned char unidad; if (pararmotor > 0) { pararmotor; if (pararmotor == 0) /* Esto para los motores de las unidades de disquetes */ asm { mov al, 0Ch mov dx, 3F2h out dx, al } }

55

60

65

/* Mapa del teclado. Asocia un cdigo de tecla con un cdigo ASCII */ static char mapateclado[ ]= { 27,1,2,3,4,5,6,7,8,9,0,-,=,8,9, Q,W,E,R,T,Y,U,I,O,P,<,>,13,1, A,S,D,F,G,H,J,K,L,164,;,135,1, <,Z,X,C,V,B,N,M,,,.,39,1,1,1, , 1,1,1,1,1,1,1,1,1,1,1,1,26,7,8,9,-, 4,5,6,+,1,2,3,0,. }; static char ultimo caracter = NULO; /* Extrae el cdigo de tecla del hardware de teclado, lo convierte en * carcter y lo almacena en la variable ultimocaracter */ void recoger caracter(void) { int i, c; /* Extraemos el cdigo de la tecla del hardware de teclado */ asm{ xor ax, ax

70

75

80

85

90

26

in al, 60h mov i, ax

95

/* Le indicamos al hardware del teclado que ya hemos retirado el cdigo de tecla */ asm { in al, 61h xor ax, 80h out 61h, al nop nop xor ax, 80h out 61h, al } /* Convertimos el cdigo de tecla en cdigo ASCII (si se puede) de acuerdo a tablateclado */ c = (i & 0x80) ? 1 : mapateclado[i1]; if (c >= 0) { ultimo caracter = c; desbloquearprocesos(BLOQ TECLADO); }

100

105

110

115

/* Devuelve el carcter almacenado en la variable ultimo caracter */ char obtener caracter(void) { char c = ultimo caracter; ultimo caracter = NULO; return c;

120

/* $Id: io.c,v 1.6 2004/04/14 20:16:40 piernas Exp $ */

125

6.1.

Lectura de un dispositivo de almacenamiento

La nica operacin de E/S que hace OSO con los dispositivos de almacenamiento es la lectura. Esta operacin se implementa en la funcin leersector (lnea 26 del chero io.c) mediante el servicio 13h de la BIOS. Al usar dicho servicio, la BIOS hace todo el trabajo por nosotros: arranca el motor de la disquetera, espera a que el disco alcance una velocidad adecuada, lee el sector solicitado de disco y lo copia en la direccin de memoria indicada. Despus de hacer la lectura, y tras unos segundos, el motor de la disquetera se debe apagar para no desgastar el disquete que se pueda encontrar en ella. El motivo por el que el motor no se apaga inmediatamente es que si se hacen varias lecturas seguidas no es necesario arrancar el motor para cada una de ellas, acelerando considerablemente el proceso de lectura. Generalmente, es la BIOS la que se encarga de apagar el motor de la disquetera tras unos pocos segundos. Para llevar una cuenta del tiempo, la BIOS hace uso de las interrupciones de reloj. El problema en el caso de OSO es que las interrupciones de reloj no ejecutan cdigo de la BIOS sino cdigo del sistema operativo (en concreto, la funcin sai_timer que se implementa en el chero sais.c). Esto hace que la BIOS sea incapaz de apagar el motor, algo que tendr que hacer el propio sistema operativo. Cada vez que se llama a la funcin leersector se asigna a la variable pararmotor un valor, en ticks de reloj, para que se pare el motor de la disquetera tras unos 2 segundos. Cada vez que se produce una interrupcin de reloj, la SAI del reloj llama a la funcin tick_pararmotor (lnea 53 de io.c) que comprueba si debe parar el motor de la disquetera, lo cual ocurrir cuando la variable pararmotor llegue a 0. Una solucin distinta a sta hubiera sido la de ejecutar, desde la SAI del reloj, la funcin de la BIOS asociada a la interrupcin 8. De esta manera la BIOS seguira recibiendo las interrupciones de reloj aunque de manera

27

indirecta. Sin embargo, se ha optado por la solucin anterior para mostrar cmo se pueden establecer alarmas dentro del sistema operativo de manera rudimentaria.

6.2.

Salida por pantalla

La salida por pantalla es la operacin de E/S ms sencilla de todas. Esta operacin se implementa en la funcin escribe_cad (lnea 7 de io.c) que hace uso de la funcin teletipo de la BIOS (funcin 13h del servicio 10h). Esta funcin reconoce ciertos caracteres especiales, como el carcter de nueva lnea, \n, y el carcter de retorno de carro, \r, que actualizan convenientemente la pantalla.

6.3.

Entrada por teclado

La entrada por teclado es una de las operaciones de E/S ms interesantes ya que supone bloquear procesos que esperan un carcter de teclado y desbloquear a dichos procesos cuando se pulse una tecla. La parte principal de la E/S de teclado la implementa la funcin recoger_caracter (lnea 84 del chero io.c). Esta funcin la ejecuta la SAI del teclado cada vez que se produce una interrupcin de teclado (vea el apartado 3.2 para ms detalles). La funcin recoger_caracter lee del hardware de teclado el cdigo de la tecla pulsada y lo traduce en un carcter segn un determinado mapa de teclado. El carcter obtenido se almacena en la variable ultimo_caracter (lnea 80 de io.c) que acta como un buffer de teclado de capacidad 1 carcter. Los caracteres del buffer se retiran con la funcin obtener_caracter. Observe que la funcin recoger_caracter no trata combinaciones de teclas y que el mapa de teclado slo devuelve caracteres en maysculas. Si se deseara aadir esta funcionalidad habra que modicar esta funcin de manera adecuada. Cuando se lee un carcter hay que desbloquear a todos los procesos que se encuentren bloqueados esperando una entrada por teclado. Esto se hace en la lnea 112 de io.c. Aunque se desbloquean todos los procesos, slo el primero que se ejecute ser el que coja el carcter. El resto de procesos volver a bloquearse a la espera del siguiente carcter. Un ltimo aspecto a tener en cuenta es que cada vez que se pulsa una tecla se producen dos interrupciones: una cuando se pulsa la tecla y otra cuando se suelta. La funcin recoger_caracter se ejecuta en los dos casos. En la primera interrupcin el cdigo de la tecla pulsada se extrae del hardware de teclado. En la segunda interrupcin se detecta que no hay cdigo de tecla que leer y no se hace nada.

7.

Llamadas al sistema

Las llamadas al sistema son los servicios que proporciona el sistema operativo a los programas de usuario. En el caso de OSO existen tres llamadas al sistema que hacen uso de las funciones que hemos descrito en las secciones anteriores para ofrecer tres servicios: creacin de procesos, salida por pantalla y entrada por teclado. Las llamadas al sistema se implementan en los cheros llamadas.h y llamadas.c que se muestran a continuacin:
#ifndef #dene LLAMADAS H LLAMADAS H
5

#dene NR LLAMADAS 3 enum { ENOERROR, ENOSYS, /* No se ha producido error */ /* No existe la llamada al sistema especicada */

28

};

EIO, EINVAL

/* Error de E/S */ /* Argumento invlido */

10

struct syscall { int (*func) (void); }; extern struct syscall tabla llamadas[ ]; #endif /* $Id: llamadas.h,v 1.3 2004/03/30 17:56:44 piernas Exp $ */

15

20

#include #include #include #include

"procesos.h" "io.h" "llamadas.h" "kernel.h"

static int sys crear proceso(void) { char far * programa; int pid; /* Vamos a habilitar las interrupciones por lo que debemos evitar que otra interrupcin (el timer, por ejemplo) nos quite la CPU */ current>estado = PROCESO SYSCALL; asm {sti} programa = (char far *)(((long)current>contexto>es << 16) + current>contexto>bx); pid = crearproceso(disquete, programa); asm {cli} current>estado = PROCESO RUN; return pid;

10

15

20

static int sys leer(void) { char far * buffer; char c; /* Slo permitimos lecturas desde el descriptor de chero 0 */ if (current>contexto>dx != 0) return EINVAL; while (!(c = obtener caracter())) { current>estado = BLOQ TECLADO; ceder CPU(); } buffer = (char far *)(((long)current>contexto>es << 16) + current>contexto>bx); *buffer = c; /* Devolvemos el total de caracteres ledos */ return 1;

25

30

35

40

45

static int sys escribir(void)

29

char far * buffer; /* Slo permitimos escrituras en el descriptor de chero 1 */ if (current>contexto>dx != 1) return EINVAL; /* Al igual que en sys crear proceso, vamos a habilitar las * interrupciones por lo que debemos evitar que nos quiten * la CPU */ current>estado = PROCESO SYSCALL; asm {sti} buffer = (char far *)(((long)current>contexto>es << 16) + current>contexto>bx); escribe cad(buffer, current>contexto>cx); asm {cli} current>estado = PROCESO RUN; /* Devolvemos el total de caracteres escritos */ return current>contexto>cx;

50

55

60

65

70

struct syscall tabla llamadas[ ] = {sys crear proceso, sys leer, sys escribir};

/* Crear un proceso */ /* Leer de un chero */ /* Escribir en un chero */

75

/* $Id: llamadas.c,v 1.6 2004/04/12 17:12:56 piernas Exp $ */

80

Las tres funciones que implementan las llamadas al sistema, sys_crear_proceso, sys_leer y sys_escribir, se ejecutan desde la SAI sai_llamadas que vimos en el apartado 3.2. Antes de describir cada una de las llamadas al sistema vamos a ver cmo se controla la concurrencia dentro del ncleo, es decir, qu mecanismos tenemos para sincronizar procesos para evitar que dos o ms procesos entren a la vez en una seccin crtica.

7.1.

Sincronizacin de procesos dentro del ncleo

El ncleo del sistema operativo OSO es reentrante, es decir, dos o ms procesos pueden estar ejecutndose a la vez dentro del ncleo. El principal problema que se debe solucionar es el de evitar que dos o ms procesos modiquen a la vez un recurso compartido. Generalmente, los recursos compartidos son las estructuras de datos globales como la tabla de procesos y la tabla de dispositivos. La BIOS es otro recurso compartido que dos o ms procesos no pueden usar a la vez ya que no es reentrante. Hay varias formas de solucionar este problema. Una de ellas, quizs la ms burda pero tambin la ms sencilla, es la de evitar que a un proceso se le quite la CPU cuando est dentro de una seccin crtica. Esta es la solucin adoptada por OSO. En OSO a un proceso se le puede quitar la CPU cuando se produce una interrupcin. Por lo tanto, la forma de evitar que se le quite la CPU al proceso es deshabilitar las interrupciones (con asm{cli}) antes de entrar a la seccin crtica. Cuando se sale de la seccin crtica lo nico que hay que hacer es habilitar de nuevo las interrupciones (con asm{sti}). Es importante recordar que cuando se produce una interrupcin stas se deshabilitan por lo que a un proceso no se le quitar la CPU a menos que l habilite las interrupciones. La otra forma que tiene un proceso en OSO de evitar que se le quite la CPU es cambiar su estado a PROCESO_SYSCALL. En este estado las interrupciones se pueden habilitar pero no van a producir un cambio de proceso. Ya que se pueden producir interrupciones es importante que las SAIs correspondientes no

30

usen estructuras de datos compartidas que puedan estar siendo usadas por el proceso interrumpido y en estado PROCESO_SYSCALL. El estado PROCESO_SYSCALL impide que otro proceso se ejecute y que pueda hacer otra llamada al sistema. Este estado es til para llamadas al sistema que, como sys_crear_proceso y sys_escribir, invocan a funciones que terminan usando la BIOS (que, como hemos dicho ya en varias ocasiones, no es reentrante). Los mecanismos de bloqueo proporcionados por la deshabilitacin de interrupciones y el estado PROCESO_SYSCALL son de grano grueso y se pueden mejorar mucho, implementando semforos, candados o algn otro mecanismo de sincronizacin. Los semforos y candados, por ejemplo, si estn bien implementados, no impiden que a un proceso se le quite la CPU pero s impiden que dos o ms procesos entren a la vez a una seccin crtica.

7.2.

La llamada sys_crear_proceso

Esta llamada al sistema es bastante sencilla. Bsicamente, toma de ES:BX la direccin de memoria de la cadena de caracteres con el nombre del programa a ejecutar. Despus llama a la funcin crearproceso, a la que le pasa como parmetro un puntero a dicha cadena, para crear el proceso. La funcin crearproceso se describe en el apartado 4.4.1 de la pgina 16. La llamada al sistema devuelve el PID del proceso creado (que puede ser -1 si se produjo algn error).

7.3.

La llamada sys_escribir

Esta llamada es muy similar a la anterior. Tras comprobar que el descriptor de chero (valor del registro DX) es 1, llama a la funcin escribe_cad (descrita en el apartado 6.2 de la pgina 28) para mostrar una cadena por pantalla. A la funcin se le pasan como parmetros la direccin de la cadena a mostrar (tomada de ES:BX) y el tamao de la misma (valor del registro CX). La llamada al sistema devuelve el nmero de caracteres escritos. Como se supone que no se va a producir error, se devuelve el valor del registro CX.

7.4.

La llamada sys_leer

La llamada al sistema sys_leer es la ms interesante de las tres, ya que muestra cmo un proceso puede bloquearse dentro del ncleo y ceder la CPU a otro proceso. La llamada al sistema se ejecuta con las interrupciones deshabilitadas pues debemos impedir que la interrupcin de teclado modique el buffer de teclado mientras estamos accediendo a l. Para leer el siguiente carcter del buffer de teclado se usa la funcin obtener_caracter (vea el apartado 6.3 en la pgina 28). Si el buffer est vaco, el proceso cambia su estado a BLOQ_TECLADO y llama a la funcin ceder_CPU (descrita en el apartado 4.4.2 de la pgina 16) para ceder la CPU a otro proceso listo. Cuando se produce una interrupcin de teclado, el proceso se desbloquea. Cuando se le concede la CPU de nuevo, el proceso obtiene el siguiente carcter del buffer de teclado. Si est vaco, se vuelve a bloquear. Si obtiene un carcter, lo copia en la direccin de memoria apuntada por ES:BX y regresa, devolviendo el valor 1 que indica que se ha ledo un carcter.

8.

Funciones auxiliares

Muchas funciones de la biblioteca estndar de C que se suelen utilizar dentro de un programa en C, como printf, no se pueden utilizar en el ncleo de nuestro sistema operativo porque internamente dichas funciones

31

utilizan llamadas al sistema que no estn disponibles dentro del ncleo del sistema operativo OSO. Sin embargo, muchas de estas funciones son necesarias si queremos que la construccin del sistema operativo no se complique demasiado. En nuestro caso, hemos decidido implementar distintas funciones de manejo de cadenas para no usar ninguna biblioteca externa. La implementacin de estas funciones aparece en los siguientes cheros, string.h y string.c:
#ifndef #dene STRING H STRING H (void *)0
5

#dene NULL

int kstrcmp(char far *r, char far *s); int kstrncmp(char far *r, char far *s, unsigned int n); char far * kstrcpy(char far *d, char far *s); char far * kstrncpy(char far *d, char far *s, unsigned int n); unsigned int kstrlen(char far *s); char far *kstrcat(char far *d, char far *s); char far *kitoa(char far *s, int n); void kputs(char far *s); char far *kstrchr(char far *s, char c); void kcadvalor(char far *s, int n); #endif /* $Id: string.h,v 1.4 2004/04/12 16:31:17 piernas Exp $ */

10

15

#include "io.h" #include "string.h" int kstrcmp(char far *r, char far *s) { while (*r && *r == *s) { r++; s++; } return (*r *s); } int kstrncmp(char far *r, char far *s, unsigned int n) { if (n == 0) return 0; while (n && *r && *r == *s) { r++; s++; } } return (*r *s);
5

10

15

20

25

char far *kstrcpy(char far *d, char far *s) { char far *t = d; while (*d++ = *s++) ; } return t;

30

35

32

char far * kstrncpy(char far *d, char far *s, unsigned int n) { char far *t = d; while (n) { *d++ = *s; if (*s) s++; } } return t;

40

45

50

unsigned int kstrlen(char far *s) { unsigned int n = 0; while(*s++) n++; } return n;

55

60

char far *kstrcat(char far *d, char far *s) { char far *t = d; while(*d++) ; d; while(*d = *s) { d++; s++; } } return t;

65

70

75

char far *kitoa(char far *s, int n) { char far *p = s, far *t = s, c; if (n < 0) { *s++=-; n *= 1; } while(n > 0) { *s++ = (char)(n % 10 + 0); n /= 10; } if (s == p) { *s++ = 0; *s = \0; return p; } *s = \0; if (*t == -) t++; while(t < s)

80

85

90

95

100

33

} return p;

c = *t; *t++ = *s; *s = c;

105

void kputs(char far *s) { int len = kstrlen(s); if (len > 0) escribe cad(s, len); } char far * kstrchr(char far *s, char c) { while (*s && *s != c) s++; if (*s | | c == \0) return s; return NULL; } void kcadvalor(char far *s, int n) { char cadena[80]; kstrcpy(cadena, s); kitoa(cadena + kstrlen(s), n); kstrcat(cadena, "\n\r"); kputs(cadena);

110

115

120

125

130

135

/* $Id: string.c,v 1.4 2004/04/12 16:31:17 piernas Exp $ */

Los nombres de las funciones empiezan por k (de kernel) para distinguirlas de las funciones del mismo nombre que se implementan en la biblioteca de C. Las nicas funciones que merecen algn comentario son: kputs, que muestra una cadena de caracteres por pantalla, y kcadvalor, que recibe como parmetros una cadena y un nmero y muestra ambos por pantalla (tras convertir el nmero a cadena).

34

Anda mungkin juga menyukai