View Single Post
Old 10-01-2005, 11:16   #12
ilsensine
Senior Member
 
L'Avatar di ilsensine
 
Iscritto dal: Apr 2000
Città: Roma
Messaggi: 15625
Ancora sulle tecniche di locking

Una delle maggiori differenze tra la programmazione in user space e in kernel space è come fare il locking. Abbiamo già visto un esempio di semafori, praticamente uguale ai classici semafori user space; presentano di diverso sostanzialmente la primitiva down_interruptible(), che consente di interrompere il tentativo di lock in presenza di un segnale. I semafori sono solo una delle tecniche a disposizione, e non possono essere usati ovunque: per questioni tecniche (non possiamo "dormire" aspettando che un semaforo si liberi in certe sezioni critiche), e per questioni di scalabilità. Esistono diverse altre tecniche che illustrerò brevemente.

- rwsem
Sono molto simili ai semafori, con la differenza di poter specificare se il tipo di lock è per operazioni di lettura o di scrittura. Sono pensati per la scalabilità, essendo idonei in situazioni dove ci sono molti "lettori" e pochi "scrittori" dell'oggetto protetto sal semaforo. E' possibile acquisire più "read lock" contemporanei, ma un "write lock" blocca qualsiasi altro tipo di lock. L'utilizzo è simile ai semafori (v. linux/rwsem.h):
struct rw_semaphore sem;
init_rwsem(&sem);
down_read[_trylock](&sem); up_read(&sem);
down_write[_trylock](&sem); up_write(&sem);
downgrade_write(&sem); (trasforma un write lock in un read lock)
Non esiste la versione _interruptible di queste funzioni. Presentano la stessa limitazione dei semafori riguardo le sezioni atomiche.

- seqlock
I rwsem scalano meglio dei semafori in lettura, ma rischiano di causare un "write starving" se ci sono molti lettori all'opera. Per ovviare a questa situazione si possono usare i seqlock, leggermente più complessi dei rwsem. Non ne parlerò in dettaglio; gli interessati possono consultare linux/seqlock.h

- Gestione irq
Non è una tecnica di locking vera e propria.
Per disabilitare/riabilitare gli irq sulla cpu corrente, usare
local_irq_disable(); local_irq_enable();
Se gli irq possono essere già disabilitati nella funzione corrente (ad es. perché la funzione può essere invocata da contesti diversi) utilizzare questa variante:
unsigned long flags;
local_irq_save(flags);
local_irq_restore(flags);
Note:
- Gli irq vengono disabilitati solo sul processore corrente. Attenzione agli ambienti smp (usare gli spinlock)
- Se possibile, è più efficiente disabilitare gli irq solo del proprio dispositivo.
- Disabilitare gli irq aumenta la latenza: usare solo per lo stretto indispensabile.
- Una sezione con gli irq disabilitati diventa una sezione atomica (non schedulare in tale contesto, e non chiamare funzioni che possono schedulare, come ad es. down() )

- Gestione della preemption
Versione più blanda della disabilitazione degli irq:
preempt_disable();
preempt_enable();
Disabilitano la preemption in kernel space. Come corollario, si è certi che il codice protetto non cambierà cpu di esecuzione (v. anche get_cpu(); put_cpu() )
Note:
- E' possibile annidare più preempt_disable().
- Aumenta la latenza delle syscall e dei wakeup, ma non della gestione degli irq

- the Big Kernel Lock (BKL)
Aiutate l'umanità a liberarsi definitivamente di questa bestia ignorandone l'esistenza.

- Gli spinlock
Tecnica di locking importantissima. E' l'unica che può essere usata con sicurezza all'interno di un irq handler.
Il comportamento degli spinlock è molto diverso a seconda di come è compilato il kernel: UP, UP+preempt, SMP, SMP+preempt. Per spiegare il loro funzionamento, partiamo dal motivo della loro introduzione: i sistemi SMP.
Uno spinlock per SMP è simile a un semaforo, con la differenza importante di tenere il processore in loop finché lo spinlock non risulta acquisibile. Il corollario ovvio è non tenere uno spinlock bloccato a lungo, ma solo per alcune istruzioni.
Con l'introduzione dei sistemi preempt, gli spinlock sono stati modificati: un sistema UP preempt si comporta in modo simile a un sistema multiprocessore. Uno spinlock per un sistema UP preempt è semplicemente un preempt_disable; per un sistema SMP preempt, la preemption viene disabilitata oltre ad acquisire il lock.
Per sistemi UP ovviamente gli spinlock sono dei no-op.
Sintassi:
spinlock_t lock;
spin_lock_init(&lock);
spin_lock(&lock);
spin_unlock(&lock);
E' disponibile anche la versione con disabilitazione degli irq:
unsigned long flags;
spin_lock_irqsave(&lock, flags); <== compilato come local_irq_save nei sistemi UP
spin_lock_irqrestore(&lock, flags); <== compilato come local_irq_restore nei sistemi UP
Gli spinlock rappresentano la tecnica di locking corretta tra process context e irq context.

Per altre informazioni sulle tecniche di locking illustrate (e alcune non illustrate, come le RCU o i lock per le Bottom Half) una buona guida sintetica di riferimento è la "Unreliable guide to Locking" di Rusty Russell:
http://www.kernel.org/pub/linux/kern...ernel-locking/
__________________
0: or %edi, %ecx; adc %eax, (%edx); popf; je 0b-22; pop %ebx; fadds 0x56(%ecx); lds 0x56(%ebx), %esp; mov %al, %al
andeqs pc, r1, #147456; blpl 0xff8dd280; ldrgtb r4, [r6, #-472]; addgt r5, r8, r3, ror #12

Ultima modifica di ilsensine : 23-02-2006 alle 17:12.
ilsensine è offline