slide - Università degli Studi di Roma "Tor Vergata"

Transcript

slide - Università degli Studi di Roma "Tor Vergata"
.
Implementazione dei timer
nel kernel Linux
.
Andrea De Cesare
Corso di Linux Avanzato
Università degli Studi di Roma Tor Vergata
Facoltà di Ingegneria
A.A. 2012/2013
Introduzione
.
▶
I timer sono uno strumento fornito dal kernel per eseguire una
funzione dopo un certo intervallo di tempo.
▶
Linux mette a disposizione due tipi di timer:
▶
Timer standard: granularità massima 1 millisecondo (di default 4
millisecondi).
▶
Timer ad alta risoluzione: granularità dell'ordine di nanosecondi,
dipendente dalla precisione del timer hardware.
Timer tradizionali
.
▶
In Linux il tempo viene conteggiato come numero di tick
dall'avvio della macchina (variabile jiffies).
▶
Un dispositivo hardware invia periodicamente un interrupt al
sistema operativo che provvede ad incrementare la variabile
jiffies.
▶
La frequenza di questo input è definita dalla variabile HZ, quindi
la massima granularità dei timer è 1/HZ.
▶
HZ viene definito in fase di compilazione del kernel e può essere
impostato a 100, 250, 300 oppure 1000 Hz.
▶
Ad ogni timer interrupt il kernel verifica se qualche timer ha
raggiunto la scadenza ed esegue la funzione associata.
.
Conoscere valore di HZ sulla propria macchina
.
cat
/boot/config-`uname -r` | grep CONFIG_HZ
.
.
.
Implementazione dei timer
Strutture dati per i timer
.
▶
L'implementazione originale dei timer consisteva in una lista
doppiamente collegata ordinata in fase di inserimento di un
nuovo timer.
▶
Questa implementazione non è però efficiente all'aumentare del
numero di timer.
▶
Molto spesso i timer vengono rimossi prima di raggiungere la
scadenza! Esempio: timer ritrasmissione pacchetti TCP.
▶
Nel 1997 è stata proposta e realizzata da Finn Arne Gangstad
una nuova struttura dati nota come Timer Wheel.
▶
I timer vengono suddivisi in bucket in base alla loro scadenza.
Timer wheel
.
▶
Non essendo efficiente, in termini di utilizzo di memoria, la
realizzazione di 232 bucket, sono state create 5 diverse
categorie note come Timer Vector.
▶
In totale 29 = 512 bucket anzichè 232 = 4.294.967.296.
Categorie
Timer Vector 1
Bucket
1
Jiffies
Da 20=1 a 28=256
Da 28+1=257 a 216
1
256
Jiffies per
bucket
... 256
2
Timer Vector 2
1
2
... 64
Timer Vector 3
1
2
... 64
Da 216+1 a 220
16.384
Timer Vector 4
1
2
... 64
Timer Vector 5
1
2
... 64
Da 220+1 a 226
da 226+1a 232
1.048.576
67.108.846
Timer wheel
.
Timer Vector 1
Timer Vector 2
Ogni 256 jiffies
~1 sec*
Timer Vector 3
Timer Vector 4
Ogni 16.384 jiffies Ogni 1.048.576 jiffies
~65 sec*
~70 min*
Timer Vector 5
Ogni 67.108.864 jiffies
~75 ore*
Con questa struttura dati:
▶
▶
▶
inserimenti in O(1);
rimozioni in O(1);
scadenze in O(1) ammortizzato (nel caso peggiore O(N)).
Si utilizza il ''lazy sorting''. Si cerca di eseguire l'ordinamento il più
tardi possibile.
* Considerando HZ = 250
Timer wheel
.
▶
▶
Una timer wheel per ogni CPU.
Ogni categoria è formata da un array di liste che compongono i
bucket.
CPU 1
...
...
CPU i
CPU N
tvec_base
tvec_root
TV1
tvec
TV2
tvec
TV3
tvec
TV4
tvec
TV5
256 bucket
64 bucket
64 bucket
64 bucket
64 bucket
Strutture dati per la timer wheel
.
struct tvec {
struct list_head vec[ TVN_SIZE ];
};
struct tvec_root {
struct list_head vec[ TVR_SIZE ];
};
Di default, con CONFIG_BASE_SMALL disabilitato:
▶ TVR_SIZE = 256
▶
TVN_SIZE = 64
Altrimenti, se CONFIG_BASE_SMALL abilitato:
▶
▶
TVR_SIZE = 64
TVN_SIZE = 16
Strutture dati per la timer wheel
.
struct tvec_base {
spinlock_t lock;
struct timer_list * running_timer ;
unsigned long timer_jiffies ;
unsigned long next_timer ;
unsigned long active_timers ;
struct tvec_root tv1;
struct tvec tv2;
struct tvec tv3;
struct tvec tv4;
struct tvec tv5;
} ____cacheline_aligned ;
▶
Struttura dati definita PER_CPU.
Tickless System (Dynamic Ticks)
.
Opzione di compilazione CONFIG_NO_HZ:
This option enables a tickless system: timer interrupts will
only trigger on an as-needed basis both when the system is
busy and when the system is idle.
▶
Alcuni timer non critici, che possono essere eseguiti con un
ritardo rispetto alla scadenza, vengono indicati con il flag
DEFERRABLE.
▶
Se una CPU è in stato sleep non viene risvegliata per eseguire
questi timer.
▶
Prima di entrare in stato sleep la CPU verifica la scadenza del
prossimo timer non deferrable da eseguire per potersi
risvegliare nel momento giusto.
.
.
Inserimento e modifica di timer
Struttura timer_list
.
▶
▶
Definita in kernel/timer.h
Rappresenta un singolo timer
struct timer_list {
struct list_head entry ;
unsigned long expires ;
struct tvec_base *base;
void (* function )( unsigned long);
unsigned long data;
int slack ;
};
Inserimento timer
.
add_timer()
mod_timer()
apply_slack()
__mod_timer()
detach_if_pending()
internal_add_timer()
__internal_add_timer()
Inserimento timer (add_timer)
.
void add_timer ( struct timer_list *timer )
{
BUG_ON ( timer_pending ( timer ));
mod_timer (timer , timer -> expires );
}
EXPORT_SYMBOL ( add_timer );
Modifica timer (mod_timer)
.
int mod_timer ( struct timer_list *timer , unsigned
long expires )
{
expires = apply_slack (timer , expires );
if ( timer_pending ( timer ) && timer -> expires ==
expires )
return 1;
return __mod_timer (timer , expires , false ,
TIMER_NOT_PINNED );
}
EXPORT_SYMBOL ( mod_timer );
Codice
.
▶
▶
__mod_timer
internal_add_timer
▶
__internal_add_timer
▶
add_timer_on
.
.
Interrupt handler
Interrupt handler
.
▶
▶
▶
Ad ogni timer interrupt è necessario verificare la presenza di
timer che hanno raggiunto la scadenza ed eseguirne la relativa
funzione.
I timer vector devono essere riorganizzati spostando ''in
cascata'' i timer tra i bucket.
All'arrivo di un TIMER_SOFTIRQ viene eseguita la funzione
run_timer_softirq, locale per ogni processore.
run_timer_softirq()
__run_timers()
cascade()
Codice
.
▶
▶
▶
run_timer_softirq
__run_timers
cascade
.
.
Eliminazione timer
Eliminazione sicura di timer
.
▶
La funzione del_timer_sync disattiva un timer ed attende che
l'handler termini nel caso in cui fosse in esecuzione.
▶
Essenziale in sistemi SMP per attendere l'eventuale
terminazione dell'handler su altre CPU.
▶
del_timer_sync è una helper function che rende bloccante
try_to_del_timer_sync.
Eliminazione timer (del_timer_sync)
.
#ifdef CONFIG_SMP
int del_timer_sync ( struct timer_list *timer)
{
for (;;) {
int ret = try_to_del_timer_sync (timer );
if (ret >= 0)
return ret;
cpu_relax ();
}
}
EXPORT_SYMBOL ( del_timer_sync );
#endif
Eliminazione timer (try_to_del_timer_sync)
.
int try_to_del_timer_sync ( struct timer_list * timer)
{
struct tvec_base *base;
unsigned long flags ;
int ret = -1;
base = lock_timer_base (timer , &flags);
if (base -> running_timer != timer) {
timer_stats_timer_clear_start_info (timer);
ret = detach_if_pending (timer , base , true);
}
spin_unlock_irqrestore (& base ->lock , flags );
return ret;
}
EXPORT_SYMBOL ( try_to_del_timer_sync );
.
.
Sleep
msleep
.
▶
▶
▶
Utilizzato per interrompere l'esecuzione per un itervallo indicato
in millisecondi.
msleep gestito con timer tradizionali, usleep gestito con timer
ad alta risoluzione.
Utilizzabile solo in ''process context'', non utilizzabile in
''interrupt context''.
Pone lo stato del processo a TASK_UNINTERRUPTIBLE
Crea un timer con il puntatore a current come data
Invoca lo scheduler
Alla scadenza del timer riporta il task in stato RUNNABLE
Codice
.
▶
▶
msleep
schedule_timeout_uninterruptible
▶
schedule_timeout
▶
process_timeout
▶
wake_up_process
Riferimenti bibliografici
.
▶
Linux kernel (versione 3.9.2)
http://www.kernel.org
▶
Ingo Molnar
kernel/timer.c design
http://lwn.net/Articles/156329/
▶
Daniel P. Bovet, Marco Cesati
Understanding Linux Kernel (3rd edition)
▶
Clockevents and dyntick
http://lwn.net/Articles/223185/
▶
Deferrable timers
http://lwn.net/Articles/228143/
▶
Jonathan Corbet, Alessandro Rubini, Greg Kroah-Hartman
Linux Device Drivers (3rd edition)