Código fuente de la puerta de tareas Tss
Linux es de código abierto, ¿por qué no leer el código fuente de Linux?
La nueva versión es demasiado larga para escribirla. Le daré la versión 0.11 más clásica del código fuente de gestión de procesos. Comprueba el resto tú mismo. Como buen programador, debes aprender a aprovechar al máximo los motores de búsqueda.
-
Linux0.11 gestiona procesos a través de matrices de procesos, permitiendo hasta 64 procesos. El estado del proceso es listo, en ejecución, suspendido, inactivo y muerto. Los estados de sueño se pueden dividir en interrumpibles e ininterrumpibles. Para la gestión de procesos, creo que quedará más claro según el estado del proceso.
Tarea 1.0
El No. 0 es especial, es "puramente artesanal", el siguiente es el proceso de producción.
mem_init(main_memory_start, memoria_end);
trap_init().
blk_dev_init();
char_dev_init();
tty_init();
time_init();
sched_init();
buffer_init(buffer_memory_end);
HD_init();
floppy_init().
STI();
move _ to _ user _ mode();
Por supuesto, muchos init no están todos preparados para 0 tareas. están inicializando el sistema. Para la tarea 0, sched_init() y move_to_user_mode() son importantes. Sched_init() establece manualmente el descriptor de segmento de tarea tss y el descriptor de segmento local ldt de la tarea 0, y establece los registros tr y ldtr:
set_tss_desc(init _ task . task . TSS) //Set tss); Descriptor de segmento
set _ ldt _ desc(gdt FIRST _ LDT _ ENTRY amp; (init _ task . task . ldt)); //Establecer descriptor ldt
.. .
ltr(0); //Carga la dirección del descriptor tss en tr
lldt(0); //Carga la dirección del descriptor ldt en ldtr
Tomemos un vistazo a cómo se ven el tss y el ldt de la tarea 0.
/*ldt*/ {0, 0}, \
{0x9f, 0xc0fa00}, \
{0x9f, 0xc0f200}, \
/*tss*/{0, PAGE_SIZE (largo) & init_task, 0x10, 0, 0, 0, 0, (largo) & pg_dir, \
0, 0 ,0,0 ,0,0,0,0,\
0,0,0x17,0x17,0x17,0x17,0x17,\
_LDT(0),0X80000000, \
{}\
},\
Como se puede ver en ldt, los segmentos de código y datos de la tarea son 640k, la dirección base es 0 y PPD =3. Esto muestra que aunque el código de la tarea 0 todavía está en el segmento del kernel, el nivel de la tarea ya está en modo de usuario. Esto también se puede ver en el tss, donde tanto el código como los datos están configurados en 0x17, que es la segunda entrada en la tabla de descriptores de segmento local.
También puede ver que la pila del kernel de la tarea 0 está configurada en la página después de init_task, y la pila del estado del usuario es la pila del estado del kernel antes de move_to_user_mode. Esto es diferente a un proceso normal, que tiene su pila en modo de usuario al final de su espacio de direcciones de 64 Mb.
Move_to_user_mode es una ilusión que crea un cambio de tarea en la pila. Use iret para saltar a la capa exterior 3, de modo que la CPU cargue automáticamente tss de acuerdo con tr, inicialice cada registro y ejecute la tarea 0. Por lo tanto, la tarea 0 es en realidad una tarea en modo de usuario en el espacio del kernel.
2. Creación de procesos
La creación del proceso se completa mediante la llamada al sistema sys_fork, utilizando principalmente las dos funciones find_empty_process y copy_process. El primero encuentra un número de proceso no utilizado para el proceso hijo en la matriz de procesos, y el segundo completa la creación de la información del proceso hijo, principalmente copiando la información del proceso padre.
Echemos un vistazo al código de copy_process:
int copy_process(int nr, long ebp, long edi, long esi, long gs, long none,
ebx largo, ecx largo, edx largo, fs largo, es largo, ds largo,
eip largo, cs largo, eflags largo, esp largo, ss largo)
{ p>
struct task _ struct * p;
int I;
archivo de estructura * f
// Primero, el proceso de el proceso hijo El descriptor asigna un bloque de memoria.
p =(struct task _ struct *)get _ free _ page();
If (!p)
return-EAGAIN; >
//Agrega el nuevo puntero de estructura de tarea a la matriz.
task[NR]= p;
//Copia la estructura de tareas del usuario actual al proceso hijo. Por supuesto, la pila no se copia.
* p = *current;
//Establezca el estado del proceso secundario en espera ininterrumpida para evitar que se programe ahora.
p->estado = tarea_ininterrumpida
p->pid = último_pid
p->padre = actual - gt ;pid
p->;cuenta = p- gt;prioridad;
p->;señal=0;
p->;alarma= 0; p->líder = 0;
p->utime = p-gt; stime = 0;
p->cutime = p-gt; >
p->; start_time = jiffies
p->; back_link = 0;
//La pila de estado del nuevo proceso está al final de la página del descriptor del proceso.
p->; TSS . esp0 = PÁGINA _ TAMAÑO (largo)p
p->; ip es la siguiente instrucción para que el proceso padre llame a fork.
p->;tss.eip = eip
El valor de retorno de fork es 0 para el proceso hijo y su pid para el proceso padre. Con esta diferencia, los segmentos de código de los procesos padre e hijo se dividen después de que regresa la llamada fork.
p->TSS.eax = 0;
// Aunque estén escritos en archivos de código.
p->;tss.ecx = ecx
p->;tss.edx = edx
p->;tss.ebx = ebx p>
p-> p>
p->; tss.esp = esp
p->; tss.ebp = ebp
p->; .esi = esi
p->; tss.edi = edi
p->; tss.es = es amp0xffff
p->; cs = cs amp0xffff
p-> = ss amp0xffff
p-> tss.ds = ds amp0xffff
p-> = fs amp0xffff
p- >tss.gs = gs amp0xffff
p->TSS.ldt = _LDT;
p->tss.trace_bitmap = 0x80000000
//Si la tarea principal usa un coprocesador, se guarda en tss.
if(last_task_used_math == actual)
_ASM("clts;fn save 0"::"m"(p-gt;TSS.i387 ));
//Establezca nuevas direcciones de base de segmentos de datos y códigos para nuevas tareas. Tenga en cuenta que debido a que Linux 0.11 solo admite un espacio de proceso de 64 M, el espacio de direcciones lineal del proceso está en el límite de 64 M.
//Luego copie la tabla de páginas del proceso principal para la nueva tarea. A través de copy_page_tales, las tareas principal y secundaria utilizan el segmento de datos de código de solo lectura en este momento. Al escribir, la copia en escritura asignará nuevas páginas físicas para el nuevo proceso.
if(copy_mem(nr, p)){
tarea[NR]= NULL;
free_page((long)p);
return-EAGAIN;
}
for(I = 0; iltNR_OPENi)
if(f = p- gt; filp)
f-gt;f_count;
if (current-gt;pwd)
current->;pwd-gt;I_count;
si (current-gt;root)
current->root-gt;I_count;
if (current-gt;executable)
Actual->Ejecutable- gt;I_count;
//Establece los descriptores tss y ldt para la nueva tarea.
set _ TSS _ desc(gdt (NR lt; lt1) FIRST _ TSS _ ENTRY & (p->TSS));
set _ ldt _ desc(gdt (NR) lt; lt1) FIRST_LDT_ENTRY; (p->ldt));
//Devuelve el pid del proceso hijo.
Devuelve el último _ pid
}
Los parámetros son todos valores en la pila, nr es el valor de retorno de find_empty_process antes de llamar a copy_process, y es el número de serie de la matriz de tareas.
3. Programación de procesos
El proceso no se ejecutará inmediatamente después de su creación.
El sistema llamará a la función de programación del sistema en el momento apropiado y seleccionará el proceso apropiado para ejecutar. Las funciones de programación se pueden llamar en muchos lugares, como llamadas al sistema, suspensión/suspensión/salida de procesos, interrupciones de reloj, etc. La función de programación selecciona principalmente un proceso con el tiempo de ejecución más corto a través del intervalo de tiempo del proceso. Si no se encuentra, se ejecuta la llamada al sistema de pausa inactiva. Durante la pausa, se llamará nuevamente a la función de programación. El siguiente es el código principal
while(1){
c =-1
next = 0
i =; NR _ TASKS
p = amp task[NR_TASKS];
//Encuentra la tarea de ejecución más corta entre las tareas listas.
mientras( - i){
si (!(* - p))
Continuar;
si((* p )- gt; estado = = tarea ejecutando amp (* p)- gt; contador gtc)
c = (* p)- contador, siguiente = I;
}
//Si lo encuentra, salga. De lo contrario, vuelva a calcular el intervalo de tiempo de la tarea. Tenga en cuenta que si no hay ninguna tarea, la siguiente siempre es 0 y el resultado cambia a la tarea 0.
Si (c) se rompe;
for(p = amp;LAST _ TASKp gt ampFIRST _ TASK- p)
Si (*p) p> p>
(* p)- gt; contador = ((* p)- gt; contador gt gt1) (*)- gt; > switch_to(next); //Convierte la tarea en una nueva tarea mediante salto largo.
}
La interrupción del reloj es un medio importante para realizar la programación. Es precisamente debido a la ejecución continua de la programación que el sistema puede cambiar entre tareas múltiples y completar los objetivos de un sistema operativo multitarea. Cuando el programador se inicializa, además de configurar manualmente la tarea 0, también inicia la interrupción del temporizador de 8253 y "enciende" el sistema. La función do_timer se llama en la interrupción. Esta es una función muy corta:
void do_timer (long cpl)
{
...
//Según la prioridad Tiempo de ejecución del proceso de ajuste de nivel.
Frecuencia intermedia (cpl)
Hora actual->u;
Otras
Hora actual->;
>//Procesando la cola de interrupciones del temporizador
if(next_timer){
next _ timer- gt; jiffies-;
while(next _ timer amp ; ampnext_timer- gt;jiffies lt=0){
void(* fn)(void);
fn = next_timer- gt;fn;
next_timer- gt;fn = NULL
next_timer = next_timer-gt;siguiente;
(fn)();
}
}
..
//El intervalo de tiempo del proceso se resta en 1. Si es mayor que 0, significa que el intervalo de tiempo no se ha agotado. continuar.
Si ((-current- gt; counter gt0) regresa;
//Tenga en cuenta que las tareas de estado del kernel no se pueden adelantar. En 0.11, a menos que la tarea del kernel abandone activamente el procesador, de lo contrario, se ejecutaría todo el tiempo.
Si (!cpl) devuelve;
//Programación
Programación();
}
4. Terminación del proceso
El proceso se puede detener por sí solo o mediante una señal de parada. Do_exit ejecuta la salida. Si tiene un proceso hijo, entonces este proceso hijo se convertirá en un proceso huérfano y su proceso hijo se confiará al proceso 1 (es decir, el proceso de inicio) para su gestión. Después de convertirse en un proceso zombie, también debe notificar al proceso principal, porque su proceso principal puede estar esperándolo.