Ć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:
Jeżeli wartość nice wynosi -20, to:
100+(-20)+20=100, czyli dla wartości
nice=-20 makro przekaże wartość 100.
Jeżeli wartość nice wynosi 0, to:
100+(0)+20=120, czyli dla wartości
nice=0 makro przekaże wartość 120.
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 |
Ć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 |
Ć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 |
Ć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 |
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);
Ć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 |