18c2ecf20Sopenharmony_ci.. include:: ../disclaimer-ita.rst 28c2ecf20Sopenharmony_ci 38c2ecf20Sopenharmony_ci.. c:namespace:: it_IT 48c2ecf20Sopenharmony_ci 58c2ecf20Sopenharmony_ci:Original: :ref:`Documentation/kernel-hacking/locking.rst <kernel_hacking_lock>` 68c2ecf20Sopenharmony_ci:Translator: Federico Vaga <federico.vaga@vaga.pv.it> 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci.. _it_kernel_hacking_lock: 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci========================================== 118c2ecf20Sopenharmony_ciL'inaffidabile guida alla sincronizzazione 128c2ecf20Sopenharmony_ci========================================== 138c2ecf20Sopenharmony_ci 148c2ecf20Sopenharmony_ci:Author: Rusty Russell 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ciIntroduzione 178c2ecf20Sopenharmony_ci============ 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ciBenvenuto, alla notevole ed inaffidabile guida ai problemi di sincronizzazione 208c2ecf20Sopenharmony_ci(locking) nel kernel. Questo documento descrive il sistema di sincronizzazione 218c2ecf20Sopenharmony_cinel kernel Linux 2.6. 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ciDato il largo utilizzo del multi-threading e della prelazione nel kernel 248c2ecf20Sopenharmony_ciLinux, chiunque voglia dilettarsi col kernel deve conoscere i concetti 258c2ecf20Sopenharmony_cifondamentali della concorrenza e della sincronizzazione nei sistemi 268c2ecf20Sopenharmony_cimulti-processore. 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ciIl problema con la concorrenza 298c2ecf20Sopenharmony_ci============================== 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_ci(Saltatelo se sapete già cos'è una corsa critica). 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_ciIn un normale programma, potete incrementare un contatore nel seguente modo: 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_ci:: 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci contatore++; 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_ciQuesto è quello che vi aspettereste che accada sempre: 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci.. table:: Risultati attesi 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_ci +------------------------------------+------------------------------------+ 458c2ecf20Sopenharmony_ci | Istanza 1 | Istanza 2 | 468c2ecf20Sopenharmony_ci +====================================+====================================+ 478c2ecf20Sopenharmony_ci | leggi contatore (5) | | 488c2ecf20Sopenharmony_ci +------------------------------------+------------------------------------+ 498c2ecf20Sopenharmony_ci | aggiungi 1 (6) | | 508c2ecf20Sopenharmony_ci +------------------------------------+------------------------------------+ 518c2ecf20Sopenharmony_ci | scrivi contatore (6) | | 528c2ecf20Sopenharmony_ci +------------------------------------+------------------------------------+ 538c2ecf20Sopenharmony_ci | | leggi contatore (6) | 548c2ecf20Sopenharmony_ci +------------------------------------+------------------------------------+ 558c2ecf20Sopenharmony_ci | | aggiungi 1 (7) | 568c2ecf20Sopenharmony_ci +------------------------------------+------------------------------------+ 578c2ecf20Sopenharmony_ci | | scrivi contatore (7) | 588c2ecf20Sopenharmony_ci +------------------------------------+------------------------------------+ 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_ciQuesto è quello che potrebbe succedere in realtà: 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci.. table:: Possibile risultato 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci +------------------------------------+------------------------------------+ 658c2ecf20Sopenharmony_ci | Istanza 1 | Istanza 2 | 668c2ecf20Sopenharmony_ci +====================================+====================================+ 678c2ecf20Sopenharmony_ci | leggi contatore (5) | | 688c2ecf20Sopenharmony_ci +------------------------------------+------------------------------------+ 698c2ecf20Sopenharmony_ci | | leggi contatore (5) | 708c2ecf20Sopenharmony_ci +------------------------------------+------------------------------------+ 718c2ecf20Sopenharmony_ci | aggiungi 1 (6) | | 728c2ecf20Sopenharmony_ci +------------------------------------+------------------------------------+ 738c2ecf20Sopenharmony_ci | | aggiungi 1 (6) | 748c2ecf20Sopenharmony_ci +------------------------------------+------------------------------------+ 758c2ecf20Sopenharmony_ci | scrivi contatore (6) | | 768c2ecf20Sopenharmony_ci +------------------------------------+------------------------------------+ 778c2ecf20Sopenharmony_ci | | scrivi contatore (6) | 788c2ecf20Sopenharmony_ci +------------------------------------+------------------------------------+ 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ciCorse critiche e sezioni critiche 828c2ecf20Sopenharmony_ci--------------------------------- 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ciQuesta sovrapposizione, ovvero quando un risultato dipende dal tempo che 858c2ecf20Sopenharmony_ciintercorre fra processi diversi, è chiamata corsa critica. La porzione 868c2ecf20Sopenharmony_cidi codice che contiene questo problema è chiamata sezione critica. 878c2ecf20Sopenharmony_ciIn particolar modo da quando Linux ha incominciato a girare su 888c2ecf20Sopenharmony_cimacchine multi-processore, le sezioni critiche sono diventate uno dei 898c2ecf20Sopenharmony_cimaggiori problemi di progettazione ed implementazione del kernel. 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_ciLa prelazione può sortire gli stessi effetti, anche se c'è una sola CPU: 928c2ecf20Sopenharmony_ciinterrompendo un processo nella sua sezione critica otterremo comunque 938c2ecf20Sopenharmony_cila stessa corsa critica. In questo caso, il thread che si avvicenda 948c2ecf20Sopenharmony_cinell'esecuzione potrebbe eseguire anch'esso la sezione critica. 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_ciLa soluzione è quella di riconoscere quando avvengono questi accessi 978c2ecf20Sopenharmony_cisimultanei, ed utilizzare i *lock* per accertarsi che solo un'istanza 988c2ecf20Sopenharmony_ciper volta possa entrare nella sezione critica. Il kernel offre delle buone 998c2ecf20Sopenharmony_cifunzioni a questo scopo. E poi ci sono quelle meno buone, ma farò finta 1008c2ecf20Sopenharmony_ciche non esistano. 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ciSincronizzazione nel kernel Linux 1038c2ecf20Sopenharmony_ci================================= 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ciSe posso darvi un suggerimento: non dormite mai con qualcuno più pazzo di 1068c2ecf20Sopenharmony_civoi. Ma se dovessi darvi un suggerimento sulla sincronizzazione: 1078c2ecf20Sopenharmony_ci**mantenetela semplice**. 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ciSiate riluttanti nell'introduzione di nuovi *lock*. 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ciAbbastanza strano, quest'ultimo è l'esatto opposto del mio suggerimento 1128c2ecf20Sopenharmony_cisu quando **avete** dormito con qualcuno più pazzo di voi. E dovreste 1138c2ecf20Sopenharmony_cipensare a prendervi un cane bello grande. 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ciI due principali tipi di *lock* nel kernel: spinlock e mutex 1168c2ecf20Sopenharmony_ci------------------------------------------------------------ 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ciCi sono due tipi principali di *lock* nel kernel. Il tipo fondamentale è lo 1198c2ecf20Sopenharmony_cispinlock (``include/asm/spinlock.h``), un semplice *lock* che può essere 1208c2ecf20Sopenharmony_citrattenuto solo da un processo: se non si può trattenere lo spinlock, allora 1218c2ecf20Sopenharmony_cirimane in attesa attiva (in inglese *spinning*) finché non ci riesce. 1228c2ecf20Sopenharmony_ciGli spinlock sono molto piccoli e rapidi, possono essere utilizzati ovunque. 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ciIl secondo tipo è il mutex (``include/linux/mutex.h``): è come uno spinlock, 1258c2ecf20Sopenharmony_cima potreste bloccarvi trattenendolo. Se non potete trattenere un mutex 1268c2ecf20Sopenharmony_ciil vostro processo si auto-sospenderà; verrà riattivato quando il mutex 1278c2ecf20Sopenharmony_civerrà rilasciato. Questo significa che il processore potrà occuparsi d'altro 1288c2ecf20Sopenharmony_cimentre il vostro processo è in attesa. Esistono molti casi in cui non potete 1298c2ecf20Sopenharmony_cipermettervi di sospendere un processo (vedere 1308c2ecf20Sopenharmony_ci:ref:`Quali funzioni possono essere chiamate in modo sicuro dalle interruzioni? <it_sleeping-things>`) 1318c2ecf20Sopenharmony_cie quindi dovrete utilizzare gli spinlock. 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ciNessuno di questi *lock* è ricorsivo: vedere 1348c2ecf20Sopenharmony_ci:ref:`Stallo: semplice ed avanzato <it_deadlock>` 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ciI *lock* e i kernel per sistemi monoprocessore 1378c2ecf20Sopenharmony_ci---------------------------------------------- 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ciPer i kernel compilati senza ``CONFIG_SMP`` e senza ``CONFIG_PREEMPT`` 1408c2ecf20Sopenharmony_cigli spinlock non esistono. Questa è un'ottima scelta di progettazione: 1418c2ecf20Sopenharmony_ciquando nessun altro processo può essere eseguito in simultanea, allora 1428c2ecf20Sopenharmony_cinon c'è la necessità di avere un *lock*. 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_ciSe il kernel è compilato senza ``CONFIG_SMP`` ma con ``CONFIG_PREEMPT``, 1458c2ecf20Sopenharmony_ciallora gli spinlock disabilitano la prelazione; questo è sufficiente a 1468c2ecf20Sopenharmony_ciprevenire le corse critiche. Nella maggior parte dei casi, possiamo considerare 1478c2ecf20Sopenharmony_cila prelazione equivalente ad un sistema multi-processore senza preoccuparci 1488c2ecf20Sopenharmony_cidi trattarla indipendentemente. 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ciDovreste verificare sempre la sincronizzazione con le opzioni ``CONFIG_SMP`` e 1518c2ecf20Sopenharmony_ci``CONFIG_PREEMPT`` abilitate, anche quando non avete un sistema 1528c2ecf20Sopenharmony_cimulti-processore, questo vi permetterà di identificare alcuni problemi 1538c2ecf20Sopenharmony_cidi sincronizzazione. 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ciCome vedremo di seguito, i mutex continuano ad esistere perché sono necessari 1568c2ecf20Sopenharmony_ciper la sincronizzazione fra processi in contesto utente. 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ciSincronizzazione in contesto utente 1598c2ecf20Sopenharmony_ci----------------------------------- 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ciSe avete una struttura dati che verrà utilizzata solo dal contesto utente, 1628c2ecf20Sopenharmony_ciallora, per proteggerla, potete utilizzare un semplice mutex 1638c2ecf20Sopenharmony_ci(``include/linux/mutex.h``). Questo è il caso più semplice: inizializzate il 1648c2ecf20Sopenharmony_cimutex; invocate mutex_lock_interruptible() per trattenerlo e 1658c2ecf20Sopenharmony_cimutex_unlock() per rilasciarlo. C'è anche mutex_lock() 1668c2ecf20Sopenharmony_cima questa dovrebbe essere evitata perché non ritorna in caso di segnali. 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ciPer esempio: ``net/netfilter/nf_sockopt.c`` permette la registrazione 1698c2ecf20Sopenharmony_cidi nuove chiamate per setsockopt() e getsockopt() 1708c2ecf20Sopenharmony_ciusando la funzione nf_register_sockopt(). La registrazione e 1718c2ecf20Sopenharmony_cila rimozione vengono eseguite solamente quando il modulo viene caricato 1728c2ecf20Sopenharmony_cio scaricato (e durante l'avvio del sistema, qui non abbiamo concorrenza), 1738c2ecf20Sopenharmony_cie la lista delle funzioni registrate viene consultata solamente quando 1748c2ecf20Sopenharmony_cisetsockopt() o getsockopt() sono sconosciute al sistema. 1758c2ecf20Sopenharmony_ciIn questo caso ``nf_sockopt_mutex`` è perfetto allo scopo, in particolar modo 1768c2ecf20Sopenharmony_civisto che setsockopt e getsockopt potrebbero dormire. 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ciSincronizzazione fra il contesto utente e i softirq 1798c2ecf20Sopenharmony_ci--------------------------------------------------- 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_ciSe un softirq condivide dati col contesto utente, avete due problemi. 1828c2ecf20Sopenharmony_ciPrimo, il contesto utente corrente potrebbe essere interroto da un softirq, 1838c2ecf20Sopenharmony_cie secondo, la sezione critica potrebbe essere eseguita da un altro 1848c2ecf20Sopenharmony_ciprocessore. Questo è quando spin_lock_bh() 1858c2ecf20Sopenharmony_ci(``include/linux/spinlock.h``) viene utilizzato. Questo disabilita i softirq 1868c2ecf20Sopenharmony_cisul processore e trattiene il *lock*. Invece, spin_unlock_bh() fa 1878c2ecf20Sopenharmony_cil'opposto. (Il suffisso '_bh' è un residuo storico che fa riferimento al 1888c2ecf20Sopenharmony_ci"Bottom Halves", il vecchio nome delle interruzioni software. In un mondo 1898c2ecf20Sopenharmony_ciperfetto questa funzione si chiamerebbe 'spin_lock_softirq()'). 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_ciDa notare che in questo caso potete utilizzare anche spin_lock_irq() 1928c2ecf20Sopenharmony_cio spin_lock_irqsave(), queste fermano anche le interruzioni hardware: 1938c2ecf20Sopenharmony_civedere :ref:`Contesto di interruzione hardware <it_hardirq-context>`. 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_ciQuesto funziona alla perfezione anche sui sistemi monoprocessore: gli spinlock 1968c2ecf20Sopenharmony_cisvaniscono e questa macro diventa semplicemente local_bh_disable() 1978c2ecf20Sopenharmony_ci(``include/linux/interrupt.h``), la quale impedisce ai softirq d'essere 1988c2ecf20Sopenharmony_cieseguiti. 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_ciSincronizzazione fra contesto utente e i tasklet 2018c2ecf20Sopenharmony_ci------------------------------------------------ 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_ciQuesto caso è uguale al precedente, un tasklet viene eseguito da un softirq. 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_ciSincronizzazione fra contesto utente e i timer 2068c2ecf20Sopenharmony_ci---------------------------------------------- 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ciAnche questo caso è uguale al precedente, un timer viene eseguito da un 2098c2ecf20Sopenharmony_cisoftirq. 2108c2ecf20Sopenharmony_ciDal punto di vista della sincronizzazione, tasklet e timer sono identici. 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_ciSincronizzazione fra tasklet e timer 2138c2ecf20Sopenharmony_ci------------------------------------ 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ciQualche volta un tasklet od un timer potrebbero condividere i dati con 2168c2ecf20Sopenharmony_ciun altro tasklet o timer 2178c2ecf20Sopenharmony_ci 2188c2ecf20Sopenharmony_ciLo stesso tasklet/timer 2198c2ecf20Sopenharmony_ci~~~~~~~~~~~~~~~~~~~~~~~ 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ciDato che un tasklet non viene mai eseguito contemporaneamente su due 2228c2ecf20Sopenharmony_ciprocessori, non dovete preoccuparvi che sia rientrante (ovvero eseguito 2238c2ecf20Sopenharmony_cipiù volte in contemporanea), perfino su sistemi multi-processore. 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_ciDifferenti tasklet/timer 2268c2ecf20Sopenharmony_ci~~~~~~~~~~~~~~~~~~~~~~~~ 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_ciSe un altro tasklet/timer vuole condividere dati col vostro tasklet o timer, 2298c2ecf20Sopenharmony_ciallora avrete bisogno entrambe di spin_lock() e 2308c2ecf20Sopenharmony_cispin_unlock(). Qui spin_lock_bh() è inutile, siete già 2318c2ecf20Sopenharmony_ciin un tasklet ed avete la garanzia che nessun altro verrà eseguito sullo 2328c2ecf20Sopenharmony_cistesso processore. 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_ciSincronizzazione fra softirq 2358c2ecf20Sopenharmony_ci---------------------------- 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_ciSpesso un softirq potrebbe condividere dati con se stesso o un tasklet/timer. 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_ciLo stesso softirq 2408c2ecf20Sopenharmony_ci~~~~~~~~~~~~~~~~~ 2418c2ecf20Sopenharmony_ci 2428c2ecf20Sopenharmony_ciLo stesso softirq può essere eseguito su un diverso processore: allo scopo 2438c2ecf20Sopenharmony_cidi migliorare le prestazioni potete utilizzare dati riservati ad ogni 2448c2ecf20Sopenharmony_ciprocessore (vedere :ref:`Dati per processore <it_per-cpu>`). Se siete arrivati 2458c2ecf20Sopenharmony_cifino a questo punto nell'uso dei softirq, probabilmente tenete alla scalabilità 2468c2ecf20Sopenharmony_cidelle prestazioni abbastanza da giustificarne la complessità aggiuntiva. 2478c2ecf20Sopenharmony_ci 2488c2ecf20Sopenharmony_ciDovete utilizzare spin_lock() e spin_unlock() per 2498c2ecf20Sopenharmony_ciproteggere i dati condivisi. 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_ciDiversi Softirqs 2528c2ecf20Sopenharmony_ci~~~~~~~~~~~~~~~~ 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_ciDovete utilizzare spin_lock() e spin_unlock() per 2558c2ecf20Sopenharmony_ciproteggere i dati condivisi, che siano timer, tasklet, diversi softirq o 2568c2ecf20Sopenharmony_cilo stesso o altri softirq: uno qualsiasi di essi potrebbe essere in esecuzione 2578c2ecf20Sopenharmony_cisu un diverso processore. 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_ci.. _`it_hardirq-context`: 2608c2ecf20Sopenharmony_ci 2618c2ecf20Sopenharmony_ciContesto di interruzione hardware 2628c2ecf20Sopenharmony_ci================================= 2638c2ecf20Sopenharmony_ci 2648c2ecf20Sopenharmony_ciSolitamente le interruzioni hardware comunicano con un tasklet o un softirq. 2658c2ecf20Sopenharmony_ciSpesso questo si traduce nel mettere in coda qualcosa da fare che verrà 2668c2ecf20Sopenharmony_cipreso in carico da un softirq. 2678c2ecf20Sopenharmony_ci 2688c2ecf20Sopenharmony_ciSincronizzazione fra interruzioni hardware e softirq/tasklet 2698c2ecf20Sopenharmony_ci------------------------------------------------------------ 2708c2ecf20Sopenharmony_ci 2718c2ecf20Sopenharmony_ciSe un gestore di interruzioni hardware condivide dati con un softirq, allora 2728c2ecf20Sopenharmony_ciavrete due preoccupazioni. Primo, il softirq può essere interrotto da 2738c2ecf20Sopenharmony_ciun'interruzione hardware, e secondo, la sezione critica potrebbe essere 2748c2ecf20Sopenharmony_cieseguita da un'interruzione hardware su un processore diverso. Questo è il caso 2758c2ecf20Sopenharmony_cidove spin_lock_irq() viene utilizzato. Disabilita le interruzioni 2768c2ecf20Sopenharmony_cisul processore che l'esegue, poi trattiene il lock. spin_unlock_irq() 2778c2ecf20Sopenharmony_cifa l'opposto. 2788c2ecf20Sopenharmony_ci 2798c2ecf20Sopenharmony_ciIl gestore d'interruzione hardware non ha bisogno di usare spin_lock_irq() 2808c2ecf20Sopenharmony_ciperché i softirq non possono essere eseguiti quando il gestore d'interruzione 2818c2ecf20Sopenharmony_cihardware è in esecuzione: per questo si può usare spin_lock(), che è un po' 2828c2ecf20Sopenharmony_cipiù veloce. L'unica eccezione è quando un altro gestore d'interruzioni 2838c2ecf20Sopenharmony_cihardware utilizza lo stesso *lock*: spin_lock_irq() impedirà a questo 2848c2ecf20Sopenharmony_cisecondo gestore di interrompere quello in esecuzione. 2858c2ecf20Sopenharmony_ci 2868c2ecf20Sopenharmony_ciQuesto funziona alla perfezione anche sui sistemi monoprocessore: gli spinlock 2878c2ecf20Sopenharmony_cisvaniscono e questa macro diventa semplicemente local_irq_disable() 2888c2ecf20Sopenharmony_ci(``include/asm/smp.h``), la quale impedisce a softirq/tasklet/BH d'essere 2898c2ecf20Sopenharmony_cieseguiti. 2908c2ecf20Sopenharmony_ci 2918c2ecf20Sopenharmony_cispin_lock_irqsave() (``include/linux/spinlock.h``) è una variante che 2928c2ecf20Sopenharmony_cisalva lo stato delle interruzioni in una variabile, questa verrà poi passata 2938c2ecf20Sopenharmony_cia spin_unlock_irqrestore(). Questo significa che lo stesso codice 2948c2ecf20Sopenharmony_cipotrà essere utilizzato in un'interruzione hardware (dove le interruzioni sono 2958c2ecf20Sopenharmony_cigià disabilitate) e in un softirq (dove la disabilitazione delle interruzioni 2968c2ecf20Sopenharmony_ciè richiesta). 2978c2ecf20Sopenharmony_ci 2988c2ecf20Sopenharmony_ciDa notare che i softirq (e quindi tasklet e timer) sono eseguiti al ritorno 2998c2ecf20Sopenharmony_cida un'interruzione hardware, quindi spin_lock_irq() interrompe 3008c2ecf20Sopenharmony_cianche questi. Tenuto conto di questo si può dire che 3018c2ecf20Sopenharmony_cispin_lock_irqsave() è la funzione di sincronizzazione più generica 3028c2ecf20Sopenharmony_cie potente. 3038c2ecf20Sopenharmony_ci 3048c2ecf20Sopenharmony_ciSincronizzazione fra due gestori d'interruzioni hardware 3058c2ecf20Sopenharmony_ci-------------------------------------------------------- 3068c2ecf20Sopenharmony_ci 3078c2ecf20Sopenharmony_ciCondividere dati fra due gestori di interruzione hardware è molto raro, ma se 3088c2ecf20Sopenharmony_cisuccede, dovreste usare spin_lock_irqsave(): è una specificità 3098c2ecf20Sopenharmony_cidell'architettura il fatto che tutte le interruzioni vengano interrotte 3108c2ecf20Sopenharmony_ciquando si eseguono di gestori di interruzioni. 3118c2ecf20Sopenharmony_ci 3128c2ecf20Sopenharmony_ciBigino della sincronizzazione 3138c2ecf20Sopenharmony_ci============================= 3148c2ecf20Sopenharmony_ci 3158c2ecf20Sopenharmony_ciPete Zaitcev ci offre il seguente riassunto: 3168c2ecf20Sopenharmony_ci 3178c2ecf20Sopenharmony_ci- Se siete in un contesto utente (una qualsiasi chiamata di sistema) 3188c2ecf20Sopenharmony_ci e volete sincronizzarvi con altri processi, usate i mutex. Potete trattenere 3198c2ecf20Sopenharmony_ci il mutex e dormire (``copy_from_user*(`` o ``kmalloc(x,GFP_KERNEL)``). 3208c2ecf20Sopenharmony_ci 3218c2ecf20Sopenharmony_ci- Altrimenti (== i dati possono essere manipolati da un'interruzione) usate 3228c2ecf20Sopenharmony_ci spin_lock_irqsave() e spin_unlock_irqrestore(). 3238c2ecf20Sopenharmony_ci 3248c2ecf20Sopenharmony_ci- Evitate di trattenere uno spinlock per più di 5 righe di codice incluse 3258c2ecf20Sopenharmony_ci le chiamate a funzione (ad eccezione di quell per l'accesso come 3268c2ecf20Sopenharmony_ci readb()). 3278c2ecf20Sopenharmony_ci 3288c2ecf20Sopenharmony_ciTabella dei requisiti minimi 3298c2ecf20Sopenharmony_ci---------------------------- 3308c2ecf20Sopenharmony_ci 3318c2ecf20Sopenharmony_ciLa tabella seguente illustra i requisiti **minimi** per la sincronizzazione fra 3328c2ecf20Sopenharmony_cidiversi contesti. In alcuni casi, lo stesso contesto può essere eseguito solo 3338c2ecf20Sopenharmony_cida un processore per volta, quindi non ci sono requisiti per la 3348c2ecf20Sopenharmony_cisincronizzazione (per esempio, un thread può essere eseguito solo su un 3358c2ecf20Sopenharmony_ciprocessore alla volta, ma se deve condividere dati con un altro thread, allora 3368c2ecf20Sopenharmony_cila sincronizzazione è necessaria). 3378c2ecf20Sopenharmony_ci 3388c2ecf20Sopenharmony_ciRicordatevi il suggerimento qui sopra: potete sempre usare 3398c2ecf20Sopenharmony_cispin_lock_irqsave(), che è un sovrainsieme di tutte le altre funzioni 3408c2ecf20Sopenharmony_ciper spinlock. 3418c2ecf20Sopenharmony_ci 3428c2ecf20Sopenharmony_ci============== ============= ============= ========= ========= ========= ========= ======= ======= ============== ============== 3438c2ecf20Sopenharmony_ci. IRQ Handler A IRQ Handler B Softirq A Softirq B Tasklet A Tasklet B Timer A Timer B User Context A User Context B 3448c2ecf20Sopenharmony_ci============== ============= ============= ========= ========= ========= ========= ======= ======= ============== ============== 3458c2ecf20Sopenharmony_ciIRQ Handler A None 3468c2ecf20Sopenharmony_ciIRQ Handler B SLIS None 3478c2ecf20Sopenharmony_ciSoftirq A SLI SLI SL 3488c2ecf20Sopenharmony_ciSoftirq B SLI SLI SL SL 3498c2ecf20Sopenharmony_ciTasklet A SLI SLI SL SL None 3508c2ecf20Sopenharmony_ciTasklet B SLI SLI SL SL SL None 3518c2ecf20Sopenharmony_ciTimer A SLI SLI SL SL SL SL None 3528c2ecf20Sopenharmony_ciTimer B SLI SLI SL SL SL SL SL None 3538c2ecf20Sopenharmony_ciUser Context A SLI SLI SLBH SLBH SLBH SLBH SLBH SLBH None 3548c2ecf20Sopenharmony_ciUser Context B SLI SLI SLBH SLBH SLBH SLBH SLBH SLBH MLI None 3558c2ecf20Sopenharmony_ci============== ============= ============= ========= ========= ========= ========= ======= ======= ============== ============== 3568c2ecf20Sopenharmony_ci 3578c2ecf20Sopenharmony_ciTable: Tabella dei requisiti per la sincronizzazione 3588c2ecf20Sopenharmony_ci 3598c2ecf20Sopenharmony_ci+--------+----------------------------+ 3608c2ecf20Sopenharmony_ci| SLIS | spin_lock_irqsave | 3618c2ecf20Sopenharmony_ci+--------+----------------------------+ 3628c2ecf20Sopenharmony_ci| SLI | spin_lock_irq | 3638c2ecf20Sopenharmony_ci+--------+----------------------------+ 3648c2ecf20Sopenharmony_ci| SL | spin_lock | 3658c2ecf20Sopenharmony_ci+--------+----------------------------+ 3668c2ecf20Sopenharmony_ci| SLBH | spin_lock_bh | 3678c2ecf20Sopenharmony_ci+--------+----------------------------+ 3688c2ecf20Sopenharmony_ci| MLI | mutex_lock_interruptible | 3698c2ecf20Sopenharmony_ci+--------+----------------------------+ 3708c2ecf20Sopenharmony_ci 3718c2ecf20Sopenharmony_ciTable: Legenda per la tabella dei requisiti per la sincronizzazione 3728c2ecf20Sopenharmony_ci 3738c2ecf20Sopenharmony_ciLe funzioni *trylock* 3748c2ecf20Sopenharmony_ci===================== 3758c2ecf20Sopenharmony_ci 3768c2ecf20Sopenharmony_ciCi sono funzioni che provano a trattenere un *lock* solo una volta e 3778c2ecf20Sopenharmony_ciritornano immediatamente comunicato il successo od il fallimento 3788c2ecf20Sopenharmony_cidell'operazione. Posso essere usate quando non serve accedere ai dati 3798c2ecf20Sopenharmony_ciprotetti dal *lock* quando qualche altro thread lo sta già facendo 3808c2ecf20Sopenharmony_citrattenendo il *lock*. Potrete acquisire il *lock* più tardi se vi 3818c2ecf20Sopenharmony_ciserve accedere ai dati protetti da questo *lock*. 3828c2ecf20Sopenharmony_ci 3838c2ecf20Sopenharmony_ciLa funzione spin_trylock() non ritenta di acquisire il *lock*, 3848c2ecf20Sopenharmony_cise ci riesce al primo colpo ritorna un valore diverso da zero, altrimenti 3858c2ecf20Sopenharmony_cise fallisce ritorna 0. Questa funzione può essere utilizzata in un qualunque 3868c2ecf20Sopenharmony_cicontesto, ma come spin_lock(): dovete disabilitare i contesti che 3878c2ecf20Sopenharmony_cipotrebbero interrompervi e quindi trattenere lo spinlock. 3888c2ecf20Sopenharmony_ci 3898c2ecf20Sopenharmony_ciLa funzione mutex_trylock() invece di sospendere il vostro processo 3908c2ecf20Sopenharmony_ciritorna un valore diverso da zero se è possibile trattenere il lock al primo 3918c2ecf20Sopenharmony_cicolpo, altrimenti se fallisce ritorna 0. Nonostante non dorma, questa funzione 3928c2ecf20Sopenharmony_cinon può essere usata in modo sicuro in contesti di interruzione hardware o 3938c2ecf20Sopenharmony_cisoftware. 3948c2ecf20Sopenharmony_ci 3958c2ecf20Sopenharmony_ciEsempi più comuni 3968c2ecf20Sopenharmony_ci================= 3978c2ecf20Sopenharmony_ci 3988c2ecf20Sopenharmony_ciGuardiamo un semplice esempio: una memoria che associa nomi a numeri. 3998c2ecf20Sopenharmony_ciLa memoria tiene traccia di quanto spesso viene utilizzato ogni oggetto; 4008c2ecf20Sopenharmony_ciquando è piena, l'oggetto meno usato viene eliminato. 4018c2ecf20Sopenharmony_ci 4028c2ecf20Sopenharmony_ciTutto in contesto utente 4038c2ecf20Sopenharmony_ci------------------------ 4048c2ecf20Sopenharmony_ci 4058c2ecf20Sopenharmony_ciNel primo esempio, supponiamo che tutte le operazioni avvengano in contesto 4068c2ecf20Sopenharmony_ciutente (in soldoni, da una chiamata di sistema), quindi possiamo dormire. 4078c2ecf20Sopenharmony_ciQuesto significa che possiamo usare i mutex per proteggere la nostra memoria 4088c2ecf20Sopenharmony_cie tutti gli oggetti che contiene. Ecco il codice:: 4098c2ecf20Sopenharmony_ci 4108c2ecf20Sopenharmony_ci #include <linux/list.h> 4118c2ecf20Sopenharmony_ci #include <linux/slab.h> 4128c2ecf20Sopenharmony_ci #include <linux/string.h> 4138c2ecf20Sopenharmony_ci #include <linux/mutex.h> 4148c2ecf20Sopenharmony_ci #include <asm/errno.h> 4158c2ecf20Sopenharmony_ci 4168c2ecf20Sopenharmony_ci struct object 4178c2ecf20Sopenharmony_ci { 4188c2ecf20Sopenharmony_ci struct list_head list; 4198c2ecf20Sopenharmony_ci int id; 4208c2ecf20Sopenharmony_ci char name[32]; 4218c2ecf20Sopenharmony_ci int popularity; 4228c2ecf20Sopenharmony_ci }; 4238c2ecf20Sopenharmony_ci 4248c2ecf20Sopenharmony_ci /* Protects the cache, cache_num, and the objects within it */ 4258c2ecf20Sopenharmony_ci static DEFINE_MUTEX(cache_lock); 4268c2ecf20Sopenharmony_ci static LIST_HEAD(cache); 4278c2ecf20Sopenharmony_ci static unsigned int cache_num = 0; 4288c2ecf20Sopenharmony_ci #define MAX_CACHE_SIZE 10 4298c2ecf20Sopenharmony_ci 4308c2ecf20Sopenharmony_ci /* Must be holding cache_lock */ 4318c2ecf20Sopenharmony_ci static struct object *__cache_find(int id) 4328c2ecf20Sopenharmony_ci { 4338c2ecf20Sopenharmony_ci struct object *i; 4348c2ecf20Sopenharmony_ci 4358c2ecf20Sopenharmony_ci list_for_each_entry(i, &cache, list) 4368c2ecf20Sopenharmony_ci if (i->id == id) { 4378c2ecf20Sopenharmony_ci i->popularity++; 4388c2ecf20Sopenharmony_ci return i; 4398c2ecf20Sopenharmony_ci } 4408c2ecf20Sopenharmony_ci return NULL; 4418c2ecf20Sopenharmony_ci } 4428c2ecf20Sopenharmony_ci 4438c2ecf20Sopenharmony_ci /* Must be holding cache_lock */ 4448c2ecf20Sopenharmony_ci static void __cache_delete(struct object *obj) 4458c2ecf20Sopenharmony_ci { 4468c2ecf20Sopenharmony_ci BUG_ON(!obj); 4478c2ecf20Sopenharmony_ci list_del(&obj->list); 4488c2ecf20Sopenharmony_ci kfree(obj); 4498c2ecf20Sopenharmony_ci cache_num--; 4508c2ecf20Sopenharmony_ci } 4518c2ecf20Sopenharmony_ci 4528c2ecf20Sopenharmony_ci /* Must be holding cache_lock */ 4538c2ecf20Sopenharmony_ci static void __cache_add(struct object *obj) 4548c2ecf20Sopenharmony_ci { 4558c2ecf20Sopenharmony_ci list_add(&obj->list, &cache); 4568c2ecf20Sopenharmony_ci if (++cache_num > MAX_CACHE_SIZE) { 4578c2ecf20Sopenharmony_ci struct object *i, *outcast = NULL; 4588c2ecf20Sopenharmony_ci list_for_each_entry(i, &cache, list) { 4598c2ecf20Sopenharmony_ci if (!outcast || i->popularity < outcast->popularity) 4608c2ecf20Sopenharmony_ci outcast = i; 4618c2ecf20Sopenharmony_ci } 4628c2ecf20Sopenharmony_ci __cache_delete(outcast); 4638c2ecf20Sopenharmony_ci } 4648c2ecf20Sopenharmony_ci } 4658c2ecf20Sopenharmony_ci 4668c2ecf20Sopenharmony_ci int cache_add(int id, const char *name) 4678c2ecf20Sopenharmony_ci { 4688c2ecf20Sopenharmony_ci struct object *obj; 4698c2ecf20Sopenharmony_ci 4708c2ecf20Sopenharmony_ci if ((obj = kmalloc(sizeof(*obj), GFP_KERNEL)) == NULL) 4718c2ecf20Sopenharmony_ci return -ENOMEM; 4728c2ecf20Sopenharmony_ci 4738c2ecf20Sopenharmony_ci strscpy(obj->name, name, sizeof(obj->name)); 4748c2ecf20Sopenharmony_ci obj->id = id; 4758c2ecf20Sopenharmony_ci obj->popularity = 0; 4768c2ecf20Sopenharmony_ci 4778c2ecf20Sopenharmony_ci mutex_lock(&cache_lock); 4788c2ecf20Sopenharmony_ci __cache_add(obj); 4798c2ecf20Sopenharmony_ci mutex_unlock(&cache_lock); 4808c2ecf20Sopenharmony_ci return 0; 4818c2ecf20Sopenharmony_ci } 4828c2ecf20Sopenharmony_ci 4838c2ecf20Sopenharmony_ci void cache_delete(int id) 4848c2ecf20Sopenharmony_ci { 4858c2ecf20Sopenharmony_ci mutex_lock(&cache_lock); 4868c2ecf20Sopenharmony_ci __cache_delete(__cache_find(id)); 4878c2ecf20Sopenharmony_ci mutex_unlock(&cache_lock); 4888c2ecf20Sopenharmony_ci } 4898c2ecf20Sopenharmony_ci 4908c2ecf20Sopenharmony_ci int cache_find(int id, char *name) 4918c2ecf20Sopenharmony_ci { 4928c2ecf20Sopenharmony_ci struct object *obj; 4938c2ecf20Sopenharmony_ci int ret = -ENOENT; 4948c2ecf20Sopenharmony_ci 4958c2ecf20Sopenharmony_ci mutex_lock(&cache_lock); 4968c2ecf20Sopenharmony_ci obj = __cache_find(id); 4978c2ecf20Sopenharmony_ci if (obj) { 4988c2ecf20Sopenharmony_ci ret = 0; 4998c2ecf20Sopenharmony_ci strcpy(name, obj->name); 5008c2ecf20Sopenharmony_ci } 5018c2ecf20Sopenharmony_ci mutex_unlock(&cache_lock); 5028c2ecf20Sopenharmony_ci return ret; 5038c2ecf20Sopenharmony_ci } 5048c2ecf20Sopenharmony_ci 5058c2ecf20Sopenharmony_ciDa notare che ci assicuriamo sempre di trattenere cache_lock quando 5068c2ecf20Sopenharmony_ciaggiungiamo, rimuoviamo od ispezioniamo la memoria: sia la struttura 5078c2ecf20Sopenharmony_cidella memoria che il suo contenuto sono protetti dal *lock*. Questo 5088c2ecf20Sopenharmony_cicaso è semplice dato che copiamo i dati dall'utente e non permettiamo 5098c2ecf20Sopenharmony_cimai loro di accedere direttamente agli oggetti. 5108c2ecf20Sopenharmony_ci 5118c2ecf20Sopenharmony_ciC'è una piccola ottimizzazione qui: nella funzione cache_add() 5128c2ecf20Sopenharmony_ciimpostiamo i campi dell'oggetto prima di acquisire il *lock*. Questo è 5138c2ecf20Sopenharmony_cisicuro perché nessun altro potrà accedervi finché non lo inseriremo 5148c2ecf20Sopenharmony_cinella memoria. 5158c2ecf20Sopenharmony_ci 5168c2ecf20Sopenharmony_ciAccesso dal contesto utente 5178c2ecf20Sopenharmony_ci--------------------------- 5188c2ecf20Sopenharmony_ci 5198c2ecf20Sopenharmony_ciOra consideriamo il caso in cui cache_find() può essere invocata 5208c2ecf20Sopenharmony_cidal contesto d'interruzione: sia hardware che software. Un esempio potrebbe 5218c2ecf20Sopenharmony_ciessere un timer che elimina oggetti dalla memoria. 5228c2ecf20Sopenharmony_ci 5238c2ecf20Sopenharmony_ciQui di seguito troverete la modifica nel formato *patch*: le righe ``-`` 5248c2ecf20Sopenharmony_cisono quelle rimosse, mentre quelle ``+`` sono quelle aggiunte. 5258c2ecf20Sopenharmony_ci 5268c2ecf20Sopenharmony_ci:: 5278c2ecf20Sopenharmony_ci 5288c2ecf20Sopenharmony_ci --- cache.c.usercontext 2003-12-09 13:58:54.000000000 +1100 5298c2ecf20Sopenharmony_ci +++ cache.c.interrupt 2003-12-09 14:07:49.000000000 +1100 5308c2ecf20Sopenharmony_ci @@ -12,7 +12,7 @@ 5318c2ecf20Sopenharmony_ci int popularity; 5328c2ecf20Sopenharmony_ci }; 5338c2ecf20Sopenharmony_ci 5348c2ecf20Sopenharmony_ci -static DEFINE_MUTEX(cache_lock); 5358c2ecf20Sopenharmony_ci +static DEFINE_SPINLOCK(cache_lock); 5368c2ecf20Sopenharmony_ci static LIST_HEAD(cache); 5378c2ecf20Sopenharmony_ci static unsigned int cache_num = 0; 5388c2ecf20Sopenharmony_ci #define MAX_CACHE_SIZE 10 5398c2ecf20Sopenharmony_ci @@ -55,6 +55,7 @@ 5408c2ecf20Sopenharmony_ci int cache_add(int id, const char *name) 5418c2ecf20Sopenharmony_ci { 5428c2ecf20Sopenharmony_ci struct object *obj; 5438c2ecf20Sopenharmony_ci + unsigned long flags; 5448c2ecf20Sopenharmony_ci 5458c2ecf20Sopenharmony_ci if ((obj = kmalloc(sizeof(*obj), GFP_KERNEL)) == NULL) 5468c2ecf20Sopenharmony_ci return -ENOMEM; 5478c2ecf20Sopenharmony_ci @@ -63,30 +64,33 @@ 5488c2ecf20Sopenharmony_ci obj->id = id; 5498c2ecf20Sopenharmony_ci obj->popularity = 0; 5508c2ecf20Sopenharmony_ci 5518c2ecf20Sopenharmony_ci - mutex_lock(&cache_lock); 5528c2ecf20Sopenharmony_ci + spin_lock_irqsave(&cache_lock, flags); 5538c2ecf20Sopenharmony_ci __cache_add(obj); 5548c2ecf20Sopenharmony_ci - mutex_unlock(&cache_lock); 5558c2ecf20Sopenharmony_ci + spin_unlock_irqrestore(&cache_lock, flags); 5568c2ecf20Sopenharmony_ci return 0; 5578c2ecf20Sopenharmony_ci } 5588c2ecf20Sopenharmony_ci 5598c2ecf20Sopenharmony_ci void cache_delete(int id) 5608c2ecf20Sopenharmony_ci { 5618c2ecf20Sopenharmony_ci - mutex_lock(&cache_lock); 5628c2ecf20Sopenharmony_ci + unsigned long flags; 5638c2ecf20Sopenharmony_ci + 5648c2ecf20Sopenharmony_ci + spin_lock_irqsave(&cache_lock, flags); 5658c2ecf20Sopenharmony_ci __cache_delete(__cache_find(id)); 5668c2ecf20Sopenharmony_ci - mutex_unlock(&cache_lock); 5678c2ecf20Sopenharmony_ci + spin_unlock_irqrestore(&cache_lock, flags); 5688c2ecf20Sopenharmony_ci } 5698c2ecf20Sopenharmony_ci 5708c2ecf20Sopenharmony_ci int cache_find(int id, char *name) 5718c2ecf20Sopenharmony_ci { 5728c2ecf20Sopenharmony_ci struct object *obj; 5738c2ecf20Sopenharmony_ci int ret = -ENOENT; 5748c2ecf20Sopenharmony_ci + unsigned long flags; 5758c2ecf20Sopenharmony_ci 5768c2ecf20Sopenharmony_ci - mutex_lock(&cache_lock); 5778c2ecf20Sopenharmony_ci + spin_lock_irqsave(&cache_lock, flags); 5788c2ecf20Sopenharmony_ci obj = __cache_find(id); 5798c2ecf20Sopenharmony_ci if (obj) { 5808c2ecf20Sopenharmony_ci ret = 0; 5818c2ecf20Sopenharmony_ci strcpy(name, obj->name); 5828c2ecf20Sopenharmony_ci } 5838c2ecf20Sopenharmony_ci - mutex_unlock(&cache_lock); 5848c2ecf20Sopenharmony_ci + spin_unlock_irqrestore(&cache_lock, flags); 5858c2ecf20Sopenharmony_ci return ret; 5868c2ecf20Sopenharmony_ci } 5878c2ecf20Sopenharmony_ci 5888c2ecf20Sopenharmony_ciDa notare che spin_lock_irqsave() disabiliterà le interruzioni 5898c2ecf20Sopenharmony_cise erano attive, altrimenti non farà niente (quando siamo già in un contesto 5908c2ecf20Sopenharmony_cid'interruzione); dunque queste funzioni possono essere chiamante in 5918c2ecf20Sopenharmony_cisicurezza da qualsiasi contesto. 5928c2ecf20Sopenharmony_ci 5938c2ecf20Sopenharmony_ciSfortunatamente, cache_add() invoca kmalloc() con 5948c2ecf20Sopenharmony_cil'opzione ``GFP_KERNEL`` che è permessa solo in contesto utente. Ho supposto 5958c2ecf20Sopenharmony_ciche cache_add() venga chiamata dal contesto utente, altrimenti 5968c2ecf20Sopenharmony_ciquesta opzione deve diventare un parametro di cache_add(). 5978c2ecf20Sopenharmony_ci 5988c2ecf20Sopenharmony_ciEsporre gli oggetti al di fuori del file 5998c2ecf20Sopenharmony_ci---------------------------------------- 6008c2ecf20Sopenharmony_ci 6018c2ecf20Sopenharmony_ciSe i vostri oggetti contengono più informazioni, potrebbe non essere 6028c2ecf20Sopenharmony_cisufficiente copiare i dati avanti e indietro: per esempio, altre parti del 6038c2ecf20Sopenharmony_cicodice potrebbero avere un puntatore a questi oggetti piuttosto che cercarli 6048c2ecf20Sopenharmony_ciogni volta. Questo introduce due problemi. 6058c2ecf20Sopenharmony_ci 6068c2ecf20Sopenharmony_ciIl primo problema è che utilizziamo ``cache_lock`` per proteggere gli oggetti: 6078c2ecf20Sopenharmony_cidobbiamo renderlo dinamico così che il resto del codice possa usarlo. Questo 6088c2ecf20Sopenharmony_cirende la sincronizzazione più complicata dato che non avviene più in un unico 6098c2ecf20Sopenharmony_ciposto. 6108c2ecf20Sopenharmony_ci 6118c2ecf20Sopenharmony_ciIl secondo problema è il problema del ciclo di vita: se un'altra struttura 6128c2ecf20Sopenharmony_cimantiene un puntatore ad un oggetto, presumibilmente si aspetta che questo 6138c2ecf20Sopenharmony_cipuntatore rimanga valido. Sfortunatamente, questo è garantito solo mentre 6148c2ecf20Sopenharmony_cisi trattiene il *lock*, altrimenti qualcuno potrebbe chiamare 6158c2ecf20Sopenharmony_cicache_delete() o peggio, aggiungere un oggetto che riutilizza lo 6168c2ecf20Sopenharmony_cistesso indirizzo. 6178c2ecf20Sopenharmony_ci 6188c2ecf20Sopenharmony_ciDato che c'è un solo *lock*, non potete trattenerlo a vita: altrimenti 6198c2ecf20Sopenharmony_cinessun altro potrà eseguire il proprio lavoro. 6208c2ecf20Sopenharmony_ci 6218c2ecf20Sopenharmony_ciLa soluzione a questo problema è l'uso di un contatore di riferimenti: 6228c2ecf20Sopenharmony_cichiunque punti ad un oggetto deve incrementare il contatore, e decrementarlo 6238c2ecf20Sopenharmony_ciquando il puntatore non viene più usato. Quando il contatore raggiunge lo zero 6248c2ecf20Sopenharmony_cisignifica che non è più usato e l'oggetto può essere rimosso. 6258c2ecf20Sopenharmony_ci 6268c2ecf20Sopenharmony_ciEcco il codice:: 6278c2ecf20Sopenharmony_ci 6288c2ecf20Sopenharmony_ci --- cache.c.interrupt 2003-12-09 14:25:43.000000000 +1100 6298c2ecf20Sopenharmony_ci +++ cache.c.refcnt 2003-12-09 14:33:05.000000000 +1100 6308c2ecf20Sopenharmony_ci @@ -7,6 +7,7 @@ 6318c2ecf20Sopenharmony_ci struct object 6328c2ecf20Sopenharmony_ci { 6338c2ecf20Sopenharmony_ci struct list_head list; 6348c2ecf20Sopenharmony_ci + unsigned int refcnt; 6358c2ecf20Sopenharmony_ci int id; 6368c2ecf20Sopenharmony_ci char name[32]; 6378c2ecf20Sopenharmony_ci int popularity; 6388c2ecf20Sopenharmony_ci @@ -17,6 +18,35 @@ 6398c2ecf20Sopenharmony_ci static unsigned int cache_num = 0; 6408c2ecf20Sopenharmony_ci #define MAX_CACHE_SIZE 10 6418c2ecf20Sopenharmony_ci 6428c2ecf20Sopenharmony_ci +static void __object_put(struct object *obj) 6438c2ecf20Sopenharmony_ci +{ 6448c2ecf20Sopenharmony_ci + if (--obj->refcnt == 0) 6458c2ecf20Sopenharmony_ci + kfree(obj); 6468c2ecf20Sopenharmony_ci +} 6478c2ecf20Sopenharmony_ci + 6488c2ecf20Sopenharmony_ci +static void __object_get(struct object *obj) 6498c2ecf20Sopenharmony_ci +{ 6508c2ecf20Sopenharmony_ci + obj->refcnt++; 6518c2ecf20Sopenharmony_ci +} 6528c2ecf20Sopenharmony_ci + 6538c2ecf20Sopenharmony_ci +void object_put(struct object *obj) 6548c2ecf20Sopenharmony_ci +{ 6558c2ecf20Sopenharmony_ci + unsigned long flags; 6568c2ecf20Sopenharmony_ci + 6578c2ecf20Sopenharmony_ci + spin_lock_irqsave(&cache_lock, flags); 6588c2ecf20Sopenharmony_ci + __object_put(obj); 6598c2ecf20Sopenharmony_ci + spin_unlock_irqrestore(&cache_lock, flags); 6608c2ecf20Sopenharmony_ci +} 6618c2ecf20Sopenharmony_ci + 6628c2ecf20Sopenharmony_ci +void object_get(struct object *obj) 6638c2ecf20Sopenharmony_ci +{ 6648c2ecf20Sopenharmony_ci + unsigned long flags; 6658c2ecf20Sopenharmony_ci + 6668c2ecf20Sopenharmony_ci + spin_lock_irqsave(&cache_lock, flags); 6678c2ecf20Sopenharmony_ci + __object_get(obj); 6688c2ecf20Sopenharmony_ci + spin_unlock_irqrestore(&cache_lock, flags); 6698c2ecf20Sopenharmony_ci +} 6708c2ecf20Sopenharmony_ci + 6718c2ecf20Sopenharmony_ci /* Must be holding cache_lock */ 6728c2ecf20Sopenharmony_ci static struct object *__cache_find(int id) 6738c2ecf20Sopenharmony_ci { 6748c2ecf20Sopenharmony_ci @@ -35,6 +65,7 @@ 6758c2ecf20Sopenharmony_ci { 6768c2ecf20Sopenharmony_ci BUG_ON(!obj); 6778c2ecf20Sopenharmony_ci list_del(&obj->list); 6788c2ecf20Sopenharmony_ci + __object_put(obj); 6798c2ecf20Sopenharmony_ci cache_num--; 6808c2ecf20Sopenharmony_ci } 6818c2ecf20Sopenharmony_ci 6828c2ecf20Sopenharmony_ci @@ -63,6 +94,7 @@ 6838c2ecf20Sopenharmony_ci strscpy(obj->name, name, sizeof(obj->name)); 6848c2ecf20Sopenharmony_ci obj->id = id; 6858c2ecf20Sopenharmony_ci obj->popularity = 0; 6868c2ecf20Sopenharmony_ci + obj->refcnt = 1; /* The cache holds a reference */ 6878c2ecf20Sopenharmony_ci 6888c2ecf20Sopenharmony_ci spin_lock_irqsave(&cache_lock, flags); 6898c2ecf20Sopenharmony_ci __cache_add(obj); 6908c2ecf20Sopenharmony_ci @@ -79,18 +111,15 @@ 6918c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&cache_lock, flags); 6928c2ecf20Sopenharmony_ci } 6938c2ecf20Sopenharmony_ci 6948c2ecf20Sopenharmony_ci -int cache_find(int id, char *name) 6958c2ecf20Sopenharmony_ci +struct object *cache_find(int id) 6968c2ecf20Sopenharmony_ci { 6978c2ecf20Sopenharmony_ci struct object *obj; 6988c2ecf20Sopenharmony_ci - int ret = -ENOENT; 6998c2ecf20Sopenharmony_ci unsigned long flags; 7008c2ecf20Sopenharmony_ci 7018c2ecf20Sopenharmony_ci spin_lock_irqsave(&cache_lock, flags); 7028c2ecf20Sopenharmony_ci obj = __cache_find(id); 7038c2ecf20Sopenharmony_ci - if (obj) { 7048c2ecf20Sopenharmony_ci - ret = 0; 7058c2ecf20Sopenharmony_ci - strcpy(name, obj->name); 7068c2ecf20Sopenharmony_ci - } 7078c2ecf20Sopenharmony_ci + if (obj) 7088c2ecf20Sopenharmony_ci + __object_get(obj); 7098c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&cache_lock, flags); 7108c2ecf20Sopenharmony_ci - return ret; 7118c2ecf20Sopenharmony_ci + return obj; 7128c2ecf20Sopenharmony_ci } 7138c2ecf20Sopenharmony_ci 7148c2ecf20Sopenharmony_ciAbbiamo incapsulato il contatore di riferimenti nelle tipiche funzioni 7158c2ecf20Sopenharmony_cidi 'get' e 'put'. Ora possiamo ritornare l'oggetto da cache_find() 7168c2ecf20Sopenharmony_cicol vantaggio che l'utente può dormire trattenendo l'oggetto (per esempio, 7178c2ecf20Sopenharmony_cicopy_to_user() per copiare il nome verso lo spazio utente). 7188c2ecf20Sopenharmony_ci 7198c2ecf20Sopenharmony_ciUn altro punto da notare è che ho detto che il contatore dovrebbe incrementarsi 7208c2ecf20Sopenharmony_ciper ogni puntatore ad un oggetto: quindi il contatore di riferimenti è 1 7218c2ecf20Sopenharmony_ciquando l'oggetto viene inserito nella memoria. In altre versione il framework 7228c2ecf20Sopenharmony_cinon trattiene un riferimento per se, ma diventa più complicato. 7238c2ecf20Sopenharmony_ci 7248c2ecf20Sopenharmony_ciUsare operazioni atomiche per il contatore di riferimenti 7258c2ecf20Sopenharmony_ci~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 7268c2ecf20Sopenharmony_ci 7278c2ecf20Sopenharmony_ciIn sostanza, :c:type:`atomic_t` viene usato come contatore di riferimenti. 7288c2ecf20Sopenharmony_ciCi sono un certo numbero di operazioni atomiche definite 7298c2ecf20Sopenharmony_ciin ``include/asm/atomic.h``: queste sono garantite come atomiche su qualsiasi 7308c2ecf20Sopenharmony_ciprocessore del sistema, quindi non sono necessari i *lock*. In questo caso è 7318c2ecf20Sopenharmony_cipiù semplice rispetto all'uso degli spinlock, benché l'uso degli spinlock 7328c2ecf20Sopenharmony_cisia più elegante per casi non banali. Le funzioni atomic_inc() e 7338c2ecf20Sopenharmony_ciatomic_dec_and_test() vengono usate al posto dei tipici operatori di 7348c2ecf20Sopenharmony_ciincremento e decremento, e i *lock* non sono più necessari per proteggere il 7358c2ecf20Sopenharmony_cicontatore stesso. 7368c2ecf20Sopenharmony_ci 7378c2ecf20Sopenharmony_ci:: 7388c2ecf20Sopenharmony_ci 7398c2ecf20Sopenharmony_ci --- cache.c.refcnt 2003-12-09 15:00:35.000000000 +1100 7408c2ecf20Sopenharmony_ci +++ cache.c.refcnt-atomic 2003-12-11 15:49:42.000000000 +1100 7418c2ecf20Sopenharmony_ci @@ -7,7 +7,7 @@ 7428c2ecf20Sopenharmony_ci struct object 7438c2ecf20Sopenharmony_ci { 7448c2ecf20Sopenharmony_ci struct list_head list; 7458c2ecf20Sopenharmony_ci - unsigned int refcnt; 7468c2ecf20Sopenharmony_ci + atomic_t refcnt; 7478c2ecf20Sopenharmony_ci int id; 7488c2ecf20Sopenharmony_ci char name[32]; 7498c2ecf20Sopenharmony_ci int popularity; 7508c2ecf20Sopenharmony_ci @@ -18,33 +18,15 @@ 7518c2ecf20Sopenharmony_ci static unsigned int cache_num = 0; 7528c2ecf20Sopenharmony_ci #define MAX_CACHE_SIZE 10 7538c2ecf20Sopenharmony_ci 7548c2ecf20Sopenharmony_ci -static void __object_put(struct object *obj) 7558c2ecf20Sopenharmony_ci -{ 7568c2ecf20Sopenharmony_ci - if (--obj->refcnt == 0) 7578c2ecf20Sopenharmony_ci - kfree(obj); 7588c2ecf20Sopenharmony_ci -} 7598c2ecf20Sopenharmony_ci - 7608c2ecf20Sopenharmony_ci -static void __object_get(struct object *obj) 7618c2ecf20Sopenharmony_ci -{ 7628c2ecf20Sopenharmony_ci - obj->refcnt++; 7638c2ecf20Sopenharmony_ci -} 7648c2ecf20Sopenharmony_ci - 7658c2ecf20Sopenharmony_ci void object_put(struct object *obj) 7668c2ecf20Sopenharmony_ci { 7678c2ecf20Sopenharmony_ci - unsigned long flags; 7688c2ecf20Sopenharmony_ci - 7698c2ecf20Sopenharmony_ci - spin_lock_irqsave(&cache_lock, flags); 7708c2ecf20Sopenharmony_ci - __object_put(obj); 7718c2ecf20Sopenharmony_ci - spin_unlock_irqrestore(&cache_lock, flags); 7728c2ecf20Sopenharmony_ci + if (atomic_dec_and_test(&obj->refcnt)) 7738c2ecf20Sopenharmony_ci + kfree(obj); 7748c2ecf20Sopenharmony_ci } 7758c2ecf20Sopenharmony_ci 7768c2ecf20Sopenharmony_ci void object_get(struct object *obj) 7778c2ecf20Sopenharmony_ci { 7788c2ecf20Sopenharmony_ci - unsigned long flags; 7798c2ecf20Sopenharmony_ci - 7808c2ecf20Sopenharmony_ci - spin_lock_irqsave(&cache_lock, flags); 7818c2ecf20Sopenharmony_ci - __object_get(obj); 7828c2ecf20Sopenharmony_ci - spin_unlock_irqrestore(&cache_lock, flags); 7838c2ecf20Sopenharmony_ci + atomic_inc(&obj->refcnt); 7848c2ecf20Sopenharmony_ci } 7858c2ecf20Sopenharmony_ci 7868c2ecf20Sopenharmony_ci /* Must be holding cache_lock */ 7878c2ecf20Sopenharmony_ci @@ -65,7 +47,7 @@ 7888c2ecf20Sopenharmony_ci { 7898c2ecf20Sopenharmony_ci BUG_ON(!obj); 7908c2ecf20Sopenharmony_ci list_del(&obj->list); 7918c2ecf20Sopenharmony_ci - __object_put(obj); 7928c2ecf20Sopenharmony_ci + object_put(obj); 7938c2ecf20Sopenharmony_ci cache_num--; 7948c2ecf20Sopenharmony_ci } 7958c2ecf20Sopenharmony_ci 7968c2ecf20Sopenharmony_ci @@ -94,7 +76,7 @@ 7978c2ecf20Sopenharmony_ci strscpy(obj->name, name, sizeof(obj->name)); 7988c2ecf20Sopenharmony_ci obj->id = id; 7998c2ecf20Sopenharmony_ci obj->popularity = 0; 8008c2ecf20Sopenharmony_ci - obj->refcnt = 1; /* The cache holds a reference */ 8018c2ecf20Sopenharmony_ci + atomic_set(&obj->refcnt, 1); /* The cache holds a reference */ 8028c2ecf20Sopenharmony_ci 8038c2ecf20Sopenharmony_ci spin_lock_irqsave(&cache_lock, flags); 8048c2ecf20Sopenharmony_ci __cache_add(obj); 8058c2ecf20Sopenharmony_ci @@ -119,7 +101,7 @@ 8068c2ecf20Sopenharmony_ci spin_lock_irqsave(&cache_lock, flags); 8078c2ecf20Sopenharmony_ci obj = __cache_find(id); 8088c2ecf20Sopenharmony_ci if (obj) 8098c2ecf20Sopenharmony_ci - __object_get(obj); 8108c2ecf20Sopenharmony_ci + object_get(obj); 8118c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&cache_lock, flags); 8128c2ecf20Sopenharmony_ci return obj; 8138c2ecf20Sopenharmony_ci } 8148c2ecf20Sopenharmony_ci 8158c2ecf20Sopenharmony_ciProteggere l'oggetto stesso 8168c2ecf20Sopenharmony_ci--------------------------- 8178c2ecf20Sopenharmony_ci 8188c2ecf20Sopenharmony_ciIn questo esempio, assumiamo che gli oggetti (ad eccezione del contatore 8198c2ecf20Sopenharmony_cidi riferimenti) non cambino mai dopo la loro creazione. Se vogliamo permettere 8208c2ecf20Sopenharmony_cial nome di cambiare abbiamo tre possibilità: 8218c2ecf20Sopenharmony_ci 8228c2ecf20Sopenharmony_ci- Si può togliere static da ``cache_lock`` e dire agli utenti che devono 8238c2ecf20Sopenharmony_ci trattenere il *lock* prima di modificare il nome di un oggetto. 8248c2ecf20Sopenharmony_ci 8258c2ecf20Sopenharmony_ci- Si può fornire una funzione cache_obj_rename() che prende il 8268c2ecf20Sopenharmony_ci *lock* e cambia il nome per conto del chiamante; si dirà poi agli utenti 8278c2ecf20Sopenharmony_ci di usare questa funzione. 8288c2ecf20Sopenharmony_ci 8298c2ecf20Sopenharmony_ci- Si può decidere che ``cache_lock`` protegge solo la memoria stessa, ed 8308c2ecf20Sopenharmony_ci un altro *lock* è necessario per la protezione del nome. 8318c2ecf20Sopenharmony_ci 8328c2ecf20Sopenharmony_ciTeoricamente, possiamo avere un *lock* per ogni campo e per ogni oggetto. 8338c2ecf20Sopenharmony_ciIn pratica, le varianti più comuni sono: 8348c2ecf20Sopenharmony_ci 8358c2ecf20Sopenharmony_ci- un *lock* che protegge l'infrastruttura (la lista ``cache`` di questo 8368c2ecf20Sopenharmony_ci esempio) e gli oggetti. Questo è quello che abbiamo fatto finora. 8378c2ecf20Sopenharmony_ci 8388c2ecf20Sopenharmony_ci- un *lock* che protegge l'infrastruttura (inclusi i puntatori alla lista 8398c2ecf20Sopenharmony_ci negli oggetti), e un *lock* nell'oggetto per proteggere il resto 8408c2ecf20Sopenharmony_ci dell'oggetto stesso. 8418c2ecf20Sopenharmony_ci 8428c2ecf20Sopenharmony_ci- *lock* multipli per proteggere l'infrastruttura (per esempio un *lock* 8438c2ecf20Sopenharmony_ci per ogni lista), possibilmente con un *lock* per oggetto. 8448c2ecf20Sopenharmony_ci 8458c2ecf20Sopenharmony_ciQui di seguito un'implementazione con "un lock per oggetto": 8468c2ecf20Sopenharmony_ci 8478c2ecf20Sopenharmony_ci:: 8488c2ecf20Sopenharmony_ci 8498c2ecf20Sopenharmony_ci --- cache.c.refcnt-atomic 2003-12-11 15:50:54.000000000 +1100 8508c2ecf20Sopenharmony_ci +++ cache.c.perobjectlock 2003-12-11 17:15:03.000000000 +1100 8518c2ecf20Sopenharmony_ci @@ -6,11 +6,17 @@ 8528c2ecf20Sopenharmony_ci 8538c2ecf20Sopenharmony_ci struct object 8548c2ecf20Sopenharmony_ci { 8558c2ecf20Sopenharmony_ci + /* These two protected by cache_lock. */ 8568c2ecf20Sopenharmony_ci struct list_head list; 8578c2ecf20Sopenharmony_ci + int popularity; 8588c2ecf20Sopenharmony_ci + 8598c2ecf20Sopenharmony_ci atomic_t refcnt; 8608c2ecf20Sopenharmony_ci + 8618c2ecf20Sopenharmony_ci + /* Doesn't change once created. */ 8628c2ecf20Sopenharmony_ci int id; 8638c2ecf20Sopenharmony_ci + 8648c2ecf20Sopenharmony_ci + spinlock_t lock; /* Protects the name */ 8658c2ecf20Sopenharmony_ci char name[32]; 8668c2ecf20Sopenharmony_ci - int popularity; 8678c2ecf20Sopenharmony_ci }; 8688c2ecf20Sopenharmony_ci 8698c2ecf20Sopenharmony_ci static DEFINE_SPINLOCK(cache_lock); 8708c2ecf20Sopenharmony_ci @@ -77,6 +84,7 @@ 8718c2ecf20Sopenharmony_ci obj->id = id; 8728c2ecf20Sopenharmony_ci obj->popularity = 0; 8738c2ecf20Sopenharmony_ci atomic_set(&obj->refcnt, 1); /* The cache holds a reference */ 8748c2ecf20Sopenharmony_ci + spin_lock_init(&obj->lock); 8758c2ecf20Sopenharmony_ci 8768c2ecf20Sopenharmony_ci spin_lock_irqsave(&cache_lock, flags); 8778c2ecf20Sopenharmony_ci __cache_add(obj); 8788c2ecf20Sopenharmony_ci 8798c2ecf20Sopenharmony_ciDa notare che ho deciso che il contatore di popolarità dovesse essere 8808c2ecf20Sopenharmony_ciprotetto da ``cache_lock`` piuttosto che dal *lock* dell'oggetto; questo 8818c2ecf20Sopenharmony_ciperché è logicamente parte dell'infrastruttura (come 8828c2ecf20Sopenharmony_ci:c:type:`struct list_head <list_head>` nell'oggetto). In questo modo, 8838c2ecf20Sopenharmony_ciin __cache_add(), non ho bisogno di trattenere il *lock* di ogni 8848c2ecf20Sopenharmony_cioggetto mentre si cerca il meno popolare. 8858c2ecf20Sopenharmony_ci 8868c2ecf20Sopenharmony_ciHo anche deciso che il campo id è immutabile, quindi non ho bisogno di 8878c2ecf20Sopenharmony_citrattenere il lock dell'oggetto quando si usa __cache_find() 8888c2ecf20Sopenharmony_ciper leggere questo campo; il *lock* dell'oggetto è usato solo dal chiamante 8898c2ecf20Sopenharmony_ciche vuole leggere o scrivere il campo name. 8908c2ecf20Sopenharmony_ci 8918c2ecf20Sopenharmony_ciInoltre, da notare che ho aggiunto un commento che descrive i dati che sono 8928c2ecf20Sopenharmony_ciprotetti dal *lock*. Questo è estremamente importante in quanto descrive il 8938c2ecf20Sopenharmony_cicomportamento del codice, che altrimenti sarebbe di difficile comprensione 8948c2ecf20Sopenharmony_cileggendo solamente il codice. E come dice Alan Cox: “Lock data, not code”. 8958c2ecf20Sopenharmony_ci 8968c2ecf20Sopenharmony_ciProblemi comuni 8978c2ecf20Sopenharmony_ci=============== 8988c2ecf20Sopenharmony_ci 8998c2ecf20Sopenharmony_ci.. _`it_deadlock`: 9008c2ecf20Sopenharmony_ci 9018c2ecf20Sopenharmony_ciStallo: semplice ed avanzato 9028c2ecf20Sopenharmony_ci---------------------------- 9038c2ecf20Sopenharmony_ci 9048c2ecf20Sopenharmony_ciEsiste un tipo di baco dove un pezzo di codice tenta di trattenere uno 9058c2ecf20Sopenharmony_cispinlock due volte: questo rimarrà in attesa attiva per sempre aspettando che 9068c2ecf20Sopenharmony_ciil *lock* venga rilasciato (in Linux spinlocks, rwlocks e mutex non sono 9078c2ecf20Sopenharmony_ciricorsivi). 9088c2ecf20Sopenharmony_ciQuesto è facile da diagnosticare: non è uno di quei problemi che ti tengono 9098c2ecf20Sopenharmony_cisveglio 5 notti a parlare da solo. 9108c2ecf20Sopenharmony_ci 9118c2ecf20Sopenharmony_ciUn caso un pochino più complesso; immaginate d'avere una spazio condiviso 9128c2ecf20Sopenharmony_cifra un softirq ed il contesto utente. Se usate spin_lock() per 9138c2ecf20Sopenharmony_ciproteggerlo, il contesto utente potrebbe essere interrotto da un softirq 9148c2ecf20Sopenharmony_cimentre trattiene il lock, da qui il softirq rimarrà in attesa attiva provando 9158c2ecf20Sopenharmony_ciad acquisire il *lock* già trattenuto nel contesto utente. 9168c2ecf20Sopenharmony_ci 9178c2ecf20Sopenharmony_ciQuesti casi sono chiamati stalli (*deadlock*), e come mostrato qui sopra, 9188c2ecf20Sopenharmony_cipuò succedere anche con un solo processore (Ma non sui sistemi 9198c2ecf20Sopenharmony_cimonoprocessore perché gli spinlock spariscano quando il kernel è compilato 9208c2ecf20Sopenharmony_cicon ``CONFIG_SMP``\ =n. Nonostante ciò, nel secondo caso avrete comunque 9218c2ecf20Sopenharmony_ciuna corruzione dei dati). 9228c2ecf20Sopenharmony_ci 9238c2ecf20Sopenharmony_ciQuesti casi sono facili da diagnosticare; sui sistemi multi-processore 9248c2ecf20Sopenharmony_ciil supervisione (*watchdog*) o l'opzione di compilazione ``DEBUG_SPINLOCK`` 9258c2ecf20Sopenharmony_ci(``include/linux/spinlock.h``) permettono di scovare immediatamente quando 9268c2ecf20Sopenharmony_cisuccedono. 9278c2ecf20Sopenharmony_ci 9288c2ecf20Sopenharmony_ciEsiste un caso più complesso che è conosciuto come l'abbraccio della morte; 9298c2ecf20Sopenharmony_ciquesto coinvolge due o più *lock*. Diciamo che avete un vettore di hash in cui 9308c2ecf20Sopenharmony_ciogni elemento è uno spinlock a cui è associata una lista di elementi con lo 9318c2ecf20Sopenharmony_cistesso hash. In un gestore di interruzioni software, dovete modificare un 9328c2ecf20Sopenharmony_cioggetto e spostarlo su un altro hash; quindi dovrete trattenete lo spinlock 9338c2ecf20Sopenharmony_cidel vecchio hash e di quello nuovo, quindi rimuovere l'oggetto dal vecchio ed 9348c2ecf20Sopenharmony_ciinserirlo nel nuovo. 9358c2ecf20Sopenharmony_ci 9368c2ecf20Sopenharmony_ciQui abbiamo due problemi. Primo, se il vostro codice prova a spostare un 9378c2ecf20Sopenharmony_cioggetto all'interno della stessa lista, otterrete uno stallo visto che 9388c2ecf20Sopenharmony_citenterà di trattenere lo stesso *lock* due volte. Secondo, se la stessa 9398c2ecf20Sopenharmony_ciinterruzione software su un altro processore sta tentando di spostare 9408c2ecf20Sopenharmony_ciun altro oggetto nella direzione opposta, potrebbe accadere quanto segue: 9418c2ecf20Sopenharmony_ci 9428c2ecf20Sopenharmony_ci+---------------------------------+---------------------------------+ 9438c2ecf20Sopenharmony_ci| CPU 1 | CPU 2 | 9448c2ecf20Sopenharmony_ci+=================================+=================================+ 9458c2ecf20Sopenharmony_ci| Trattiene *lock* A -> OK | Trattiene *lock* B -> OK | 9468c2ecf20Sopenharmony_ci+---------------------------------+---------------------------------+ 9478c2ecf20Sopenharmony_ci| Trattiene *lock* B -> attesa | Trattiene *lock* A -> attesa | 9488c2ecf20Sopenharmony_ci+---------------------------------+---------------------------------+ 9498c2ecf20Sopenharmony_ci 9508c2ecf20Sopenharmony_ciTable: Conseguenze 9518c2ecf20Sopenharmony_ci 9528c2ecf20Sopenharmony_ciEntrambe i processori rimarranno in attesa attiva sul *lock* per sempre, 9538c2ecf20Sopenharmony_ciaspettando che l'altro lo rilasci. Sembra e puzza come un blocco totale. 9548c2ecf20Sopenharmony_ci 9558c2ecf20Sopenharmony_ciPrevenire gli stalli 9568c2ecf20Sopenharmony_ci-------------------- 9578c2ecf20Sopenharmony_ci 9588c2ecf20Sopenharmony_ciI libri di testo vi diranno che se trattenete i *lock* sempre nello stesso 9598c2ecf20Sopenharmony_ciordine non avrete mai un simile stallo. La pratica vi dirà che questo 9608c2ecf20Sopenharmony_ciapproccio non funziona all'ingrandirsi del sistema: quando creo un nuovo 9618c2ecf20Sopenharmony_ci*lock* non ne capisco abbastanza del kernel per dire in quale dei 5000 *lock* 9628c2ecf20Sopenharmony_cisi incastrerà. 9638c2ecf20Sopenharmony_ci 9648c2ecf20Sopenharmony_ciI *lock* migliori sono quelli incapsulati: non vengono esposti nei file di 9658c2ecf20Sopenharmony_ciintestazione, e non vengono mai trattenuti fuori dallo stesso file. Potete 9668c2ecf20Sopenharmony_cirileggere questo codice e vedere che non ci sarà mai uno stallo perché 9678c2ecf20Sopenharmony_cinon tenterà mai di trattenere un altro *lock* quando lo ha già. 9688c2ecf20Sopenharmony_ciLe persone che usano il vostro codice non devono nemmeno sapere che voi 9698c2ecf20Sopenharmony_cistate usando dei *lock*. 9708c2ecf20Sopenharmony_ci 9718c2ecf20Sopenharmony_ciUn classico problema deriva dall'uso di *callback* e di *hook*: se li 9728c2ecf20Sopenharmony_cichiamate mentre trattenete un *lock*, rischiate uno stallo o un abbraccio 9738c2ecf20Sopenharmony_cidella morte (chi lo sa cosa farà una *callback*?). 9748c2ecf20Sopenharmony_ci 9758c2ecf20Sopenharmony_ciOssessiva prevenzione degli stalli 9768c2ecf20Sopenharmony_ci~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 9778c2ecf20Sopenharmony_ci 9788c2ecf20Sopenharmony_ciGli stalli sono un problema, ma non così terribile come la corruzione dei dati. 9798c2ecf20Sopenharmony_ciUn pezzo di codice trattiene un *lock* di lettura, cerca in una lista, 9808c2ecf20Sopenharmony_cifallisce nel trovare quello che vuole, quindi rilascia il *lock* di lettura, 9818c2ecf20Sopenharmony_citrattiene un *lock* di scrittura ed inserisce un oggetto; questo genere di 9828c2ecf20Sopenharmony_cicodice presenta una corsa critica. 9838c2ecf20Sopenharmony_ci 9848c2ecf20Sopenharmony_ciSe non riuscite a capire il perché, per favore state alla larga dal mio 9858c2ecf20Sopenharmony_cicodice. 9868c2ecf20Sopenharmony_ci 9878c2ecf20Sopenharmony_cicorsa fra temporizzatori: un passatempo del kernel 9888c2ecf20Sopenharmony_ci-------------------------------------------------- 9898c2ecf20Sopenharmony_ci 9908c2ecf20Sopenharmony_ciI temporizzatori potrebbero avere dei problemi con le corse critiche. 9918c2ecf20Sopenharmony_ciConsiderate una collezione di oggetti (liste, hash, eccetera) dove ogni oggetto 9928c2ecf20Sopenharmony_ciha un temporizzatore che sta per distruggerlo. 9938c2ecf20Sopenharmony_ci 9948c2ecf20Sopenharmony_ciSe volete eliminare l'intera collezione (diciamo quando rimuovete un modulo), 9958c2ecf20Sopenharmony_cipotreste fare come segue:: 9968c2ecf20Sopenharmony_ci 9978c2ecf20Sopenharmony_ci /* THIS CODE BAD BAD BAD BAD: IF IT WAS ANY WORSE IT WOULD USE 9988c2ecf20Sopenharmony_ci HUNGARIAN NOTATION */ 9998c2ecf20Sopenharmony_ci spin_lock_bh(&list_lock); 10008c2ecf20Sopenharmony_ci 10018c2ecf20Sopenharmony_ci while (list) { 10028c2ecf20Sopenharmony_ci struct foo *next = list->next; 10038c2ecf20Sopenharmony_ci del_timer(&list->timer); 10048c2ecf20Sopenharmony_ci kfree(list); 10058c2ecf20Sopenharmony_ci list = next; 10068c2ecf20Sopenharmony_ci } 10078c2ecf20Sopenharmony_ci 10088c2ecf20Sopenharmony_ci spin_unlock_bh(&list_lock); 10098c2ecf20Sopenharmony_ci 10108c2ecf20Sopenharmony_ciPrimo o poi, questo esploderà su un sistema multiprocessore perché un 10118c2ecf20Sopenharmony_citemporizzatore potrebbe essere già partiro prima di spin_lock_bh(), 10128c2ecf20Sopenharmony_cie prenderà il *lock* solo dopo spin_unlock_bh(), e cercherà 10138c2ecf20Sopenharmony_cidi eliminare il suo oggetto (che però è già stato eliminato). 10148c2ecf20Sopenharmony_ci 10158c2ecf20Sopenharmony_ciQuesto può essere evitato controllando il valore di ritorno di 10168c2ecf20Sopenharmony_cidel_timer(): se ritorna 1, il temporizzatore è stato già 10178c2ecf20Sopenharmony_cirimosso. Se 0, significa (in questo caso) che il temporizzatore è in 10188c2ecf20Sopenharmony_ciesecuzione, quindi possiamo fare come segue:: 10198c2ecf20Sopenharmony_ci 10208c2ecf20Sopenharmony_ci retry: 10218c2ecf20Sopenharmony_ci spin_lock_bh(&list_lock); 10228c2ecf20Sopenharmony_ci 10238c2ecf20Sopenharmony_ci while (list) { 10248c2ecf20Sopenharmony_ci struct foo *next = list->next; 10258c2ecf20Sopenharmony_ci if (!del_timer(&list->timer)) { 10268c2ecf20Sopenharmony_ci /* Give timer a chance to delete this */ 10278c2ecf20Sopenharmony_ci spin_unlock_bh(&list_lock); 10288c2ecf20Sopenharmony_ci goto retry; 10298c2ecf20Sopenharmony_ci } 10308c2ecf20Sopenharmony_ci kfree(list); 10318c2ecf20Sopenharmony_ci list = next; 10328c2ecf20Sopenharmony_ci } 10338c2ecf20Sopenharmony_ci 10348c2ecf20Sopenharmony_ci spin_unlock_bh(&list_lock); 10358c2ecf20Sopenharmony_ci 10368c2ecf20Sopenharmony_ciUn altro problema è l'eliminazione dei temporizzatori che si riavviano 10378c2ecf20Sopenharmony_cida soli (chiamando add_timer() alla fine della loro esecuzione). 10388c2ecf20Sopenharmony_ciDato che questo è un problema abbastanza comune con una propensione 10398c2ecf20Sopenharmony_cialle corse critiche, dovreste usare del_timer_sync() 10408c2ecf20Sopenharmony_ci(``include/linux/timer.h``) per gestire questo caso. Questa ritorna il 10418c2ecf20Sopenharmony_cinumero di volte che il temporizzatore è stato interrotto prima che 10428c2ecf20Sopenharmony_cifosse in grado di fermarlo senza che si riavviasse. 10438c2ecf20Sopenharmony_ci 10448c2ecf20Sopenharmony_ciVelocità della sincronizzazione 10458c2ecf20Sopenharmony_ci=============================== 10468c2ecf20Sopenharmony_ci 10478c2ecf20Sopenharmony_ciCi sono tre cose importanti da tenere in considerazione quando si valuta 10488c2ecf20Sopenharmony_cila velocità d'esecuzione di un pezzo di codice che necessita di 10498c2ecf20Sopenharmony_cisincronizzazione. La prima è la concorrenza: quante cose rimangono in attesa 10508c2ecf20Sopenharmony_cimentre qualcuno trattiene un *lock*. La seconda è il tempo necessario per 10518c2ecf20Sopenharmony_ciacquisire (senza contese) e rilasciare un *lock*. La terza è di usare meno 10528c2ecf20Sopenharmony_ci*lock* o di più furbi. Immagino che i *lock* vengano usati regolarmente, 10538c2ecf20Sopenharmony_cialtrimenti, non sareste interessati all'efficienza. 10548c2ecf20Sopenharmony_ci 10558c2ecf20Sopenharmony_ciLa concorrenza dipende da quanto a lungo un *lock* è trattenuto: dovreste 10568c2ecf20Sopenharmony_citrattenere un *lock* solo il tempo minimo necessario ma non un istante in più. 10578c2ecf20Sopenharmony_ciNella memoria dell'esempio precedente, creiamo gli oggetti senza trattenere 10588c2ecf20Sopenharmony_ciil *lock*, poi acquisiamo il *lock* quando siamo pronti per inserirlo nella 10598c2ecf20Sopenharmony_cilista. 10608c2ecf20Sopenharmony_ci 10618c2ecf20Sopenharmony_ciIl tempo di acquisizione di un *lock* dipende da quanto danno fa 10628c2ecf20Sopenharmony_cil'operazione sulla *pipeline* (ovvero stalli della *pipeline*) e quant'è 10638c2ecf20Sopenharmony_ciprobabile che il processore corrente sia stato anche l'ultimo ad acquisire 10648c2ecf20Sopenharmony_ciil *lock* (in pratica, il *lock* è nella memoria cache del processore 10658c2ecf20Sopenharmony_cicorrente?): su sistemi multi-processore questa probabilità precipita 10668c2ecf20Sopenharmony_cirapidamente. Consideriamo un processore Intel Pentium III a 700Mhz: questo 10678c2ecf20Sopenharmony_ciesegue un'istruzione in 0.7ns, un incremento atomico richiede 58ns, acquisire 10688c2ecf20Sopenharmony_ciun *lock* che è nella memoria cache del processore richiede 160ns, e un 10698c2ecf20Sopenharmony_citrasferimento dalla memoria cache di un altro processore richiede altri 10708c2ecf20Sopenharmony_ci170/360ns (Leggetevi l'articolo di Paul McKenney's `Linux Journal RCU 10718c2ecf20Sopenharmony_ciarticle <http://www.linuxjournal.com/article.php?sid=6993>`__). 10728c2ecf20Sopenharmony_ci 10738c2ecf20Sopenharmony_ciQuesti due obiettivi sono in conflitto: trattenere un *lock* per il minor 10748c2ecf20Sopenharmony_citempo possibile potrebbe richiedere la divisione in più *lock* per diverse 10758c2ecf20Sopenharmony_ciparti (come nel nostro ultimo esempio con un *lock* per ogni oggetto), 10768c2ecf20Sopenharmony_cima questo aumenta il numero di acquisizioni di *lock*, ed il risultato 10778c2ecf20Sopenharmony_cispesso è che tutto è più lento che con un singolo *lock*. Questo è un altro 10788c2ecf20Sopenharmony_ciargomento in favore della semplicità quando si parla di sincronizzazione. 10798c2ecf20Sopenharmony_ci 10808c2ecf20Sopenharmony_ciIl terzo punto è discusso di seguito: ci sono alcune tecniche per ridurre 10818c2ecf20Sopenharmony_ciil numero di sincronizzazioni che devono essere fatte. 10828c2ecf20Sopenharmony_ci 10838c2ecf20Sopenharmony_ciRead/Write Lock Variants 10848c2ecf20Sopenharmony_ci------------------------ 10858c2ecf20Sopenharmony_ci 10868c2ecf20Sopenharmony_ciSia gli spinlock che i mutex hanno una variante per la lettura/scrittura 10878c2ecf20Sopenharmony_ci(read/write): ``rwlock_t`` e :c:type:`struct rw_semaphore <rw_semaphore>`. 10888c2ecf20Sopenharmony_ciQueste dividono gli utenti in due categorie: i lettori e gli scrittori. 10898c2ecf20Sopenharmony_ciSe state solo leggendo i dati, potete acquisire il *lock* di lettura, ma 10908c2ecf20Sopenharmony_ciper scrivere avrete bisogno del *lock* di scrittura. Molti possono trattenere 10918c2ecf20Sopenharmony_ciil *lock* di lettura, ma solo uno scrittore alla volta può trattenere 10928c2ecf20Sopenharmony_ciquello di scrittura. 10938c2ecf20Sopenharmony_ci 10948c2ecf20Sopenharmony_ciSe il vostro codice si divide chiaramente in codice per lettori e codice 10958c2ecf20Sopenharmony_ciper scrittori (come nel nostro esempio), e il *lock* dei lettori viene 10968c2ecf20Sopenharmony_citrattenuto per molto tempo, allora l'uso di questo tipo di *lock* può aiutare. 10978c2ecf20Sopenharmony_ciQuesti sono leggermente più lenti rispetto alla loro versione normale, quindi 10988c2ecf20Sopenharmony_cinella pratica l'uso di ``rwlock_t`` non ne vale la pena. 10998c2ecf20Sopenharmony_ci 11008c2ecf20Sopenharmony_ciEvitare i *lock*: Read Copy Update 11018c2ecf20Sopenharmony_ci-------------------------------------------- 11028c2ecf20Sopenharmony_ci 11038c2ecf20Sopenharmony_ciEsiste un metodo di sincronizzazione per letture e scritture detto 11048c2ecf20Sopenharmony_ciRead Copy Update. Con l'uso della tecnica RCU, i lettori possono scordarsi 11058c2ecf20Sopenharmony_cicompletamente di trattenere i *lock*; dato che nel nostro esempio ci 11068c2ecf20Sopenharmony_ciaspettiamo d'avere più lettore che scrittori (altrimenti questa memoria 11078c2ecf20Sopenharmony_cisarebbe uno spreco) possiamo dire che questo meccanismo permette 11088c2ecf20Sopenharmony_ciun'ottimizzazione. 11098c2ecf20Sopenharmony_ci 11108c2ecf20Sopenharmony_ciCome facciamo a sbarazzarci dei *lock* di lettura? Sbarazzarsi dei *lock* di 11118c2ecf20Sopenharmony_cilettura significa che uno scrittore potrebbe cambiare la lista sotto al naso 11128c2ecf20Sopenharmony_cidei lettori. Questo è abbastanza semplice: possiamo leggere una lista 11138c2ecf20Sopenharmony_ciconcatenata se lo scrittore aggiunge elementi alla fine e con certe 11148c2ecf20Sopenharmony_ciprecauzioni. Per esempio, aggiungendo ``new`` ad una lista concatenata 11158c2ecf20Sopenharmony_cichiamata ``list``:: 11168c2ecf20Sopenharmony_ci 11178c2ecf20Sopenharmony_ci new->next = list->next; 11188c2ecf20Sopenharmony_ci wmb(); 11198c2ecf20Sopenharmony_ci list->next = new; 11208c2ecf20Sopenharmony_ci 11218c2ecf20Sopenharmony_ciLa funzione wmb() è una barriera di sincronizzazione delle 11228c2ecf20Sopenharmony_ciscritture. Questa garantisce che la prima operazione (impostare l'elemento 11238c2ecf20Sopenharmony_ci``next`` del nuovo elemento) venga completata e vista da tutti i processori 11248c2ecf20Sopenharmony_ciprima che venga eseguita la seconda operazione (che sarebbe quella di mettere 11258c2ecf20Sopenharmony_ciil nuovo elemento nella lista). Questo è importante perché i moderni 11268c2ecf20Sopenharmony_cicompilatori ed i moderni processori possono, entrambe, riordinare le istruzioni 11278c2ecf20Sopenharmony_cise non vengono istruiti altrimenti: vogliamo che i lettori non vedano 11288c2ecf20Sopenharmony_cicompletamente il nuovo elemento; oppure che lo vedano correttamente e quindi 11298c2ecf20Sopenharmony_ciil puntatore ``next`` deve puntare al resto della lista. 11308c2ecf20Sopenharmony_ci 11318c2ecf20Sopenharmony_ciFortunatamente, c'è una funzione che fa questa operazione sulle liste 11328c2ecf20Sopenharmony_ci:c:type:`struct list_head <list_head>`: list_add_rcu() 11338c2ecf20Sopenharmony_ci(``include/linux/list.h``). 11348c2ecf20Sopenharmony_ci 11358c2ecf20Sopenharmony_ciRimuovere un elemento dalla lista è anche più facile: sostituiamo il puntatore 11368c2ecf20Sopenharmony_cial vecchio elemento con quello del suo successore, e i lettori vedranno 11378c2ecf20Sopenharmony_cil'elemento o lo salteranno. 11388c2ecf20Sopenharmony_ci 11398c2ecf20Sopenharmony_ci:: 11408c2ecf20Sopenharmony_ci 11418c2ecf20Sopenharmony_ci list->next = old->next; 11428c2ecf20Sopenharmony_ci 11438c2ecf20Sopenharmony_ciLa funzione list_del_rcu() (``include/linux/list.h``) fa esattamente 11448c2ecf20Sopenharmony_ciquesto (la versione normale corrompe il vecchio oggetto, e non vogliamo che 11458c2ecf20Sopenharmony_ciaccada). 11468c2ecf20Sopenharmony_ci 11478c2ecf20Sopenharmony_ciAnche i lettori devono stare attenti: alcuni processori potrebbero leggere 11488c2ecf20Sopenharmony_ciattraverso il puntatore ``next`` il contenuto dell'elemento successivo 11498c2ecf20Sopenharmony_citroppo presto, ma non accorgersi che il contenuto caricato è sbagliato quando 11508c2ecf20Sopenharmony_ciil puntatore ``next`` viene modificato alla loro spalle. Ancora una volta 11518c2ecf20Sopenharmony_cic'è una funzione che viene in vostro aiuto list_for_each_entry_rcu() 11528c2ecf20Sopenharmony_ci(``include/linux/list.h``). Ovviamente, gli scrittori possono usare 11538c2ecf20Sopenharmony_cilist_for_each_entry() dato che non ci possono essere due scrittori 11548c2ecf20Sopenharmony_ciin contemporanea. 11558c2ecf20Sopenharmony_ci 11568c2ecf20Sopenharmony_ciIl nostro ultimo dilemma è il seguente: quando possiamo realmente distruggere 11578c2ecf20Sopenharmony_cil'elemento rimosso? Ricordate, un lettore potrebbe aver avuto accesso a questo 11588c2ecf20Sopenharmony_cielemento proprio ora: se eliminiamo questo elemento ed il puntatore ``next`` 11598c2ecf20Sopenharmony_cicambia, il lettore salterà direttamente nella spazzatura e scoppierà. Dobbiamo 11608c2ecf20Sopenharmony_ciaspettare finché tutti i lettori che stanno attraversando la lista abbiano 11618c2ecf20Sopenharmony_cifinito. Utilizziamo call_rcu() per registrare una funzione di 11628c2ecf20Sopenharmony_cirichiamo che distrugga l'oggetto quando tutti i lettori correnti hanno 11638c2ecf20Sopenharmony_citerminato. In alternative, potrebbe essere usata la funzione 11648c2ecf20Sopenharmony_cisynchronize_rcu() che blocca l'esecuzione finché tutti i lettori 11658c2ecf20Sopenharmony_cinon terminano di ispezionare la lista. 11668c2ecf20Sopenharmony_ci 11678c2ecf20Sopenharmony_ciMa come fa l'RCU a sapere quando i lettori sono finiti? Il meccanismo è 11688c2ecf20Sopenharmony_ciil seguente: innanzi tutto i lettori accedono alla lista solo fra la coppia 11698c2ecf20Sopenharmony_circu_read_lock()/rcu_read_unlock() che disabilita la 11708c2ecf20Sopenharmony_ciprelazione così che i lettori non vengano sospesi mentre stanno leggendo 11718c2ecf20Sopenharmony_cila lista. 11728c2ecf20Sopenharmony_ci 11738c2ecf20Sopenharmony_ciPoi, l'RCU aspetta finché tutti i processori non abbiano dormito almeno 11748c2ecf20Sopenharmony_ciuna volta; a questo punto, dato che i lettori non possono dormire, possiamo 11758c2ecf20Sopenharmony_cidedurre che un qualsiasi lettore che abbia consultato la lista durante la 11768c2ecf20Sopenharmony_cirimozione abbia già terminato, quindi la *callback* viene eseguita. Il vero 11778c2ecf20Sopenharmony_cicodice RCU è un po' più ottimizzato di così, ma questa è l'idea di fondo. 11788c2ecf20Sopenharmony_ci 11798c2ecf20Sopenharmony_ci:: 11808c2ecf20Sopenharmony_ci 11818c2ecf20Sopenharmony_ci --- cache.c.perobjectlock 2003-12-11 17:15:03.000000000 +1100 11828c2ecf20Sopenharmony_ci +++ cache.c.rcupdate 2003-12-11 17:55:14.000000000 +1100 11838c2ecf20Sopenharmony_ci @@ -1,15 +1,18 @@ 11848c2ecf20Sopenharmony_ci #include <linux/list.h> 11858c2ecf20Sopenharmony_ci #include <linux/slab.h> 11868c2ecf20Sopenharmony_ci #include <linux/string.h> 11878c2ecf20Sopenharmony_ci +#include <linux/rcupdate.h> 11888c2ecf20Sopenharmony_ci #include <linux/mutex.h> 11898c2ecf20Sopenharmony_ci #include <asm/errno.h> 11908c2ecf20Sopenharmony_ci 11918c2ecf20Sopenharmony_ci struct object 11928c2ecf20Sopenharmony_ci { 11938c2ecf20Sopenharmony_ci - /* These two protected by cache_lock. */ 11948c2ecf20Sopenharmony_ci + /* This is protected by RCU */ 11958c2ecf20Sopenharmony_ci struct list_head list; 11968c2ecf20Sopenharmony_ci int popularity; 11978c2ecf20Sopenharmony_ci 11988c2ecf20Sopenharmony_ci + struct rcu_head rcu; 11998c2ecf20Sopenharmony_ci + 12008c2ecf20Sopenharmony_ci atomic_t refcnt; 12018c2ecf20Sopenharmony_ci 12028c2ecf20Sopenharmony_ci /* Doesn't change once created. */ 12038c2ecf20Sopenharmony_ci @@ -40,7 +43,7 @@ 12048c2ecf20Sopenharmony_ci { 12058c2ecf20Sopenharmony_ci struct object *i; 12068c2ecf20Sopenharmony_ci 12078c2ecf20Sopenharmony_ci - list_for_each_entry(i, &cache, list) { 12088c2ecf20Sopenharmony_ci + list_for_each_entry_rcu(i, &cache, list) { 12098c2ecf20Sopenharmony_ci if (i->id == id) { 12108c2ecf20Sopenharmony_ci i->popularity++; 12118c2ecf20Sopenharmony_ci return i; 12128c2ecf20Sopenharmony_ci @@ -49,19 +52,25 @@ 12138c2ecf20Sopenharmony_ci return NULL; 12148c2ecf20Sopenharmony_ci } 12158c2ecf20Sopenharmony_ci 12168c2ecf20Sopenharmony_ci +/* Final discard done once we know no readers are looking. */ 12178c2ecf20Sopenharmony_ci +static void cache_delete_rcu(void *arg) 12188c2ecf20Sopenharmony_ci +{ 12198c2ecf20Sopenharmony_ci + object_put(arg); 12208c2ecf20Sopenharmony_ci +} 12218c2ecf20Sopenharmony_ci + 12228c2ecf20Sopenharmony_ci /* Must be holding cache_lock */ 12238c2ecf20Sopenharmony_ci static void __cache_delete(struct object *obj) 12248c2ecf20Sopenharmony_ci { 12258c2ecf20Sopenharmony_ci BUG_ON(!obj); 12268c2ecf20Sopenharmony_ci - list_del(&obj->list); 12278c2ecf20Sopenharmony_ci - object_put(obj); 12288c2ecf20Sopenharmony_ci + list_del_rcu(&obj->list); 12298c2ecf20Sopenharmony_ci cache_num--; 12308c2ecf20Sopenharmony_ci + call_rcu(&obj->rcu, cache_delete_rcu); 12318c2ecf20Sopenharmony_ci } 12328c2ecf20Sopenharmony_ci 12338c2ecf20Sopenharmony_ci /* Must be holding cache_lock */ 12348c2ecf20Sopenharmony_ci static void __cache_add(struct object *obj) 12358c2ecf20Sopenharmony_ci { 12368c2ecf20Sopenharmony_ci - list_add(&obj->list, &cache); 12378c2ecf20Sopenharmony_ci + list_add_rcu(&obj->list, &cache); 12388c2ecf20Sopenharmony_ci if (++cache_num > MAX_CACHE_SIZE) { 12398c2ecf20Sopenharmony_ci struct object *i, *outcast = NULL; 12408c2ecf20Sopenharmony_ci list_for_each_entry(i, &cache, list) { 12418c2ecf20Sopenharmony_ci @@ -104,12 +114,11 @@ 12428c2ecf20Sopenharmony_ci struct object *cache_find(int id) 12438c2ecf20Sopenharmony_ci { 12448c2ecf20Sopenharmony_ci struct object *obj; 12458c2ecf20Sopenharmony_ci - unsigned long flags; 12468c2ecf20Sopenharmony_ci 12478c2ecf20Sopenharmony_ci - spin_lock_irqsave(&cache_lock, flags); 12488c2ecf20Sopenharmony_ci + rcu_read_lock(); 12498c2ecf20Sopenharmony_ci obj = __cache_find(id); 12508c2ecf20Sopenharmony_ci if (obj) 12518c2ecf20Sopenharmony_ci object_get(obj); 12528c2ecf20Sopenharmony_ci - spin_unlock_irqrestore(&cache_lock, flags); 12538c2ecf20Sopenharmony_ci + rcu_read_unlock(); 12548c2ecf20Sopenharmony_ci return obj; 12558c2ecf20Sopenharmony_ci } 12568c2ecf20Sopenharmony_ci 12578c2ecf20Sopenharmony_ciDa notare che i lettori modificano il campo popularity nella funzione 12588c2ecf20Sopenharmony_ci__cache_find(), e ora non trattiene alcun *lock*. Una soluzione 12598c2ecf20Sopenharmony_cipotrebbe essere quella di rendere la variabile ``atomic_t``, ma per l'uso 12608c2ecf20Sopenharmony_ciche ne abbiamo fatto qui, non ci interessano queste corse critiche perché un 12618c2ecf20Sopenharmony_cirisultato approssimativo è comunque accettabile, quindi non l'ho cambiato. 12628c2ecf20Sopenharmony_ci 12638c2ecf20Sopenharmony_ciIl risultato è che la funzione cache_find() non ha bisogno di alcuna 12648c2ecf20Sopenharmony_cisincronizzazione con le altre funzioni, quindi è veloce su un sistema 12658c2ecf20Sopenharmony_cimulti-processore tanto quanto lo sarebbe su un sistema mono-processore. 12668c2ecf20Sopenharmony_ci 12678c2ecf20Sopenharmony_ciEsiste un'ulteriore ottimizzazione possibile: vi ricordate il codice originale 12688c2ecf20Sopenharmony_cidella nostra memoria dove non c'erano contatori di riferimenti e il chiamante 12698c2ecf20Sopenharmony_cisemplicemente tratteneva il *lock* prima di accedere ad un oggetto? Questo è 12708c2ecf20Sopenharmony_ciancora possibile: se trattenete un *lock* nessuno potrà cancellare l'oggetto, 12718c2ecf20Sopenharmony_ciquindi non avete bisogno di incrementare e decrementare il contatore di 12728c2ecf20Sopenharmony_ciriferimenti. 12738c2ecf20Sopenharmony_ci 12748c2ecf20Sopenharmony_ciOra, dato che il '*lock* di lettura' di un RCU non fa altro che disabilitare 12758c2ecf20Sopenharmony_cila prelazione, un chiamante che ha sempre la prelazione disabilitata fra le 12768c2ecf20Sopenharmony_cichiamate cache_find() e object_put() non necessita 12778c2ecf20Sopenharmony_cidi incrementare e decrementare il contatore di riferimenti. Potremmo 12788c2ecf20Sopenharmony_ciesporre la funzione __cache_find() dichiarandola non-static, 12798c2ecf20Sopenharmony_cie quel chiamante potrebbe usare direttamente questa funzione. 12808c2ecf20Sopenharmony_ci 12818c2ecf20Sopenharmony_ciIl beneficio qui sta nel fatto che il contatore di riferimenti no 12828c2ecf20Sopenharmony_civiene scritto: l'oggetto non viene alterato in alcun modo e quindi diventa 12838c2ecf20Sopenharmony_cimolto più veloce su sistemi molti-processore grazie alla loro memoria cache. 12848c2ecf20Sopenharmony_ci 12858c2ecf20Sopenharmony_ci.. _`it_per-cpu`: 12868c2ecf20Sopenharmony_ci 12878c2ecf20Sopenharmony_ciDati per processore 12888c2ecf20Sopenharmony_ci------------------- 12898c2ecf20Sopenharmony_ci 12908c2ecf20Sopenharmony_ciUn'altra tecnica comunemente usata per evitare la sincronizzazione è quella 12918c2ecf20Sopenharmony_cidi duplicare le informazioni per ogni processore. Per esempio, se volete 12928c2ecf20Sopenharmony_ciavere un contatore di qualcosa, potreste utilizzare uno spinlock ed un 12938c2ecf20Sopenharmony_cisingolo contatore. Facile e pulito. 12948c2ecf20Sopenharmony_ci 12958c2ecf20Sopenharmony_ciSe questo dovesse essere troppo lento (solitamente non lo è, ma se avete 12968c2ecf20Sopenharmony_cidimostrato che lo è devvero), potreste usare un contatore per ogni processore 12978c2ecf20Sopenharmony_cie quindi non sarebbe più necessaria la mutua esclusione. Vedere 12988c2ecf20Sopenharmony_ciDEFINE_PER_CPU(), get_cpu_var() e put_cpu_var() 12998c2ecf20Sopenharmony_ci(``include/linux/percpu.h``). 13008c2ecf20Sopenharmony_ci 13018c2ecf20Sopenharmony_ciIl tipo di dato ``local_t``, la funzione cpu_local_inc() e tutte 13028c2ecf20Sopenharmony_cile altre funzioni associate, sono di particolare utilità per semplici contatori 13038c2ecf20Sopenharmony_ciper-processore; su alcune architetture sono anche più efficienti 13048c2ecf20Sopenharmony_ci(``include/asm/local.h``). 13058c2ecf20Sopenharmony_ci 13068c2ecf20Sopenharmony_ciDa notare che non esiste un modo facile ed affidabile per ottenere il valore 13078c2ecf20Sopenharmony_cidi un simile contatore senza introdurre altri *lock*. In alcuni casi questo 13088c2ecf20Sopenharmony_cinon è un problema. 13098c2ecf20Sopenharmony_ci 13108c2ecf20Sopenharmony_ciDati che sono usati prevalentemente dai gestori d'interruzioni 13118c2ecf20Sopenharmony_ci-------------------------------------------------------------- 13128c2ecf20Sopenharmony_ci 13138c2ecf20Sopenharmony_ciSe i dati vengono utilizzati sempre dallo stesso gestore d'interruzioni, 13148c2ecf20Sopenharmony_ciallora i *lock* non vi servono per niente: il kernel già vi garantisce che 13158c2ecf20Sopenharmony_ciil gestore d'interruzione non verrà eseguito in contemporanea su diversi 13168c2ecf20Sopenharmony_ciprocessori. 13178c2ecf20Sopenharmony_ci 13188c2ecf20Sopenharmony_ciManfred Spraul fa notare che potreste comunque comportarvi così anche 13198c2ecf20Sopenharmony_cise i dati vengono occasionalmente utilizzati da un contesto utente o 13208c2ecf20Sopenharmony_cida un'interruzione software. Il gestore d'interruzione non utilizza alcun 13218c2ecf20Sopenharmony_ci*lock*, e tutti gli altri accessi verranno fatti così:: 13228c2ecf20Sopenharmony_ci 13238c2ecf20Sopenharmony_ci spin_lock(&lock); 13248c2ecf20Sopenharmony_ci disable_irq(irq); 13258c2ecf20Sopenharmony_ci ... 13268c2ecf20Sopenharmony_ci enable_irq(irq); 13278c2ecf20Sopenharmony_ci spin_unlock(&lock); 13288c2ecf20Sopenharmony_ci 13298c2ecf20Sopenharmony_ciLa funzione disable_irq() impedisce al gestore d'interruzioni 13308c2ecf20Sopenharmony_cid'essere eseguito (e aspetta che finisca nel caso fosse in esecuzione su 13318c2ecf20Sopenharmony_ciun altro processore). Lo spinlock, invece, previene accessi simultanei. 13328c2ecf20Sopenharmony_ciNaturalmente, questo è più lento della semplice chiamata 13338c2ecf20Sopenharmony_cispin_lock_irq(), quindi ha senso solo se questo genere di accesso 13348c2ecf20Sopenharmony_ciè estremamente raro. 13358c2ecf20Sopenharmony_ci 13368c2ecf20Sopenharmony_ci.. _`it_sleeping-things`: 13378c2ecf20Sopenharmony_ci 13388c2ecf20Sopenharmony_ciQuali funzioni possono essere chiamate in modo sicuro dalle interruzioni? 13398c2ecf20Sopenharmony_ci========================================================================= 13408c2ecf20Sopenharmony_ci 13418c2ecf20Sopenharmony_ciMolte funzioni del kernel dormono (in sostanza, chiamano schedule()) 13428c2ecf20Sopenharmony_cidirettamente od indirettamente: non potete chiamarle se trattenere uno 13438c2ecf20Sopenharmony_cispinlock o avete la prelazione disabilitata, mai. Questo significa che 13448c2ecf20Sopenharmony_cidovete necessariamente essere nel contesto utente: chiamarle da un 13458c2ecf20Sopenharmony_cicontesto d'interruzione è illegale. 13468c2ecf20Sopenharmony_ci 13478c2ecf20Sopenharmony_ciAlcune funzioni che dormono 13488c2ecf20Sopenharmony_ci--------------------------- 13498c2ecf20Sopenharmony_ci 13508c2ecf20Sopenharmony_ciLe più comuni sono elencate qui di seguito, ma solitamente dovete leggere 13518c2ecf20Sopenharmony_ciil codice per scoprire se altre chiamate sono sicure. Se chiunque altro 13528c2ecf20Sopenharmony_cile chiami dorme, allora dovreste poter dormire anche voi. In particolar 13538c2ecf20Sopenharmony_cimodo, le funzioni di registrazione e deregistrazione solitamente si 13548c2ecf20Sopenharmony_ciaspettano d'essere chiamante da un contesto utente e quindi che possono 13558c2ecf20Sopenharmony_cidormire. 13568c2ecf20Sopenharmony_ci 13578c2ecf20Sopenharmony_ci- Accessi allo spazio utente: 13588c2ecf20Sopenharmony_ci 13598c2ecf20Sopenharmony_ci - copy_from_user() 13608c2ecf20Sopenharmony_ci 13618c2ecf20Sopenharmony_ci - copy_to_user() 13628c2ecf20Sopenharmony_ci 13638c2ecf20Sopenharmony_ci - get_user() 13648c2ecf20Sopenharmony_ci 13658c2ecf20Sopenharmony_ci - put_user() 13668c2ecf20Sopenharmony_ci 13678c2ecf20Sopenharmony_ci- kmalloc(GFP_KERNEL) <kmalloc>` 13688c2ecf20Sopenharmony_ci 13698c2ecf20Sopenharmony_ci- mutex_lock_interruptible() and 13708c2ecf20Sopenharmony_ci mutex_lock() 13718c2ecf20Sopenharmony_ci 13728c2ecf20Sopenharmony_ci C'è anche mutex_trylock() che però non dorme. 13738c2ecf20Sopenharmony_ci Comunque, non deve essere usata in un contesto d'interruzione dato 13748c2ecf20Sopenharmony_ci che la sua implementazione non è sicura in quel contesto. 13758c2ecf20Sopenharmony_ci Anche mutex_unlock() non dorme mai. Non può comunque essere 13768c2ecf20Sopenharmony_ci usata in un contesto d'interruzione perché un mutex deve essere rilasciato 13778c2ecf20Sopenharmony_ci dallo stesso processo che l'ha acquisito. 13788c2ecf20Sopenharmony_ci 13798c2ecf20Sopenharmony_ciAlcune funzioni che non dormono 13808c2ecf20Sopenharmony_ci------------------------------- 13818c2ecf20Sopenharmony_ci 13828c2ecf20Sopenharmony_ciAlcune funzioni possono essere chiamate tranquillamente da qualsiasi 13838c2ecf20Sopenharmony_cicontesto, o trattenendo un qualsiasi *lock*. 13848c2ecf20Sopenharmony_ci 13858c2ecf20Sopenharmony_ci- printk() 13868c2ecf20Sopenharmony_ci 13878c2ecf20Sopenharmony_ci- kfree() 13888c2ecf20Sopenharmony_ci 13898c2ecf20Sopenharmony_ci- add_timer() e del_timer() 13908c2ecf20Sopenharmony_ci 13918c2ecf20Sopenharmony_ciRiferimento per l'API dei Mutex 13928c2ecf20Sopenharmony_ci=============================== 13938c2ecf20Sopenharmony_ci 13948c2ecf20Sopenharmony_ci.. kernel-doc:: include/linux/mutex.h 13958c2ecf20Sopenharmony_ci :internal: 13968c2ecf20Sopenharmony_ci 13978c2ecf20Sopenharmony_ci.. kernel-doc:: kernel/locking/mutex.c 13988c2ecf20Sopenharmony_ci :export: 13998c2ecf20Sopenharmony_ci 14008c2ecf20Sopenharmony_ciRiferimento per l'API dei Futex 14018c2ecf20Sopenharmony_ci=============================== 14028c2ecf20Sopenharmony_ci 14038c2ecf20Sopenharmony_ci.. kernel-doc:: kernel/futex/core.c 14048c2ecf20Sopenharmony_ci :internal: 14058c2ecf20Sopenharmony_ci 14068c2ecf20Sopenharmony_ciApprofondimenti 14078c2ecf20Sopenharmony_ci=============== 14088c2ecf20Sopenharmony_ci 14098c2ecf20Sopenharmony_ci- ``Documentation/locking/spinlocks.rst``: la guida di Linus Torvalds agli 14108c2ecf20Sopenharmony_ci spinlock del kernel. 14118c2ecf20Sopenharmony_ci 14128c2ecf20Sopenharmony_ci- Unix Systems for Modern Architectures: Symmetric Multiprocessing and 14138c2ecf20Sopenharmony_ci Caching for Kernel Programmers. 14148c2ecf20Sopenharmony_ci 14158c2ecf20Sopenharmony_ci L'introduzione alla sincronizzazione a livello di kernel di Curt Schimmel 14168c2ecf20Sopenharmony_ci è davvero ottima (non è scritta per Linux, ma approssimativamente si adatta 14178c2ecf20Sopenharmony_ci a tutte le situazioni). Il libro è costoso, ma vale ogni singolo spicciolo 14188c2ecf20Sopenharmony_ci per capire la sincronizzazione nei sistemi multi-processore. 14198c2ecf20Sopenharmony_ci [ISBN: 0201633388] 14208c2ecf20Sopenharmony_ci 14218c2ecf20Sopenharmony_ciRingraziamenti 14228c2ecf20Sopenharmony_ci============== 14238c2ecf20Sopenharmony_ci 14248c2ecf20Sopenharmony_ciGrazie a Telsa Gwynne per aver formattato questa guida in DocBook, averla 14258c2ecf20Sopenharmony_cipulita e aggiunto un po' di stile. 14268c2ecf20Sopenharmony_ci 14278c2ecf20Sopenharmony_ciGrazie a Martin Pool, Philipp Rumpf, Stephen Rothwell, Paul Mackerras, 14288c2ecf20Sopenharmony_ciRuedi Aschwanden, Alan Cox, Manfred Spraul, Tim Waugh, Pete Zaitcev, 14298c2ecf20Sopenharmony_ciJames Morris, Robert Love, Paul McKenney, John Ashby per aver revisionato, 14308c2ecf20Sopenharmony_cicorretto, maledetto e commentato. 14318c2ecf20Sopenharmony_ci 14328c2ecf20Sopenharmony_ciGrazie alla congrega per non aver avuto alcuna influenza su questo documento. 14338c2ecf20Sopenharmony_ci 14348c2ecf20Sopenharmony_ciGlossario 14358c2ecf20Sopenharmony_ci========= 14368c2ecf20Sopenharmony_ci 14378c2ecf20Sopenharmony_ciprelazione 14388c2ecf20Sopenharmony_ci Prima del kernel 2.5, o quando ``CONFIG_PREEMPT`` non è impostato, i processi 14398c2ecf20Sopenharmony_ci in contesto utente non si avvicendano nell'esecuzione (in pratica, il 14408c2ecf20Sopenharmony_ci processo userà il processore fino al proprio termine, a meno che non ci siano 14418c2ecf20Sopenharmony_ci delle interruzioni). Con l'aggiunta di ``CONFIG_PREEMPT`` nella versione 14428c2ecf20Sopenharmony_ci 2.5.4 questo è cambiato: quando si è in contesto utente, processi con una 14438c2ecf20Sopenharmony_ci priorità maggiore possono subentrare nell'esecuzione: gli spinlock furono 14448c2ecf20Sopenharmony_ci cambiati per disabilitare la prelazioni, anche su sistemi monoprocessore. 14458c2ecf20Sopenharmony_ci 14468c2ecf20Sopenharmony_cibh 14478c2ecf20Sopenharmony_ci Bottom Half: per ragioni storiche, le funzioni che contengono '_bh' nel 14488c2ecf20Sopenharmony_ci loro nome ora si riferiscono a qualsiasi interruzione software; per esempio, 14498c2ecf20Sopenharmony_ci spin_lock_bh() blocca qualsiasi interuzione software sul processore 14508c2ecf20Sopenharmony_ci corrente. I *Bottom Halves* sono deprecati, e probabilmente verranno 14518c2ecf20Sopenharmony_ci sostituiti dai tasklet. In un dato momento potrà esserci solo un 14528c2ecf20Sopenharmony_ci *bottom half* in esecuzione. 14538c2ecf20Sopenharmony_ci 14548c2ecf20Sopenharmony_cicontesto d'interruzione 14558c2ecf20Sopenharmony_ci Non è il contesto utente: qui si processano le interruzioni hardware e 14568c2ecf20Sopenharmony_ci software. La macro in_interrupt() ritorna vero. 14578c2ecf20Sopenharmony_ci 14588c2ecf20Sopenharmony_cicontesto utente 14598c2ecf20Sopenharmony_ci Il kernel che esegue qualcosa per conto di un particolare processo (per 14608c2ecf20Sopenharmony_ci esempio una chiamata di sistema) o di un thread del kernel. Potete 14618c2ecf20Sopenharmony_ci identificare il processo con la macro ``current``. Da non confondere 14628c2ecf20Sopenharmony_ci con lo spazio utente. Può essere interrotto sia da interruzioni software 14638c2ecf20Sopenharmony_ci che hardware. 14648c2ecf20Sopenharmony_ci 14658c2ecf20Sopenharmony_ciinterruzione hardware 14668c2ecf20Sopenharmony_ci Richiesta di interruzione hardware. in_irq() ritorna vero in un 14678c2ecf20Sopenharmony_ci gestore d'interruzioni hardware. 14688c2ecf20Sopenharmony_ci 14698c2ecf20Sopenharmony_ciinterruzione software / softirq 14708c2ecf20Sopenharmony_ci Gestore di interruzioni software: in_irq() ritorna falso; 14718c2ecf20Sopenharmony_ci in_softirq() ritorna vero. I tasklet e le softirq sono entrambi 14728c2ecf20Sopenharmony_ci considerati 'interruzioni software'. 14738c2ecf20Sopenharmony_ci 14748c2ecf20Sopenharmony_ci In soldoni, un softirq è uno delle 32 interruzioni software che possono 14758c2ecf20Sopenharmony_ci essere eseguite su più processori in contemporanea. A volte si usa per 14768c2ecf20Sopenharmony_ci riferirsi anche ai tasklet (in pratica tutte le interruzioni software). 14778c2ecf20Sopenharmony_ci 14788c2ecf20Sopenharmony_cimonoprocessore / UP 14798c2ecf20Sopenharmony_ci (Uni-Processor) un solo processore, ovvero non è SMP. (``CONFIG_SMP=n``). 14808c2ecf20Sopenharmony_ci 14818c2ecf20Sopenharmony_cimulti-processore / SMP 14828c2ecf20Sopenharmony_ci (Symmetric Multi-Processor) kernel compilati per sistemi multi-processore 14838c2ecf20Sopenharmony_ci (``CONFIG_SMP=y``). 14848c2ecf20Sopenharmony_ci 14858c2ecf20Sopenharmony_cispazio utente 14868c2ecf20Sopenharmony_ci Un processo che esegue il proprio codice fuori dal kernel. 14878c2ecf20Sopenharmony_ci 14888c2ecf20Sopenharmony_citasklet 14898c2ecf20Sopenharmony_ci Un'interruzione software registrabile dinamicamente che ha la garanzia 14908c2ecf20Sopenharmony_ci d'essere eseguita solo su un processore alla volta. 14918c2ecf20Sopenharmony_ci 14928c2ecf20Sopenharmony_citimer 14938c2ecf20Sopenharmony_ci Un'interruzione software registrabile dinamicamente che viene eseguita 14948c2ecf20Sopenharmony_ci (circa) in un determinato momento. Quando è in esecuzione è come un tasklet 14958c2ecf20Sopenharmony_ci (infatti, sono chiamati da ``TIMER_SOFTIRQ``). 1496