Download Trabajo Práctico Nº2 - Método Diferencial

Document related concepts

Linux Unified Kernel wikipedia , lookup

Cheat Engine wikipedia , lookup

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