Planificador de Linux O (n)

Anteriormente aprendimos algunos puntos a los que el diseño del planificador debe prestar atención, revise aquí:

  1. Rendimiento (correspondiente a procesos que consumen CPU)
  2. Velocidad de respuesta (correspondiente al proceso de consumo de IO)
  3. Justicia, para garantizar que cada proceso pueda tener la oportunidad de ejecutarse
  4. Consumo de energía de dispositivos móviles.

Diseño de planificador en Linux, el concepto introducido

  1. El proceso común y el proceso en tiempo real utilizan la prioridad para distinguir, 0-99 significa proceso en tiempo real, 100-139 significa proceso ordinario
  2. El proceso en tiempo real utiliza dos estrategias de programación SCHED_RR o SCHED_FIFO
  3. Los procesos ordinarios usan buenos valores para ajustar dinámicamente la prioridad de los procesos ordinarios.
  4. Los procesos que a menudo duermen intentan aumentar la siguiente prioridad, a menudo ocupando la CPU correctamente para reducir la prioridad

En esta sección, primero aprenderemos el diseño de algoritmos de programación temprana en Linux, comenzando con el algoritmo del programador más temprano. La complejidad temporal de este programador es O (n), por lo que también se puede llamar algoritmo de programación O (n). La versión del núcleo que elegimos es linux-2.4.19.

 

Principio de implementación del planificador O (n)

O (n) representa la complejidad temporal de encontrar un proceso adecuado. El planificador define una cola de ejecución de la cola de ejecución , que se agregará a la cola de ejecución cuando el estado del proceso se cambie a Ejecutar. Por supuesto, si se trata de un proceso en tiempo real o un proceso normal se agregará a esta cola de ejecución. Cuando se necesita ejecutar un proceso adecuado desde la cola de ejecución, es necesario atravesar desde la cabeza hasta la cola de la cola, por lo que la complejidad temporal de encontrar un proceso adecuado es O (n), cuando el número de procesos en la cola de ejecución aumenta gradualmente Más grande, la eficiencia del planificador se reducirá significativamente.

Los procesos en la cola de ejecución no están en orden, y los procesos en tiempo real y los procesos ordinarios están ordenados de manera desordenada. Cuando el planificador necesita seleccionar el siguiente proceso, debe recorrer desde el principio, comparar la prioridad de cada proceso y ejecutar primero la prioridad más alta. Por supuesto, solo cuando se completa el proceso en tiempo real puede ser el proceso ordinario.

estructura task_struct estructura

struct task_struct {

    long counter;
    long nice;
    unsigned long policy;
    int processor;

    unsigned long cpus_runnable, cpus_allowed;
}
  • Counter representa el segmento de tiempo del proceso, que es el tiempo que el proceso puede ejecutarse en un ciclo de programación.
  • Niza representa la prioridad estática de este proceso. Con la macro NICE_TO_TICKS, el valor agradable correspondiente se puede convertir en un segmento de tiempo correspondiente y almacenarse en el contador
  • La política es la estrategia de programación del proceso. El proceso en tiempo real utiliza SCHED_RR o SCHED_FIFO. El proceso ordinario usa SCHED_OTHER
    • SCHED_RR: se rota la misma prioridad, primero se programa una prioridad diferente o una prioridad alta
    • SCHED_FIFO: la misma prioridad adopta el orden de orden de llegada, es decir, si el proceso programado primero no se completa, este último solo se puede poner en cola. Las diferentes prioridades también son de alta prioridad. Si el proceso en tiempo real de alta prioridad no está terminado, el proceso de baja prioridad no se puede ejecutar.
  • pocessor: representa el procesador en el que se ejecuta el proceso actual y se utilizará en el sistema SMP
  • cpu_allowed: representa que el proceso actual puede ejecutarse en esas CPU.

Cálculo del segmento de tiempo

El planificador O (n) utiliza el método TICK para calcular el segmento de tiempo correspondiente en función del valor agradable del proceso correspondiente.

#if HZ < 200
#define TICK_SCALE(x)	((x) >> 2)
#elif HZ < 400
#define TICK_SCALE(x)	((x) >> 1)
#elif HZ < 800
#define TICK_SCALE(x)	(x)
#elif HZ < 1600
#define TICK_SCALE(x)	((x) << 1)
#else
#define TICK_SCALE(x)	((x) << 2)
#endif

#define NICE_TO_TICKS(nice)	(TICK_SCALE(20-(nice))+1)

El rango de valores agradable es -20 ~ +19, cuanto menor es el valor, mayor es la prioridad. El valor agradable predeterminado del proceso es 0, luego la prioridad estática predeterminada del proceso es igual a 20.

Usamos 100HZ para calcular el intervalo de tiempo que puede ocupar el siguiente proceso para cada valor agradable.

buen valor -20 -10 0 0 +10 +19
100Hz 11 marca 8 marca 6tick 3 marca 1 marca
Rebanada de tiempo 110ms 80ms 60ms 30ms 10ms

Por supuesto, estos intervalos de tiempo se calculan de acuerdo con la prioridad estática, y cuando el proceso se está ejecutando, compensará el proceso de suspensión.

O (n) núcleo del algoritmo del planificador

Seleccione un proceso con la máxima prioridad de la cola de ejecución

	list_for_each(tmp, &runqueue_head) {
		p = list_entry(tmp, struct task_struct, run_list);
		if (can_schedule(p, this_cpu)) {
			int weight = goodness(p, this_cpu, prev->active_mm);
			if (weight > c)
				c = weight, next = p;
		}
	}

Es recorrer uno por uno desde la cola de ejecución runqueue. La función can_schedule se usa para determinar si el proceso actual puede ejecutarse en this_cpu, que es para el sistema SMP.

El algoritmo principal principal es encontrar el proceso con la máxima prioridad en la función de bondad.

static inline int goodness(struct task_struct * p, int this_cpu, struct mm_struct *this_mm)
{

	/*
	 * Non-RT process - normal case first.
	 */
	if (p->policy == SCHED_OTHER) {
		
		weight = p->counter;
		if (!weight)
			goto out;
			
#ifdef CONFIG_SMP
		/* Give a largish advantage to the same processor...   */
		/* (this is equivalent to penalizing other processors) */
		if (p->processor == this_cpu)
			weight += PROC_CHANGE_PENALTY;
#endif

		/* .. and a slight advantage to the current MM */
		if (p->mm == this_mm || !p->mm)
			weight += 1;
		weight += 20 - p->nice;
		goto out;
	}

}
  • El fragmento de código anterior es para procesos ordinarios. Si la estrategia de programación es SCHED_OTHER, corresponde a un proceso ordinario. Si weigt = 0 significa que no hay intervalo de tiempo para este proceso, simplemente salte
  • En el sistema SMP, si este proceso se estaba ejecutando previamente en la CPU actual, debido a las características de la memoria caché, aumentará el intervalo de tiempo correspondiente para este tipo de CPU, correspondiente a castigar otros procesos
  • Si este proceso y el proceso actual comparten una estructura mm_struct, o si el proceso actual es un subproceso del núcleo, aumente el intervalo de tiempo.
  • En circunstancias normales, la prioridad dinámica del proceso ordinario = el segmento de tiempo restante + la prioridad estática del proceso

El proceso en tiempo real es simple y aproximado, agregando directamente 1000 a la prioridad estática del proceso en tiempo real, porque la prioridad estática de cada proceso en tiempo real es diferente.

weight = 1000 + p->rt_priority;

 

Inicialización de tiempo de proceso

A medida que pasa el tiempo, pueden ejecutarse los segmentos de tiempo de todos los procesos. En este momento, todos los procesos deben inicializarse una vez.

	/* Do we need to re-calculate counters? */
	if (unlikely(!c)) {
		struct task_struct *p;

		spin_unlock_irq(&runqueue_lock);
		read_lock(&tasklist_lock);
		for_each_task(p)
			p->counter = (p->counter >> 1) + NICE_TO_TICKS(p->nice);
		read_unlock(&tasklist_lock);
		spin_lock_irq(&runqueue_lock);
		goto repeat_schedule;
	}

Es decir, cuando no se puede encontrar ningún proceso desde la cola de ejecución, el contador se reiniciará para todos los procesos en este momento. Por supuesto, el proceso de suspensión puede no completar el intervalo de tiempo, debe agregar el intervalo de tiempo restante del proceso de suspensión. Sin embargo, para evitar que la prioridad acumulada del proceso de sueño que consume IO sea demasiado alta, debe dividir la mitad.

Actualización de intervalo de tiempo

La interrupción de marca en el sistema actualizará el segmento de tiempo del proceso actual.

void update_process_times(int user_tick)
{
	struct task_struct *p = current;
	int cpu = smp_processor_id(), system = user_tick ^ 1;

	update_one_process(p, user_tick, system, cpu);
	if (p->pid) {
		if (--p->counter <= 0) {
			p->counter = 0;
			p->need_resched = 1;
		}
		if (p->nice > 0)
			kstat.per_cpu_nice[cpu] += user_tick;
		else
			kstat.per_cpu_user[cpu] += user_tick;
		kstat.per_cpu_system[cpu] += system;
	} else if (local_bh_count(cpu) || local_irq_count(cpu) > 1)
		kstat.per_cpu_system[cpu] += system;
}

Cuando llega cada pausa, el contador se reducirá en 1. Si el valor del contador es 0, significa que el intervalo de tiempo se ha agotado y debe establecer el indicador need_resced. En el punto de programación, determinará si el proceso actual establece este valor y, si está configurado, programe.

Problemas que enfrenta el planificador O (n)

  • La complejidad del tiempo es O (n). El rendimiento está bien cuando hay pocos procesos en el sistema, pero cuando los procesos en el sistema aumentan gradualmente, el tiempo para seleccionar el siguiente proceso aumenta gradualmente. Y cuando no hay un proceso en ejecución en el sistema, la reinicialización del segmento de tiempo del proceso también consume bastante tiempo, en el caso de muchos procesos en el sistema.
  • Problema de expansión SMP. Cuando necesita elegir el siguiente proceso, debe bloquear toda la cola de ejecución, spin_lock_irq (& runqueue_lock); cuando hay más procesos en el sistema, el tiempo en la sección crítica es más largo, lo que hace que la CPU restante gire Más derrochador
  • La eficiencia de ejecución del proceso en tiempo real, ya que el proceso en tiempo real y el proceso ordinario están en una lista, cada vez que se verifica el proceso en tiempo real, se debe escanear toda la lista, lo que da como resultado que el proceso en tiempo real no sea muy "en tiempo real"
  • Problema de desperdicio de recursos de CPU: debido a que solo hay una cola de ejecución en el sistema, cuando el número de procesos en la cola de ejecución es menor que el número de CPU, las CPU restantes están casi inactivas, desperdiciando recursos
  • Problema de caché de caché: cuando el proceso en el sistema se reduce gradualmente, el proceso que originalmente se ejecutó en la CPU1 tiene que ejecutarse en la CPU 2. Como resultado, cuando se ejecuta en la CPU2, la línea de caché está casi en blanco, lo que afecta la eficiencia.
  • En resumen, el planificador O (n) tiene muchos problemas, pero definitivamente hay problemas que resolver. Por lo tanto, el planificador O (1) se introdujo en Linux2.6.
187 artículos originales publicados · ganó 108 · 370,000 visitas

Supongo que te gusta

Origin blog.csdn.net/longwang155069/article/details/104428914
Recomendado
Clasificación