Programación del CFS análisis de código principal dos

En el último artículo, analizamos el código principal de CFS, y los contenidos diseñados son:

  • Cómo el planificador inicializa un proceso cuando se crea el proceso
  • Cómo se agrega el proceso a la cola de ejecución de CFS
  • Cuando se agrega un proceso a la cola de ejecución de CFS, cómo elegir el siguiente proceso para ejecutar

¿Esta sección continúa analizando cómo se adelanta un proceso alrededor del ciclo de vida de un proceso? Como dormir ¿Cómo fue despachado?

Schedule_tick (programación periódica)

La programación periódica significa que el kernel de Linux actualizará el tiempo de ejecución del proceso actual en cada tic. Se ha determinado si el proceso actual necesita ser programado.

Update_process_times se llamará en la función de procesamiento de la interrupción del reloj, y finalmente se llamará a la función Scheduler_tick relacionada con el planificador

void scheduler_tick(void)
{
	int cpu = smp_processor_id();
	struct rq *rq = cpu_rq(cpu);
	struct task_struct *curr = rq->curr;
	struct rq_flags rf;

	sched_clock_tick();

	rq_lock(rq, &rf);

	update_rq_clock(rq);
	curr->sched_class->task_tick(rq, curr, 0);
	cpu_load_update_active(rq);
	calc_global_load_tick(rq);
	psi_task_tick(rq);

	rq_unlock(rq, &rf);

	perf_event_task_tick();

#ifdef CONFIG_SMP
	rq->idle_balance = idle_cpu(cpu);
	trigger_load_balance(rq);
#endif
}

Obtenga la cola de ejecución rq en la CPU actual y llame a la función task_tick en la clase de programación del proceso de acuerdo con la clase de programación sched_class. Aquí solo describimos la clase de programación CFS

static void task_tick_fair(struct rq *rq, struct task_struct *curr, int queued)
{
	struct cfs_rq *cfs_rq;
	struct sched_entity *se = &curr->se;

	for_each_sched_entity(se) {
		cfs_rq = cfs_rq_of(se);
		entity_tick(cfs_rq, se, queued);
	}

	if (static_branch_unlikely(&sched_numa_balancing))
		task_tick_numa(rq, curr);

	update_misfit_status(curr, rq);
	update_overutilized_status(task_rq(curr));
}

Obtenga la entidad de programación se a través de la task_struct actual, luego obtenga la cola de ejecución CFS de acuerdo con la entidad de programación se, y realice otras operaciones a través de la función entity_tick

static void
entity_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr, int queued)
{
	/*
	 * Update run-time statistics of the 'current'.
	 */
	update_curr(cfs_rq);

	/*
	 * Ensure that runnable average is periodically updated.
	 */
	update_load_avg(cfs_rq, curr, UPDATE_TG);
	update_cfs_group(curr);


	if (cfs_rq->nr_running > 1)
		check_preempt_tick(cfs_rq, curr);
}
  • update_curr ha sido analizado anteriormente, esta función es principalmente para actualizar el tiempo de ejecución del proceso actual actual, vruntime y min_vruntime de la cola de ejecución CFS
  • update_load_avg se usa principalmente para actualizar la carga de la entidad de programación y la carga de la cola en ejecución CFS, que se describen en detalle en la sección de carga
  • Si el número actual de colas de ejecución de CFS es mayor que 1, debe atenerse a si necesita evitar el proceso actual.
static void
check_preempt_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr)
{
	unsigned long ideal_runtime, delta_exec;
	struct sched_entity *se;
	s64 delta;

	ideal_runtime = sched_slice(cfs_rq, curr);
	delta_exec = curr->sum_exec_runtime - curr->prev_sum_exec_runtime;
	if (delta_exec > ideal_runtime) {
		resched_curr(rq_of(cfs_rq));
		/*
		 * The current task ran long enough, ensure it doesn't get
		 * re-elected due to buddy favours.
		 */
		clear_buddies(cfs_rq, curr);
		return;
	}

	/*
	 * Ensure that a task that missed wakeup preemption by a
	 * narrow margin doesn't have to wait for a full slice.
	 * This also mitigates buddy induced latencies under load.
	 */
	if (delta_exec < sysctl_sched_min_granularity)
		return;

	se = __pick_first_entity(cfs_rq);
	delta = curr->vruntime - se->vruntime;

	if (delta < 0)
		return;

	if (delta > ideal_runtime)
		resched_curr(rq_of(cfs_rq));
}
  • sched_slice se utiliza para obtener el tiempo de ejecución ideal del proceso actual en un ciclo de programación
  • sum_exec_runtime representa el tiempo total de ejecución en esta programación, que se actualiza cada vez en update_curr
  • prev_sum_exec_runtime representa la última hora programada y se configura en la función pick_next.
  • El tiempo delta_exec representa el tiempo de ejecución real en este ciclo de programación.
  • Si el tiempo de ejecución es mayor que el tiempo de programación racional, significa que el tiempo de programación ha superado las expectativas y debe programarse, debe establecer el indicador need_resched
  • Si el tiempo de ejecución del tiempo es menor que sysctl_sched_min_granularity, no se requiere programación. sysctl_sched_min_granularity Este valor garantiza el tiempo mínimo de ejecución en un ciclo de programación
  • Encuentre la entidad de programación más a la izquierda del árbol rojo-negro del CFS. Compare el vruntime del proceso actual con el vruntime de se
  • Si delta es menor que 0, significa que el vruntime del proceso actual es más pequeño que el vruntime más reciente.
  • Si es mayor que ideal_runtime, si es mayor que el tiempo ideal, significa que el tiempo de ejecución ha excedido demasiado y necesita ser programado.

Proceso de sueño

Cuando un proceso tiene que abandonar la CPU debido a la espera de recursos, elegirá programarse. Por ejemplo, cuando el puerto serie está esperando que se envíen los datos, debe abandonar la CPU, dejar que otros procesos ocupen la CPU y utilizar la CPU para obtener los máximos recursos. Por lo general, el proceso que necesita dormir utilizará la función de programación para abandonar la CPU

asmlinkage __visible void __sched schedule(void)
{
	struct task_struct *tsk = current;

	sched_submit_work(tsk);
	do {
		preempt_disable();
		__schedule(false);
		sched_preempt_enable_no_resched();
	} while (need_resched());
}

static void __sched notrace __schedule(bool preempt)
{
	cpu = smp_processor_id();
	rq = cpu_rq(cpu);
	prev = rq->curr;

	if (!preempt && prev->state) {
		if (signal_pending_state(prev->state, prev)) {
			prev->state = TASK_RUNNING;
		} else {
			deactivate_task(rq, prev, DEQUEUE_SLEEP | DEQUEUE_NOCLOCK);
			prev->on_rq = 0;

       ........
}

 Cuando un proceso programa una función de programación, el parámetro pasado es plano. Falso significa que la preferencia no se está llevando a cabo actualmente. Anteriormente, el estado del proceso se describía en el concepto básico del proceso: cuando el estado del proceso se está ejecutando, es igual a 0 y el resto no es cero. Luego, a través de la función deactivate_task, el proceso actual se elimina de rq.

void deactivate_task(struct rq *rq, struct task_struct *p, int flags)
{
	if (task_contributes_to_load(p))
		rq->nr_uninterruptible++;

	dequeue_task(rq, p, flags);
}

static inline void dequeue_task(struct rq *rq, struct task_struct *p, int flags)
{

	p->sched_class->dequeue_task(rq, p, flags);
}

Finalmente, se llama a la función dequeue_task en la clase de programación que pertenece al proceso. Aquí está la clase de programación CFS como ejemplo.

static void dequeue_task_fair(struct rq *rq, struct task_struct *p, int flags)
{
	struct cfs_rq *cfs_rq;
	struct sched_entity *se = &p->se;
	int task_sleep = flags & DEQUEUE_SLEEP;

	for_each_sched_entity(se) {
		cfs_rq = cfs_rq_of(se);
		dequeue_entity(cfs_rq, se, flags);

                cfs_rq->h_nr_running--;
       }
}

Obtenga la entidad de programación del proceso y luego obtenga la cola de ejecución CFS a la que pertenece la entidad de programación, y elimine la entidad de programación de la cola de ejecución CFS a través de la función dequeue_entity

static void
dequeue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags)
{
	/*
	 * Update run-time statistics of the 'current'.
	 */
	update_curr(cfs_rq);

	/*
	 * When dequeuing a sched_entity, we must:
	 *   - Update loads to have both entity and cfs_rq synced with now.
	 *   - Subtract its load from the cfs_rq->runnable_avg.
	 *   - Subtract its previous weight from cfs_rq->load.weight.
	 *   - For group entity, update its weight to reflect the new share
	 *     of its group cfs_rq.
	 */
	update_load_avg(cfs_rq, se, UPDATE_TG);
	dequeue_runnable_load_avg(cfs_rq, se);

	update_stats_dequeue(cfs_rq, se, flags);

	clear_buddies(cfs_rq, se);

	if (se != cfs_rq->curr)
		__dequeue_entity(cfs_rq, se);
	se->on_rq = 0;
  •  Cuando se elimina una cola CFS Runxing de un producto de entidad de programación, se deben hacer las siguientes cosas
  • Actualice la carga de entidades de programación y colas de ejecución CFS
  • Reste la carga de la entidad de programación de CFS_rq-> runnable_avg
  • Reste el peso de la entidad de programación y el peso de programación del grupo, etc.
  • Llame a la función __dequeue_entity para eliminar la entidad de planificación que debe eliminarse del árbol rojo-negro CFS
  • Luego actualice el valor de on_rq igual a 0, lo que indica que esta entidad de programación ya no está en la cola de CFS.

Despierta un proceso

Antes de bifurcar un nuevo proceso, finalmente active un proceso a través de wake_up_new_task, esta función ha hablado sobre cómo agregar un proceso a la cola preparada de CFS

void wake_up_new_task(struct task_struct *p)
{
    p->state = TASK_RUNNING;

    activate_task(rq, p, ENQUEUE_NOCLOCK);
    p->on_rq = TASK_ON_RQ_QUEUED;
	
    check_preempt_curr(rq, p, WF_FORK);
}

Esta función se agregará a la cola lista a través de activar_tarea, y la función check_preempt_curr se usa para verificar si el proceso despertado puede forzar el proceso actual. Debido a que un proceso de activación puede ser un proceso en tiempo real de mayor prioridad, el proceso actual es un proceso ordinario, etc., puede ocurrir.

void check_preempt_curr(struct rq *rq, struct task_struct *p, int flags)
{
	const struct sched_class *class;

	if (p->sched_class == rq->curr->sched_class) {
		rq->curr->sched_class->check_preempt_curr(rq, p, flags);
	} else {
		for_each_class(class) {
			if (class == rq->curr->sched_class)
				break;
			if (class == p->sched_class) {
				resched_curr(rq);
				break;
			}
		}
	}

}
  • Si la clase de programación del proceso despertado y la clase de programación del proceso actualmente en ejecución son las mismas, se llama a la devolución de llamada check_preempt_curr en la clase de programación
  • Si la clase de programación del proceso despertado es diferente de la clase de programación actualmente en ejecución. Si el proceso actual es un proceso ordinario y el proceso en tiempo real se despierta aquí, se llama directamente a la función reshced_curr para establecer el indicador need_sched para el proceso actual y enviarlo al siguiente punto de envío.
  • Si el proceso actual es de una clase de programación más baja que el proceso despertado, debe establecer el indicador de programación para programar el proceso actual
  • Si el proceso actual y la clase de programación del proceso de activación son iguales, compruebe si la función check_preempt_curr requiere la programación
  • Si el proceso actual tiene una clase de programación más alta que el proceso despertado, entonces no se hace nada
static void check_preempt_wakeup(struct rq *rq, struct task_struct *p, int wake_flags)
{
    if (wakeup_preempt_entity(se, pse) == 1) 
           resched_curr(rq);

}

Determine si el proceso actual puede ser forzado por wakeup_preempt_entity, y establezca el indicador need_sched si es posible


/*
 * Should 'se' preempt 'curr'.
 *
 *             |s1
 *        |s2
 *   |s3
 *         g
 *      |<--->|c
 *
 *  w(c, s1) = -1
 *  w(c, s2) =  0
 *  w(c, s3) =  1
 *
 */

static int
wakeup_preempt_entity(struct sched_entity *curr, struct sched_entity *se)
{
	s64 gran, vdiff = curr->vruntime - se->vruntime;

	if (vdiff <= 0)
		return -1;

	gran = wakeup_gran(se);
	if (vdiff > gran)
		return 1;

	return 0;
}
  • El primer parámetro es la entidad de programación del proceso actual, y el segundo parámetro es la entidad de programación del proceso despertado
  • El valor de vdiff es la diferencia entre el tiempo virtual de la entidad de planificación actual y el tiempo virtual de la entidad de planificación del proceso de activación
  • Si vdiff es menor que 0, significa que el tiempo virtual del proceso actual es menor que el tiempo de ejecución de activación, por lo que no se adelanta
  • wakeup_gran se usa para calcular el tiempo de ejecución de la entidad de planificación de reactivación en el tiempo sysctl_sched_wakeup_granularity.
  • Probablemente significa que la entidad de programación del proceso actual es menor que la entidad de programación del proceso de activación y el valor es mayor que gran, entonces puede elegir la programación
  • Si la diferencia entre el tiempo de ejecución del proceso actual y el proceso de activación no alcanza el gran, no se selecciona y se puede ver claramente a través del comentario

Resumen

  • Cuando fork crea un proceso, la clase de planificación correspondiente se establecerá para este proceso en la función sched_fork, se establecerá la prioridad y se actualizará el valor de vruntime
  • En este momento, debe agregar el proceso a la cola lista. Para la cola lista CFS, debe agregarlo al árbol rojo y negro CFS. El tiempo de ejecución del proceso de seguimiento se agrega como el valor clave. Debido a que se agrega un nuevo proceso a la cola lista, la carga y el peso de toda la cola lista cambiarán y es necesario volver a calcularla.
  • Cuando se agrega a la cola lista, debe seleccionar un nuevo proceso a través de la devolución de llamada de pick_next. La estrategia es elegir el proceso de CFS vruntime de árbol rojo negro para ejecutar
  • Cuando este proceso se ejecuta durante un período de tiempo, utilizará la función schedule_tick para determinar si el tiempo de ejecución del proceso actual excede el tiempo ideal, si lo excede, se enviará
  • O cuando este proceso necesita esperar los recursos del sistema, también cederá la CPU a través de la función de programación, luego el proceso se eliminará de la cola preparada CFS, la eliminación de un proceso también cambiará el peso y la carga de toda la cola de ejecución CFS. , Necesitas recalcular
  • Cuando el recurso esté listo, debe reactivar el proceso actual. Al reactivar, debe verificar si el proceso actual será reemplazado por el proceso de alta prioridad. Si hay una clase de programación de alta prioridad, se anticipará. Si es la misma clase de programación, debe Determine si el valor de vruntime es mayor que un rango y, de ser así, establezca el indicador de programación.

 

187 artículos originales publicados · ganó 108 · 370,000 visitas

Supongo que te gusta

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