Núcleo de un Sistema Operativo
Núcleo de un Sistema Operativo
Núcleo de un Sistema Operativo
Create successful ePaper yourself
Turn your PDF publications into a flip-book with our unique Google optimized e-Paper software.
Departamento <strong>de</strong> Arquitectura y Tecnología <strong>de</strong> Computadores<br />
Konputagailuen Arkitektura eta Teknologia Saila<br />
_________________________________________<br />
Laboratorio <strong>de</strong> <strong>Sistema</strong>s <strong>Operativo</strong>s<br />
_________________________________________<br />
Núcleo <strong>de</strong> <strong>un</strong> <strong>Sistema</strong><br />
<strong>Operativo</strong><br />
Alberto Lafuente<br />
Febrero 2006
Contenido<br />
1 Introducción<br />
2 Una estructura en capas para el sistema operativo<br />
3 Estructura <strong>de</strong> <strong>un</strong> núcleo <strong>de</strong> sistema operativo<br />
3.1 Rutinas <strong>de</strong>pendientes <strong>de</strong>l hardware<br />
3.2 Rutinas <strong>de</strong> manejo <strong>de</strong> colas y auxiliares<br />
3.3 Rutinas para la gestión <strong>de</strong> procesos<br />
3.4 Definiciones y estructuras <strong>de</strong> datos<br />
3.5 Primitivas <strong>de</strong>l núcleo<br />
4 F<strong>un</strong>cionamiento <strong>de</strong>l núcleo<br />
4.1 Gestión <strong>de</strong> procesos<br />
4.2 Primitivas bloqueantes<br />
4.3 Rutinas <strong>de</strong> tratamiento <strong>de</strong> interrupciones<br />
4.4 Primitivas no bloqueantes<br />
4.5 Primitivas <strong>de</strong> sincronización<br />
5 Puesta en marcha<br />
A.1 Prueba <strong>de</strong>l núcleo<br />
UPV/EHU ATC Laboratorio <strong>de</strong> <strong>Sistema</strong>s <strong>Operativo</strong>s 2
1 Introducción<br />
Un sistema operativo se <strong>de</strong>fine <strong>de</strong>s<strong>de</strong> dos p<strong>un</strong>tos <strong>de</strong> vista. En primer lugar, el sistema<br />
operativo constituye la interfaz entre el usuario <strong>de</strong> <strong>un</strong> computador y los recursos <strong>de</strong> éste<br />
(hardware y software), proporcionando <strong>un</strong>a visión f<strong>un</strong>cional <strong>de</strong>l sistema en forma <strong>de</strong> llamadas<br />
al sistema. En seg<strong>un</strong>do lugar, el sistema operativo es el encargado <strong>de</strong> gestionar eficientemente<br />
la utilización <strong>de</strong> los recursos por los usuarios.<br />
Los servicios que <strong>un</strong> sistema operativo gestiona suelen dividirse en cuatro: procesador,<br />
memoria, dispositivos y ficheros. La complejidad inherente a la gestión <strong>de</strong> alg<strong>un</strong>o <strong>de</strong> estos<br />
servicios hace necesario estructurar el sistema operativo en varias capas o niveles, cada <strong>un</strong>a<br />
ofreciendo <strong>un</strong> conj<strong>un</strong>to <strong>de</strong> primitivas a la inmediatamente superior. Por ejemplo, el sistema <strong>de</strong><br />
ficheros resi<strong>de</strong> sobre el dispositivo disco, por lo que la gestión <strong>de</strong> ficheros se especificará en<br />
base a las primitivas que proporcione la gestión <strong>de</strong>l disco, que será la que programe el<br />
hardware <strong>de</strong>l dispositivo.<br />
El nivel básico <strong>de</strong> <strong>un</strong> sistema operativo, que oculta las características hardware <strong>de</strong> la máquina,<br />
se conoce como núcleo o kernel. En este documento se proporciona <strong>un</strong>a <strong>de</strong>scripción completa<br />
<strong>de</strong> la estructura <strong>de</strong>l núcleo <strong>de</strong> <strong>un</strong> sistema operativo multiprogramado, que incluye gestión <strong>de</strong><br />
procesos basada en priorida<strong>de</strong>s, gestión <strong>de</strong> dispositivos (disco flexible, teclado, pantalla,<br />
impresora, línea serie y reloj), y primitivas <strong>de</strong> sincronización (semáforos). Se ha escogido<br />
como plataforma soporte la arquitectura PC basada en la familia i80x86. Ya que la mayor<br />
parte <strong>de</strong> las características <strong>de</strong>pendientes <strong>de</strong> la arquitectura están encapsuladas en <strong>un</strong> conj<strong>un</strong>to<br />
<strong>de</strong> rutinas <strong>de</strong>pendientes <strong>de</strong>l hardware, esta elección no es especialmente <strong>de</strong>terminante para el<br />
diseño <strong>de</strong>l sistema operativo. La difusión <strong>de</strong> esta arquitectura es la única razón para su<br />
elección. Por otra parte, las rutinas <strong>de</strong>pendientes <strong>de</strong>l hardware proporcionan <strong>un</strong>a interfaz C<br />
para su uso en el núcleo, lo que facilita la portabilidad a otras plataformas.<br />
2 Una estructura en capas para el sistema operativo<br />
El esquema general propuesto es <strong>un</strong>a estructura en tres niveles o capas, <strong>de</strong> abajo arriba:<br />
1 Nivel Núcleo. Gestión básica <strong>de</strong> procesos: planificación a corto plazo, cambio <strong>de</strong><br />
contexto. Primitivas <strong>de</strong> sincronización. Gestión <strong>de</strong> E/S y tiempo, rutinas <strong>de</strong> atención.<br />
2 Nivel <strong>de</strong>l <strong>Sistema</strong> Básico <strong>de</strong> Ficheros, BFS. <strong>Sistema</strong> básico <strong>de</strong> ficheros: ubicación en<br />
disco, directorios. Rutinas <strong>de</strong> E/S, servidores <strong>de</strong> dispositivos.<br />
3 Nivel <strong>Sistema</strong>. Implementación <strong>de</strong> las llamadas al sistema; in<strong>de</strong>pen<strong>de</strong>ncia <strong>de</strong>l<br />
dispositivo (tablas <strong>de</strong> canales); gestión <strong>de</strong> buffers para acceso a ficheros; carga,<br />
ejecución y finalización <strong>de</strong> procesos.<br />
La Figura 1 muestra la estructura en niveles <strong>de</strong> <strong>un</strong> sistema operativo. Para <strong>un</strong> nivel n sólo son<br />
visibles las primitivas <strong>de</strong>l nivel n-1. En general <strong>un</strong> nivel utilizará a<strong>de</strong>más <strong>un</strong>a serie <strong>de</strong> rutinas<br />
auxiliares internas que no son visibles <strong>de</strong>s<strong>de</strong> el nivel superior.<br />
3 Estructura <strong>de</strong> <strong>un</strong> núcleo <strong>de</strong> sistema operativo<br />
Como ejemplo <strong>de</strong> núcleo <strong>de</strong> sistema operativo, en esta sección <strong>de</strong>scribiremos las rutinas<br />
internas, estructuras <strong>de</strong> datos y primitivas <strong>de</strong> <strong>un</strong> núcleo multiprogramado para la arquitectura<br />
UPV/EHU ATC Laboratorio <strong>de</strong> <strong>Sistema</strong>s <strong>Operativo</strong>s 3
i80x86. Este núcleo es el que usaremos en el laboratorio. El f<strong>un</strong>cionamiento <strong>de</strong>l núcleo <strong>de</strong><br />
presentará en la siguiente sección.<br />
USUARIO<br />
Rutinas<br />
<strong>de</strong><br />
Librería<br />
NIVEL<br />
SISTEMA<br />
Rutinas<br />
internas<br />
Nivel <strong>Sistema</strong><br />
Primitivas<br />
Nivel <strong>Sistema</strong><br />
NIVEL<br />
BFS<br />
Rutinas<br />
internas<br />
Nivel BFS<br />
Primitivas<br />
Nivel BFS<br />
NIVEL<br />
NUCLEO<br />
Rutinas<br />
internas<br />
Nivel Núcleo<br />
Primitivas<br />
Nivel Núcleo<br />
Rutinas<br />
<strong>de</strong>pendientes<br />
<strong>de</strong>l Hardware<br />
HARDWARE<br />
Figura 1. Niveles en <strong>un</strong> sistema operativo<br />
3.1 Rutinas <strong>de</strong>pendientes <strong>de</strong>l hardware<br />
Son las rutinas <strong>de</strong>l núcleo <strong>de</strong> más bajo nivel. Distinguiremos dos grupos. Las más elementales<br />
están escritas en ensamblador y tratan con direcciones específicas <strong>de</strong> E/S o <strong>de</strong> memoria (para<br />
los dispositivos memory-mapped), o bien inhiben/activan interrupciones o manipulan<br />
directamente la pila <strong>de</strong>l programa para proporcionar los cambios <strong>de</strong> contexto. Sus prototipos<br />
se <strong>de</strong>scriben en la Figura 2. El seg<strong>un</strong>do grupo <strong>de</strong> rutinas internas <strong>de</strong>l núcleo, que programan<br />
los dispositivos, están codificadas en C. Estas se <strong>de</strong>scriben en la Figura 3.<br />
Las rutinas <strong>de</strong>pendientes <strong>de</strong>l hardware se llamarán únicamente <strong>de</strong>s<strong>de</strong> el núcleo. Encapsulan<br />
las características <strong>de</strong> la arquitectura.<br />
UPV/EHU ATC Laboratorio <strong>de</strong> <strong>Sistema</strong>s <strong>Operativo</strong>s 4
RUTINAS BASICAS DEL NUCLEO<br />
int flag inhibir ();<br />
void <strong>de</strong>sinhibir (int flag);<br />
int in_port (int puerto);<br />
void out_port (int puerto, int dato);<br />
void strobe (int puerto, char mascara);<br />
void eoi ();<br />
void vector_int (int num_int , void (*rutina )());<br />
int mascara_anterior enmascarar (int mascara);<br />
void salvar_flags ();<br />
void salvar_reg ();<br />
void restaurar ();<br />
void reset ();<br />
void timer (int valor);<br />
Figura 2. Rutinas <strong>de</strong>pendientes <strong>de</strong>l hardware en ensamblador<br />
RELACIONADAS CON EL TERMINAL<br />
char leer_teclado ();<br />
void escribir_pantalla (int fila, int columna, char car, char atrib);<br />
void posicionar_cursor (int fila , int columna);<br />
void scroll (int fila_ini , int fila_fin);<br />
int estado escribir_impresora (char car);<br />
void beep (long tono_sonido);<br />
RELACIONADAS CON LA LINEA SERIE<br />
void ini_ls ();<br />
int estado escribir_ls (char car);<br />
char leer_ls ();<br />
int estado consulta_ls_iir ();<br />
int estado consulta_ls_lsr ();<br />
int estado consulta_ls_msr ();<br />
RELACIONADAS CON EL DISCO<br />
void motor_on (int drive);<br />
void motor_off ();<br />
void posicionar_pista (int pista, int drive);<br />
void programar_disco (int op, int cara, int pista, int sector, int drive);<br />
int estado programar_dma (int operacion, char *buffer );<br />
void comando_disco (int comando);<br />
int estado comprobar_posicionado ();<br />
int estado comprobar_trasferencia ();<br />
int estado leer_status ();<br />
void recalibrar (int drive);<br />
Figura 3. Rutinas <strong>de</strong>pendientes <strong>de</strong>l hardware en C<br />
3.2 Rutinas <strong>de</strong> manejo <strong>de</strong> colas y auxiliares<br />
La mayoría se utilizan en el núcleo para el manejo <strong>de</strong> los PCBs. F<strong>un</strong>damentalmente son<br />
rutinas <strong>de</strong> acceso a colas, cuyos prototipos se <strong>de</strong>scriben en la Figura 4. Otras rutinas<br />
auxiliares, <strong>de</strong> apoyo a la programación, se ilustran en la Figura 5. En general, las rutinas<br />
auxiliares se utilizarán también en los niveles superiores. Por esta razón se <strong>de</strong>finen las<br />
estructuras item y cola <strong>de</strong> forma genérica, sin incluir información específica <strong>de</strong> los elementos<br />
que enlazan (en general procesos).<br />
UPV/EHU ATC Laboratorio <strong>de</strong> <strong>Sistema</strong>s <strong>Operativo</strong>s 5
RUTINAS PARA MANEJO DE COLAS<br />
struct item {<br />
struct item *siguiente;<br />
struct item *anterior;<br />
int prioridad;<br />
};<br />
struct cola {<br />
struct item *primero;<br />
struct item *ultimo;<br />
};<br />
void inicializar_cola (struct cola *cola);<br />
int vacia (struct cola *cola);<br />
struct item *primero (struct cola *cola);<br />
/* Elimina y <strong>de</strong>vuelve el primer elemento <strong>de</strong> cola */<br />
struct item *extraer (struct cola *cola, int prio);<br />
/* Elimina y <strong>de</strong>vuelve el primero <strong>de</strong> los <strong>de</strong> prioridad prio */<br />
struct item *extirpar (struct cola *cola, struct item *elem);<br />
/* Elimina y <strong>de</strong>vuelve el elemento ap<strong>un</strong>tado por elem */<br />
void encolar (struct cola *cola, struct item *elem);<br />
/* Encola elem al final <strong>de</strong> cola */<br />
void insertar(struct cola *cola, struct item *elem, int prio);<br />
/* Encola elem al final <strong>de</strong> los <strong>de</strong> prioridad prio */<br />
void meter(struct cola *cola, struct item *elem, int estado);<br />
/* Inserta elem actualizando estado en el PCB */<br />
RUTINAS AUXILIARES DIVERSAS<br />
Figura 4. Rutinas auxiliares para manejo <strong>de</strong> colas<br />
void copiar (char *str1 , char *str2 , int lon);<br />
int comparar (char *str1, char *str2 , int lon);<br />
int buscar (char *str, char car, int lon);<br />
void rellenar (char *str, char car, int lon);<br />
void mayuscula (char *str, int lon);<br />
void minuscula (char *str, int lon);<br />
int long_a_ascii (long num, char *str, int base, int lon);<br />
int int_a_ascii (int num, char *str, int base, int lon);<br />
long ascii_a_long (char *str, int lon, int base);<br />
int ascii_a_ int (char *str, int lon, int base);<br />
Figura 5. Otras rutinas auxiliares<br />
3.3 Rutinas para la gestión <strong>de</strong> procesos<br />
La gestión <strong>de</strong> procesos está soportada por rutinas que manejan colas y proporcionan cambios<br />
<strong>de</strong> estado en los procesos: bloquear(), scheduler(), dispatcher. Otras rutinas inicializan<br />
estructuras <strong>de</strong> datos (PCB y pila) para los procesos en su creación. Estas rutinas se muestran<br />
en la Figura 6 y se estudiarán más a<strong>de</strong>lante.<br />
RUTINAS PARA LA GESTION DE PROCESOS<br />
void bloquear (struct cola *cola, int estado);<br />
struct item *scheduler();<br />
void dispatcher (struct item este_item)<br />
void crear_pcb(void (*codigo)(), int *pila, int prio, int q, int id_proc));<br />
void crear ();<br />
Figura 6. Rutinas para la gestión <strong>de</strong> procesos<br />
UPV/EHU ATC Laboratorio <strong>de</strong> <strong>Sistema</strong>s <strong>Operativo</strong>s 6
3.4 Definiciones y estructuras <strong>de</strong> datos<br />
Se refieren a la estructura <strong>de</strong>l PCB y a las colas que mo<strong>de</strong>lan el f<strong>un</strong>cionamiento <strong>de</strong>l núcleo.<br />
Aparecen en la Figura 7. Existe a<strong>de</strong>más <strong>un</strong>a serie <strong>de</strong> <strong>de</strong>finiciones <strong>de</strong> símbolos para los estados<br />
<strong>de</strong> los procesos, dispositivos, etc, que no se muestran en la figura.<br />
#<strong>de</strong>fine NUM_PCBS … /* numero maximo <strong>de</strong> PCB */<br />
#<strong>de</strong>fine TAM_PILA … /* tamaño <strong>de</strong> la pila <strong>de</strong> <strong>un</strong> proceso */<br />
struct pcb {<br />
struct item elemento_cola;<br />
int id_proceso;<br />
int ax;<br />
int dx;<br />
int far *pila;<br />
int quantum;<br />
long cpu_time;<br />
int status;<br />
};<br />
struct pcb proc[NUM_PCBS];<br />
int pilas[NUM_PCBS][TAM_PILA];<br />
struct info_proc {<br />
int quantum;<br />
int prioridad;<br />
long t_cpu;<br />
int status;<br />
};<br />
struct cola libres,<br />
ready,<br />
r<strong>un</strong>,<br />
teclado,<br />
disco,<br />
retardo,<br />
lectura_ls,<br />
escritura_ls;<br />
int ntr = 0; /* numero <strong>de</strong> ticks <strong>de</strong> reloj */<br />
#<strong>de</strong>fine NSEM … /* semáforos */<br />
struct semaforo {<br />
struct cola s;<br />
int cont;<br />
};<br />
struct semaforo sem[NSEM];<br />
Figura 7. Definiciones f<strong>un</strong>damentales <strong>de</strong>l núcleo<br />
Otro conj<strong>un</strong>to <strong>de</strong> <strong>de</strong>finiciones (que tampoco se muestran aquí) especifican la configuración <strong>de</strong><br />
la máquina concreta (direcciones <strong>de</strong> pantalla, mapa <strong>de</strong> teclado, etc).<br />
3.5 Primitivas <strong>de</strong>l núcleo<br />
Las primitivas <strong>de</strong>l núcleo constituyen el conj<strong>un</strong>to <strong>de</strong> rutinas visibles <strong>de</strong>s<strong>de</strong> el nivel superior.<br />
Se <strong>de</strong>finen en C y utilizan las rutinas <strong>de</strong>pendientes <strong>de</strong>l hardware y a las rutinas auxiliares<br />
introducidas anteriormente. Las diferenciaremos <strong>de</strong>nominándolas con el sufijo _nuc. Se<br />
pue<strong>de</strong>n dividir en los siguientes grupos:<br />
UPV/EHU ATC Laboratorio <strong>de</strong> <strong>Sistema</strong>s <strong>Operativo</strong>s 7
1 Control <strong>de</strong> dispositivos<br />
(a) disco<br />
void motor_on_nuc (int drive)<br />
void motor_off_nuc ()<br />
int posicionar_pista_nuc (int n_pista, int drive)<br />
int leer_sector_nuc (int n_cara, int n_sector, char *p_buff)<br />
int escribir_sector_nuc (int n_cara, int n_sector, char *p_buff)<br />
int recalibrar_nuc (int drive)<br />
(b) terminal<br />
char leer_teclado_nuc ()<br />
int escribir_pantalla_nuc (int lin, int col, char car, char atributo)<br />
int scroll_nuc (int lin_sup, int lin_inf)<br />
(c) línea serie<br />
int leer_l_s_nuc ()<br />
void escribir_l_s_nuc (char car)<br />
void init_l_s_nuc ()<br />
(d) impresora<br />
int escribir_impresora_nuc (char car)<br />
2 Control <strong>de</strong> procesos<br />
int crear_pcb_nuc (void (*cod)(), int *pila, int prio, int quantum, int pid)<br />
int <strong>de</strong>struir_pcb_nuc (int id_proc)<br />
int quisoc_nuc ()<br />
int info_proc_nuc (int id_proceso, struct info_proc *p_info)<br />
int modif_proc_nuc (int id_proceso, struct info_proc *p_info)<br />
3 Espera por tiempo<br />
void retardo_nuc (int n_tics)<br />
4 Sincronización entre procesos<br />
5 Reset<br />
int wait_nuc (int n_sem)<br />
int signal_nuc (int n_sem)<br />
int init_sem_nuc (int n_sem, int valor_inicial)<br />
void reset_nuc ()<br />
UPV/EHU ATC Laboratorio <strong>de</strong> <strong>Sistema</strong>s <strong>Operativo</strong>s 8
4 F<strong>un</strong>cionamiento <strong>de</strong>l núcleo<br />
4.1 Gestión <strong>de</strong> procesos<br />
Incluiremos aquí planificación, creación y <strong>de</strong>strucción <strong>de</strong> procesos. Un proceso se representa<br />
en <strong>un</strong> PCB (struct pcb) que se i<strong>de</strong>ntifica por <strong>un</strong> entero (índice en la tabla <strong>de</strong> PCBs).<br />
3.4.1 Planificación <strong>de</strong> procesos<br />
En la versión <strong>de</strong>l núcleo propuesta, se proporciona planificación <strong>de</strong> procesos <strong>de</strong> tiempo<br />
compartido y priorida<strong>de</strong>s, con expulsión por <strong>de</strong>sbloqueo <strong>de</strong> <strong>un</strong> proceso. Esto implica que la<br />
gestión <strong>de</strong> procesos involucra a las rutinas <strong>de</strong> atención (muy en particular a la <strong>de</strong> reloj para<br />
control <strong>de</strong> fin <strong>de</strong> quantum), a<strong>de</strong>más <strong>de</strong> las que hacen que <strong>un</strong> proceso pase a estado bloqueado<br />
(más a<strong>de</strong>lante nos referiremos a estas rutinas como bloqueantes).<br />
Los procesos preparados para ejecución se encolan en la cola ready <strong>de</strong> acuerdo a su prioridad,<br />
por lo que la rutina <strong>de</strong>l scheduler se limita a coger el primer elemento <strong>de</strong> la cola, que pasa al<br />
dispatcher para que lo cargue en ejecución (cola r<strong>un</strong>, que tendrá <strong>un</strong> único elemento). Por<br />
ortogonalidad, se asume la existencia <strong>de</strong> <strong>un</strong> proceso nulo, que siempre estará preparado para<br />
ejecución o ejecutándose. La Figura 8 muestra el código <strong>de</strong> las rutinas scheduler() y el<br />
dispatcher(). La rutina bloquear() es la encargada <strong>de</strong> sacar a <strong>un</strong> proceso <strong>de</strong> la cola, lo que<br />
implica como efecto colateral la actualización <strong>de</strong>l tiempo <strong>de</strong> CPU. Su código se muestra en la<br />
Figura 9.<br />
struct item *scheduler()<br />
{<br />
return (primero(&ready));<br />
}<br />
void dispatcher (este_item)<br />
struct item *este_item;<br />
{<br />
meter (&r<strong>un</strong>, este_item, RUN);<br />
}<br />
Figura 8. Rutinas scheduler y dispatcher<br />
void bloquear (c, status)<br />
struct cola *c;<br />
int status;<br />
{<br />
((struct pcb *)r<strong>un</strong>.primero)->cpu_time += ntr;<br />
meter(c, primero(&r<strong>un</strong>), status);<br />
ntr=0;<br />
}<br />
Figura 9. Rutina bloquear<br />
Una vez actualizadas la colas <strong>de</strong> procesos, la multiplexación se completa reestableciendo el<br />
estado <strong>de</strong>l procesador para que ejecute el proceso planificado. Básicamente esto implica hacer<br />
que el SP ap<strong>un</strong>te a la pila <strong>de</strong>l nuevo proceso. La rutina restaurar(), codificada en<br />
ensamblador, es la que se encarga <strong>de</strong> ello, manipulando el bloque <strong>de</strong> activación anterior.<br />
Previamente, la rutina salvar_reg() habrá salvado el estado <strong>de</strong>l proceso que estaba en<br />
ejecución.<br />
UPV/EHU ATC Laboratorio <strong>de</strong> <strong>Sistema</strong>s <strong>Operativo</strong>s 9
La gestión <strong>de</strong>l tiempo compartido la realiza la rutina <strong>de</strong> atención al reloj, multiplexar(), como<br />
se ilustra en la Figura 10. Esta rutina incluye otras f<strong>un</strong>ciones (apagado automático <strong>de</strong>l motor<br />
<strong>de</strong> la <strong>un</strong>idad <strong>de</strong> disco y control <strong>de</strong>l periodo transitorio tras el encendido, retardo <strong>de</strong> procesos<br />
por tiempo), que no aparecen en la figura.<br />
void multiplexar ()<br />
{<br />
salvar_reg();<br />
/* FIN DE QUANTUM: */<br />
if (++ntr==((struct pcb*)r<strong>un</strong>.primero)->quantum) cambiar = TRUE;<br />
…<br />
}<br />
if (cambiar) {<br />
bloquear (&ready, READY);<br />
dispatcher (scheduler());<br />
}<br />
eoi();<br />
restaurar();<br />
Figura 10. Rutina multiplexar. Control <strong>de</strong> quantum.<br />
3.4.2 Creación, control y <strong>de</strong>strucción <strong>de</strong> procesos<br />
La rutina crear_pcb_nuc() asigna <strong>un</strong> PCB para <strong>un</strong> nuevo proceso, provocando la expulsión<br />
<strong>de</strong>l que está en ejecución. Existen primitivas para extraer información <strong>de</strong> <strong>un</strong> proceso y su<br />
i<strong>de</strong>ntificador (info_proc_nuc(), quisoc_nuc()) y para modificar su quantum y prioridad<br />
(modif_proc_nuc()). Finalmente, en la Figura 11 se incluye el código <strong>de</strong><br />
<strong>de</strong>struir_pcb_nuc(). Nótese las situaciones en que el PCB no se libera (el proceso tiene <strong>un</strong>a<br />
petición pendiente), sino que el proceso queda en estado finalizado (DEAD). La rutina <strong>de</strong><br />
atención correspondiente liberará el PCB <strong>de</strong>l proceso DEAD con la petición pendiente.<br />
4.2 Primitivas bloqueantes<br />
Llamaremos rutinas bloqueantes <strong>de</strong>l núcleo a las que solicitan <strong>un</strong> servicio <strong>de</strong>l hardware (<strong>un</strong><br />
dispositivo, el reloj…) y <strong>de</strong>jan al proceso en estado bloqueado, provocando la planificación<br />
<strong>de</strong> <strong>un</strong> nuevo proceso. Son varias las primitivas que trabajan <strong>de</strong> esta forma, en general aquéllas<br />
cuyo servicio se aten<strong>de</strong>rá mediante interrupciones, como motor_on_nuc(), retardo_nuc() —<br />
por tiempo—; leer_teclado_nuc() —por teclado—; escribir_impresora_nuc() —por<br />
impresora— leer_l_s_nuc, escribir_l_s_nuc() —por línea serie—; posicionar_pista_nuc(),<br />
leer_sector_nuc(), escribir_sector_nuc(), y recalibrar_nuc() —por disco—. Como<br />
ejemplo, en la Figura 12 se muestra el código <strong>de</strong> leer_teclado_nuc(). Todas las rutinas<br />
bloqueantes siguen el protocolo <strong>de</strong> salvar el contexto <strong>de</strong>l proceso (salvar_flags y<br />
salvar_reg), bloquear al proceso y planificar otro proceso cargando su contexto. Nótese<br />
también que se exige que la cola <strong>de</strong> bloqueado esté vacía: se sugiere la implementación <strong>de</strong> <strong>un</strong><br />
esquema cliente-servidor en el nivel superior.<br />
UPV/EHU ATC Laboratorio <strong>de</strong> <strong>Sistema</strong>s <strong>Operativo</strong>s 10
int <strong>de</strong>struir_pcb_nuc (id_proceso)<br />
int id_proceso;<br />
{<br />
inhibir();<br />
salvar_flags();<br />
salvar_reg();<br />
}<br />
id_proceso = id_proceso % NUM_PCBS;<br />
switch (proc[id_proceso].status) {<br />
case LIBRE:<br />
((struct pcb *)r<strong>un</strong>.primero)->ax = -1;<br />
break;<br />
case READY:<br />
proc[id_proceso].status = LIBRE;<br />
encolar (&libres, extirpar(&ready,<br />
&(proc[id_proceso].elemento_cola)));<br />
((struct pcb *)r<strong>un</strong>.primero)->ax = 0;<br />
break;<br />
case RUN:<br />
proc[id_proceso].status = LIBRE;<br />
encolar (&libres,primero (&r<strong>un</strong>));<br />
dispatcher (scheduler());<br />
break;<br />
case BLOQ_TEC :<br />
case BLOQ_DISC:<br />
case BLOQ_RET:<br />
proc[id_proceso].status = DEAD;<br />
((struct pcb *)r<strong>un</strong>.primero)->ax = 0;<br />
break;<br />
<strong>de</strong>fault:<br />
proc[id_proceso].status = LIBRE;<br />
encolar(&libres, extirpar (&sem[proc[id_proceso].status].s,<br />
&(proc[id_proceso].elemento_cola)));<br />
((struct pcb *)r<strong>un</strong>.primero)->ax = 0;<br />
break;<br />
}<br />
restaurar();<br />
Figura 11. Rutina <strong>de</strong>struir_pcb_nuc<br />
char leer_teclado_nuc ()<br />
{<br />
inhibir();<br />
salvar_flags();<br />
salvar_reg();<br />
if (vacia (&teclado)) {<br />
bloquear(&teclado, BLOQ_TEC);<br />
dispatcher (scheduler());<br />
restaurar();<br />
}<br />
else crash();<br />
}<br />
Figura 12. Ejemplo <strong>de</strong> rutina bloqueante<br />
4.3 Rutinas <strong>de</strong> tratamiento <strong>de</strong> interrupciones<br />
A<strong>un</strong>que la rutina multiplexar pue<strong>de</strong> servir como ejemplo <strong>de</strong> rutina <strong>de</strong> tratamiento <strong>de</strong><br />
interrupciones, resultará clarificador en este momento introducir el código <strong>de</strong> la rutina <strong>de</strong><br />
tratamiento <strong>de</strong> las interrupciones <strong>de</strong> teclado (Figura 13) para completar el ejemplo <strong>de</strong><br />
tratamiento <strong>de</strong> peticiones <strong>de</strong>l apartado anterior. Hay que resaltar alg<strong>un</strong>a cuestión importante,<br />
común al resto <strong>de</strong> rutinas <strong>de</strong> este tipo:<br />
UPV/EHU ATC Laboratorio <strong>de</strong> <strong>Sistema</strong>s <strong>Operativo</strong>s 11
• Tratamiento <strong>de</strong> peticiones pendientes <strong>de</strong> pocesos finalizados. La rutina <strong>de</strong> atención<br />
<strong>de</strong>tecta que el proceso está en estado DEAD (véase <strong>de</strong>struir_pcb_nuc()) y libera su<br />
PCB.<br />
• Valores <strong>de</strong> retorno. El carácter leído (o <strong>un</strong> código <strong>de</strong> error en otras rutinas) <strong>de</strong>be ser<br />
<strong>de</strong>vuelto a través <strong>de</strong>l PCB <strong>de</strong>l proceso (campo ax), que será lo que entregue como<br />
valor <strong>de</strong> retorno la primitiva que produjo el bloqueo (leer_teclado en nuestro ejemplo)<br />
al restaurarse el contexto <strong>de</strong>l proceso (rutina restaurar()) cuando éste sea planificado<br />
nuevamente.<br />
void int_teclado()<br />
{<br />
salvar_reg();<br />
if ((c=leer_teclado()) != -1)<br />
if (!vacia(&teclado))<br />
if (((struct pcb *)teclado.primero)->status == DEAD) {<br />
((struct pcb *)teclado.primero)->status= LIBRE;<br />
encolar(&libres, primero(&teclado));<br />
}<br />
else {<br />
((struct pcb *)teclado.primero)->ax= c;<br />
meter(&ready, primero(&teclado), READY);<br />
bloquear(&ready, READY);<br />
dispatcher (scheduler());<br />
}<br />
else beep();<br />
eoi();<br />
restaurar();<br />
}<br />
Figura 13. Ejemplo <strong>de</strong> rutina <strong>de</strong> tratamiento <strong>de</strong> interrupciones<br />
4.4 Primitivas no bloqueantes<br />
Llamaremos rutinas no bloqueantes a las que no <strong>de</strong>jan al proceso bloqueado. En general serán<br />
<strong>de</strong> este tipo aquellas primitivas que no se sirven mediante interrupciones, como es el caso <strong>de</strong><br />
esribir_pantalla_nuc(), que se muestra en la Figura 14. Otras primitivas <strong>de</strong> este tipo son:<br />
scroll_nuc(), motor_off_nuc(), quisoc_nuc() e info_proc_nuc(). Estas primitivas se<br />
resuelven como simples llamadas a f<strong>un</strong>ciones y no producen cambios <strong>de</strong> contexto.<br />
int escribir_pantalla_nuc (linea, columna, caracter, atributo)<br />
int linea, columna;<br />
char caracter, atributo;<br />
{<br />
if (0
4.5 Sincronización entre procesos<br />
El núcleo propuesto ofrece semáforos como mecanismo básico <strong>de</strong> sincronización entre<br />
procesos. Un semáforo se i<strong>de</strong>ntifica por <strong>un</strong> entero. Se introduce <strong>un</strong>a nueva condición <strong>de</strong><br />
bloqueo para los procesos, con tantas colas como semáforos disponibles, NSEM. El bloqueo<br />
por semáforo presenta la particularidad <strong>de</strong> que <strong>de</strong>be seguir estrictamente <strong>un</strong>a disciplina FCFS,<br />
por lo que el mecanismo <strong>de</strong> bloqueo estándar introducido para las rutinas bloqueantes no se<br />
pue<strong>de</strong> aplicar en la primitiva <strong>de</strong> espera por semáforo, wait_nuc(), <strong>de</strong>scrita en la Figura 15. La<br />
primitiva signal_nuc() se ha implementado como expulsora.<br />
int wait_nuc (n_semaforo)<br />
int n_semaforo;<br />
{<br />
inhibir();<br />
salvar_flags();<br />
salvar_reg();<br />
if (0 0) sem[n_semaforo].cont--;<br />
else {<br />
((struct pcb *)r<strong>un</strong>.primero)->status = n_semaforo;<br />
((struct pcb *)r<strong>un</strong>.primero)->cpu_time += ntr;<br />
encolar (&sem[n_semaforo].s, primero(&r<strong>un</strong>));<br />
ntr=0;<br />
dispatcher (scheduler());<br />
}<br />
}<br />
else ((struct pcb *)r<strong>un</strong>.primero)->ax = -1;<br />
restaurar();<br />
}<br />
5 Puesta en marcha<br />
Figura 15. Rutina wait_nuc<br />
Sobre el núcleo propuesto se pue<strong>de</strong> construir <strong>un</strong> sistema operativo completo. Como no<br />
disponemos <strong>de</strong> <strong>un</strong> sistema operativo completo con todas sus herramientas para soportar<br />
aplicaciones, probaremos el f<strong>un</strong>cionamiento <strong>de</strong>l núcleo con <strong>un</strong> conj<strong>un</strong>to <strong>de</strong> programas <strong>de</strong><br />
prueba que utilicen sus primitivas y monitoricen su f<strong>un</strong>cionamiento. En cualquier caso, se<br />
requiere <strong>un</strong> programa que instale el núcleo y lance los procesos <strong>de</strong>l sistema. Este programa<br />
incluirá:<br />
• El código completo <strong>de</strong>l núcleo, que incluye primitivas, rutinas internas y librerías <strong>de</strong><br />
bajo nivel.<br />
• El código <strong>de</strong> la prueba <strong>de</strong>l núcleo, que es <strong>un</strong> conj<strong>un</strong>to <strong>de</strong> f<strong>un</strong>ciones en código C que se<br />
ejecutarán asíncronamente, como threads o procesos a ser gestionados por el núcleo.<br />
• El procedimiento principal (main) <strong>de</strong> puesta en marcha.<br />
El procedimiento principal realizará las siguientes acciones:<br />
(1) Instalación <strong>de</strong> las rutinas <strong>de</strong> tratamiento <strong>de</strong> interrupciones, mediante vector_int().<br />
(2) Inicialización <strong>de</strong> las colas <strong>de</strong>l núcleo (r<strong>un</strong>, ready, teclado, semáforos, …).<br />
UPV/EHU ATC Laboratorio <strong>de</strong> <strong>Sistema</strong>s <strong>Operativo</strong>s 13
(3) Inicialización <strong>de</strong> los PCBs como libres, encolándolos en la cola <strong>de</strong> libres.<br />
(4) Creación <strong>de</strong> los procesos <strong>de</strong>l sistema. Al menos son necesarios dos: el proceso nulo, y<br />
<strong>un</strong> proceso <strong>de</strong> inicialización, que lanzará el resto ya sobre el núcleo. Estos procesos se<br />
crean rellenando directamente los campos <strong>de</strong> los PCBs y utilizando la rutina<br />
crear_pcb() para asignar la pila correspondiente y ap<strong>un</strong>tar al código <strong>de</strong>l proceso.<br />
(5) Planificar (dispatcher y scheduler) y cargar el estado <strong>de</strong>l proceso seleccionado<br />
(mediante restaurar()). Este proceso será el <strong>de</strong> inicialización, ya que el nulo se habrá<br />
creado con la prioridad mínima.<br />
Ya que no disponemos <strong>de</strong> <strong>un</strong> dispositivo <strong>de</strong> arranque propio, la puesta en marcha <strong>de</strong>l núcleo<br />
requiere <strong>de</strong> <strong>un</strong> sistema operativo anfitrión, MS-DOS. El programa <strong>de</strong> prueba permite volver a<br />
MS-DOS (al menos si no se provocan situaciones <strong>de</strong> fallo que requieran reiniciar el sistema).<br />
Naturalmente, el núcleo pue<strong>de</strong> ser lanzado <strong>de</strong>s<strong>de</strong> cualquier sistema operativo Windows 95 o<br />
98, lo que facilita el <strong>de</strong>sarrollo y manejo <strong>de</strong>l software, pero no <strong>de</strong>s<strong>de</strong> <strong>un</strong> sistema Windows<br />
basado en NT (2000 o XP). En este último caso, aún se pue<strong>de</strong> recurrir a <strong>un</strong> emulador<br />
(VMware o Virtual PC) que soporte MS-DOS.<br />
UPV/EHU ATC Laboratorio <strong>de</strong> <strong>Sistema</strong>s <strong>Operativo</strong>s 14
A1 Prueba <strong>de</strong>l núcleo<br />
A1.1 Compilación y montaje<br />
Los ficheros fuente <strong>de</strong>l núcleo que se proporcionan son:<br />
rutinas.h<br />
maquina.h<br />
nucleo.h<br />
Definiciones f<strong>un</strong>damentales <strong>de</strong>l núcleo; <strong>de</strong>claraciones <strong>de</strong> rutinas auxiliares y<br />
<strong>de</strong> manejo <strong>de</strong> colas, y <strong>de</strong>claraciones <strong>de</strong> rutinas ensamblador y C<br />
<strong>de</strong>pendientes <strong>de</strong>l hardware.<br />
Definiciones (condicionales) <strong>de</strong>pendientes <strong>de</strong> la pantalla y el teclado.<br />
Primitivas <strong>de</strong>l núcleo.<br />
Para probar esta versión <strong>de</strong>l núcleo, se proporciona también <strong>un</strong> programa <strong>de</strong> prueba,<br />
pruebas.h (que hace las veces <strong>de</strong> nivel superior). pruebas.h se incluye j<strong>un</strong>to a nucleo.h en <strong>un</strong><br />
fichero fuente fase0.c, que contiene el código para poner en marcha el sistema. Un resumen<br />
<strong>de</strong> la organización <strong>de</strong> los módulos fuente pue<strong>de</strong> verse en la Figura A1.1.<br />
Se proporciona <strong>un</strong> comando para compilar el código:<br />
ccc nombre_programa<br />
Para la prueba <strong>de</strong> la versión propuesta, nombre_programa será fase0<br />
Se proporciona otro comando para montar el código obtenido con los módulos objeto<br />
proporcionados, <strong>de</strong> acuerdo al esquema <strong>de</strong> la Figura A1.2:<br />
A1.2 Ejecución<br />
li nombre_programa<br />
Una vez compilado y montado el código, se pue<strong>de</strong> probar el f<strong>un</strong>cionamiento <strong>de</strong>l núcleo con<br />
ayuda <strong>de</strong>l código ejecutable obtenido. Al ejecutar fase0 se ofrecen mediante <strong>un</strong> menú las<br />
pruebas <strong>de</strong> diferentes aspectos <strong>de</strong>l núcleo:<br />
• Control <strong>de</strong> procesos. Permite manejar priorida<strong>de</strong>s y quanta <strong>de</strong> procesos.<br />
• Control <strong>de</strong>l disco flexible. Permite operar el motor, posicionar el cabezal y<br />
transferir información entre disco y memoria.<br />
La introducción <strong>de</strong> otras opciones se realizará modificando el código <strong>de</strong> pruebas.h.<br />
UPV/EHU ATC Laboratorio <strong>de</strong> <strong>Sistema</strong>s <strong>Operativo</strong>s 15
utinas.h<br />
nucleo.h<br />
fase0.c<br />
Rutinas Definiciones, auxiliares<br />
rutinas Definiciones auxiliares,<br />
Rutinas ensambador etc<br />
(namejo <strong>de</strong>l Hw)<br />
Rutinas <strong>de</strong>pendientes<br />
<strong>de</strong> la máquina<br />
#inclu<strong>de</strong> "rutinas.h"<br />
#inclu<strong>de</strong> "maquina.h"<br />
Código <strong>de</strong> las<br />
primitivas <strong>de</strong>l núcleo<br />
Programas que<br />
usan el núcleo<br />
#inclu<strong>de</strong> "nucleo.h"<br />
#inclu<strong>de</strong> "pruebas.h"<br />
Programa <strong>de</strong><br />
activación <strong>de</strong>l núcleo<br />
maquina.h<br />
pruebas.h<br />
Figura A1.1. Organización <strong>de</strong> los módulos fuente<br />
fase0. c<br />
nucleo.h<br />
pruebas.h<br />
main()<br />
…<br />
ccc fase0<br />
fase0. obj<br />
li fase0<br />
fase0. exe<br />
solib. lib<br />
Figura A1.2. Esquema para compilar y montar el núcleo.<br />
UPV/EHU ATC Laboratorio <strong>de</strong> <strong>Sistema</strong>s <strong>Operativo</strong>s 16