[Notas de lectura] Diseño del kernel de Linux y programación del proceso de implementación

Programación del proceso: el núcleo decide qué proceso se está ejecutando, cuándo y en qué orden de esta manera sutil e interesante.

Los procesos aparecen para el sistema operativo como una forma de ejecución del programa.

Un planificador de procesos (planificador para abreviar) puede verse como un subsistema del núcleo que asigna recursos de tiempo de procesador limitados entre procesos ejecutables .

El principio del uso máximo del planificador del tiempo del procesador es: siempre que haya procesos que se puedan ejecutar, siempre habrá procesos que se estén ejecutando.

Seleccionar uno de un conjunto de procesos en un estado de ejecución para ejecutar es el trabajo básico requerido por el planificador.

1. Prevención multitarea, franjas horarias y concesiones

Un sistema operativo multitarea es un sistema operativo que puede ejecutar simultáneamente múltiples procesos de forma simultánea e interactiva.
Ya sea en una máquina de procesador único o multiprocesador, un sistema operativo multitarea puede mantener múltiples procesos en un estado bloqueado o inactivo, es decir, en realidad no se ponen en ejecución, sabiendo que el trabajo está realmente listo.

Los sistemas multitarea se pueden dividir en: multitarea no preventiva (multitarea cooperativa) y multitarea preventiva (multitarea preventiva).

P: ¿Qué es la preferencia?
R: El planificador decide cuándo detener la ejecución de un proceso para que otros procesos puedan tener la oportunidad de ejecutarse. Esta acción de suspensión forzada se denomina preferencia.

P: ¿Qué es un segmento de tiempo (segmento de tiempo)?
R: El tiempo que el proceso puede ejecutarse antes de que se sustituya se establece de antemano. Esta vez es el segmento de tiempo del proceso.

P: ¿Qué es el rendimiento?
R: El proceso suspende activamente su propio sistema operativo que se denomina rendimiento.

Las ventajas y desventajas de los tipos preventivos y no preventivos son evidentes: en un modo multitarea no preventivo, a menos que el proceso ceda activamente, otros procesos pueden tener la oportunidad de ejecutarse. El proceso de suspensión puede bloquear el sistema.

2. Programador de procesos de Linux-O (1) planificador

3. Estrategia: decida cuándo y qué proceso ejecutará el programador

3.1 Procesos que consumen E / S y procesadores

Los procesos se pueden dividir en consumo de E / S y consumo de procesador.
El consumo de E / S significa que la mayor parte del proceso se utiliza para enviar solicitudes de E / S o esperar solicitudes de E / S.
El consumo del procesador significa que el proceso pasa la mayor parte de su tiempo ejecutando código. A menos que sean adelantados, generalmente corren sin parar.

ps: El proceso puede mostrar ambos, es decir, tanto el tipo de consumo de E / S como el tipo de consumo del procesador.

Las estrategias de programación generalmente buscan el equilibrio entre dos objetivos contradictorios: el proceso es correspondientemente rápido (tiempo de respuesta corto) y la máxima utilización del sistema (alto rendimiento) .

3.2 Prioridad de proceso

El tipo más básico de algoritmo de programación es la programación basada en prioridades.
El planificador siempre selecciona el proceso con la máxima prioridad y el segmento de tiempo no se agota.

Linux usa dos rangos de prioridad diferentes. Buen valor y prioridad en tiempo real

Tipo Explicación
buen valor 1: el rango es de -20 a +19, el valor predeterminado es 0;
2: el valor agradable más grande significa menor prioridad;
3: el comando relacionado ps -el, la columna NI es el valor agradable para el proceso
Prioridad en tiempo real 1: el valor se puede configurar;
2: el rango predeterminado varía de 0 a 99 (inclusive);
3: un mayor valor de prioridad en tiempo real significa una mayor prioridad de proceso;
4: cualquier prioridad de proceso en tiempo real es mayor que el proceso normal (La prioridad en tiempo real y la prioridad agradable se encuentran en dos categorías disjuntas)
5: Comandos relacionados: estado ps-eo, uid, pid, ppid, rtprio, time, comm () Detalles como se muestra a continuación
ps -eo state,uid,pid,ppid,rtprio,time,comm

Inserte la descripción de la imagen aquí

3.3 Intervalo de tiempo

Un segmento de tiempo es un valor numérico que indica cuánto tiempo puede continuar ejecutándose un proceso antes de que se le sustituya.

La experiencia muestra que cualquier película a largo plazo dará como resultado un rendimiento de interacción del sistema deficiente.

El planificador CFS (programación completamente justa) de Linux no asigna directamente segmentos de tiempo a los procesos, sino que divide la proporción de uso del procesador en los procesos.
Por lo tanto, el tiempo de procesador ganado por el proceso está realmente relacionado con la carga del sistema. Y esta relación también se verá afectada por el buen valor del proceso. El buen valor como peso ajustará la relación de uso del tiempo de procesador utilizado por el proceso.

Linux es preventivo. Cuando un proceso entra en un estado ejecutable, se le permite ejecutarse en el planificador CFS de Linux. El tiempo de preferencia depende de la cantidad de uso de procesador que consume el nuevo programa ejecutable. Si el uso consumido es menor que el proceso actual, el nuevo proceso se pone inmediatamente en funcionamiento, evitando el subproceso actual, de lo contrario, se retrasará.

3.4 Programación de actividades de estrategia: se recomienda leer esta sección del libro original, que describe claramente la "estrategia"

4. Algoritmo de programación de Linux

ps: salta al capítulo 11 temporalmente

4.1 Clases de planificador (clases de planificador) -estructura modular

El programador de Linux se proporciona como un módulo, cuyo propósito es permitir que diferentes tipos de procesos seleccionen algoritmos de programación de una manera específica.

La clase del planificador permite múltiples algoritmos de planificación diferentes que se pueden agregar dinámicamente para coexistir y programar procesos que pertenecen a su propia categoría.

Cada planificador tiene una prioridad, y el código básico del planificador se define en el archivo kernel / sched.c.

La programación completamente justa (CFS) es una clase de programación para procesos ordinarios, llamada SCHED_NORMAL en Linux (llamada SCHED_OTHER en POSIX), y la implementación del algoritmo CFS se define en el archivo kernel / sched_fair.c.

4.2 Programación de procesos en sistemas Unix

4.3 Programación justa-CFS

El punto de partida de CFS se basa en un concepto simple: el efecto de la programación del proceso debería ser como si el sistema tuviera un procesador ideal para tareas múltiples perfecto.

En este sistema, cada proceso podrá obtener 1 / n de tiempo de procesador. N se refiere a la cantidad de procesos en ejecución.

El modelo ideal anterior no es realista, porque es imposible ejecutar múltiples procesos en un procesador al mismo tiempo.

La práctica de CFS es permitir que cada proceso se ejecute por un período de tiempo, rotar y rotar, y seleccionar el proceso que ejecute menos como el siguiente proceso en ejecución, en lugar de adoptar el método de asignación de intervalos de tiempo para cada proceso.

CFS calcula cuánto tiempo debe ejecutarse un proceso en función del número total de todos los procesos que se pueden ejecutar, en lugar de depender del valor agradable para calcular el segmento de tiempo.

El valor agradable se usa como el peso de la relación de ejecución del procesador obtenida por el proceso en CFS: cuanto mayor sea el valor agradable (menor prioridad), el proceso obtiene el menor peso de uso del procesador, que es relativo al proceso predeterminado de valor agradable ; Por el contrario, los procesos con valores agradables más bajos (prioridad más alta) reciben pesos de procesador más altos.

Cada proceso se ejecuta de acuerdo con el "intervalo de tiempo" de su peso en todos los procesos ejecutables. Para calcular intervalos de tiempo precisos, CFS establece una meta para el valor aproximado de un período de programación infinitamente pequeño en multitarea perfecta ... Retraso objetivo ".

CFS introduce el resultado final del intervalo de tiempo obtenido por cada proceso, que se denomina granularidad mínima .

Solo el valor relativo afectará la relación de distribución del tiempo del procesador.

5. Implementación de Linux scheduling-kernel / sched_fair.c

5.1 Contabilidad de tiempos

Todos los planificadores deben realizar un seguimiento del tiempo de ejecución del proceso.
Cuando el segmento de tiempo de un proceso se reduce a 0, será reemplazado por otro proceso ejecutable que no se ha reducido a 0.

CFS ya no tiene el concepto de segmentos de tiempo, pero también debe mantener una contabilidad de tiempo para cada proceso para garantizar que cada proceso solo se ejecute dentro del tiempo de procesador asignado de manera justa.
CFS utiliza la estructura de entidad del planificador definida en la estructura sched_entity en el archivo <linux / sched.h> para rastrear el proceso de ejecución de la contabilidad.

ps: la estructura de la entidad del planificador está incrustada como una variable miembro llamada se en el descriptor de proceso struct task_struct.

La variable vruntime (definida en struct sched_entity) almacena el tiempo de ejecución virtual del proceso. El cálculo del tiempo de ejecución (el tiempo dedicado al operador) se normaliza por el número total de todos los procesos ejecutables.

CFS usa la variable vruntime para registrar cuánto tiempo ha estado ejecutándose un programa y cuánto tiempo debería ejecutarse.

La función update_curr () definida en el archivo kernel / sched_fair.c implementa esta función de contabilidad.

5.2 Selección de procesos: algoritmo de programación CFS: seleccione el proceso con el valor de tiempo de ejecución más pequeño

CFS usa un árbol rojo-negro (rbtree, árbol de búsqueda binaria autoequilibrado) para organizar una cola de procesos ejecutables y lo usa para encontrar rápidamente el proceso con el menor valor de tiempo de ejecución.

  1. Elige la siguiente tarea;
  2. Agregar procesos al árbol;
  3. Eliminar el proceso del árbol.

5.3 Entrada del programador

El punto de entrada principal para la programación del proceso es la función schedule (), que se define en kernel / sched.c.
Es el punto de entrada utilizado por otras partes del núcleo para llamar al planificador de procesos: elija qué proceso puede ejecutarse y cuándo ponerlo en funcionamiento.

schedule () generalmente debe asociarse con una clase de programación específica, es decir, encontrará una clase de programación con la prioridad más alta; este último debe tener su propia cola ejecutable y luego preguntarle a este último quién es el próximo en ejecutarse Proceso.

La implementación del cronograma es bastante simple, llamará a pick_next_task () (también definido en el archivo kernel / sched.c).

pick_next_task () verificará cada clase de programación en orden de prioridad de mayor a menor, y seleccionará el proceso con la prioridad más alta de la clase de programación con la prioridad más alta .

La implementación de pick_next_task () en CFS llamará a pick_next_entity (), y esta función llamará a la función __pick_next_entity () descrita en 5.2 arriba.

ps: CFS es una clase de programación para procesos ordinarios. Para más detalles, consulte este artículo Programación del proceso del sistema Linux: análisis detallado de la arquitectura de programación

5.4 Dormir y despertar

El proceso de suspensión (bloqueado) está en un estado especial no ejecutable .

Hay muchas razones para que el proceso se duerma, pero definitivamente se trata de esperar algunos eventos.

La operación de activación / desactivación de la suspensión del núcleo es la misma :
suspensión : el proceso se marca a sí mismo como inactivo, sale del árbol ejecutable rojo-negro, lo pone en la cola de espera y luego llama a horario () para seleccionar y ejecutar otro proceso.
Despertar : el proceso de despertar es justo lo opuesto a dormir. El proceso se establece en estado ejecutable y luego se mueve de la cola de espera al árbol rojo y negro ejecutable.

Cola de espera (suspensión) : la
cola de espera procesa la suspensión.
La cola de espera es una simple lista vinculada compuesta de procesos que esperan que ocurran ciertos eventos .
El kernel usa wake_queue_head_t para representar la cola de espera.
La cola de espera se puede crear estáticamente a través de DECLARE_WAITQUEUE () o dinámicamente creada por init_waitqueue_head ().

El proceso se agrega a una cola de espera realizando los siguientes pasos:

  1. Llame a la macro DEFINE_WAIT () para crear un elemento de cola de espera;
  2. Llame a add_wait_queue () para agregarse a la cola. La cola activará el proceso cuando se cumplan las condiciones de espera. Por supuesto, debemos escribir código relevante en otros lugares y realizar la operación wake_up () en la cola de espera cuando se produce el evento.
  3. Llame al método prepare_to_wait () para cambiar el estado del proceso a TASK_INTERRUPTIBLE o TASK_UNINTERRUPTIBLE. Y esta función agregará el proceso nuevamente a la cola de espera si es necesario, lo cual es necesario en el próximo recorrido del ciclo;
  4. Si el estado se establece en TASK_INTERRUPTIBLE, la señal activa el proceso. Esto se llama un despertador falso (el despertar no se debe a un evento). Así que verifique y procese la señal;
  5. Cuando se despierte el proceso, volverá a verificar si la condición es verdadera. Si es así, sale del bucle; de ​​lo contrario, vuelve a llamar a schedule () y repite este paso todo el tiempo;
  6. Cuando se cumplen las condiciones, el proceso cambiará su serpiente a TASK_RUNNING y llamará al método finish_wait () para eliminarse de la cola de espera.

p.ej:

/* 'q' 是希望休眠的等待队列 */
DEFINE_WAIT(wait);

add_wait_queue(q,&wait);
while(!condition){ /* 'condition' 是在等待的事件 */
	prepare_to_wait(&q,&wait,TASK_INTERRUPTIBLE);
	if(signal_pending(current))
	{
		/* 处理信号 如:*/
		/* do_something; */
	}
	schedule();
};
finish_wait(&q,&wait);

pd: si aún tiene bloqueos cuando planea dormir, recuerde liberar los bloqueos antes de llamar al horario () y volver a adquirirlos después, o responder a otros eventos.

La función inotify_read () se encuentra en el archivo fs / notify / inotify / inotify_user.c y copia la información leída del descriptor del archivo de notificación. Su implementación es un uso típico de la cola de espera (el mismo dispositivo tty de comunicación en serie n_tty.c La función n_tty_write () también es un uso típico de la cola de espera).

Wake up :
la función wake_up (), que activa todos los procesos en la cola de espera especificada, realiza la operación de activación.
La función wake_up llama a try_to_wake_up (), que copia el proceso para establecer el proceso en el estado TASK_RUNNING y llama a enqueue_task () para colocar el proceso en el árbol rojo-negro. Necesito establecer la bandera need_resched.

ps: qué código generalmente hace que se complete la condición de espera, copiará y luego llamará a la función wake_up ().
PD: Una cosa a tener en cuenta sobre la hibernación, hay un falso despertar. Es decir, a veces el proceso se despierta no porque esté esperando que se cumpla la condición. Es necesario utilizar un proceso circular para garantizar que la condición que está esperando se cumpla realmente.
Inserte la descripción de la imagen aquí

6. Preemption y cambio de contexto

El cambio de contexto, es decir, el cambio de un proceso ejecutable a otro, es manejado por la función context_switch () definida en kernel / sched.c.
Siempre que se seleccione un nuevo proceso y esté listo para ejecutarse, schedule () llamará a esta función.
La función context_switch () completa las siguientes dos tareas básicas:

  1. Llame a switch_mm () declarado en <asm / mmu_context.h>, esta función es responsable de cambiar la memoria virtual del mapa de proceso anterior al nuevo proceso;
  2. Llame a switch_to () declarado en <asm / system.h>. Esta función es responsable de cambiar del estado del procesador del proceso anterior al estado del procesador del nuevo proceso. Incluyendo guardar, responder a la información de la pila y la información de registro, así como cualquier otra información de estado relacionada con la arquitectura, cada proceso debe ser administrado y guardado.

El núcleo debe saber cuándo llamar a schedule (). Se proporciona un indicador need_resched para indicar si la programación debe realizarse nuevamente.

Las funciones utilizadas para acceder y operar need_resched son las siguientes:

Función Propósito
set_tsk_need_resched () Establecer el indicador need_resched en el proceso especificado
clear_tsk_need_resched () Borra la bandera need_resched en el proceso especificado
need_resched () Verifique el valor del indicador need_resched y devuelva verdadero si está configurado, de lo contrario falso

P: ¿Cuándo se establece el indicador need_resched?
R: Cuando se debe adelantar un proceso, Scheduler_tick () establecerá este indicador; cuando un proceso con alta prioridad ingrese al estado ejecutable, try_to_wake_up establecerá este indicador, el núcleo verifica el indicador para confirmar que está establecido , Llame al horario () para cambiar a un nuevo proceso. Al regresar al espacio de usuario y regresar de una interrupción, el kernel también verificará el indicador need_resched. Si está configurado, el kernel llamará al planificador antes de continuar la ejecución.

Cada proceso contiene un indicador need_resched porque acceder al valor en el descriptor de proceso es más rápido que acceder a una variable global (porque la macro actual es rápida y el descriptor generalmente está en la memoria caché). Need_resched después de la versión 2.6 se movió a la estructura thread_info y se representó con un bit en una variable de bandera especial.

6.1 Preferencia de usuario

Cuando el núcleo está a punto de regresar al espacio del usuario, si se establece el indicador need_resched, se llamará a schedule () y se producirá la preferencia del usuario.

La preferencia de usuario ocurre cuando:

  1. Al volver al espacio de usuario desde una llamada del sistema;
  2. Al volver al espacio de usuario desde un controlador de interrupciones.

6.2 Preferencia del núcleo: siempre que no se mantenga el bloqueo, el núcleo puede evitar

Linux es totalmente compatible con la preferencia de kernel. Es decir, el planificador puede reprogramar mientras se ejecuta una tarea a nivel de kernel.

Mientras la programación sea segura, el núcleo puede adelantarse a la tarea que se está ejecutando en cualquier momento.

P: ¿ Cuándo es seguro reprogramar?
R: Mientras no se mantenga el bloqueo, el núcleo puede adelantarse. La cerradura es un signo de un área no preferente.

La preferencia de kernel ocurre cuando:

  1. El controlador de interrupciones se está ejecutando y antes de que vuelva al espacio del kernel;
  2. Cuando el código del núcleo es una vez más preferente;
  3. Si la tarea en el núcleo llama explícitamente a schedule ();
  4. Si la tarea en el kernel está bloqueada (esto también hará que se llame a schedule ()).

ps: El primer cambio para admitir la preferencia de kernel es introducir un contador de preempt_count para thread_info de cada proceso. El valor inicial del contador es 0, y el valor aumenta en 1 cada vez que se usa el bloqueo, y disminuye en 1 cuando se libera el bloqueo. Cuando el valor es 0, el núcleo puede realizar la preferencia.

7. Estrategia de programación en tiempo real-SCHED_FIFO y SCHED_RR

Linux proporciona dos estrategias de programación en tiempo real: SCHED_FIFO y SCHED_RR.
La estrategia común de programación en tiempo no real es SCHED_NORMAL.

La estrategia en tiempo real no es administrada por el planificador regular tardío, sino por un planificador especial en tiempo real . La implementación se define en el archivo kernel / sched_rt.c.

Estrategia de programación en tiempo real Descripción detallada Lo mismo
SCHED_FIFO SCHED_FIFO implementa un algoritmo de programación simple, primero en entrar, primero en salir : no utiliza segmentos de tiempo. Los procesos de nivel SCHED_FIFO que están en estado de ejecución se programan antes que cualquier proceso de nivel SCHED_NORMAL. Una vez que un proceso de nivel SCHED_FIFO esté en un estado ejecutable, continuará ejecutándose hasta que se bloquee o libere explícitamente el procesador; no se basa en segmentos de tiempo y puede continuar ejecutándose, solo tareas de mayor prioridad SCHED_FIFO o SCHED_RR Para evitar la tarea SCHED_FIFO. Si hay dos o más procesos de nivel SCHED_FIFO de nivel de prioridad, se ejecutarán por turnos, pero se dejarán salir solo si están dispuestos a renunciar al procesador. Mientras haya un proceso de nivel SCHED_FIFO ejecutándose, otros procesos de nivel inferior solo pueden esperar a que deje de funcionar antes de que tengan la oportunidad de ejecutarse Lo que se logra es una prioridad estática . El núcleo no calcula las prioridades dinámicas para procesos en tiempo real. Esto garantiza que los procesos en tiempo real de un nivel de prioridad dado siempre puedan adelantarse a los procesos con menor prioridad que
SCHED_RR SCHED_RR es aproximadamente lo mismo que SCHED_RR, excepto que el proceso de nivel SCHED_RR no puede continuar la ejecución después de agotar el tiempo asignado por adelantado. En otras palabras, SCHED_RR es SCHED_FIFO con segmentos de tiempo; este es un algoritmo de programación de rotación en tiempo real . Cuando la tarea SCHED_RR se agota en sus segmentos de tiempo, se programan sucesivamente otros procesos en tiempo real con la misma prioridad. Los segmentos de tiempo solo se utilizan para reprogramar procesos de la misma prioridad. Para el proceso SCHED_FIFO, la prioridad alta siempre prevalece inmediatamente sobre la prioridad baja, pero el proceso de prioridad baja no debe adelantarse a la tarea SCHED_RR, incluso si su segmento de tiempo está agotado. Lo que se logra es una prioridad estática. El núcleo no calcula las prioridades dinámicas para procesos en tiempo real. Esto garantiza que los procesos en tiempo real de un nivel de prioridad dado siempre puedan adelantarse a los procesos con menor prioridad que

El algoritmo de programación en tiempo real de Linux proporciona un método de trabajo suave en tiempo real . El significado del tiempo real suave es: el núcleo programa el proceso, tratando de hacer que el proceso se ejecute antes de su tiempo limitado, pero el núcleo no garantiza que siempre pueda cumplir con los requisitos de estos procesos.

La prioridad en tiempo real varía de 0 a MAX_RT_PRIO menos 1 (0-99). Por defecto, MAX_RT_PRIO es 100.
El valor agradable del proceso de nivel SCHED_NORMAL comparte este espacio de valores; su rango de valores es de MAX_RT_PRIO a (MAX_RT_PRIO + 40), es decir, por defecto, el valor agradable de -20 a +19 corresponde directamente de 100 a 139 Rango de prioridad en tiempo real.

8. Llamadas del sistema relacionadas con la programación

Linux proporciona una familia de llamadas al sistema para administrar parámetros relacionados con el planificador.
Las llamadas al sistema relacionadas con la programación se muestran en la siguiente tabla

Llamada al sistema Descripción
bonito() Establecer el buen valor del proceso
sched_setscheduler () Establecer la estrategia de programación del proceso
sched_getscheduler () Obtenga la estrategia de programación del proceso
sched_setparam () Establecer la prioridad en tiempo real del proceso
sched_getparam () Obtenga la prioridad en tiempo real de un proceso
sched_get_priority_max () Obtenga el valor máximo de prioridad en tiempo real
sched_get_priority_min () Obtenga el valor mínimo de prioridad en tiempo real
sched_rr_get_interval () Obtenga el valor del segmento de tiempo del proceso
sched_setaffinity () Establecer la afinidad del procesador del proceso.
sched_getaffinity () Obtenga la afinidad del procesador del proceso
sched_yield () Renunciar temporalmente al procesador
91 artículos originales publicados · 17 elogiados · 50,000+ vistas

Supongo que te gusta

Origin blog.csdn.net/qq_23327993/article/details/105072421
Recomendado
Clasificación