Valid HTML 4.01!

Scenariusz do ćwiczeń 4
Szeregowanie procesów w Linuksie

Spis treści


Bibliografia


Zakres wartości makr używanych do obsługi priorytetów

Ćwiczenie 1

Makra wykorzystywane do obsługi priorytetów są zdefiniowane następująco:


#define MAX_USER_RT_PRIO        100
#define MAX_RT_PRIO             MAX_USER_RT_PRIO
#define MAX_PRIO                (MAX_RT_PRIO + 40)
/*
 * Convert user-nice values [ -20 ... 0 ... 19 ]
 * to static priority [ MAX_RT_PRIO..MAX_PRIO-1 ],
 * and back.
 */
#define NICE_TO_PRIO(nice)      (MAX_RT_PRIO + (nice) + 20)
#define PRIO_TO_NICE(prio)      ((prio) - MAX_RT_PRIO - 20)
#define TASK_NICE(p)            PRIO_TO_NICE((p)->static_prio)

/*
 * 'User priority' is the nice value converted to something we
 * can work with better when scaling various scheduler parameters,
 * it's a [ 0 ... 39 ] range.
 */
#define USER_PRIO(p)            ((p)-MAX_RT_PRIO)
#define TASK_USER_PRIO(p)       USER_PRIO((p)->static_prio)
#define MAX_USER_PRIO           (USER_PRIO(MAX_PRIO))

Parametr nice przyjmuje wartości z zakresu od -20 do +19 (-20 oznacza wysoki priorytet, a +19 - niski priorytet)

Jakie wartości może przyjmować makro NICE_TO_PRIO?

Policz także zakres wartości dla makra PRIO_TO_NICE (podpowiedź: [-20,19]).

Jakie wartości są przechowywane w polu static_prio struktury task_struct? (podpowiedź: [100,139])

Jaka jest wartość MAX_USER_PRIO? (podpowiedź: 40)

Pomocne przykłady, tabelka i rysunek:

  1. Jeżeli wartość nice wynosi -20, to:
    100+(-20)+20=100, czyli dla wartości nice=-20 makro przekaże wartość 100.

  2. Jeżeli wartość nice wynosi 0, to:
    100+(0)+20=120, czyli dla wartości nice=0 makro przekaże wartość 120.

  3. Jeżeli wartość nice wynosi +19, to:
    100+(+19)+20=139, czyli dla wartości nice=19 makro przekaże wartość 139.

nice
PRIO_TO_NICE
TASK_NICE
static_prio
NICE_TO_PRIO
TASK_NICE
USER_PRIO
TASK_USER_PRIO
-20 100 0
0 120 20
19 139 39


NICE_TO_PRIO



Zakres wartości kwantu czasu (pola timeslice)

Ćwiczenie 2

Jakie wartości może przyjmować kwant czasu, przechowywany w polu timeslice w task_struct procesu? Przeanalizuj podane fragmenty kodu i wypełnij drugą kolumnę poniższej tabelki. Można założyć, że MIN_TIMESLICE = 5 msec, a DEF_TIMESLICE = 100 msec.


/*
 * Minimum timeslice is 5 msecs (or 1 jiffy, whichever is larger),
 * default timeslice is 100 msecs, maximum timeslice is 800 msecs.
 * Timeslices get refilled after they expire.
 */
#define MIN_TIMESLICE           max(5 * HZ / 1000, 1)
#define DEF_TIMESLICE           (100 * HZ / 1000)

/*
 * task_timeslice() scales user-nice values [ -20 ... 0 ... 19 ]
 * to time slice values: [800ms ... 100ms ... 5ms]
 *
 * The higher a thread's priority, the bigger timeslices
 * it gets during one round of execution. But even the lowest
 * priority thread gets MIN_TIMESLICE worth of execution time.
 */

#define SCALE_PRIO(x, prio) \
        max(x * (MAX_PRIO - prio) / (MAX_USER_PRIO/2), MIN_TIMESLICE)
 
static unsigned int task_timeslice(task_t *p)
{
        if (p->static_prio < NICE_TO_PRIO(0))
                return SCALE_PRIO(DEF_TIMESLICE*4, p->static_prio);
        else
                return SCALE_PRIO(DEF_TIMESLICE, p->static_prio);
}

p->time_slice = task_timeslice(p);

p->static_prio task_time_slice(p) (w msec)
100 800
105 700
110 600
115 500
119 420
120 100
125 75
130 50
135 25
139 5


Zakres wartości priorytetu dynamicznego

Ćwiczenie 3

Przeanalizuj załączony kod i wypełnij drugą kolumnę podanej dalej tabelki.


/*
 * Some helpers for converting nanosecond timing to jiffy resolution
 */
#define NS_TO_JIFFIES(TIME)     ((TIME) / (1000000000 / HZ))
#define JIFFIES_TO_NS(TIME)     ((TIME) * (1000000000 / HZ))

#define PRIO_BONUS_RATIO         25
#define MAX_BONUS               (MAX_USER_PRIO * PRIO_BONUS_RATIO / 100)
#define MAX_SLEEP_AVG           (DEF_TIMESLICE * MAX_BONUS)

#define CURRENT_BONUS(p) \
        (NS_TO_JIFFIES((p)->sleep_avg) * MAX_BONUS / MAX_SLEEP_AVG)

Jaką jest wartość MAX_BONUS? (podpowiedź: 10)

Jaka jest wartość MAX_SLEEP_AVG? (podpowiedź: 1000 msec)

NS_TO_JIFFIES(p->sleep_avg) CURRENT_BONUS(p)
5 0
100 1
500 5
1000 10

Ćwiczenie 4

Za wyliczanie priorytetu dynamicznego odpowiada funkcja effective_prio(). Przeanalizuj załączony kod i policz wyliczony wynik funkcji dla przykładowych wartości priorytetu statycznego i dynamicznego procesu


#define rt_task(p)              ((p)->prio < MAX_RT_PRIO)
#define batch_task(p)           ((p)->policy == SCHED_BATCH)

/*
 * effective_prio - return the priority that is based on the static
 * priority but is modified by bonuses/penalties.
 *
 * We scale the actual sleep average [0 .... MAX_SLEEP_AVG]
 * into the -5 ... 0 ... +5 bonus/penalty range.
 *
 * We use 25% of the full 0...39 priority range so that:
 *
 * 1) nice +19 interactive tasks do not preempt nice 0 CPU hogs.
 * 2) nice -20 CPU hogs do not get preempted by nice 0 tasks.
 *
 * Both properties are important to certain workloads.
 */
static int effective_prio(task_t *p)
{
        int bonus, prio;

        if (rt_task(p))
                return p->prio;

        bonus = CURRENT_BONUS(p) - MAX_BONUS / 2;

        prio = p->static_prio - bonus;
        if (prio < MAX_RT_PRIO)
                prio = MAX_RT_PRIO;
        if (prio > MAX_PRIO-1)
                prio = MAX_PRIO-1;
        return prio;
}

p->prio = effective_prio(p);

CURRENT_BONUS(p) bonus prio dla static_prio=100 prio dla static_prio=120 prio dla static_prio=139
0 -5 105 125 139
1 -4 104 124 139
5 0 100 120 139
10 5 100 115 134


Szeregowanie procesów

Ćwiczenie 5

W funkcji scheduler_tick() bada się 'stopień interaktywności' procesu przy użyciu makra TASK_INTERACTIVE. Jakie wartości wylicza makro DELTA dla różnych wartości priorytetu statycznego procesu (wypełnij drugą, trzecią i czwartą kolumnę poniższej tabelki)?


#define INTERACTIVE_DELTA         2
#define SCALE(v1,v1_max,v2_max) \
        (v1) * (v2_max) / (v1_max)

#define DELTA(p) \
        (SCALE(TASK_NICE(p) + 20, 40, MAX_BONUS) - 20 * MAX_BONUS / 40 + \
                INTERACTIVE_DELTA)

#define TASK_INTERACTIVE(p) ((p)->prio <= (p)->static_prio - DELTA(p))

Warto zauważyć, że - 20 * MAX_BONUS / 40 + INTERACTIVE_DELTA = -3.

p->static_prio TASK_NICE(p) SCALE(TASK_NICE(p) + 20, 40, MAX_BONUS) DELTA(p) p->static_prio - DELTA(p)
100 -20 0 -3 103
110 -10 2 -1 111
120 0 5 2 118
130 10 7 4 126
139 19 9 6 133

delta

Porównaj tę tabelkę z tabelką z ćwiczenia 4. Policz wartość makra TASK_INTERACTIVE(p) dla przykładowych wartości p->static_prio i p->prio. Sprawdź czy zgadza się to z komentarzem umieszczonym w źródłach jądra:


/*
 * Here are a few examples of different nice levels:
 *
 *  TASK_INTERACTIVE(-20): [1,1,1,1,1,1,1,1,1,0,0]
 *  TASK_INTERACTIVE(-10): [1,1,1,1,1,1,1,0,0,0,0]
 *  TASK_INTERACTIVE(  0): [1,1,1,1,0,0,0,0,0,0,0]
 *  TASK_INTERACTIVE( 10): [1,1,0,0,0,0,0,0,0,0,0]
 *  TASK_INTERACTIVE( 19): [0,0,0,0,0,0,0,0,0,0,0]
 *
 * (the X axis represents the possible -5 ... 0 ... +5 dynamic
 *  priority range a task can explore, a value of '1' means the
 *  task is rated interactive.)
 *
 * Ie. nice +19 tasks can never get 'interactive' enough to be
 * reinserted into the active array. And only heavily CPU-hog nice -20
 * tasks will be expired. Default nice 0 tasks are somewhere between,
 * it takes some effort for them to get interactive, but it's not
 * too hard.
 */

Ćwiczenie 6

W funkcji scheduler_tick() bada się także, czy pozostawienie procesu uznanego za 'interaktywny' nie spowoduje zagłodzenia procesów czekających na koniec epoki (czyli umieszczonych w kolejce expired). Do tego służy makro EXPIRED_STARVING. Przeanalizuj treść makro.


#define STARVATION_LIMIT        (MAX_SLEEP_AVG)

/*
 * We place interactive tasks back into the active array, if possible.
 *
 * To guarantee that this does not starve expired tasks we ignore the
 * interactivity of a task if the first expired task had to wait more
 * than a 'reasonable' amount of time. This deadline timeout is
 * load-dependent, as the frequency of array switched decreases with
 * increasing number of running tasks. We also ignore the interactivity
 * if a better static_prio task has expired:
 */
#define EXPIRED_STARVING(rq) \
        ((STARVATION_LIMIT && ((rq)->expired_timestamp && \
                (jiffies - (rq)->expired_timestamp >= \
                        STARVATION_LIMIT * ((rq)->nr_running) + 1))) || \
                        ((rq)->curr->static_prio > (rq)->best_expired_prio))

Jeśli pole rq->expired_timestamp ma wartość zero, to kolejka expired jest pusta (pole jest zerowane podczas zamiany wskaźników active i expired). Niezerowa wartość pola to stempel czasowy wskazujący kiedy do kolejki expired został wstawiony pierwszy proces.

Wartość rq->best_expired_prio jest równa maksymalnej wartości priorytetu statycznego procesu przebywającego w kolejce expired. Proces o niższym priorytecie statycznym nigdy nie zostanie wstawiony ponownie do kolejki active, jeśli w kolejce expired znajduje się proces o wyższym priorytecie statycznym - niezależnie od ich priorytetów dynamicznych.

Ćwiczenie 7

W funkcji scheduler_tick() bada się także, czy proces nie próbuje zmonopolizować procesora (tzn. czy nie ma zbyt długiego kwantu). Wykorzystuje się w tym celu makro TIMESLICE_GRANULARITY. Przeanalizuj treść makro.


#ifdef CONFIG_SMP
#define TIMESLICE_GRANULARITY(p)        (GRANULARITY * \
      (1 << (((MAX_BONUS - CURRENT_BONUS(p)) ?  \
           (MAX_BONUS - CURRENT_BONUS(p)) : 1) - 1)) * num_online_cpus())
#else
#define TIMESLICE_GRANULARITY(p)        (GRANULARITY * \
      (1 << (((MAX_BONUS - CURRENT_BONUS(p)) ?  \
           (MAX_BONUS - CURRENT_BONUS(p)) : 1) - 1)))

A oto jak makro jest używane w funkcji scheduler_tick(). Przeanalizuj i zweryfikuj treść załączonego komentarza.


/*
 * Prevent a too long timeslice allowing a task to monopolize
 * the CPU. We do this by splitting up the timeslice into
 * smaller pieces.
 *
 * Note: this does not mean the task's timeslices expire or
 * get lost in any way, they just might be preempted by
 * another task of equal priority. (one with higher
 * priority would have preempted this task already.) We
 * requeue this task to the end of the list on this priority
 * level, which is in essence a round-robin of tasks with
 * equal priority.
 *
 * This only applies to tasks in the interactive
 * delta range with at least TIMESLICE_GRANULARITY to requeue.
 */
  if (TASK_INTERACTIVE(p) && !((task_timeslice(p) -
       p->time_slice) % TIMESLICE_GRANULARITY(p)) &&
       (p->time_slice >= TIMESLICE_GRANULARITY(p)) &&
       (p->array == rq->active)) {

        requeue_task(p, rq->active);
        set_tsk_need_resched(p);

Operacje na kolejce procesów gotowych

Ćwiczenie 8

Napisz treść funkcji:

Treść tych funkcji jest umieszczona w pliku /source/kernel/sched.c.


/*
 * Adding/removing a task to/from a priority array:
 */
static void dequeue_task(struct task_struct *p, prio_array_t *array)
{
        array->nr_active--;
        list_del(&p->run_list);
        if (list_empty(array->queue + p->prio))
                __clear_bit(p->prio, array->bitmap);
}

static void enqueue_task(struct task_struct *p, prio_array_t *array)
{
        /* ... */
        list_add_tail(&p->run_list, array->queue + p->prio);
        __set_bit(p->prio, array->bitmap);
        array->nr_active++;
        p->array = array;
}

/*
 * Put task to the end of the run list without the overhead of dequeue
 * followed by enqueue.
 */
static void requeue_task(struct task_struct *p, prio_array_t *array)
{
        list_move_tail(&p->run_list, array->queue + p->prio);
}

static inline void enqueue_task_head(struct task_struct *p, prio_array_t *array)
{
        list_add(&p->run_list, array->queue + p->prio);
        __set_bit(p->prio, array->bitmap);
        array->nr_active++;
        p->array = array;
}

©Janina Mincer-Daszkiewicz