Beruflich Dokumente
Kultur Dokumente
_________________________________________
Núcleo de un Sistema
Operativo
Alberto Lafuente
Febrero 2006
Contenido
1 Introducción
5 Puesta en marcha
Los servicios que un sistema operativo gestiona suelen dividirse en cuatro: procesador,
memoria, dispositivos y ficheros. La complejidad inherente a la gestión de alguno de estos
servicios hace necesario estructurar el sistema operativo en varias capas o niveles, cada una
ofreciendo un conjunto de primitivas a la inmediatamente superior. Por ejemplo, el sistema de
ficheros reside sobre el dispositivo disco, por lo que la gestión de ficheros se especificará en
base a las primitivas que proporcione la gestión del disco, que será la que programe el
hardware del dispositivo.
El nivel básico de un sistema operativo, que oculta las características hardware de la máquina,
se conoce como núcleo o kernel. En este documento se proporciona una descripción completa
de la estructura del núcleo de un sistema operativo multiprogramado, que incluye gestión de
procesos basada en prioridades, gestión de dispositivos (disco flexible, teclado, pantalla,
impresora, línea serie y reloj), y primitivas de sincronización (semáforos). Se ha escogido
como plataforma soporte la arquitectura PC basada en la familia i80x86. Ya que la mayor
parte de las características dependientes de la arquitectura están encapsuladas en un conjunto
de rutinas dependientes del hardware, esta elección no es especialmente determinante para el
diseño del sistema operativo. La difusión de esta arquitectura es la única razón para su
elección. Por otra parte, las rutinas dependientes del hardware proporcionan una interfaz C
para su uso en el núcleo, lo que facilita la portabilidad a otras plataformas.
2 Nivel del Sistema Básico de Ficheros, BFS. Sistema básico de ficheros: ubicación en
disco, directorios. Rutinas de E/S, servidores de dispositivos.
La Figura 1 muestra la estructura en niveles de un sistema operativo. Para un nivel n sólo son
visibles las primitivas del nivel n-1. En general un nivel utilizará además una serie de rutinas
auxiliares internas que no son visibles desde el nivel superior.
Rutinas
USUARIO de
Librería
NIVEL
SISTEMA Rutinas
internas Primitivas
Nivel Sistema Nivel Sistema
NIVEL
BFS Rutinas
internas Primitivas
Nivel BFS Nivel BFS
NIVEL
NUCLEO Rutinas Primitivas
internas Nivel Núcleo
Nivel Núcleo
Rutinas
dependientes
del Hardware
HARDWARE
Las rutinas dependientes del hardware se llamarán únicamente desde el núcleo. Encapsulan
las características de la arquitectura.
struct semaforo {
struct cola s;
int cont;
};
struct semaforo sem[NSEM];
(a) disco
(b) terminal
char leer_teclado_nuc ()
int escribir_pantalla_nuc (int lin, int col, char car, char atributo)
int scroll_nuc (int lin_sup, int lin_inf)
int leer_l_s_nuc ()
void escribir_l_s_nuc (char car)
void init_l_s_nuc ()
(d) impresora
2 Control de procesos
int crear_pcb_nuc (void (*cod)(), int *pila, int prio, int quantum, int pid)
int destruir_pcb_nuc (int id_proc)
int quisoc_nuc ()
int info_proc_nuc (int id_proceso, struct info_proc *p_info)
int modif_proc_nuc (int id_proceso, struct info_proc *p_info)
5 Reset
void reset_nuc ()
Los procesos preparados para ejecución se encolan en la cola ready de acuerdo a su prioridad,
por lo que la rutina del scheduler se limita a coger el primer elemento de la cola, que pasa al
dispatcher para que lo cargue en ejecución (cola run, que tendrá un único elemento). Por
ortogonalidad, se asume la existencia de un proceso nulo, que siempre estará preparado para
ejecución o ejecutándose. La Figura 8 muestra el código de las rutinas scheduler() y el
dispatcher(). La rutina bloquear() es la encargada de sacar a un proceso de la cola, lo que
implica como efecto colateral la actualización del tiempo de CPU. Su código se muestra en la
Figura 9.
void multiplexar ()
{
salvar_reg();
/* FIN DE QUANTUM: */
if (++ntr==((struct pcb*)run.primero)->quantum) cambiar = TRUE;
…
if (cambiar) {
bloquear (&ready, READY);
dispatcher (scheduler());
}
eoi();
restaurar();
}
char leer_teclado_nuc ()
{
inhibir();
salvar_flags();
salvar_reg();
if (vacia (&teclado)) {
bloquear(&teclado, BLOQ_TEC);
dispatcher (scheduler());
restaurar();
}
else crash();
}
• Valores de retorno. El carácter leído (o un código de error en otras rutinas) debe ser
devuelto a través del PCB del proceso (campo ax), que será lo que entregue como
valor de retorno la primitiva que produjo el bloqueo (leer_teclado en nuestro ejemplo)
al restaurarse el contexto del proceso (rutina restaurar()) cuando éste sea planificado
nuevamente.
void int_teclado()
{
salvar_reg();
if ((c=leer_teclado()) != -1)
if (!vacia(&teclado))
if (((struct pcb *)teclado.primero)->status == DEAD) {
((struct pcb *)teclado.primero)->status= LIBRE;
encolar(&libres, primero(&teclado));
}
else {
((struct pcb *)teclado.primero)->ax= c;
meter(&ready, primero(&teclado), READY);
bloquear(&ready, READY);
dispatcher (scheduler());
}
else beep();
eoi();
restaurar();
}
5 Puesta en marcha
Sobre el núcleo propuesto se puede construir un sistema operativo completo. Como no
disponemos de un sistema operativo completo con todas sus herramientas para soportar
aplicaciones, probaremos el funcionamiento del núcleo con un conjunto de programas de
prueba que utilicen sus primitivas y monitoricen su funcionamiento. En cualquier caso, se
requiere un programa que instale el núcleo y lance los procesos del sistema. Este programa
incluirá:
• El código completo del núcleo, que incluye primitivas, rutinas internas y librerías de
bajo nivel.
(2) Inicialización de las colas del núcleo (run, ready, teclado, semáforos, …).
(4) Creación de los procesos del sistema. Al menos son necesarios dos: el proceso nulo, y
un proceso de inicialización, que lanzará el resto ya sobre el núcleo. Estos procesos se
crean rellenando directamente los campos de los PCBs y utilizando la rutina
crear_pcb() para asignar la pila correspondiente y apuntar al código del proceso.
Para probar esta versión del núcleo, se proporciona también un programa de prueba,
pruebas.h (que hace las veces de nivel superior). pruebas.h se incluye junto a nucleo.h en un
fichero fuente fase0.c, que contiene el código para poner en marcha el sistema. Un resumen
de la organización de los módulos fuente puede verse en la Figura A1.1.
ccc nombre_programa
Se proporciona otro comando para montar el código obtenido con los módulos objeto
proporcionados, de acuerdo al esquema de la Figura A1.2:
li nombre_programa
A1.2 Ejecución
Una vez compilado y montado el código, se puede probar el funcionamiento del núcleo con
ayuda del código ejecutable obtenido. Al ejecutar fase0 se ofrecen mediante un menú las
pruebas de diferentes aspectos del núcleo:
maquina.h pruebas.h
li fase0