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)