SISTEMAS OPERATIVOS TEMA 1: INTRODUCCIÓN AL SISTEMA OPERATIVO ONION TEMA 1: INTRODUCCIÓN AL SISTEMA OPERATIVO ONION Descripción general El sistema operativo (S.O.) ONION posee las siguientes características: • Multiusuario: al ser multiusuario necesitaremos proteger los datos de cada usuario. Para esto necesitaremos los comandos de login y logout los cuales, dependiendo de un username y un password tendrán unos privilegios u otros. • Multiprogramado: esto quiere decir que se pueden tener n procesos ejecutándose al mismo tiempo, pero si el ordenador solo tiene un procesador se ejecuta un solo proceso y el resto estará bloqueado o preparados para ser ejecutados. Por lo tanto los estados de un proceso son: run (ejecutándose), preparado y bloqueado. • Estructurado en capas: Esto quiere decir que se diferencian diferentes capas según la cercanía al hardware. Las capas del ONION son la de sistema la BFS (Basic file System) y el núcleo. Si se modifica el hardware solo se debería modificar la capa del núcleo ya que es la capa más cercana al hardware. Las llamadas al sistema (system calls) llaman a la capa del sistema, y la forma de comunicarse esta con las demás capas es mediante llamadas especiales, pero si se quiere comunicar con la capa del núcleo antes debe pasar la información por la capa de BFS. Cada capa resuelve un determinado problema y si una capa no es capaz de resolver dicho problema se baja a la capa siguiente. Visión externa La visión externa de un sistema operativo es lo que ofrece este al exterior. Dispositivos Los dispositivos que nos ofrece ONION son de dos tipos, físicos (consola, terminal, impresora y el disco) i lógicos (mailbox y nulo). • Consola: la consola esta formada por un teclado y una pantalla por donde se comunica el usuario y el ordenador de forma interactiva. La consola es donde trabaja el usuario principal. A la consola se le asocia un interprete de comandos al igual que a los terminales. • Terminal: igual que la consola pero que está conectado a la lincea serie. • Mailbox: a diferencia con UNIX, donde un mailbox es un archivo temporal, en ONION es un buffer de memoria que sirve para comunicar y sincronizar procesos. • Nulo: este dispositivo sirve para borrar, ya que la información que se inserte en dicho dispositivo ya no se puede recuperar, porque no podemos leer de este dispositivo. La conexión entre ordenadores con el sistema operativo ONION queda reflejada en el siguiente gráfico: Como se puede observar la conexión de los ordenadores se realiza mediante los terminales, donde cada terminal tiene su intérprete de comandos el cual no enviará ningún prompt que indique que están esperando una orden nueva. 1 Ahora veremos las características de los dispositivos: • ECHO/NO ECHO: solo tiene sentido en dispositivos de entrada/salida. Significa que lo que se introduce por la entrada sale por la salida. Por defecto la característica echo está activada (consola y terminal). • COOKED/RAW: cooked significa que el dispositivo es capaz de interpretar caracteres especiales (return, delete, tabulador, etc.). La consola y el terminal tienen por defecto la característica de cooked y el mailbox, la impresora y los ficheros la raw. • EOT/NOEOT: característica de fin de transmisión. Indica si un proceso ha dejado de trabajar con algún dispositivo o se cierra un canal de escritura en el terminal. El terminal y el mailbox tienen por defecto la característica de Noeot. • SPOOL/NOSPOOL: solo tiene sentido en la impresora, donde por defecto esta en nospool. La impresora es un dispositivo no compartible, es decir, solo un proceso puede acceder, y además es lenta. Debido a este motivo y para poder permitir que varios procesos puedan acceder a la impresora existe un dispositivo intermedio que permite que varios procesos puedan imprimir a la vez gracias a que crean un fichero temporal. Aquí interviene SPOOL el cual va cogiendo estos ficheros temporales y los envía a la impresora uno tras otro para que sean impresos. De este modo la impresora pasa a ser compartible. • PERMANENTE/NO PERMANENTE: característica de los mailbox. Si un mailbox es permanente este guarda la información que se le introduce aunque el proceso que lo invoca termine, en cambio en un mailbox no permanente cuando el proceso que lo invoca termina la información de dicho mailbox se pierde. Por defecto los mailbox son permanentes. Interprete de comandos El interprete de comandos muestre el prompt formado solo por el símbolo del dólar. Además acepta los caracteres de redireccionamiento de entrada, salida y error. redireccionar salida > redireccionar entrada < redireccionar error ^ nom hará referencia a un fichero o a un dispositivo y nom_fitxer a un fichero. El interprete de comandos reconoce los siguientes comandos: • Comandos de manejo de ficheros y dispositivos: $ dir [>nom] $ copy [<nom] [>nom] $ del nom_fitxer $ mou nom_fitxer_vell nom_fitxer_nou (cambio del nombre del fichero) $ prot nom [prot] [uid] (modifica las protecciones y el ususario del nom) $ set nom característica (característica de los dispositivos) 2 • Gestión de procesos: $ run nom_fitxer [<nom][>nom][^nom][parámetres] Ejecuta un proceso pero espera a que termine el proceso hijo. $ spawn nom_fitxer [<nom][>nom][^nom][parámetres] Ejecuta el proceso hijo y el padre de forma concurrente. $ stop id_proces. $ sys [>nom] Da información sobre el sistema (tiempo des de la puesta en marcha del sistema, y indica que fichero ejecuta el proceso y el identificador de usuario, uid). $ login [nmbre usuario] $ logout Visión interna y Llamadas al sistema Llamadas sobre la capa sistema La única puerta de acceso a los sistemas operativos son la llamadas al sistema. Estas no son mas que un conjunto de llamadas a funciones las cuales implementan los servicios ofrecidos por el sistema. En el caso de ONION el conjunto de llamadas serán las primitivas que el nivel más externo (nivel sistema) ofrecerá a los usuarios. Como hemos dicho el único modo de comunicar las diferentes capas del S.O. es a través de llamadas. El nivel sistema es el que servirá de interprete entre los usuarios del S.O. y el mismo sistema. Las primitivas constituirán lo que se llama conjunto de llamadas al sistema. Este nivel ofrecerá a los programas de usuario una máquina virtual de alto nivel que garantizará una serie de servicios a los usuarios. Estos servicios pueden agruparse de la siguiente manera: • Entrada/salida: ONION ofrece E/S independencia del dispositivo sobre el que trabajan (la E/S se realiza con dispositivos virtuales, los cuales se identifican por un canal). Para garantizar esta independencia del dispositivo las llamadas Llegir (canal, buffer, longitud) Escriure (canal, buffer, longitud) Actuaran sobre canales que podrán representar cualquier dispositivo o fichero. Estas operaciones aran un acceso secuencial i serán asíncronas. Estas llamadas no se bloquearán aunque no hayan terminado su trabajo y se les invoque de nuevo. Las llamadas Esperar (id_io) −> nos indica como ha terminado un dispositivo Cancel−lar (id_io2) 3 Ofrecerán la posibilidad de anular una operación de E/S i de sincronizar la finalización con la ejecución del resto del programa. Hay dos formas de asociar el canal al dispositivo: · Mediante la herencia: el proceso hijo hereda la tabla de canales del padre. Por ejemplo: executar_programa(fitxer, input, output, error, p1,..., (char *)0 donde input, output y error indica que canal se quiere para la entrada, salida y error del proceso, si aquí se indica (char *)0, quiere decir que los canales son los mismos que los del padre que lo ha invocado. · Mediante las llamadas obrir (canal, nom, modo, protecciones) tancar (canal) las cuales asocian un dispositivo a o un fichero a un canal, y liberan un canal determinado permitiendo así una futura asociación con otro dispositivo o fichero, respectivamente. En ONION solo disponemos de 5 dispositivos virtuales, esto quiere decir que solo tenemos cinco entradas a la tabla de canales. Cada dispositivo apunta a un solo descriptor de dispositivos. • Ficheros: ofreceran a los usuarios tres funciones sobre ficheros heredados del nivel BFS. Seran las de leer directorio del disquete, borrar un fichero i cambiar el nombre a un fichero. • Control de procesos: dos de las tareas fundamentales de todo el sistema operativo son lacreación i destrucción de procesos. En el caso de un sistema multiprogramado como ONION la creación de procesos implicrá una ejecución concurrente del proceso crador y del proceso creado. Executar_programa (fitxer, input, output, error, p1,..., (char *)0 Crea un proceso nuevo, creando su PCB con su tabla de canales correspondiente. En el nivel BFS se hace la llamada Crear_pcb_bfs (pid, prioritat, codi, pila) Y se crea otra PCB en este nivel y se hace una llamada en el nivel de núcleo Crear_pcb_nuc (codi, pila, prioritat, quantum, id_proc) La cual crea otra PCB en este nivel y donde quantum es el tiempo de CPU asignado a este proceso. Fi_programa (codi_retorn) Stop (id_proces) Esperar_fill (&codi_retorn) Esta última llamada realiza una sincronización entre el proceso padre e hijo, donde el proceso padre se bloquea hasta que termina el proceso hijo. Con stop lo que realizamos es que el proceso indicado finalice por lo tanto es como si lo destruyéramos, en cambio fi_programa hace que el proceso que invoca esta llamada 4 termine, seria como si se autodestruyera. Según como se termine un proceso codi_retorn de esperar_fill tendrá dos valores diferentes, −2 si el proceso que esperaba se ha terminado con stop y codi_retorn si el proceso se ha terminado con fi_programa. • Temporización: permiten que los procesos de usuario puedan bloquearse durante un cierto número de segundos en espera de algún acontecimiento. • Protección: será en este nivel donde se hará la gestión de usuarios i todas las comprobaciones necesarias para garantizar la protección de procesos y de dispositivos. Existen dos usuarios en un sistema con ONION: super−Onion (que es el sistema y que tiene todos los privilegios) y el ordinario. Los procesos solo podrán ser destruidos por su propietario o por el super−ONION. También será posible para el super−Onion cambiar el propietario de un proceso. Al haber el usuario ordinario y el resto de usuarios se definen dos dominios de protección, para el usuario y para el resto de usuarios. La protección puede ser de lectura, escritura o lectura/escritura. Tener permiso de ejecución sobre un dispositivo es tener la posibilidad de modificar las características del dispositivo. Cuando un usuario abre un dispositivo, y es el primero en abrirlo, este se convierte en el propietario y el resto, por lo tanto no podrá acceder si no se cambian las protecciones. Llamadas sobre la capa BFS El BFS será un nivel intermedio entre la independencia de dispositivos que ofrece el nivel sistema i la dependencia respecto al hardware de la maquina que tendremos en el nivel inferior (núcleo). Este nivel ofrecerá primitivas especificas para cada dispositivo que permitirán implementar las E/S que el nivel sistema ofrecía a los usuarios. DE toda forma estas primitivas diferenciadas para cada dispositivo serán independientes del hardware de la maquina ya que utilizaran las funciones que el nivel mas bajo les ofrecerá. Las funciones de este nivel pueden agruparse de la siguiente forma: • Acceso i manejo de ficheros (la mas importante en este nivel). • Entrada/salida de dispositivos. • Control de procesos. • Temporización. • Planificación de la utilización de recursos. • Protección de ficheros. En este nivel tenemos dos tablas diferentes: • TFA (tabla de ficheros abiertos): aquí hay una entrada abierta por cada fichero diferente abierto. Tiene un campo que indica cuantas veces se ha abierto el mismo fichero y un puntero a la FAT. • FAT (File Allocation Table): indica a cada registro lógico correspondiente a un fichero donde se encuentra el registro físico. Esta tabla nos dice para todos los ficheros en que bloque físico se encuentra sus registros lógicos. Con cada lectura, en este nivel, se leen 512 bytes que es lo mide un bloque entero. Llamadas sobre la capa núcleo Es el nivel más próximo al hardware de la maquina. Por debajo de este encontraremos los controladores de los dispositivos, la memoria y el procesador. Este nivel depende totalmente de la maquina sobe la qual estemos trabajando y se habrá de modificar cada vez que queramos transportar nuestro S.O. a un nuevo computador. La función del núcleo será la de liberar a los niveles superiores del manejo de los dispositivos. Des de este 5 nivel programaremos todos los controladores de la maquina de tal forma que los niveles superiores puedan excluirse de esta función. Así mismo será el único nivel que gestionará los registros del procesador i peor lo tanto, se habrá de utilizar rutinas en ensamblador. Des de este nivel ofreceremos al nivel BFS una maquina virtual con los siguientes conjuntos de servicios: • Gestión de procesos (la más importante en este nivel). • Gestión del tiempo. • Sincronización i comunicación entre procesos. • Entrada/salida sobre dispositivos. • Gestión de memoria. Para cada nuevo proceso tendremos una pila en el espacio de memoria del usuario, y una PCB en el nivel del núcleo o espacio de direcciones del sistema, aunque en este nivel también seria bueno tener una pila. Los procesos se pueden encontrar en diversos estados (solo se puede ejecutar un proceso y el resto de procesos activos estarán en estado ready o bloqueados). La política para ver que proceso se ejecuta és la Round−Robin por prioridades. Esta política significa que la CPU siempre la ocupará el proceso mas prioritario, y si hay varios con la misma prioridad entonces cuando se acaba el quantum del proceso este pasa a estado ready (pasa a la cola ordenada por prioridades) y el primero de esta cola es el que se ejecuta. En la cola se inserta, el proceso, en el último lugar de los procesos con la misma prioridad. Dentro de esta política de Round−Robin existen diversas opciones: • Apropiación inmediata: los procesos se pueden bloquear, y por lo tanto pasa a ejecutarse el siguiente proceso en estado ready. Si se desbloquea y este es más prioritario que el que se está ejecutando, entonces la rutina que desbloquea el proceso hace que se ejecute inmediatamente y el que se estaba ejecutando pase a estado ready. • Apropiación diferida: la rutina de desbloqueo hace que el proceso desbloqueado pase a ready y que se ejecute, si es mas prioritario que el que se está ejecutando, en la siguiente interrupción de reloj. La rutina que se encarga de ver si se le acaba el quantum al proceso será la encargada de hacer el intercambio de procesos. TEMA 2: PROCESOS Planificadores El objetivo de los planificadores es maximizar la utilización de la CPU referido a procesos del usuario. Existen tres tipos de planificadores: • Planificadores a largo plazo (Large term): estos planificadores deciden que procesos entran en el sistema. Decide el grado de multiprogramación. Este planificador carga el ejecutable, asigna un PCB i las pilas de usuario y de sistema y por último asigna los recursos. • Planificadores a corto plazo (Short term): en la CPU solo se puede ejecutar un proceso y por lo tanto hay procesos en estado ready o bloqueados. Este planificador decide que proceso, entre uno de los que están listos y el que se esta ejecutando, se ejecutará en la CPU. • Planificador a medio plazo (Medium term): este planificador se utiliza en sistemas con memoria virtual. Un fallo de pagina se produce cuando la CPU da una dirección de memoria que no se encuentra en la memoria física. Si la memoria se divide en muchas paginas se puede producir Trashing, que consiste en que se ejecutan más fallos de pagina que procesos. La utilización de la CPU aumenta con el aumento de la multiprogramación pero puede llegar a un momento 6 en que la utilización de la CPU desciende por los fallos de paginas. El planificador de medio plazo entra en acción en este punto de Trashing. Este planificador decide que procesos se quitan de la CPU en este punto, lo que hace es sacar, normalmente, los procesos bloqueados que pasan a un área de swap (disco), aunque también puede sacar procesos en estado ready. Planificador a corto plazo Los criterios de este planificador son lo siguientes: · Justicia: se intenta aumentar la justicia, es decir, que todos los procesos tendrían que recibir algún tiempo de CPU. Lo contrario a este criterio es la inanición, haciendo que procesos nunca se ejecuten. · Eficiencia: este criterio tiene que ser lo más eficiente posible, por lo tanto se intenta maximizar. Este criterio mantiene la CPU el tiempo máximo con procesos de usuarios. · Productividad: (Throughput) este criterio intenta maximizar el número de trabajos procesados por unidad de tiempo. Tiene prioridad los procesos cortos frente a los largos. · Tiempos de retorno (Turnaround): este criterio intenta minimizar este tiempo, que consiste en el intervalo desde que mandamos que se ejecute un proceso hasta que termina. (Característico de los sistemas batch). · Tiempo de respuesta: criterio que minimiza el tiempo que transcurre desde que hay una petición E/S hasta la respuesta del dispositivo E/S. Se utiliza en sistemas interactivos. · Tiempo de espera: minimiza el tiempo de espera en la cola de ready. Políticas/Algoritmos de planificación Políticas Existen dos tipos de políticas: • Apropiativa: un proceso que ocupa la CPU, abandona la CPU de forma no voluntaria, ni termina ni se bloquea, ya que el sistema se la quita. Esta ploítica tiene dos variantes la apropiativa inmediata y la diferido (estas se basan si se introduce en la siguiente interrupción de reloj o se introduce inmediatamente). • No apropiativa: el proceso se bloquea o termina por su voluntad. Algoritmos FCFS: el primero en legar es el primero servido. Sigue una política no apropiativa. SI un proceso se bloquea, entra en la CPU otro proceso, pero si el bloqueado se desbloquea hay dos opciones: ponerlo en la cola de ready o ejecutarlo, pero al tener una política no apropiativa el proceso bloqueado pasará a ready pero como si hubiera acabado de llegar. SJF: se ejecuta el proceso más corto. Sigue una política no apropiativa. Este tiene una variante que consiste en la versión apropiativa. SJF (Shortetst Job First) es útil en sistemas batch. El propio usuario es el que da el tiempo que va a durar el proceso. En sistemas interactivos nos interesa saber cuanto va a durar la siguiente ráfaga de CPU, pero no se calcula el tiempo exacto sino que se hace una aproximación con la siguiente fórmula: 7 n+1= tn +(1−)n donde: n+1: es el tiempo de la siguiente ráfaga. n: es el tiempo de la ráfaga. tn: es el tiempo de la ráfaga anterior. : es un valor entre 0 i 1. Des de que le asignamos el tiempo hasta que finaliza este tiempo, el proceso se ejecutará y si el proceso necesita mas tiempo este seguirá ejecutándose en la CPU hasta que lo abandone por voluntad (finalice o se bloquee). SRT (Shortest remaining Time) és la versión apropiativa del algoritmo SJF y lo que hace es que cuando un proceso se desbloquea o se crea un proceso y el tiempo de ráfaga es menor que el tiempo de ráfaga del proceso que se esta ejecutando, se hace un cambio de contexto, el bloqueado se ejecuta y el que se estaba ejecutando pasa a ready. Cuando se desbloquea o entra un proceso nuevo, se calcula su tiempo próximo de ráfaga. Si el proceso que se está ejecutando le queda mas tiempo de ráfaga que nuestro tiempo de ráfaga calculado hacemos el cambio de contexto. Por prioridades: es un algoritmo con política apropiativa. Ejecuta el proceso con la mayor prioridad. Estos algoritmos pueden producir inanición y por lo tanto no cumplen el criterio de Justicia. Round−Robin: asignamos un determinado tiempo a los procesos (quantum). EL proceso ocupa la CPU el número de ciclos o interrupciones de reloj que se le indique en el quantum. Este algoritmo sigue una política apropiativa, ya que expulsamos al proceso cuando se acaba su quantum. Todos los procesos en ready tienen la misma prioridad. Colas multinivel Con estas colas se tienen diferentes colas de ready para procesos batch y para procesos interactivos, a las cuales se le asocia un porcentaje de uso de la CPU. Hay colas multinivel realimentadas, en estas colas los procesos entran en una cola donde se asigna un quantum, si no tiene suficiente quantum, el proceso pasa a otra cola con un quantum mayor. En estas colas multinivel realimentadas, se ejecutan los procesos de la cola que asigna menor quantum, en la última cola habría un algoritmo de planificación FCFS. Ejemplo: UNIX Diagrama de estados en UNIX. User runnig: ejecutando código de usuario. Kernel runnig: ejecutando código de sistema (llamadas al sistemas, servicio a las interrupciones, etc.). Preempted: se ha sacado un proceso de la CPU, porque se ha acabado el quantum o aparece un proceso mas 8 prioritario que el que se estaba ejecutando. El algoritmo de planificación que utiliza es el Round−Robi con colas multinivel autoalimentdas. Unix distingue entre procesos expulsados y procesos bloqueados. Si un proceso se ha expulsado va a la parte de arriba de una pila de prioridades, donde hay menos prioridad, y no podrá bajar del umbral, a partir del cual de guardan los procesos que se han bloqueado voluntariamente. La parte de arriba de dicha pila esta realimentada, es decir, cuando un proceso de esta parte pasa a ejecutarse, los que están por encima descienden. TEMA 3: MECANISMOS DE ENTRADA EN EL S.O. Introducción Aquí veremos como una llamada al sistema entra en el sistema operativo (entra a ejecutar código del S.O.). Hay tres métodos de acceso: • Interrupciones: creadas por el hardware de dispositivos externos y que se producen de forma asíncrona. Estas se miran siempre después de ejecutar una instrucciones máquina. • Excepciones: interrupciones creadas de forma involuntaria en el proceso (fallo de pagina, división por cero, overflow, etc.). Hay excepciones que destruyen el proceso y otras que después de ejecutar un código vuelve al proceso como el fallo de página. Una excepción se mira entre microinstrucciones (fetch del operando 1, fetch del operando 2, etc.). • Traps: mediante llamadas al sistema. Un trap es una interrupción software provocada por la instrucción de lenguaje máquina INT. Llamadas al sistema Las llamadas tendrán que salvar el contexto del proceso, cambiar a modo privilegiado (este cambio se hace por hardware y es el modo en el cual se puede ejecutar código del sistema, nosotros simularemos el cambio por software), restaurar al modo usuario y por último restaurar el contexto. Desde el proceso de usuario se puede hacer la llamada a la librería interna del S.O. pero deberíamos saber: • El trap de la llamada al sistema. • Como se hace el paso de parámetros. • Como se devuelve el resultado. Pero con las librerías del S.O. no hace falta que sepamos estos tres puntos. Se puede hacer una llamada pero gracias a las librerías del S.O. La librería del S.O. es quién ejecuta el trap correspondiente a la acción que pedimos. Hacemos que todas las llamadas al sistema generen la misma interrupción software, ya que no hay espacio en el vector de interrupción para guardar las direcciones de todas las rutinas de servicio (la interrupción que utilizaremos será la 64). En el vector de interrupciones tenemos guardada la rutina intermedia (Trap()) que comprobará que llamada se ha ejecutado y ha provocada la interrupción, una vez hecha esta comprobación se ejecutará la rutina interna adecuada a la llamada al sistema. Para comprobar que llamada se ha ejecutado se utilizan los registros de la CPU, en ONION utilizaremos el registro AX del proceso que ha hecho la llamada, ahora cada llamada moverá al AX un código que indicará que llamada ha ejecutado el trap. Paso de parámetros 9 Los parámetros se introducen de forma inversa a como se han escrito en la pila, una vez pasados los parámetros se pasa la dirección de retorno. Al producirse un trap se guardan los flags y la dirección de retorno a la librería del S.O., donde se produjo la interrupción. La rutina TRAP salvará el contexto del proceso que se estaba ejecutando, mira el contenido del registro AX y ejecuta la rutina interna del S.O. correspondiente al código que contenía el registro AX. El acceso a los parámetros se realiza en la pila pero no encima del la última dirección de retorno sino al principio de la pila. Para solucionar esto, se pueden tener dos pilas, una pila del usuario y la pila del sistema que tendrá copias de los parámetros del usuario, esta es la mejor forma. También se podría reestructurar la pila para que los parámetros estén después de la última dirección de retorno, pero, en este caso, también se debería reestructurar por segunda vez, para dejar la pila como estaba. Pero nosotros declararemos un parámetros que tendrá la longitud desde la última dirección de retorno hasta los parámetros, este parámetro será un struct llamado dummy. Struct dummy { int p[16]; } Resultados Cuando termina la ejecución de la llamada volvemos a la dirección de retorno 3, restauramos el contexto, y se vuelve al lugar donde se produjo la interrupción, y por último se sacan los parámetros de la pila. La llamada al sistema normalmente devuelven un entero, este resultado se devuelve en el registro AX, si es un carácter o un entero, o con el DX y AX si el resultado es un long o un far pointer. Todos devuelven en AX el resultado, pero en la rutina TRAP se hace un restaurar y por lo tanto el contenido de AX se podría perder con el contenido de la pila. Para devolver correctamente el resultado se debería: • Acceder a la posición de la pila correspondiente a AX e introducir el resultado dentro de la llamada al sistema. sci (dummy, par1, par2) {. *(run−>sssp+3)=res; . } • O en la rutina de TRAP hacer que devuelva un resultado que se almacena en la pila. TRAP() {. *(run−>sssp+3)=sci(); 10 . } • O utilizar el parámetro dummy donde se introducirá el resultado. Typedef struct { int bp, es, ... } dummy; dins de la rutina interna: dummy.ax=res. Niveles de interrupción Ejecutando código de las rutinas de servicio a las interrupciones, ejecutando el TRAP o el código de la llamada, las interrupciones están inhibidas y por tanto no nos llega ninguna interrupción. Ejecutando una llamada de sistema, haremos un desinhibir interrupciones y así nos llegan interrupciones hardware (desinhibir(0x200)). Al poder llegar interrupciones hardware el puntero sssp podría ser que nos apuntara a la segunda vez que se ha salvado el contexto producido por una interrupción y al hacer el restaurar del TRAP podría ser que no se restaurara bien ya que sssp apunta a otra posición (final de la segunda salvación del contexto). Por tanto tendremos dos niveles de interrupciones y por tanto podríamos tener dos apuntadores de pila sssp[0] que apunte a la primera vez que salvamos y sssp[1] que apunte a la segunda vez que salvamos. Para implementar esto necesitaremos otro campo que indica en que nivel nos encontramos: • Modo 0= interrupción cuando se ejecuta código de usuario. • Modo 1= interrupción cuando se ejecuta TRAP o una llamada al sistema. O ejecutando una RSI si venimos de interrumpir código de usuario. • Modo 2= ejecutando una RSI si venimos de interrumpir el código de sistema. Struct pcb{ . int *sssp[2]; int modo; } Salvar = run−>sssp[run−>modo]=salvar(); run −> modo++; Restaurar = run −> modo−−; Restaurar(run−>sssp[run−>modo]); 11 TEMA 4: SISTEMA DE FICHEROS Visión Estática Los soportes físicos están estructurados en bloques. Existen bloques libres y ocupados por el fichero. Gestión de bloques libres Existen diversas formas de gestión: Gestión con un mapa de bits donde 0 indica que el bloques está libre i 1 si está ocupado. Necesitamos un espacio fijo para esta gestión (para guardar el mapa de bits). También existe la lista de bloques libres. Esta lista puede estar encadenada donde los lugares libres se encadenan entre ellos. La lista encadenada tiene el defecto que si tenemos N bloques libres necesitaremos N lecturas para encadenar estos lugares libres. Otra versión de las listas es la que consiste en el agrupamiento (listas de agrupamiento). En esta versión se guarda en el primer bloque libre la lista de todos los bloques libres. Si en un bloque no cogen todos los punteros de las posiciones libres, se utilizaría otro bloque, pero donde el último elemento del primer bloque incidiría el bloque siguiente donde se sigue guardando la información (lugares libres). La última versión consiste en el recuento, donde se introduce en un bloque el número de bloques libres consecutivos que le siguen. Gestión del espacio ocupado Asignación contigua: obligamos a que todos los ficheros tengan todos loa bloques en forma contigua dentro del disco, esto permite acceso secuencial y acceso directo. Esta asignación es problemática por la asignación de espacio al fichero, ya que crea fragmentación externa (existe lugar libre pero no hay los suficientes bloques consecutivos para insertar un fichero). Existen 3 métodos para asignar espacio: • First Fit: primer espacio libre en el que coja el fichero. • Best Fit: buscamos el espacio libre que mas se ajusta a nuestro fichero. • Worst Fit: se coloca en el peor espacio, así en el espacio libre que queda pueda coger otro fichero. Asignación encadenada/enlazada: los bloques de los ficheros están encadenados (al igual que con las listas encadenadas de los espacios libres). Ahora no se produce fragmentación externa (se puede insertar en cualquier bloque) y por lo tanto no existen problemas de asignación de espacio. Pero ahora el acceso solo se podrá realizar de una forma: de forma secuencial ya que no sabemos donde está el bloque i−esimo de u fichero y se tendría que recorrer todos los bloques encadenados. Necesitamos por lo tanto un puntero para hacer la encadenación y estos se pueden perder si se cae el sistema. Un caso particular seria la FAT del DOS (File Allocation Table). Esta tabla tiene en la posición i la información del bloque i (la dirección del siguiente bloque del fichero, estado del bloque y si es el final del fichero −eof−). Esta FAT debería estar en memoria ya que si no esta deberíamos acceder siempre primero al 12 disco. Con la FAT aún tenemos el acceso secuencial ya que se recorrería la FAT en busca del bloque deseado. Por lo tanto el mayor problema es que la FAT no nos cogiera en memoria, y por lo tanto no tendría ninguna ventaja. La FAT es más grande conforme más grande sea la capacidad del disco. Si no nos cogiera la FAT podríamos dividir el disco en cluster y no en bloques, donde un cluster tendría 1, 2 o 4 bloques (siempre múltiple de dos), así cada entrada a la FAT representa un cluster (que será la unidad de asignación de espacio). La unidad de asignación de espacio es la cantidad de memoria que se le asigna a un fichero cuando necesita mas espacio y lo que se libera cuando el fichero no necesita ese espacio. La unidad de transferencia es la cantidad de información que se puede transportar de disco a RAM, esta cantidad es de 1 sector (1 bloque). Otra solución es el volume, que consiste en dividir el disco en particiones donde cada una tiene su propio sistema de ficheros, así se reduce el espacio de FAT ya que cada partición tiene su propia FAT. Para acceder a los ficheros necesitamos el fichero directorio, el cual tiene información sobre otros ficheros (nombre, número de bloques que ocupa en disco). Este directorio en asignación contigua contiene la posición del primer bloque del fichero. Y en asignación encadenada contiene la posición del primer bloque del fichero, pero para acceder, por ejemplo, al tercer bloque se debería pasar por la entrada que indique el directorio referente a ese fichero, después por la siguiente hasta de la FAT hasta que se llega al bloque deseado. Si deseamos el primer bloque, o los ficheros ocupan un solo bloque, entonces no hace falta pasar a la FAT ya que el directorio contiene el primer bloque de los ficheros. En cada partición se tiene un superbloque que indica donde se encuentra la FAT, donde se encuentra el directorio raíz, etc. Asignación indexada: ahora tenemos un bloque por fichero (bloque índice). En este bloque índice tendremos la dirección de los bloques que pertenecen al fichero. Para ficheros muy pequeños necesitaremos un bloque índice pero donde se utilizarán muy pocas entradas. Ahora cada fichero ocupará n+1 bloques. Si los índices de los bloques no cogen en el bloque índice se pueden tener diferentes bloques índices encadenados o bien tener diferentes niveles de bloques de índice. Con esta asignación se puede tener acceso directo pero siempre se ha de pasar por los diferentes bloques índices que tenga el archivo. Un caso particular son los Inodes de UNIX. Cada fichero tiene un inode que contiene la información de los ficheros, dentro de cada inode hay 10 apuntadores directos a bloques del fichero. Si con los 10 apuntadores no tenemos suficientes para representar el archivo, se crearía un apuntador indirecto (apuntador a un bloque de índice donde caven aproximadamente 256 apuntadores a bloques de datos), si todavía no tuviéramos suficientes apuntadores se crea un apuntador doblemente indirecto (este apunta a un bloque y las posiciones de este bloque apuntan a otros bloques de índices), y si todavía no tenemos suficientes se pueden crear apuntadores triplemente indirectos (donde cada puntero del bloque apuntado por cada puntero directo, apunta a otros bloques y las posiciones de estos a bloques de índices). Con inodes en el directorio solo tendremos el nombre del fichero y el número de inode. Así pues existe una tabla de inodes, indexada por el número de inode, donde está toda la información de los ficheros (nombre del dueño, protecciones, etc.). Ahora para acceder al directorio raíz, tenemos que pasar por el inode en lugar de por el superbloque. Cada inode está en el disco y ocupa 1 bloque de espacio. Por lo tanto para acceder al tercer bloque de un fichero 13 tendríamos que: • Obtener el inode raíz del disco. • Obtener el directorio raíz a partir del inode raíz. • Obtener el inode j correspondiente al directorio donde se encuentre el fichero. • Obtener el directorio correspondiente. • Obtener el inode k correspondiente al fichero deseado. • Acceder al tercer bloque. Compartición de ficheros Cualquier modificación se podrá ver por otras vías si el fichero está siendo compartido. Hay dos formas de compartir ficheros: Hard link: entrada de directorio que apunta al fichero el cual queremos compartir. Por lo tanto apuntan al mismo inode en el caso de UNIX. En MS−DOS no se puede compartir ficheros ya que al modificar un fichero se modifica la información de la entrada al directorio, pero si estuviera compartido otra entrada de directorio tendría la información antigua que ya no sería válida. Soft link: creamos un fichero que contendrá el camino de acceso al fichero compartido (pathname). El pathname puede ser absoluto, dando la dirección des del directorio raíz, o puede ser relativo si se da la dirección a partir del directorio de trabajo. En este caso se crea un inode diferente ya que se crea un nuevo fichero, aunque contenga solo la dirección de otro fichero. Directorios Los directorios son ficheros con una estructura interna especial donde guardan la dirección de todos los ficheros que contiene dicho directorio. Cada directorio tiene su propio inode. Sistema de ficheros (S.F.) Hay diferentes sistemas de ficheros: • 1 nivel: un directorio raíz, donde se encuentran todos los ficheros del ordenador. El directorio tendrá tantas entradas como ficheros diferentes tenga. Todos los nombres de los ficheros tienen que ser diferentes. • 2 niveles: un directorio raíz, donde se encuentran los directorios/usuarios que corresponden, uno a cada usuario. Pero todos los ficheros de cada directorio/usuario tienen que ser diferentes en cada directorio. • Jerárquico: (DOS) un directorio raíz a partir del cual se pueden colgar diferentes ficheros y directorios. Para cada fichero se tiene su pathname (absoluto si se especifica a partir del directorio raíz o relativo si se especifica a partir del directorio de trabajo). No se pueden compartir ficheros, ya que solo existe un camino para cada fichero. • En grafos: acíclicos (Unix) son los que no permiten bucles, es decir, que no permiten que un directorio llame a otro directorio superior a él. Permiten compartir ficheros y por lo tanto se puede acceder a un fichero por diferentes caminos. Cíclicos o general permiten los bucles dentro del sistema de ficheros donde un directorio puede apuntar a un directorio anterior a él 14 Ejemplos 1 nivel CP/m. Jerárquico DOS: no permite hard link. Entrada de directorio. Bytes 8 3 1 10 2 2 2 15