Escritura del sistema operativo: multitarea

imagen
Buen día, querido lector, lo más probable es que hayas visto mi artículo anterior que tú mismo puedes escribir un sistema operativo viable en un período de tiempo bastante corto. Bueno, hoy hablaremos sobre la implementación de la multitarea en mi sistema operativo.

Bueno, probablemente no puedas imaginar un sistema operativo de una sola tarea en 2018, así que decidí hablar sobre la implementación de la multitarea en mi sistema operativo. Y así, lo primero: debe decidir el tipo de multitarea, elegí preventivo.
Como es ella La multitarea preventiva es un sistema para distribuir la potencia informática del procesador entre procesos: cada uno tiene su propio segmento de tiempo, cada uno tiene su propia prioridad. Y el primer problema es qué cuántico elegir en longitud, ¿cómo detener la ejecución del proceso después de que expire el cuanto? De hecho, ¡todo es más fácil que nunca! Usaremos PIT con una frecuencia inicialmente establecida de 10026 con centavos de interrupciones por segundo, allí mismo resolveremos un problema más: ya estamos deteniendo el proceso anterior. Y así, comencemos con PIT.

Hoyo


PIT (Temporizador de intervalo programable): un contador que, al alcanzar cualquier número programado de incrementos, emite una señal. Además, con este temporizador, puede chirriar un chirrido en la computadora (lo que chirría después de pasar la prueba del dispositivo). Entonces, cuenta con una frecuencia de 1193182 hertz, lo que significa que necesitamos programarlo en 119 (1193182/119 es aproximadamente igual a 10026). Para hacer esto, envíe 2 bytes al puerto del primer generador, primero el byte bajo y luego el alto:

unsigned short hz = 119; outportb(0x43, 0x34); outportb(0x40, (unsigned char)hz & 0xFF); //Low outportb(0x40, (unsigned char)(hz >> 8) & 0xFF); //Hight, about 10026 times per second 


Ahora vale la pena comenzar la programación de la interrupción desde PIT, tiene un IRQ de 0, y después de la reasignación, el PIC será de 0x20m. Para la IRQ del primer PIC, escribí esta macro:

 //PIC#0; port 0x20 #define IRQ_HANDLER(func) char func = 0x90;\ __asm__(#func ": \npusha \n call __"#func " \n movb $0x20,\ %al \n outb %al, $0x20 \n popa \n iret \n");\ void _## func() 


Estructura y procesos.


Entonces, como comprenderán, necesitamos desarrollar una estructura para cada proceso, así como una estructura que me permita recordar todas mis asignaciones de memoria.
Esto es lo que tengo:
 typedef struct _pralloc { void * addr; struct _pralloc * next; } processAlloc; typedef struct { void * entry; processAlloc *allocs; } ELF_Process; typedef struct __attribute__((packed)) _E { unsigned int eax;//4 unsigned int ebx;//8 unsigned int ecx;//12 unsigned int edx;//16 unsigned int ebp;//20 unsigned int esp;//24 unsigned int esi;//28 unsigned int edi;//32 unsigned int eflags;//36 unsigned int state;//40 void * startAddr;//44 void * currentAddr;//48 void * stack;//52 unsigned int sse[4 * 8];// unsigned int mmx[2 * 8];//244 unsigned int priority;//248 unsigned int priorityL;//252 void * elf_process;//256 char ** argv;//260 unsigned int argc;//264 unsigned int runnedFrom;//268 char * workingDir;//272 unsigned int cs;//276 - pop is 4 byte in IRET unsigned int ds;//280 } Process; 


Para empezar, debemos entender lo siguiente: podemos en algún lugar de la dirección global, por ejemplo, en 0xDEAD poner el número del proceso actualmente en ejecución, luego, al ejecutar cualquier código, podemos estar seguros: tenemos el número del proceso actualmente en ejecución, esto significa que Al acceder a Malloc, sabemos a quién estamos asignando memoria, y podemos agregar inmediatamente la dirección de la memoria asignada a la lista de asignaciones.
 void addProcessAlloc(ELF_Process * p, void * addr) { void * z = p->allocs; p->allocs = malloc_wo_adding_to_process(sizeof(processAlloc)); p->allocs->addr = addr; p->allocs->next = z; } 


Bueno, escribimos la estructura de la tabla con la descripción de los procesos, ¿qué sigue, cómo cambiar las tareas?
Para empezar, quiero señalar que, por ejemplo, en el controlador, las variables locales se almacenan en la pila, lo que significa que después de ingresar al controlador, el compilador nos estropea especialmente. Para evitar que esto suceda, cree una variable con una dirección absoluta y, antes de llamar al controlador, colocaremos el ESP allí. En el controlador, debemos enviar el EOI al primer PIC y encontrar el proceso al que debemos cambiar (no describiré el mecanismo de prioridad: es simple, como un atasco de tráfico). A continuación, debemos guardar todos los registros e indicadores del proceso actual, por lo que inmediatamente antes de colocar el ESP en una variable, guardaremos todos los registros (incluidos los segmentos) en la pila. En el controlador en sí, debemos eliminarlos de la pila con mucho cuidado, al tiempo que conservamos las banderas y la dirección de retorno. Quiero señalar que la pila crece (es decir, ESP disminuye), lo que significa que el último registro que guardó en la pila estará en ESP, el penúltimo será ESP +4, etc.
imagen
Ahora nos queda poner los valores de los registros de proceso en los registros a los que cambiamos y ejecutamos IRET. Beneficio!

Inicio del proceso


Al iniciar el proceso, es suficiente para nosotros asignar la pila para el proceso, y luego poner argc y argv en él, la dirección de la función que se le dará el control después de que se complete el proceso. También debe establecer los indicadores del procesador en el valor que necesita, por ejemplo, para mi sistema operativo es 0x216, puede leer sobre el registro de indicadores en Wikipedia.

Al final quiero desearles éxito, pronto escribiré sobre cómo trabajar con la memoria y otros artículos de su interés.
¡Buena suerte y piratería ética!

Source: https://habr.com/ru/post/es426807/


All Articles