CFS ordonnancement analyse de code principal deux

Dans le dernier article, nous avons analysé le code principal de CFS, et le contenu conçu est:

  • Comment le planificateur initialise un processus lors de sa création
  • Comment le processus est ajouté à la file d'attente d'exécution CFS
  • Lorsqu'un processus est ajouté à la file d'attente d'exécution CFS, comment choisir le prochain processus à exécuter

Cette section continue d'analyser comment un processus est anticipé sur le cycle de vie d'un processus? Comment dormir? Comment a-t-il été envoyé?

Schedule_tick (planification périodique)

Une planification périodique signifie que le noyau Linux mettra à jour le temps d'exécution du processus en cours à chaque tick. Il a été déterminé si le processus en cours doit être planifié.

Update_process_times sera appelé dans la fonction de traitement de l'interruption d'horloge, et enfin sera appelé dans la fonction scheduler_tick liée à l'ordonnanceur

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
}

Obtenez la file d'attente en cours d'exécution sur la CPU actuelle et appelez la fonction task_tick dans la classe de planification de processus en fonction de la classe de planification sched_class. Ici, nous décrivons uniquement la classe de planification 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));
}

Obtenez l'entité de planification se par le biais de la tâche_struct en cours, puis obtenez la file d'attente d'exécution CFS selon l'entité de planification se et effectuez d'autres opérations via la fonction 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 a été analysé auparavant, cette fonction consiste principalement à mettre à jour le temps d'exécution du processus courant, vruntime et min_vruntime de la file d'attente d'exécution CFS
  • update_load_avg est principalement utilisé pour mettre à jour la charge de l'entité de planification et la charge de la file d'attente en cours d'exécution CFS, qui sont décrites en détail dans la section charge
  • Si le nombre actuel de files d'attente d'exécution CFS est supérieur à 1, vous devez vous demander si vous devez préempter le processus en cours.
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 est utilisé pour obtenir le temps d'exécution idéal du processus en cours dans un cycle de planification
  • sum_exec_runtime représente le temps d'exécution total dans cette planification, qui est mis à jour à chaque fois dans update_curr
  • prev_sum_exec_runtime représente la dernière heure planifiée et est défini dans la fonction pick_next.
  • Le temps delta_exec représente le temps d'exécution réel dans ce cycle de planification.
  • Si le temps d'exécution est supérieur au temps de planification rationnel, cela signifie que le temps de planification a dépassé les attentes et doit être planifié, vous devez définir l'indicateur need_resched
  • Si la durée d'exécution de l'heure est inférieure à sysctl_sched_min_granularity, aucune planification n'est requise. sysctl_sched_min_granularity Cette valeur garantit le temps d'exécution minimum dans un cycle de planification
  • Recherchez l'entité de planification la plus à gauche dans l'arbre rouge-noir du CFS. Comparer le temps d'exécution du processus actuel avec le temps d'exécution de soi
  • Si delta est inférieur à 0, cela signifie que le processus en cours vruntime est plus petit que le dernier vruntime.
  • S'il est supérieur à idéal_runtime, s'il est supérieur à l'heure idéale, cela signifie que la durée d'exécution a trop dépassé et doit être planifiée.

Processus de sommeil

Lorsqu'un processus doit abandonner le CPU en raison de l'attente de ressources, il choisit de se planifier lui-même. Par exemple, lorsque le port série attend l'envoi de données, vous devez abandonner le processeur, laisser d'autres processus occuper le processeur et utiliser le processeur pour des ressources maximales. Habituellement, le processus qui doit dormir utilisera la fonction de planification pour abandonner le processeur

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;

       ........
}

 Lorsqu'un processus planifie une fonction de planification, le paramètre transmis est flase. Faux signifie que la préemption n'a pas lieu actuellement. Auparavant, l'état du processus était décrit dans le concept de base du processus. Lorsque l'état du processus est en cours d'exécution, il est égal à 0 et le reste est non nul. Ensuite, grâce à la fonction deactivate_task, le processus en cours est supprimé de la 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);
}

Enfin, elle est appelée à la fonction dequeue_task dans la classe d'ordonnancement appartenant au processus. Voici la classe d'ordonnancement CFS à titre d'exemple.

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--;
       }
}

Obtenez l'entité de planification du processus, puis obtenez la file d'attente d'exécution CFS à laquelle appartient l'entité de planification et supprimez l'entité de planification de la file d'attente d'exécution CFS via la fonction 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;
  •  Lorsqu'une file d'attente CFS Runxing est supprimée d'un produit d'entité de planification, les opérations suivantes doivent être effectuées
  • Mettre à jour la charge des entités de planification et des files d'attente d'exécution CFS
  • Soustrayez la charge de l'entité de planification de CFS_rq-> runnable_avg
  • Soustrayez le poids de l'entité de planification et du poids de planification de groupe, etc.
  • Appelez la fonction __dequeue_entity pour supprimer l'entité de planification qui doit être supprimée de l'arborescence CFS rouge-noir
  • Mettez ensuite à jour la valeur de on_rq égale à 0, indiquant que cette entité de planification n'est plus dans la file d'attente CFS ready.

Réveillez un processus

Avant de bifurquer un nouveau processus, réveillez enfin un processus via wake_up_new_task. Cette fonction a expliqué comment ajouter un processus à la file d'attente CFS ready dans l'article précédent.

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);
}

Cette fonction sera ajoutée à la file d'attente prête via activate_task, et la fonction check_preempt_curr est utilisée pour vérifier si le processus éveillé peut forcer le processus en cours. Étant donné qu'un processus de réveil peut être un processus en temps réel de priorité plus élevée, le processus en cours est un processus ordinaire, etc., peut se produire.

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 classe de planification du processus éveillé et la classe de planification du processus en cours d'exécution sont identiques, le rappel check_preempt_curr dans la classe de planification est appelé
  • Si la classe de planification du processus éveillé est différente de la classe de planification en cours d'exécution. Si le processus en cours est un processus ordinaire et que le processus en temps réel est réveillé ici, la fonction reshced_curr est directement appelée pour définir l'indicateur need_sched du processus en cours et le distribuer au point de répartition suivant.
  • Si le processus en cours est d'une classe de planification inférieure à celle du processus éveillé, vous devez définir l'indicateur de planification pour planifier le processus en cours
  • Si le processus en cours et la classe de planification du processus de réveil sont identiques, vérifiez si la planification est requise par la fonction check_preempt_curr
  • Si le processus en cours a une classe d'ordonnancement plus élevée que le processus éveillé, rien n'est fait
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);

}

Déterminez si le processus en cours peut être forcé par wakeup_preempt_entity et définissez l'indicateur need_sched si possible


/*
 * 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;
}
  • Le premier paramètre est l'entité de planification du processus en cours, et le deuxième paramètre est l'entité de planification du processus éveillé
  • La valeur de vdiff est la différence entre l'heure virtuelle de l'entité de planification actuelle et l'heure virtuelle de l'entité de planification du processus de réveil
  • Si vdiff est inférieur à 0, cela signifie que l'heure virtuelle du processus en cours est inférieure à l'heure de réveil, il n'est donc pas préempté
  • wakeup_gran est utilisé pour calculer le temps d'exécution de l'entité de planification de réveil en temps sysctl_sched_wakeup_granularity.
  • Signifie probablement que l'entité de planification du processus en cours est inférieure à l'entité de planification du processus de réveil et que la valeur est supérieure à gran, vous pouvez alors choisir la planification
  • Si la différence entre le vrumtime du processus en cours et le processus de réveil n'atteint pas le gran, il n'est pas sélectionné et il peut être clairement vu à travers le commentaire

Résumé

  • Lorsqu'un processus est créé par fork, la classe de planification correspondante sera définie pour ce processus dans la fonction sched_fork, la priorité sera définie et la valeur de vruntime sera mise à jour
  • À ce stade, vous devez ajouter le processus à la file d'attente prête. Pour la file d'attente prête CFS, vous devez l'ajouter à l'arborescence rouge et noire CFS. Le temps d'exécution du processus de suivi est ajouté comme valeur clé. Étant donné qu'un nouveau processus est ajouté à la file d'attente prête, la charge et le poids de toute la file d'attente prête changeront et il doit être recalculé.
  • Une fois ajouté à la file d'attente prête, vous devez sélectionner un nouveau processus via le rappel de pick_next. La stratégie consiste à choisir le processus de CFS red black tree vruntime à exécuter
  • Lorsque ce processus s'exécute pendant une période de temps, il utilisera la fonction schedule_tick pour déterminer si la durée d'exécution du processus en cours dépasse l'heure idéale, si elle la dépasse, elle sera envoyée
  • Ou lorsque ce processus doit attendre les ressources système, il abandonne également le CPU via la fonction de planification, puis le processus sera supprimé de la file d'attente CFS ready, la suppression d'un processus modifiera également le poids et la charge de la file d'attente d'exécution CFS entière. , Vous devez recalculer
  • Lorsque la ressource est prête, vous devez réactiver le processus en cours. Lorsque vous vous réveillez, vous devez vérifier si le processus en cours sera préempté par le processus à priorité élevée. S'il existe une classe de planification à priorité élevée, elle sera préemptée. S'il s'agit de la même classe de planification, vous devez Déterminez si la valeur de vruntime est supérieure à une plage et, le cas échéant, définissez l'indicateur de planification.

 

Publié 187 articles originaux · gagné 108 · 370 000 vues

Je suppose que tu aimes

Origine blog.csdn.net/longwang155069/article/details/104654875
conseillé
Classement