El problema del diseño Diseño de interfaces Implementación Rendimiento El mı́tico hombre–mes Bibliografı́a Tema 1. Diseño de Sistemas Operativos Juan Piernas Cánovas Departamento de Ingenierı́a y Tecnologı́a de Computadores Universidad de Murcia Juan Piernas Cánovas Tema 1. Diseño de Sistemas Operativos El problema del diseño Diseño de interfaces Implementación Rendimiento El mı́tico hombre–mes Bibliografı́a Índice 1 2 3 El problema del diseño Metas ¿Por qué es difı́cil diseñar sistemas operativos? Diseño de interfaces Principios para el diseño de interfaces Paradigmas o modelos La interfaz de llamadas al sistema Implementación Estructura del sistema operativo Mecanismos y polı́ticas Ortogonalidad Asignación de nombres Estructuras estáticas y dinámicas Diversas técnicas útiles Juan Piernas Cánovas Tema 1. Diseño de Sistemas Operativos El problema del diseño Diseño de interfaces Implementación Rendimiento El mı́tico hombre–mes Bibliografı́a Índice (continuación. . . ) 4 Rendimiento Equilibrio espacio-tiempo Uso de cachés Optimización del caso común 5 El mı́tico hombre–mes Juan Piernas Cánovas Tema 1. Diseño de Sistemas Operativos El problema del diseño Diseño de interfaces Implementación Rendimiento El mı́tico hombre–mes Bibliografı́a Metas ¿Por qué es difı́cil diseñar sistemas operativos? Metas ¡Es importante tener una idea clara de lo que se quiere! Principales objetivos que se suelen perseguir: Definir abstracciones: procesos, ficheros, hilos, . . . Proporcionar operaciones primitivas para manejar las abstracciones definidas Garantizar el aislamiento: los usuarios sólo puede ejecutar operaciones autorizadas con datos autorizados aislar fallos Administrar el hardware ¡No hay una solución única! Juan Piernas Cánovas Tema 1. Diseño de Sistemas Operativos El problema del diseño Diseño de interfaces Implementación Rendimiento El mı́tico hombre–mes Bibliografı́a Metas ¿Por qué es difı́cil diseñar sistemas operativos? Razones por las que es difı́cil diseñar un sistema operativo 1 2 3 4 5 6 7 8 Los SSOO son programas extremadamente grandes Los SSOO tienen que manejar concurrencia Los SSOO tienen que enfrentarse a usuarios hostiles en potencia Los SSOO deben permitir a los usuarios compartir información y recursos con otros usuarios seleccionados Los SSOO deben ser flexibles para poder adaptarse a posibles cambios futuros en el Hardware y en el Software Los SSOO deben ser generales para poder ser usados de muchas formas distintas Los SSOO deben ser (trans)portables Muchos SSOO deben ser compatibles con algún SO anterior Juan Piernas Cánovas Tema 1. Diseño de Sistemas Operativos El problema del diseño Diseño de interfaces Implementación Rendimiento El mı́tico hombre–mes Bibliografı́a Principios para el diseño de interfaces Paradigmas o modelos La interfaz de llamadas al sistema ¿Por dónde empezar a diseñar un sistema operativo? Por definir la interfaz (abstracciones y operaciones primitivas) a proporcionar a los programadores de sistemas Sin olvidar las interfaces internas Juan Piernas Cánovas Tema 1. Diseño de Sistemas Operativos El problema del diseño Diseño de interfaces Implementación Rendimiento El mı́tico hombre–mes Bibliografı́a Principios para el diseño de interfaces Paradigmas o modelos La interfaz de llamadas al sistema Principios para el diseño de interfaces Principio 1. Sencillez Las interfaces sencillas son más fáciles de entender e implementar Principio 2. Integridad La interfaz debe permitir hacer todo lo que los usuarios necesitan hacer Pero los mecanismos que soportan la interfaz deben ser pocos y sencillos (deben hacer una única cosa, pero deben hacerla bien) Principio 3. Eficiencia La implementación de los mecanismos debe ser eficiente Debe ser intuitivamente obvio para el programador cuánto cuesta una llamada al sistema Juan Piernas Cánovas Tema 1. Diseño de Sistemas Operativos El problema del diseño Diseño de interfaces Implementación Rendimiento El mı́tico hombre–mes Bibliografı́a Principios para el diseño de interfaces Paradigmas o modelos La interfaz de llamadas al sistema Paradigmas o modelos Para comenzar el diseño, un buen punto de partida es pensar en cómo van los clientes a ver el sistema Para los clientes es importante que todas las caracterı́sticas del sistema formen un conjunto consistente ⇒ coherencia arquitectónica Básicamente, hay dos tipos de clientes: Los usuarios, que interactúan con los programas de aplicación y la interfaz gráfica de usuario (GUI) Los programadores, que tratan principalmente con la interfaz de llamadas al sistema Atendiendo a un tipo u otro de cliente, se puede crear primero la GUI (diseño descendente) o primero la interfaz de llamadas al sistema (diseño ascendente) Juan Piernas Cánovas Tema 1. Diseño de Sistemas Operativos El problema del diseño Diseño de interfaces Implementación Rendimiento El mı́tico hombre–mes Bibliografı́a Principios para el diseño de interfaces Paradigmas o modelos La interfaz de llamadas al sistema Paradigmas de interfaz de usuario Se refieren a la forma en la que el usuario interactúa con el sistema operativo y con los programas de aplicación que usa Lo importante no es el paradigma escogido, sino que haya uno solo que unifique toda la interfaz de usuario También es importante que todos los programas de aplicación lo usen: los diseñadores del sistema deben proporcionar herramientas para ello Ejemplos: Windows: acciones de ratón, opciones del menú (((Archivo)), ((Edición)), . . . ) PalmOS: letra manuscrita y puntero Juan Piernas Cánovas Tema 1. Diseño de Sistemas Operativos El problema del diseño Diseño de interfaces Implementación Rendimiento El mı́tico hombre–mes Bibliografı́a Principios para el diseño de interfaces Paradigmas o modelos La interfaz de llamadas al sistema Paradigmas de ejecución Paradigma algorı́tmico La lógica básica se encuentra en el código El programa realiza llamadas al sistema para solicitar servicios Ejemplo: Unix Paradigma controlado por eventos Tras una preparación inicial, el programa se queda esperando a que el SO le notifique un evento Útil para programas interactivos Ejemplo: Windows Estos paradigmas no son excluyentes Juan Piernas Cánovas Tema 1. Diseño de Sistemas Operativos El problema del diseño Diseño de interfaces Implementación Rendimiento El mı́tico hombre–mes Bibliografı́a Principios para el diseño de interfaces Paradigmas o modelos La interfaz de llamadas al sistema Paradigmas de ejecución (continuación. . . ) main( ) { int ... ; main( ) { mess_t msg; init( ); do_something( ); read(...); do_something _else( ); write(...); keep_going( ); exit(0); init( ); while (get _message(&msg)) { switch (msg.type) { case 1: ... ; case 2: ... ; case 3: ... ; } } } } (a) (b) Juan Piernas Cánovas Tema 1. Diseño de Sistemas Operativos El problema del diseño Diseño de interfaces Implementación Rendimiento El mı́tico hombre–mes Bibliografı́a Principios para el diseño de interfaces Paradigmas o modelos La interfaz de llamadas al sistema Paradigmas de datos Todo SO debe tener un paradigma de datos que unifique la forma en la que la interfaz de llamadas al sistema representa a los recursos (lógicos o fı́sicos) definidos en el sistema Ejemplos: FORTRAN: todo es una cinta Unix: todo es un fichero Windows: todo es un objeto Web: todo es un documento Juan Piernas Cánovas Tema 1. Diseño de Sistemas Operativos El problema del diseño Diseño de interfaces Implementación Rendimiento El mı́tico hombre–mes Bibliografı́a Principios para el diseño de interfaces Paradigmas o modelos La interfaz de llamadas al sistema La interfaz de llamadas al sistema Un SO deberı́a ofrecer el menor número posible de llamadas al sistema. Para ello: Importante tener un paradigma de datos unificador Llamadas al sistema que manejen el caso general (¡sin perder la sencillez!) y procedimientos de biblioteca especı́ficos. Por ejemplo: execl, execlp, execle, execv y execvp se basan en la misma llamada al sistema: execve. Las llamadas al sistema no deben ocultar la potencia del hardware, sólo ocultar propiedades indeseables Habrá que decidir si se implementan llamadas al sistema orientadas a conexiones o no Habrá que decidir si las llamadas al sistema son públicas (Unix) o no (Windows) Juan Piernas Cánovas Tema 1. Diseño de Sistemas Operativos El problema del diseño Diseño de interfaces Implementación Rendimiento El mı́tico hombre–mes Bibliografı́a Estructura del sistema operativo Mecanismos y polı́ticas Ortogonalidad Asignación de nombres Estructuras estáticas y dinámicas Diversas técnicas útiles Estructura del sistema operativo Hay varias alternativas: por capas, exokernels, cliente-servidor, etc. (véase Tema 2) Partes del SO deben facilitar la construcción de otras partes del SO: Ocultar interrupciones Proporcionar mecanismos sencillos de concurrencia Posibilitar la construcción de estructuras de datos dinámicas Etc. ¡Prestar especial atención a los manejadores de dispositivo! Constituyen una parte muy importante del sistema global Son la principal fuente de inestabilidad Juan Piernas Cánovas Tema 1. Diseño de Sistemas Operativos El problema del diseño Diseño de interfaces Implementación Rendimiento El mı́tico hombre–mes Bibliografı́a Estructura del sistema operativo Mecanismos y polı́ticas Ortogonalidad Asignación de nombres Estructuras estáticas y dinámicas Diversas técnicas útiles Mecanismos y polı́ticas La separación entre mecanismos y polı́ticas ayuda a la coherencia arquitectónica y a la estructuración del sistema Los mecanismos se pueden implementar en el núcleo y las polı́ticas fuera o dentro del núcleo Ejemplo 1. Planificación de procesos Mecanismo: colas multinivel por prioridad donde el planificador siempre selecciona al proceso listo de mayor prioridad Polı́tica: planificación apropiativa o no, asignación de prioridades a procesos por usuario, etc. Ejemplo 2. Gestión de la memoria virtual Mecanismo: administración de la MMU, listas de páginas ocupadas y libres, transferencia de págs. entre memoria y disco Polı́tica: reemplazo de páginas global o local, algoritmo de reemplazo de páginas, etc. Juan Piernas Cánovas Tema 1. Diseño de Sistemas Operativos El problema del diseño Diseño de interfaces Implementación Rendimiento El mı́tico hombre–mes Bibliografı́a Estructura del sistema operativo Mecanismos y polı́ticas Ortogonalidad Asignación de nombres Estructuras estáticas y dinámicas Diversas técnicas útiles Ortogonalidad Ortogonalidad: capacidad de combinar conceptos distintos de forma independiente. Es consecuencia directa de los principios de sencillez e integridad. Ejemplo 1. Procesos e hilos: separan la unidad de asignación de recursos (proceso) de la unidad de planificación y ejecución (hilo), permitiendo tener varios hilos dentro de un mismo proc. Ejemplo 2. Creación de procesos en Unix: se diferencia entre crear el proceso en sı́ (fork) y ejecutar un nuevo programa (exec), lo que permite heredar y manipular descs. de fichero En general, tener un número reducido de elementos ortogonales que pueden combinarse de muchas maneras da pie a un sistema pequeño, sencillo y elegante. Juan Piernas Cánovas Tema 1. Diseño de Sistemas Operativos El problema del diseño Diseño de interfaces Implementación Rendimiento El mı́tico hombre–mes Bibliografı́a Estructura del sistema operativo Mecanismos y polı́ticas Ortogonalidad Asignación de nombres Estructuras estáticas y dinámicas Diversas técnicas útiles Asignación de nombres Casi todas las estructuras de datos duraderas que utiliza un SO tienen algún tipo de nombre o identificador (nombre de dispositivo, de fichero, identificador de proceso, etc.) Es común que los nombres se asignen a dos niveles: Externo: cadenas de caracteres (estructuradas o no) que usan los usuarios Interno: identificación usada internamente por el SO Debe existir algún mecanismo que permita asociar unos nombres con otros. Ejemplo: los directorios (enlazan nombres de ficheros con nodos-i) Un buen diseño debe estudiar con detenimiento cuántos espacios de nombres van a necesitarse, qué sintaxis tendrán los nombres, cómo van a distinguirse, etc. Juan Piernas Cánovas Tema 1. Diseño de Sistemas Operativos El problema del diseño Diseño de interfaces Implementación Rendimiento El mı́tico hombre–mes Bibliografı́a Estructura del sistema operativo Mecanismos y polı́ticas Ortogonalidad Asignación de nombres Estructuras estáticas y dinámicas Diversas técnicas útiles Estructuras estáticas y dinámicas ¿Las estructuras de datos del SO deben ser estáticas o dinámicas? Las estáticas son más comprensibles, más fáciles de programar y de uso más rápido Las dinámicas son más flexibles y permiten adaptarse a la cantidad de recursos disponibles. Problema: necesitan un gestor de memoria dentro del propio SO Según el caso, puede ser más adecuado un tipo u otro. Ejemplo: Pila de un proceso en el espacio de usuario: estructura dinámica Pila de un proceso en el espacio de núcleo: estructura estática También son posibles estructuras pseudo-dinámicas Juan Piernas Cánovas Tema 1. Diseño de Sistemas Operativos El problema del diseño Diseño de interfaces Implementación Rendimiento El mı́tico hombre–mes Bibliografı́a Estructura del sistema operativo Mecanismos y polı́ticas Ortogonalidad Asignación de nombres Estructuras estáticas y dinámicas Diversas técnicas útiles Diversas técnicas útiles para la implementación Ocultación del hardware Ocultar las interrupciones, convirtiéndolas en operaciones de sincronización entre hilos (unlock en un mutex, envı́o de un mensaje, etc.) Ocultar las peculiaridades de la arquitectura hardware si se desea facilitar la transportabilidad del SO Los fuentes del SO deben ser únicos La compilación debe ser condicional La parte de código dependiente debe ser lo más pequeña posible Ejemplo 1: HAL (Hardware Abstraction Layer) de Windows Ejemplo 2: directorios arch e include/asm-* en Linux Juan Piernas Cánovas Tema 1. Diseño de Sistemas Operativos El problema del diseño Diseño de interfaces Implementación Rendimiento El mı́tico hombre–mes Bibliografı́a Estructura del sistema operativo Mecanismos y polı́ticas Ortogonalidad Asignación de nombres Estructuras estáticas y dinámicas Diversas técnicas útiles Diversas técnicas útiles para la implementación Indirección ¡Flexibilidad! Ejemplo: entrada por teclado. Al pulsar una tecla se obtiene un valor que no se corresponde con su código ASCII ⇒ Posibilidad de utilizar configuraciones distintas de teclados Ejemplo: salida por pantalla. El código ASCII es el ı́ndice de una tabla con el patrón de bits del carácter a representar ⇒ Posibilidad de configurar el tipo de letra de pantalla Ejemplo: nombres de dispositivo. En Linux, los nombres de los ficheros especiales son independientes de los números mayor y menor de dispositivo ⇒ Posibilidad de cambiar el nombre a un dispositivo David Wheeler: “All problems in computer science can be solved by another level of indirection.” Juan Piernas Cánovas Tema 1. Diseño de Sistemas Operativos El problema del diseño Diseño de interfaces Implementación Rendimiento El mı́tico hombre–mes Bibliografı́a Estructura del sistema operativo Mecanismos y polı́ticas Ortogonalidad Asignación de nombres Estructuras estáticas y dinámicas Diversas técnicas útiles Diversas técnicas útiles para la implementación Reentrabilidad Se refiere a la capacidad de un fragmento de código dado para ejecutarse dos o más veces de forma simultánea La ejecución simultánea se puede dar en varios casos: En un multiprocesador dos o más procesadores pueden estar ejecutando la misma porción de código En un monoprocesador puede llegar una interrupción que ejecute la misma porción de código que se estaba ejecutando Lo mejor, incluso en un monoprocesador, es que: la mayor parte del SO sea reentrante (para que no se pierdan interrupciones), que las secciones crı́ticas se protejan que las interrupciones se inhabiliten cuando no se puedan tolerar Juan Piernas Cánovas Tema 1. Diseño de Sistemas Operativos El problema del diseño Diseño de interfaces Implementación Rendimiento El mı́tico hombre–mes Bibliografı́a Estructura del sistema operativo Mecanismos y polı́ticas Ortogonalidad Asignación de nombres Estructuras estáticas y dinámicas Diversas técnicas útiles Diversas técnicas útiles para la implementación Fuerza bruta ¡Optimizar cuando realmente merezca la pena! Ejemplo 1. ¿Merece la pena tener ordenada una tabla de 100 elementos que cambia continuamente para que las búsquedas sean más rápidas? Ejemplo 2. ¿Merece la pena optimizar una llamada al sistema que tarda 10 ms para que tarde 1 ms si se utiliza una vez cada 10 segundos? ¡Optimizar las funciones y estructuras de datos de la ruta crı́tica! Ejemplo: cambio de contexto Juan Piernas Cánovas Tema 1. Diseño de Sistemas Operativos El problema del diseño Diseño de interfaces Implementación Rendimiento El mı́tico hombre–mes Bibliografı́a Estructura del sistema operativo Mecanismos y polı́ticas Ortogonalidad Asignación de nombres Estructuras estáticas y dinámicas Diversas técnicas útiles Diversas técnicas útiles para la implementación static inline void check for tasks(int cpu) { struct task struct *p; write lock irq(&tasklist lock); for each process(p) { if (task cpu(p) == cpu && (p->utime != 0 || p->stime != 0)) printk(KERN WARNING ”Task %s (pid = %d) is on cpu %d\ (state = %ld, flags = %lx) \n”, p->comm, p->pid, cpu, p->state, p->flags); } write unlock irq(&tasklist lock); } Juan Piernas Cánovas Tema 1. Diseño de Sistemas Operativos El problema del diseño Diseño de interfaces Implementación Rendimiento El mı́tico hombre–mes Bibliografı́a Estructura del sistema operativo Mecanismos y polı́ticas Ortogonalidad Asignación de nombres Estructuras estáticas y dinámicas Diversas técnicas útiles Diversas técnicas útiles para la implementación Verificar primero si hay errores Una llamada al sistema puede fallar por muchas razones: ficheros que no existen o que pertenecen a otra persona, destinatario de un mensaje que no existe, etc. También hay llamadas al sistema que necesitan obtener recursos Lo mejor es comprobar primero si en verdad puede efectuarse la llamada al sistema ⇒ Situar todas las pruebas al principio del procedimiento que ejecuta la llamada al sistema Tras asegurarse el éxito, hay que obtener los recursos necesarios (¡cuidado con los posibles interbloqueos!) Acelera la ejecución en el caso de que la llamada no se pueda realizar Juan Piernas Cánovas Tema 1. Diseño de Sistemas Operativos El problema del diseño Diseño de interfaces Implementación Rendimiento El mı́tico hombre–mes Bibliografı́a Estructura del sistema operativo Mecanismos y polı́ticas Ortogonalidad Asignación de nombres Estructuras estáticas y dinámicas Diversas técnicas útiles Diversas técnicas útiles para la implementación static int ext2 unlink(struct inode * dir, struct dentry *dentry) { struct inode * inode = dentry->d inode; struct ext2 dir entry 2 * de; struct page * page; int err = -ENOENT; de = ext2 find entry (dir, dentry, &page); if (!de) goto out; err = ext2 delete entry (de, page); if (err) goto out; inode->i ctime = dir->i ctime; ext2 dec count(inode); err = 0; out: return err; } Juan Piernas Cánovas Tema 1. Diseño de Sistemas Operativos El problema del diseño Diseño de interfaces Implementación Rendimiento El mı́tico hombre–mes Bibliografı́a Equilibrio espacio-tiempo Uso de cachés Optimización del caso común Rendimiento ¿Qué es mejor, un SO rápido y poco fiable o uno lento pero fiable? Las optimizaciones complejas suelen llevar a errores ⇒ Optimizar sólo si realmente es necesario ¿Qué es mejor, un SO sencillo y rápido o uno complejo y lento? ¡Lo pequeño es bello! ⇒ Antes de añadir una funcionalidad nueva compruebe que realmente merece la pena En cualquier caso, antes de optimizar, tenga en cuenta que lo bastante bueno es generalmente suficientemente bueno (good enough is good enough) Otra consideración importante es el lenguaje de programación a utilizar Juan Piernas Cánovas Tema 1. Diseño de Sistemas Operativos El problema del diseño Diseño de interfaces Implementación Rendimiento El mı́tico hombre–mes Bibliografı́a Equilibrio espacio-tiempo Uso de cachés Optimización del caso común Equilibrio espacio-tiempo #define BYTE _SIZE 8 int bit_count(int byte) { int i, count = 0; for (i = 0; i < BYTE_SIZE; i++) if ((byte >> i) & 1) count++; return(count); } (a) /* A byte contains 8 bits */ /* Count the bits in a byte. */ /* loop over the bits in a byte */ /* if this bit is a 1, add to count */ /* return sum */ /*Macro to add up the bits in a byte and return the sum. */ #define bit_count(b) (b&1) + ((b>>1)&1) + ((b>>2)&1) + ((b>>3)&1) + \ ((b>>4)&1) + ((b>>5)&1) + ((b>>6)&1) + ((b>>7)&1) (b) /*Macro to look up the bit count in a table. */ char bits[256] = {0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, ...}; #define bit_count(b) (int) bits[b] (c) Juan Piernas Cánovas Tema 1. Diseño de Sistemas Operativos El problema del diseño Diseño de interfaces Implementación Rendimiento El mı́tico hombre–mes Bibliografı́a Equilibrio espacio-tiempo Uso de cachés Optimización del caso común Uso de cachés Se aplican en situaciones en las que es probable que el mismo resultado se necesite varias veces Especialmente útiles para dispositivos de E/S Ejemplo 1. Caché de bloques o caché de disco Ejemplo 2. Caché de entradas de directorio Ejemplo 3. Caché de páginas Juan Piernas Cánovas Tema 1. Diseño de Sistemas Operativos El problema del diseño Diseño de interfaces Implementación Rendimiento El mı́tico hombre–mes Bibliografı́a Equilibrio espacio-tiempo Uso de cachés Optimización del caso común Optimización del caso común En muchos casos es recomendable distinguir entre el caso más común y el peor caso posible: Es importante que el caso común sea rápido El peor caso, si no se presenta a menudo, sólo tiene que manejarse correctamente Ejemplo: llamada EnterCriticalSection del API Win32. Algoritmo: Comprobar y cerrar mutex en espacio de usuario En caso de éxito ⇒ Entrar a la sección crı́tica (no ha hecho falta una llamada al sistema) En caso de fracaso ⇒ Ejecutar una llamada al sistema que bloquee al proceso en un semáforo hasta que pueda entrar a la sección crı́tica Si lo normal es que la primera comprobación tenga éxito, nos habremos ahorrado entrar al núcleo del SO Juan Piernas Cánovas Tema 1. Diseño de Sistemas Operativos El problema del diseño Diseño de interfaces Implementación Rendimiento El mı́tico hombre–mes Bibliografı́a El mı́tico hombre–mes Según Brooks: un programador sólo puede producir 1000 lı́neas de código depurado al año en un proyecto grande Los proyectos grandes, con cientos de programadores 6= proyectos pequeños ⇒ Resultados no extrapolables En proyectos grandes, el trabajo se compone de: 1/3 planificación 1/6 codificación Juan Piernas Cánovas 1/4 prueba de módulos 1/4 prueba del sistema Tema 1. Diseño de Sistemas Operativos El problema del diseño Diseño de interfaces Implementación Rendimiento El mı́tico hombre–mes Bibliografı́a El mı́tico hombre–mes (continuación. . . ) Para Brooks, no existe una unidad hombre–mes: si 15 personas tardan dos años en un proyecto, 360 no van a tardar 1 mes. Razones: El trabajo no puede dividirse totalmente en actividades paralelas El trabajo debe dividirse en muchos módulos y las interacciones entre ellos crecen con con el cuadrado del no de los mismos La depuración es secuencial Ley de Brooks: ((la adición de personal a un proyecto de software atrasado lo atrasa más aún)) Juan Piernas Cánovas Tema 1. Diseño de Sistemas Operativos El problema del diseño Diseño de interfaces Implementación Rendimiento El mı́tico hombre–mes Bibliografı́a Bibliografı́a Andrew Tanenbaum. ((Sistemas Operativos Modernos)), 2a edición, capı́tulo 12. Prentice Hall, 2003 Gary Nutt. ((Sistemas Operativos)), 3a edición, capı́tulo 19. Addison Wesley, 2004 Juan Piernas Cánovas Tema 1. Diseño de Sistemas Operativos