Download Trabajo Práctico Nº2 - Método Diferencial
Transcript
“Sistema Operativo Routix” Sistema Operativo Routix - Página 2 of 17 INDICE 1. Introducción ................................................................................................... 4 2. Objetivo .......................................................................................................... 4 3. Software Utilizado .......................................................................................... 4 4. De Donde Obtener Routix ............................................................................... 5 5. Detalles Técnicos ............................................................................................ 5 A. Protección, Segmentación y Paginación ..................................................... 5 B. Segmentación ............................................................................................. 5 C. Paginación .................................................................................................. 6 D. Memory Manager ........................................................................................ 6 E. Multitarea ................................................................................................... 7 F. Llamadas al sistema ..................................................................................10 G. Controlador de Floppy y DMA ....................................................................11 H. Controlador de FAT12 ...............................................................................12 I. Llamadas para el manejo de archivos .......................................................13 J. Timers .......................................................................................................13 K. Scheduler ..................................................................................................15 L. Aplicaciones ..............................................................................................16 M. Funciones implementadas.........................................................................16 6. Roadmap ......................................................................................................17 Sistema Operativo Routix - Página 3 of 17 1. INTRODUCCIÓN Es un sistema operativo para plataformas IA32 el cuál posee como objeto ser estable, adaptable y escalable para poder ser utilizado como base operativa multitarea en la búsqueda de soluciones que requieran confiabilidad y eficiencia. 2. OBJETIVO Este proyecto comenzó como un desarrollo orientado a un Router de capa 3. A medida que fuimos desarrollando parte de él, asimilando realmente como programar un procesador Intel 80386 (con todas sus prestaciones) y comprendiendo como funcionan los sistemas operativos actuales, nos dimos cuenta que requeríamos una base sólida sobre la cuál en un futuro podríamos implementar cualquier tipo de sistema operativo, ya sea orientado a un Router como a un Firewall, servidor o workstation. Es decir dejamos un poco de lado el objeto final del proyecto y nos centramos en desarrollar un kernel sólido y abstracto, sobre el cuál podamos escalar en buenas proporciones (queríamos evitar por todos los medios, desarrollar un código que en poco tiempo se viera saturado de errores, que no pueda crecer, y que sea difícil de comprender por terceros). 3. SOFTWARE UTILIZADO 1. La mayor parte del proyecto fue desarrollada usando lenguaje C (más específicamente el compilador GCC). Nuestra elección radico en que requeríamos de un lenguaje lo suficientemente sólido y flexible como para poder vincularnos con el hardware, pero sin perder una metodología de programación la cuál simplifique el desarrollo, permite construir estructuras fácilmente modificables y expandibles, con código no solo reeditable por nosotros, si no por cualquier persona que quiera sumarse al proyecto. 2. Otra parte del código fue desarrollada en lenguaje ensamblado (usando NASM e Inline Assembly), más específicamente todo aquel código de más bajo nivel, junto con algunos fragmentos que requerían optimizaciones manuales. Por ejemplo, debido a la gran cantidad de veces que se ejecuta el scheduler (o planificador) por segundo, era fundamental conseguir reducir su tiempo de ejecución tanto como fuera posible. 3. Para Linkear el código recurrimos al LD de GNU. 4. También utilizamos el Bochs, el cuál nos ahorro muchísimo tiempo en un comienzo, no solo por evitarnos resetear miles de veces nuestras computadoras, si no porque brinda una excelente herramienta de depuración. Cabe aclarar que todo el software utilizado para el desarrollo de Routix es de tipo Open source. Las direcciones para obtenerlos son: Sistema Operativo Routix - Página 4 of 17 Software Dirección de Internet NASM - Netwide Assembler GCC DJGPP LD BOCHS http://nasm.sf.net http://gcc.gnu.org http://www.delorie.com/djgpp http://www.gnu.org/software/binutils http://bochs.sourceforge.net 4. DE DONDE OBTENER ROUTIX Por el momento tenemos hosteado a Routix en SourceForge, ya que ellos nos dan espacio para hostear un sitio web y un servidor CVS (Concurrent Versión System) para simplificar el desarrollo en equipo de un proyecto de esta magnitud. http://routix.sourceforge.net 5. DETALLES TÉCNICOS A. PROTECCIÓN, SEGMENTACIÓN Y PAGINACIÓN Es sabido que no todo el código posee la misma prioridad o importancia dentro de un sistema. Existen partes vitales, las cuales en caso de fallar, colapsarían todo el conjunto. Por otro lado, muchas partes del código son prescindibles, es decir, que si llegaran a fallar o a faltar, el sistema completo o al menos parte de él podría seguir funcionando sin grandes inconvenientes. Podría citar como ejemplo un device driver de una placa Token Ring, el cuál en un router podría momentáneamente fallar, sin embargo la conmutación y enrutamiento de paquetes en las demás interfaces seguiría funcionando. Es por esto que decidimos utilizar dos niveles de protección (los cuales coinciden con el Anillo 0 y 3 de la unidad de segmentación y con los privilegios User y admin de la de paginación). B. SEGMENTACIÓN A medida que fuimos aprendiendo como utilizar objetos realizados en C en modo protegido fuimos descubriendo que esta técnica de protección era útil en algunos casos, pero nos imposibilitada desarrollar el código libremente. ¿Por qué pasa esto?. Los compiladores de C, utilizan un modelo de segmentación Flat, el cual equivale a decir que los segmentos de Código, Datos y Stack se encuentran superpuestos en sus comienzos. Esto a primera vista parece romper los esquemas y técnicas creadas por Intel para proteger los sistemas multitarea sin embargo, como es lógico, posee una explicación. Sistema Operativo Routix - Página 5 of 17 El lenguaje C fue concebido mucho antes que Intel creara su primer microprocesador, por eso la mayoría de las cualidades de este lenguaje son genéricas para casi todas las arquitecturas de microprocesadores que existen. Teniendo todo esto en cuenta nos limitamos a utilizar sólo dos niveles de privilegio (Kernel y User), superponiendo los segmentos de código y datos con el fin de poder programar libremente. C. PAGINACIÓN Esta técnica es la que nos permite realmente evitar que un proceso corriendo en modo User pueda inferir con código o datos vitales del sistema, los cuales se encuentran en modo Kernel. Algo muy interesante que nos permitió la paginación y que realmente nos facilitó mucho la tarea de correr paralelamente aplicaciones fue la memoria virtual. Como es bien conocido por todos la unidad de paginación permite que una aplicación “crea” que está corriendo en una dirección dada (o lógica) cuando en realidad su posición física es diferente. Esta excelente capacidad nos permitió dos cosas: Reacomodar el código de Kernel, sus Datos y Su Pila, a direcciones virtuales, las cuales nos permitirán en un futuro expandir con facilidad el tamaño del kernel. Correr varias aplicaciones de usuario simultáneamente. Cuando uno linkea un archivo objeto, el linker puede traducir todas esas direcciones relativas al comienzo del código a direcciones específicas dictadas por el programador. Por ejemplo nuestras aplicaciones fueron linkeadas para correr en la dirección 0x80000000 (equivalente a 2GB). ¿ Porqué tuvimos que hacer esto ? Porque no teníamos forma de saber que posiciones están libres, asi que sencillamente buscamos cualquier espacio de memoria capaz de contener el proceso a correr y lo mapeamos virtualmente en la dirección deseada ( 0x80000000 en este caso). D. MEMORY MANAGER El Memory Manager tiene por función primordial la administración de memoria, es decir, deberá saber que bloques (páginas en nuestro caso) están libres y cuáles fueron entregados a algún proceso o aplicación. El Memory Manager comienza por su inicialización contando la memoria disponible, y calcula cuantas páginas podrá brindar a los procesos que las soliciten. Para administrar la memoria utilizamos una estructura de tipo Pila, es decir, en ella se encuentran “apiladas” las direcciones de todas las páginas disponibles. Cada vez que un proceso pide una página, el MM simplemente la “popea” del stack, mientras que cuando un proceso la libera, la “pushea” nuevamente dentro de él. Las funciones que se encargan de esto son: Sistema Operativo Routix - Página 6 of 17 int kmalloc_page_start (addr_t *inicio, addr_t *fin); addr_t kmalloc_page(); int kfree_page(addr_t direccion); El Memory Manager también implementa las clásicas funciones de alocación dinámica de bloques de tamaño variable: malloc y free. Hay muchas tareas que debe implementar a nivel paginación. Una de las más importantes es kmapmem la cuál se encarga de mapear en el directorio y tabla de página correspondiente una dirección física en una lógica. Su prototipo es: int kmapmem ( addr_t fisica, addr_t logica, addr_t directorio, word atributo Existen bastantes funciones auxiliares dentro del memory manager, para simplificar y estructurar el código que lo conforma. E. MULTITAREA Algo fundamental e infaltable en cualquier sistema operativo es la capacidad de simular la ejecución de varias tareas en forma simultanea, brindando una performance más alta y un mayor aprovechamiento de los recursos. En un comienzo utilizamos todos los recursos que nos brindaba la arquitectura IA32, es decir, usábamos una estructura TSS por cada proceso que fuera a correr en el CPU. ¿ Que inconvenientes tuvo esto ? Existen algunos casos, donde necesito frenar la ejecución del proceso actual y realizar ciertas comprobaciones, pero sin llegar a realizar un cambio de contexto (el cuál significa transferir 104 bytes de la memoria al microprocesador y viceversa). Por ejemplo, cada vez que se cumple un TIC de reloj (impuesto por el Timer Tick) el scheduler debe verificar si la tarea actual excedió su tiempo de ejecución (por lo que deberá dar el control a otra tarea, luego de realizar todas las acciones administrativas correspondientes) en caso negativo, debe permitir que esta tarea siga ejecutándose. Si en vez de realizar una conmutación de contexto vía TSS (es decir, mover 208 bytes) sólo “pusheamos” y “popeamos” algunos registros de propósito general, estaremos ahorrando mucho tiempo, más si se tiene en cuenta que se produce un Tick cada 10 mili segundos (es decir, 100 ticks por segundo). Todas las tareas, ya sean en modo Usuario o modo Kernel, utilizan el mismo TSS el cual es modificado por una rutina para ese fin. A este tipo de multitarea se la llama “Multitarea por software”. Cada vez que llega una interrupción de Timer Tick, el Kernel produce un Stack Switching (en caso que se pase de nivel de privilegio 3 a nivel 0), para poder guardar en el Stack de modo Kernel de la tarea, los valores de los registros EFLAGS, EIP y CS. Esta acción es totalmente automática y es la que permite asegurar que una tarea nunca pueda corromper la información salvada en una pila al ocurrirse una interrupción o una excepción. Como señalamos anteriormente, cada 10 milisegundos aparece un pulso de timertick, originado por el timer 0 del 8254, esto hace que el Controlador Programable de interrupciones genera una interrupción externa que es atendida por el Kernel. Ocurre entonces la siguiente secuencia de eventos: Sistema Operativo Routix - Página 7 of 17 Con la llegada de la interrupción de timertick, se produce un cambio de nivel (de modo Usuario a modo Kernel), este cambio es realizado por el microprocesador quien realiza un stack switching permitiendo que el kernel trabaje sin riesgos de desborde del array de usuario. Con lo que en nuestro stack de modo kernel tenemos la dirección y segmento de retorno (registros EIP y CS), el estado de los flags (EFLAGS) y la dirección y segmento del stack de modo usuario. En este momento guardamos en la pila (recordemos que ya estamos en modo kernel) los registros que cambian entre tareas (el código siguiente pertenece al archivo task.asm): _switch_to_kernel_mode: ;(punto de entrada de la IRQ0) .... push push push push push push push push push push push ebp esi edi edx ecx ebx eax ds es fs gs ..... Resguardados estos registros, debemos comenzar a utilizar un stack de modo kernel pero de mayor tamaño e independiente de la tarea, para esto primero guardamos el puntero de pila y registro de paginación en las variables esp0 y cr3 de la estructura task_struct_t propia de la tareas: ; Guardamos el valor del esp de modo kernel de la tarea mov ebx, dword [ _actual ] mov dword [ ebx ], esp ; Guardamos en la task_struct el valor del CR3 mov ebx, dword [ _actual ] add ebx, 4 ;desplazar 4 bytes la direccion ya que ;CR3 es el 2do elemento de la estructura mov eax, cr3 mov dword [ ebx ], eax Y cargamos un stack de modo kernel de mayor tamaño: ; Recuperamos el stack del kernel, así trabajamos ; tranquilos y sín límites mov eax, KERNEL_STACK_TOP mov esp, eax Por fin, entonces pasamos el control al kernel ; Pasamos el control al kernel call _timertick_handler Sistema Operativo Routix - Página 8 of 17 El handler de timertick es una función que actualiza los ticks transcurridos, actualiza el reloj del sistema, actualiza y cheque que no se hayan vencidos los timers, actualiza la estadísticas de uso del proceso en curso (ticks restantes y ticks totales utilizados), y en caso que haya terminado el tiempo de ejecución del proceso se llama al scheduler quien selecciona la próxima tarea a ejecutar. Todo esto lo veremos en mayor detalle en los punto “Timers” y “Scheduler” El scheduler puede seleccionar otra tarea para ejecutar, con lo que cambiará la dirección apuntada por la variable actual a la estructura de la nueva tarea. Una vez que retornamos de la función timertick_handler debemos recuperar el stack de modo kernel de la tarea: _entrada_de_inicio: ; Volvimos, ahora recuperamos el stack de modo kernel de la tarea ; que puede ser diferente a la anterior, todo depende de si se venció ; el tiempo de permanencia y/o el scheduler la switcheo (por medio ; del puntero _actual) mov ebx, dword [ _actual ] mov eax, dword [ ebx ] mov esp, eax Recuperamos su CR3 ; También acá recuperamos el CR3 mov ebx, dword [ _actual ] add ebx, 4 mov eax, dword [ ebx ] mov cr3, eax Si se a producido un cambio de tarea, el valor de puntero de pila de modo kernel (SS0) del TSS debe cambiarse, para poder acceder en la próxima interrupción, para ello: ; ; ; ; ; ; ; Pisamos el valor de esp0 en el TSS para que la próxima interrupción pueda producirse el cambio de modo ( usuario -> kernel ) de manera correcta calculamos el top del esp0, recordando que estamos usando una página para el descriptor del proceso y el stack, el cual encuentra su top al final del mismo, o sea: top_esp0 = ( esp0 & 0xffffff00 ) + 4096 mov eax, esp and eax, 0xfffff000 add eax, 0x1000 mov dword [ _tss + OFFSET_ESP0 ], eax Para entender un poco el código precedente debe tenerse en cuenta que cada tarea aloja en una misma página de 4Kb el contenido de su estructura (task_struct_t) al principio y su stack de modo kernel desde el final de dicha página. Finalmente recuperamos todos los registros de este stack y retornamos: Sistema Operativo Routix - Página 9 of 17 ; Recuperamos el contenido de todos los registros pop gs pop fs pop es pop ds pop eax pop ebx pop ecx pop edx pop edi pop esi pop ebp iret Cuando el microprocesador ejecuta la instrucción iret pasa de modo Kernel a modo Usuario y recupera los registros de contexto de ejecución CS, EIP y EFLAGS, y la tarea vuelve a su ejecución habitual. F. LLAMADAS AL SISTEMA Tienen por objeto que los procesos que corren en espacio de Usuario puedan tener acceso a diferentes funciones del kernel, acceso a periféricos, comunicación con otros procesos, etc. Las llamadas al sistema las implementamos vía interrupciones, específicamente en la int 0x50. Cuando alguna función de librería o el código usuario ejecuta la instrucción “int 0x50” inmediatamente se realiza un Stack Switching y la correspondiente toma de los parámetros. Estos últimos son recibidos mediante los registros de propósito general (eax, ebx, ecx, edx, esi y edi). Ejemplo de llamada al sistema FORK: int fork(void) { __asm__ __volatile__ ("movl %0, %%eax" : : "g" (SYS_PROCESS | "eax"); __asm__ __volatile__ ("int $0x50"); } SYS_FORK) : A diferencia de Linux que recibe las llamadas y las ejecuta apoyandose en un vector de punteros, nosotros implementamos varios vectores de punteros, separando las llamadas y clasificándolas en grupos). Si bien nuestro sistema es levemente mas lento que el utilizado por linux, permite mayor claridad al mirar el código fuente, con una consecuente mayor facilidad para interpretarlo. Qué código es el que corre al ejecutarse “int 0x50” _intSysCall: push eax shr eax, 16 ;Verificar si el numero de grupo existe cmp eax, MAX_SYSCALLS_GROUPS ja error_syscall_no pop eax Pusheo los registros donde recibo los parámetros push ebp push esi push edi push edx Sistema Operativo Routix - Página 10 of 17 push ecx push ebx Usar como directorio de paginas el CR3 del Kernel mov ebx, [ _KERNEL_PDT ] mov cr3, ebx mov ebx, eax shr ebx, 16 ;Pongo en ebx el numero de grupo de llamadas jmp [_syscall_group_vector + ebx * 4] ; (El vector de grupos de llamadas a funciones se encuentra definido en syscalls.c ) Las llamadas implementadas al momento son: Funciones de grupo PROCESS #define SYS_EXEC 0 #define SYS_VOID 1 #define SYS_FORK 2 Funciones de grupo CONSOLE #define SYS_PRINT #define SYS_GETS 1 #define SYS_CLRSCR 0 2 Funciones de grupo TIMER #define #define #define #define SYS_SLEEP SYS_PROC_DUMP SYS_KILL 2 SYS_USLEEP 0 1 3 G. CONTROLADOR DE FLOPPY Y DMA Este driver lo implementamos para poder comprobar que tan estable era nuestro kernel ejecutando tareas dinámicas (es decir, cargadas desde algún medio, luego de haberse establecido las funcionalidades de sistema operativo). Realmente nos tomó un poco más del tiempo del que esperábamos, ya que nos topamos con problemas “mecánicos” que nunca antes habíamos tenido. Como el controlador de floppy Intel 82077ª fue evolucionando a lo largo del tiempo para poder manejar discos de diferente tamaños físicos, densidades y estructuras lógicas, fue manteniendo compatibilidad hacia atrás, a medida que pasaba le tiempo. Esto que desde algún punto de vista es una característica muy positiva, se transforma en una carga a la hora de escribir un driver que pueda manejarlo. Existen múltiples comandos que realizan las mismas tareas, pero dejan algunos registros en estados diferentes, por lo que el programador debe tener mucho cuidado en como los utiliza. El acceso a los sectores de disco es de tipo CHS (cilindro, cara, pista) por lo que el driver deberá encargarse de hacer las traducciones pertinentes dependiendo de la capacidad y del formateo del disco. El hecho que posea una gran cantidad de mecánica hace que deban tenerse en cuenta consideraciones necesarias, por ejemplo, para esperar a que el motor alcance la velocidad de régimen permanente. También deberá mantenerse una relación de compromiso en el apagado del motor, ya que si este permanece siempre encendido, su vida útil será muy baja; mientras que si lo apagamos inmediatamente después de un acceso, deberemos nuevamente esperar a que alcance la velocidad de régimen permanente antes de volver a realizar otra lectura (aumentando mucho el tiempo de lectura). Además nos vimos en la obligación de aprender a configurar el controlador de DMA 8237ª, para lograr hacer transferencias por bloques y no por bytes. Sistema Operativo Routix - Página 11 of 17 Las funciones que componen al driver son: 1. void motor_on(); (Enciende el motor) 2. void motor_off(); (Apaga el motor) 3. int floppy_sendbyte(byte valor); (Envia un byte al registro de datos del controlador ) 4. int floppy_get_result(); (Lee un byte del registro de datos del controlador) 5. int init_floppy(); (Inicializa al controlador) 6. int recalibrate(); (Recalibra la posicion de la cabeza a la pista 0) 7. byte seek (byte cara, byte pista); (Posicionar el cabezal sobre la pista indicada) 8. int read_sector (byte cara, byte cilindro, byte sector); (Lee un sector físico (previamente el cabezal debe estar calibrado y posicionado) 9. int block(void); (block: bloquea la ejecución del proceso que la llama hasta recibir una interrupción de floppy) 10. void init_floppy_DMA ( byte comando ); (Inicializa el controlador DMA, tanto para leer como para escribir (según comando)) 11. int leer_escribir(byte operacion, dword sector_logico, byte *buffer); (Acepta como operación READ_SECTOR y WRITE_SECTOR. Es la encargada de convertir un sector lógico (o LBA) en el sector físico (CHS) correspondiente. Inicializa el controlador de floppy y enciende el motor. En caso de time outs, intenta hasta MAX_TRYS) H. CONTROLADOR DE FAT12 Implementamos este file system ya que es uno de los más sencillos, es leído por casi todos los sistemas operativos y es el sistema de archivos utilizado generalmente en los discos flexibles. Sistema Operativo Routix - Página 12 of 17 Las funciones comprendidas por este controlador tienen por objeto hacer transparente a las llamadas de archivos el acceso lógico a los archivos. El controlador está formado por: 1. int fat_12(void); (Lee el sector logico 0 (Boot sector), y levanta todos los parametros fisicos y logicos del disco para que sean usados por las demás funciones) 2. int levantar_fat(void); (Levanta todos los sectores correspondientes a la FAT y los coloca en la memoria Debera tenerse en cuenta que al cambiar el diskette, el flag "fat_levantada" debera volverse a FALSE) 3. fat12_entry_t *fat_file_find (char *nombre, fat12_entry_t *datos_archivo); (Recibe un nombre de archivo, y lo busca en el disco. En caso negativo retorna NULL) 4. int fat_next_sector ( dword sector_inicial ); (Esta función recibe como parámetro un numero de sector y mediante la FAT obtiene cual es el siguiente) 5. void fat_adapta_name (byte *nombre_fat, byte *adaptado); (Recibe un nombre en formato FAT ( KERNEL BIN ) y lo transforma a un formato tipo string: KERNEL.BIN\0 ) 6. void *floppy_cache (dword sector); (Esta funcion mantiene una lista enlazada con sectores leidos del floppy. Cualquier funcion de fs que quiera leer o escribir un sector, deberá recurrir a esta. Devuelve un puntero a la dirección de memoria donde se encuentra el sector pedido) I. LLAMADAS PARA EL MANEJO DE ARCHIVOS Dentro de este conjunto caen las funciones open, close, read y lseek (la función write no ha sido implementada, debido a que requiere para un correcto funcionamiento la implementación de spinlocks o candados dentro del kernel). Estas funciones poseen el mismo prototipo que sus pares del estándar Unix , con el fin de mantener cierta compatibilidad en lo que a desarrollo de aplicaciones se refiere. Los prototipos son: 1. 2. 3. 4. int open (char *nombre); int close (int fd); ssize_t read (int fd, void *buf, size_t len); int lseek (int fd, int offset, int donde); J. TIMERS Sistema Operativo Routix - Página 13 of 17 Los timers son la base para el manejo de tiempos en el sistema, desde la simple actualización horaria pasando por funciones que permiten detenar la ejecución de una tarea durante cierto. En nuestro sistema operativo implementamos el manejo y actualización de la fecha y hora, leyendo el Real Time Clock durante el inicio y actualizándolo con cada tick recibido, así como las funciones de librería necesarias para acceder a ellas: 1. struct tm *localtime(const time_t *clock) 2. time_t mktime(struct tm *tm) 3. char *asctime(const struct tm *tm) siendo struct tm: // Estructura tm struct tm { int tm_sec; int int int int int int int int tm_min; tm_hour; tm_mday; tm_mon; tm_year; tm_wday; tm_yday; tm_isdst; /* /* /* /* /* /* /* /* /* /* seconds after the minute - [0, 61] */ for leap seconds */ minutes after the hour - [0, 59] */ hour since midnight - [0, 23] */ day of the month - [1, 31] */ months since January - [0, 11] */ years since 1900 */ days since Sunday - [0, 6] */ days since January 1 - [0, 365] */ flag for alternate daylight savings time */ }; typedef int time_t; El kernel maneja también timers, que son activados ante ciertos eventos, por ejemplo, surgió como necesidad fundamental cuando se requería realizar demoras controladas en los programas, veamos el siguiente ejemplo: for ( i=0; i<CUENTA_DEMORA; i++) { .... } el código precedente permite realizar una demora, pero tiene dos falencias fundamentales: No puede controlarse con exactitud el tiempo de demora, y de hecho varía según la velocidad de ejecución del microprocesador Durante el tiempo de demora se sigue utilizando el procesador. Estas dos condiciones resultan inaceptables. Para solucionar este inconveniente aparecen las funciones sleep y usleep que permiten dormir una tarea durante un cierto tiempo, o sea, la tarea queda en estado TASK_STOPPED mientras no se llego al tiempo especificado, con lo que no es ejecutada por el scheduler y entonces libera tiempo para que otros procesos utilicen el procesador. 1. int sleep(int segundos) Sistema Operativo Routix - Página 14 of 17 2. int usleep(int usegundos) K. SCHEDULER Es el encargado de seleccionar la próxima tarea que se ejecutará en el microprocesador. Cuando una tarea en ejecución vence el tiempo actividad (ver la secuencia explicada en la sección “Multitarea”) el scheduler toma control de la situación y decide cuál será la tarea a ejecutarse. Para realizar esta selección el Scheduler cuenta con una lista enlazada de tareas (figura 1), cada tarea esta unívocamente caracterizada por la estructura task_struct_t: typedef struct task_struct_t { dword esp0; dword cr3; dword cr3_backup; pid_t pid; struct task_struct_t *proxima; char descripcion[MAX_DESCRIPTION]; byte estado; word prioridad; word cuenta; dword tiempo_cpu; struct file *open_files [MAX_FILES_POR_TAREA]; //definido en file.h void *paginas[MAX_PAGINAS_POR_TAREA]; word num_code; //cantidad de paginas de codigo de la tarea word num_data; //cantidad de paginas de datos y bss int err_no; signal_struct_t senales; } task_struct_t ; El scheduler utiliza los campos proxima y estado para encontrar la próxima tarea a ejecutar, lo cual es un método sencillo pero eficiente de selección, en una primer etapa, luego seguiremos avanzando con el fin de que el algoritmo de selección tenga en cuenta muchas mas variables (prioridad por ejemplo). Inicio Tarea 1 Tarea 2 Tarea 3 Tarea 4 Tarea 5 TASK_RUNNING TASK_STOPPED TASK_RUNNING TASK_RUNNING TASK_STOPPED Proxima Proxima Proxima Proxima Proxima NULL Figura 1 Sistema Operativo Routix - Página 15 of 17 L. APLICACIONES Como ya hemos comentado anteriormente, el kernel corre aplicaciones en un nivel de ejecucion 3 de la unidad de segmentacion y en modo usuario de la de paginacion. Al referirnos a aplicaciones podemos estar hablando tanto de programas de usuario normales, de drivers, o de la implementacion de algun tipo de servicio de red. Esto dependera de las funcionalidades del sistema, ya que como dijimos anteriormente nuestra intencion es general un kernel funcional, el cual sea facilmente adaptable para cumplir diferentes tareas. Alguien podria querer, por ejemplo, implementar un Firewall compuesto por varios modulos independientes al kernel, entonces para evitar o reducir los problemas que pueda causar alguno de ellos, simplemente se los ejecuta en modo User, es decir como una aplicacion. Las aplicaciones deben estar realizadas en formato COFF32 (en un futuro proximo se implementara la ejecucion de tareas en formato ELF32). Poseen como requisito fundamental ser linkeadas en la direccion virtual VMA (virtual memory address) 0x80000000 mediante el GNU LD. Cada tarea poseera, al ejecutarse, su propio espacio de direcciones, es decir, que al ser ejecutada, se cambia el directorio de paginas de la tarea anterior por el de la tarea actual, el cual contiene los mapeos de los datos y codigo del kernel, sumados a los de esta tarea en particular. M. FUNCIONES IMPLEMENTADAS Hasta el momento hemos implementado muchas de las funciones estandar conocidas en un sistema operativo. Si bien algunas no son accesibles desde el espacio de usuario debido a que falta la entrada al sistema via interrupción, esperamos tenerlas funcionando pronto. Todas las funciones que se detallan a continuacion, han sido desarrolladas teniendo en cuenta los prototipos de las funciones originales, para mantener compatibilidad. void printf ( char *string, ...); void puts(char *str); int putchar (char car); void gets (char *str); int strcmp(const char *s1, const char *s2); char *strcpy (char *dest, const char *src); char *strcat(char *dest, char *src); size_t strlen(const char *s); char *strncpy (char *dest, const char *src, size_t nbytes); char *str_to_upper (char *str); int memcmp ( const void *s1, const void *s2, dword n); void *memcpy ( void *dest, const void *src, dword n); struct tm *localtime(const time_t *clock); time_t mktime(struct tm *tm); char *asctime(const struct tm *tm); int sleep(int segundos); int usleep(int usegundos); Sistema Operativo Routix - Página 16 of 17 6. ROADMAP Realmente queda mucho por hacer para que Routix logre ser un sistema solido, sobre el cual se pueda crecer desarrollando multiples soluciones. A Continuacion se detallan los puntos principales sobre los cuales vamos a trabajar en un futuro inmediato: Señales: Las señales son un componente basico y fundamental de todo Kernel serio. Ya hemos delineado gran parte de su diseño, pero por carencias de tiempo, no hemos desarrollado nada estable que pueda ser incluido en el codigo. El objetivo seria respetar, como en los demas puntos, el estandar POSIX, con el fin que sea facil portar aplicaciones desde otros sistemas operativos. Mecanismos de IPC: Tienen gran importancia, no solo para brindar al usuario la posibilidad de comunicar sus procesos, si no que son muy importantes en el desarrollo de un stack de comunicaciones, como el TCP/IP. Llamadas al sistema: Necesitamos desarrollar llamadas al sistema para el control de procesos y para obtener informacion de la ejecucion de aplicaciones desde el modo User. Debido a que el Kernel ya maneja esta informacion, no es una gran cantidad de trabajo la que debe realizarse. Consola: Por el momento tenemos un driver de video (en modo texto) y uno de teclado que son utilizados “simultaneamente” por todas las aplicaciones que corren sobre el shell. Lo ideal seria crear una abstraccion de tipo “Consola” como la que utilizan la mayoria de los sistemas operativos, la cual se “ate” al shell, y sea este quien la vincule con un proceso. Shell: Si bien en este momento tenemos un shell, el cual nos permite la ejecucion de tareas en forma dinamica, debemos realizar un shell mas solido, que nos de mas funcionalidad. En un futuro un poco mas lejano, cuando hayamos desarrollado basandonos en POSIX, podremos correr algun shell conocido (como ASH, SH, o quiza BASH). Sistema Operativo Routix - Página 17 of 17