요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
.. include:: ../disclaimer-ita.rst
:Original: :ref:`Documentation/process/4.Coding.rst <development_coding>`
:Translator: Alessia Mantegazza <[email protected]>
.. _it_development_coding:
Scrivere codice corretto
========================
Nonostante ci sia molto da dire sul processo di creazione, sulla sua solidità
e sul suo orientamento alla comunità, la prova di ogni progetto di sviluppo
del kernel si trova nel codice stesso. È il codice che sarà esaminato dagli
altri sviluppatori ed inserito (o no) nel ramo principale. Quindi è la
qualità di questo codice che determinerà il successo finale del progetto.
Questa sezione esaminerà il processo di codifica. Inizieremo con uno sguardo
sulle diverse casistiche nelle quali gli sviluppatori kernel possono
sbagliare. Poi, l'attenzione si sposterà verso "il fare le cose
correttamente" e sugli strumenti che possono essere utili in questa missione.
Trappole
--------
Lo stile del codice
*******************
Il kernel ha da tempo delle norme sullo stile di codifica che sono descritte in
:ref:`Documentation/translations/it_IT/process/coding-style.rst <codingstyle>`.
Per la maggior parte del tempo, la politica descritta in quel file è stata
praticamente informativa. Ne risulta che ci sia una quantità sostanziale di
codice nel kernel che non rispetta le linee guida relative allo stile.
La presenza di quel codice conduce a due distinti pericoli per gli
sviluppatori kernel.
Il primo di questi è credere che gli standard di codifica del kernel
non sono importanti e possono non essere applicati. La verità è che
aggiungere nuovo codice al kernel è davvero difficile se questo non
rispetta le norme; molti sviluppatori richiederanno che il codice sia
riformulato prima che anche solo lo revisionino. Una base di codice larga
quanto il kernel richiede una certa uniformità, in modo da rendere possibile
per gli sviluppatori una comprensione veloce di ogni sua parte. Non ci sono,
quindi, più spazi per un codice formattato alla carlona.
Occasionalmente, lo stile di codifica del kernel andrà in conflitto con lo
stile richiesto da un datore di lavoro. In alcuni casi, lo stile del kernel
dovrà prevalere prima che il codice venga inserito. Mettere il codice
all'interno del kernel significa rinunciare a un certo grado di controllo
in differenti modi - incluso il controllo sul come formattare il codice.
L’altra trappola è quella di pensare che il codice già presente nel kernel
abbia urgentemente bisogno di essere sistemato. Gli sviluppatori potrebbero
iniziare a generare patch che correggono lo stile come modo per prendere
famigliarità con il processo, o come modo per inserire i propri nomi nei
changelog del kernel – o entrambe. La comunità di sviluppo vede un attività
di codifica puramente correttiva come "rumore"; queste attività riceveranno
una fredda accoglienza. Di conseguenza è meglio evitare questo tipo di patch.
Mentre si lavora su un pezzo di codice è normale correggerne anche lo stile,
ma le modifiche di stile non dovrebbero essere fatte fini a se stesse.
Il documento sullo stile del codice non dovrebbe essere letto come una legge
assoluta che non può mai essere trasgredita. Se c’è un a buona ragione
(per esempio, una linea che diviene poco leggibile se divisa per rientrare
nel limite di 80 colonne), fatelo e basta.
Notate che potete utilizzare lo strumento “clang-format” per aiutarvi con
le regole, per una riformattazione automatica e veloce del vostro codice
e per revisionare interi file per individuare errori nello stile di codifica,
refusi e possibili miglioramenti. Inoltre è utile anche per classificare gli
``#includes``, per allineare variabili/macro, per testi derivati ed altri
compiti del genere. Consultate il file
:ref:`Documentation/translations/it_IT/dev-tools/clang-format.rst <clangformat>`
per maggiori dettagli
Se utilizzate un programma compatibile con EditorConfig, allora alcune
configurazioni basilari come l'indentazione e la fine delle righe verranno
applicate automaticamente. Per maggiori informazioni consultate la pagina:
https://editorconfig.org/
Livelli di astrazione
*********************
I professori di Informatica insegnano ai propri studenti a fare ampio uso dei
livelli di astrazione nel nome della flessibilità e del nascondere informazioni.
Certo il kernel fa un grande uso dell'astrazione; nessun progetto con milioni
di righe di codice potrebbe fare altrimenti e sopravvivere. Ma l'esperienza
ha dimostrato che un'eccessiva o prematura astrazione può rivelarsi dannosa
al pari di una prematura ottimizzazione. L'astrazione dovrebbe essere usata
fino al livello necessario e non oltre.
Ad un livello base, considerate una funzione che ha un argomento che viene
sempre impostato a zero da tutti i chiamanti. Uno potrebbe mantenere
quell'argomento nell'eventualità qualcuno volesse sfruttare la flessibilità
offerta. In ogni caso, tuttavia, ci sono buone possibilità che il codice
che va ad implementare questo argomento aggiuntivo, sia stato rotto in maniera
sottile, in un modo che non è mai stato notato - perché non è mai stato usato.
Oppure, quando sorge la necessità di avere più flessibilità, questo argomento
non la fornisce in maniera soddisfacente. Gli sviluppatori di Kernel,
sottopongono costantemente patch che vanno a rimuovere gli argomenti
inutilizzate; anche se, in generale, non avrebbero dovuto essere aggiunti.
I livelli di astrazione che nascondono l'accesso all'hardware -
spesso per poter usare dei driver su diversi sistemi operativi - vengono
particolarmente disapprovati. Tali livelli oscurano il codice e possono
peggiorare le prestazioni; essi non appartengono al kernel Linux.
D'altro canto, se vi ritrovate a dover copiare una quantità significativa di
codice proveniente da un altro sottosistema del kernel, è tempo di chiedersi
se, in effetti, non avrebbe più senso togliere parte di quel codice e metterlo
in una libreria separata o di implementare quella funzionalità ad un livello
più elevato. Non c'è utilità nel replicare lo stesso codice per tutto
il kernel.
#ifdef e l'uso del preprocessore in generale
********************************************
Il preprocessore C sembra essere una fonte di attrazione per qualche
programmatore C, che ci vede una via per ottenere una grande flessibilità
all'interno di un file sorgente. Ma il preprocessore non è scritto in C,
e un suo massiccio impiego conduce a un codice che è molto più difficile
da leggere per gli altri e che rende più difficile il lavoro di verifica del
compilatore. L'uso eccessivo del preprocessore è praticamente sempre il segno
di un codice che necessita di un certo lavoro di pulizia.
La compilazione condizionata con #ifdef è, in effetti, un potente strumento,
ed esso viene usato all'interno del kernel. Ma esiste un piccolo desiderio:
quello di vedere il codice coperto solo da una leggera spolverata di
blocchi #ifdef. Come regola generale, quando possibile, l'uso di #ifdef
dovrebbe essere confinato nei file d'intestazione. Il codice compilato
condizionatamente può essere confinato a funzioni tali che, nel caso in cui
il codice non deve essere presente, diventano vuote. Il compilatore poi
ottimizzerà la chiamata alla funzione vuota rimuovendola. Il risultato è
un codice molto più pulito, più facile da seguire.
Le macro del preprocessore C presentano una serie di pericoli, inclusi
valutazioni multiple di espressioni che hanno effetti collaterali e non
garantiscono una sicurezza rispetto ai tipi. Se siete tentati dal definire
una macro, considerate l'idea di creare invece una funzione inline. Il codice
che ne risulterà sarà lo stesso, ma le funzioni inline sono più leggibili,
non considerano i propri argomenti più volte, e permettono al compilatore di
effettuare controlli sul tipo degli argomenti e del valore di ritorno.
Funzioni inline
***************
Comunque, anche le funzioni inline hanno i loro pericoli. I programmatori
potrebbero innamorarsi dell'efficienza percepita derivata dalla rimozione
di una chiamata a funzione. Queste funzioni, tuttavia, possono ridurre le
prestazioni. Dato che il loro codice viene replicato ovunque vi sia una
chiamata ad esse, si finisce per gonfiare le dimensioni del kernel compilato.
Questi, a turno, creano pressione sulla memoria cache del processore, e questo
può causare rallentamenti importanti. Le funzioni inline, di norma, dovrebbero
essere piccole e usate raramente. Il costo di una chiamata a funzione, dopo
tutto, non è così alto; la creazione di molte funzioni inline è il classico
esempio di un'ottimizzazione prematura.
In generale, i programmatori del kernel ignorano gli effetti della cache a
loro rischio e pericolo. Il classico compromesso tempo/spazio teorizzato
all'inizio delle lezioni sulle strutture dati spesso non si applica
all'hardware moderno. Lo spazio *è* tempo, in questo senso un programma
più grande sarà più lento rispetto ad uno più compatto.
I compilatori più recenti hanno preso un ruolo attivo nel decidere se
una data funzione deve essere resa inline oppure no. Quindi l'uso
indiscriminato della parola chiave "inline" potrebbe non essere non solo
eccessivo, ma anche irrilevante.
Sincronizzazione
****************
Nel maggio 2006, il sistema di rete "Devicescape" fu rilasciato in pompa magna
sotto la licenza GPL e reso disponibile per la sua inclusione nella ramo
principale del kernel. Questa donazione fu una notizia bene accolta;
il supporto per le reti senza fili era considerata, nel migliore dei casi,
al di sotto degli standard; il sistema Deviscape offrì la promessa di una
risoluzione a tale situazione. Tuttavia, questo codice non fu inserito nel
ramo principale fino al giugno del 2007 (2.6.22). Cosa accadde?
Quel codice mostrava numerosi segnali di uno sviluppo in azienda avvenuto
a porte chiuse. Ma in particolare, un grosso problema fu che non fu
progettato per girare in un sistema multiprocessore. Prima che questo
sistema di rete (ora chiamato mac80211) potesse essere inserito, fu necessario
un lavoro sugli schemi di sincronizzazione.
Una volta, il codice del kernel Linux poteva essere sviluppato senza pensare
ai problemi di concorrenza presenti nei sistemi multiprocessore. Ora,
comunque, questo documento è stato scritto su di un portatile dual-core.
Persino su sistemi a singolo processore, il lavoro svolto per incrementare
la capacità di risposta aumenterà il livello di concorrenza interno al kernel.
I giorni nei quali il codice poteva essere scritto senza pensare alla
sincronizzazione sono da passati tempo.
Ogni risorsa (strutture dati, registri hardware, etc.) ai quali si potrebbe
avere accesso simultaneo da più di un thread deve essere sincronizzato. Il
nuovo codice dovrebbe essere scritto avendo tale accortezza in testa;
riadattare la sincronizzazione a posteriori è un compito molto più difficile.
Gli sviluppatori del kernel dovrebbero prendersi il tempo di comprendere bene
le primitive di sincronizzazione, in modo da sceglier lo strumento corretto
per eseguire un compito. Il codice che presenta una mancanza di attenzione
alla concorrenza avrà un percorso difficile all'interno del ramo principale.
Regressioni
***********
Vale la pena menzionare un ultimo pericolo: potrebbe rivelarsi accattivante
l'idea di eseguire un cambiamento (che potrebbe portare a grandi
miglioramenti) che porterà ad alcune rotture per gli utenti esistenti.
Questa tipologia di cambiamento è chiamata "regressione", e le regressioni son
diventate mal viste nel ramo principale del kernel. Con alcune eccezioni,
i cambiamenti che causano regressioni saranno fermati se quest'ultime non
potranno essere corrette in tempo utile. È molto meglio quindi evitare
la regressione fin dall'inizio.
Spesso si è argomentato che una regressione può essere giustificata se essa
porta risolve più problemi di quanti non ne crei. Perché, dunque, non fare
un cambiamento se questo porta a nuove funzionalità a dieci sistemi per
ognuno dei quali esso determina una rottura? La migliore risposta a questa
domanda ci è stata fornita da Linus nel luglio 2007:
::
Dunque, noi non sistemiamo bachi introducendo nuovi problemi. Quella
via nasconde insidie, e nessuno può sapere del tutto se state facendo
dei progressi reali. Sono due passi avanti e uno indietro, oppure
un passo avanti e due indietro?
(http://lwn.net/Articles/243460/).
Una particolare tipologia di regressione mal vista consiste in una qualsiasi
sorta di modifica all'ABI dello spazio utente. Una volta che un'interfaccia
viene esportata verso lo spazio utente, dev'essere supportata all'infinito.
Questo fatto rende la creazione di interfacce per lo spazio utente
particolarmente complicato: dato che non possono venir cambiate introducendo
incompatibilità, esse devono essere fatte bene al primo colpo. Per questa
ragione sono sempre richieste: ampie riflessioni, documentazione chiara e
ampie revisioni dell'interfaccia verso lo spazio utente.
Strumenti di verifica del codice
--------------------------------
Almeno per ora la scrittura di codice priva di errori resta un ideale
irraggiungibile ai più. Quello che speriamo di poter fare, tuttavia, è
trovare e correggere molti di questi errori prima che il codice entri nel
ramo principale del kernel. A tal scopo gli sviluppatori del kernel devono
mettere insieme una schiera impressionante di strumenti che possano
localizzare automaticamente un'ampia varietà di problemi. Qualsiasi problema
trovato dal computer è un problema che non affliggerà l'utente in seguito,
ne consegue che gli strumenti automatici dovrebbero essere impiegati ovunque
possibile.
Il primo passo consiste semplicemente nel fare attenzione agli avvertimenti
proveniente dal compilatore. Versioni moderne di gcc possono individuare
(e segnalare) un gran numero di potenziali errori. Molto spesso, questi
avvertimenti indicano problemi reali. Di regola, il codice inviato per la
revisione non dovrebbe produrre nessun avvertimento da parte del compilatore.
Per mettere a tacere gli avvertimenti, cercate di comprenderne le cause reali
e cercate di evitare le "riparazioni" che fan sparire l'avvertimento senza
però averne trovato la causa.
Tenete a mente che non tutti gli avvertimenti sono disabilitati di default.
Costruite il kernel con "make KCFLAGS=-W" per ottenerli tutti.
Il kernel fornisce differenti opzioni che abilitano funzionalità di debugging;
molti di queste sono trovano all'interno del sotto menu "kernel hacking".
La maggior parte di queste opzioni possono essere attivate per qualsiasi
kernel utilizzato per lo sviluppo o a scopo di test. In particolare dovreste
attivare:
- FRAME_WARN per ottenere degli avvertimenti su stack frame più
grandi di un dato valore. Il risultato generato da questi
avvertimenti può risultare verboso, ma non bisogna preoccuparsi per
gli avvertimenti provenienti da altre parti del kernel.
- DEBUG_OBJECTS aggiungerà un codice per tracciare il ciclo di vita di
diversi oggetti creati dal kernel e avvisa quando qualcosa viene eseguito
fuori controllo. Se state aggiungendo un sottosistema che crea (ed
esporta) oggetti complessi propri, considerate l'aggiunta di un supporto
al debugging dell'oggetto.
- DEBUG_SLAB può trovare svariati errori di uso e di allocazione di memoria;
esso dovrebbe esser usato dalla maggior parte dei kernel di sviluppo.
- DEBUG_SPINLOCK, DEBUG_ATOMIC_SLEEP, e DEBUG_MUTEXES troveranno un certo
numero di errori comuni di sincronizzazione.
Esistono ancora delle altre opzioni di debugging, di alcune di esse
discuteremo qui sotto. Alcune di esse hanno un forte impatto e non dovrebbero
essere usate tutte le volte. Ma qualche volta il tempo speso nell'capire
le opzioni disponibili porterà ad un risparmio di tempo nel breve termine.
Uno degli strumenti di debugging più tosti è il *locking checker*, o
"lockdep". Questo strumento traccerà qualsiasi acquisizione e rilascio di
ogni *lock* (spinlock o mutex) nel sistema, l'ordine con il quale i *lock*
sono acquisiti in relazione l'uno con l'altro, l'ambiente corrente di
interruzione, eccetera. Inoltre esso può assicurare che i *lock* vengano
acquisiti sempre nello stesso ordine, che le stesse assunzioni sulle
interruzioni si applichino in tutte le occasioni, e così via. In altre parole,
lockdep può scovare diversi scenari nei quali il sistema potrebbe, in rari
casi, trovarsi in stallo. Questa tipologia di problema può essere grave
(sia per gli sviluppatori che per gli utenti) in un sistema in uso; lockdep
permette di trovare tali problemi automaticamente e in anticipo.
In qualità di programmatore kernel diligente, senza dubbio, dovrete controllare
il valore di ritorno di ogni operazione (come l'allocazione della memoria)
poiché esso potrebbe fallire. Il nocciolo della questione è che i percorsi
di gestione degli errori, con grande probabilità, non sono mai stati
collaudati del tutto. Il codice collaudato tende ad essere codice bacato;
potrete quindi essere più a vostro agio con il vostro codice se tutti questi
percorsi fossero stati verificati un po' di volte.
Il kernel fornisce un framework per l'inserimento di fallimenti che fa
esattamente al caso, specialmente dove sono coinvolte allocazioni di memoria.
Con l'opzione per l'inserimento dei fallimenti abilitata, una certa percentuale
di allocazione di memoria sarà destinata al fallimento; questi fallimenti
possono essere ridotti ad uno specifico pezzo di codice. Procedere con
l'inserimento dei fallimenti attivo permette al programmatore di verificare
come il codice risponde quando le cose vanno male. Consultate:
Documentation/fault-injection/fault-injection.rst per avere maggiori
informazioni su come utilizzare questo strumento.
Altre tipologie di errori possono essere riscontrati con lo strumento di
analisi statica "sparse". Con Sparse, il programmatore può essere avvisato
circa la confusione tra gli indirizzi dello spazio utente e dello spazio
kernel, un miscuglio fra quantità big-endian e little-endian, il passaggio
di un valore intero dove ci sia aspetta un gruppo di flag, e così via.
Sparse deve essere installato separatamente (se il vostra distribuzione non
lo prevede, potete trovarlo su https://sparse.wiki.kernel.org/index.php/Main_Page);
può essere attivato sul codice aggiungendo "C=1" al comando make.
Lo strumento "Coccinelle" (http://coccinelle.lip6.fr/) è in grado di trovare
una vasta varietà di potenziali problemi di codifica; e può inoltre proporre
soluzioni per risolverli. Un buon numero di "patch semantiche" per il kernel
sono state preparate nella cartella scripts/coccinelle; utilizzando
"make coccicheck" esso percorrerà tali patch semantiche e farà rapporto su
qualsiasi problema trovato. Per maggiori informazioni, consultate
:ref:`Documentation/dev-tools/coccinelle.rst <devtools_coccinelle>`.
Altri errori di portabilità sono meglio scovati compilando il vostro codice
per altre architetture. Se non vi accade di avere un sistema S/390 o una
scheda di sviluppo Blackfin sotto mano, potete comunque continuare la fase
di compilazione. Un vasto numero di cross-compilatori per x86 possono
essere trovati al sito:
http://www.kernel.org/pub/tools/crosstool/
Il tempo impiegato nell'installare e usare questi compilatori sarà d'aiuto
nell'evitare situazioni imbarazzanti nel futuro.
Documentazione
--------------
La documentazione è spesso stata più un'eccezione che una regola nello
sviluppo del kernel. Nonostante questo, un'adeguata documentazione aiuterà
a facilitare l'inserimento di nuovo codice nel kernel, rende la vita più
facile per gli altri sviluppatori e sarà utile per i vostri utenti. In molti
casi, la documentazione è divenuta sostanzialmente obbligatoria.
La prima parte di documentazione per qualsiasi patch è il suo changelog.
Questi dovrebbero descrivere le problematiche risolte, la tipologia di
soluzione, le persone che lavorano alla patch, ogni effetto rilevante
sulle prestazioni e tutto ciò che può servire per la comprensione della
patch. Assicuratevi che il changelog dica *perché*, vale la pena aggiungere
la patch; un numero sorprendente di sviluppatori sbaglia nel fornire tale
informazione.
Qualsiasi codice che aggiunge una nuova interfaccia in spazio utente - inclusi
nuovi file in sysfs o /proc - dovrebbe includere la documentazione di tale
interfaccia così da permette agli sviluppatori dello spazio utente di sapere
con cosa stanno lavorando. Consultate: Documentation/ABI/README per avere una
descrizione di come questi documenti devono essere impostati e quali
informazioni devono essere fornite.
Il file :ref:`Documentation/translations/it_IT/admin-guide/kernel-parameters.rst <kernelparameters>`
descrive tutti i parametri di avvio del kernel. Ogni patch che aggiunga
nuovi parametri dovrebbe aggiungere nuove voci a questo file.
Ogni nuova configurazione deve essere accompagnata da un testo di supporto
che spieghi chiaramente le opzioni e spieghi quando l'utente potrebbe volerle
selezionare.
Per molti sottosistemi le informazioni sull'API interna sono documentate sotto
forma di commenti formattati in maniera particolare; questi commenti possono
essere estratti e formattati in differenti modi attraverso lo script
"kernel-doc". Se state lavorando all'interno di un sottosistema che ha
commenti kerneldoc dovreste mantenerli e aggiungerli, in maniera appropriata,
per le funzioni disponibili esternamente. Anche in aree che non sono molto
documentate, non c'è motivo per non aggiungere commenti kerneldoc per il
futuro; infatti, questa può essere un'attività utile per sviluppatori novizi
del kernel. Il formato di questi commenti, assieme alle informazione su come
creare modelli per kerneldoc, possono essere trovati in
:ref:`Documentation/translations/it_IT/doc-guide/ <doc_guide>`.
Chiunque legga un ammontare significativo di codice kernel noterà che, spesso,
i commenti si fanno maggiormente notare per la loro assenza. Ancora una volta,
le aspettative verso il nuovo codice sono più alte rispetto al passato;
inserire codice privo di commenti sarà più difficile. Detto ciò, va aggiunto
che non si desiderano commenti prolissi per il codice. Il codice dovrebbe
essere, di per sé, leggibile, con dei commenti che spieghino gli aspetti più
sottili.
Determinate cose dovrebbero essere sempre commentate. L'uso di barriere
di memoria dovrebbero essere accompagnate da una riga che spieghi perché sia
necessaria. Le regole di sincronizzazione per le strutture dati, generalmente,
necessitano di una spiegazioni da qualche parte. Le strutture dati più
importanti, in generale, hanno bisogno di una documentazione onnicomprensiva.
Le dipendenze che non sono ovvie tra bit separati di codice dovrebbero essere
indicate. Tutto ciò che potrebbe indurre un inserviente del codice a fare
una "pulizia" incorretta, ha bisogno di un commento che dica perché è stato
fatto in quel modo. E così via.
Cambiamenti interni dell'API
----------------------------
L'interfaccia binaria fornita dal kernel allo spazio utente non può essere
rotta tranne che in circostanze eccezionali. L'interfaccia di programmazione
interna al kernel, invece, è estremamente fluida e può essere modificata al
bisogno. Se vi trovate a dover lavorare attorno ad un'API del kernel o
semplicemente non state utilizzando una funzionalità offerta perché questa
non rispecchia i vostri bisogni, allora questo potrebbe essere un segno che
l'API ha bisogno di essere cambiata. In qualità di sviluppatore del kernel,
hai il potere di fare questo tipo di modifica.
Ci sono ovviamente alcuni punti da cogliere. I cambiamenti API possono essere
fatti, ma devono essere giustificati. Quindi ogni patch che porta ad una
modifica dell'API interna dovrebbe essere accompagnata da una descrizione
della modifica in sé e del perché essa è necessaria. Questo tipo di
cambiamenti dovrebbero, inoltre, essere fatti in una patch separata, invece di
essere sepolti all'interno di una patch più grande.
L'altro punto da cogliere consiste nel fatto che uno sviluppatore che
modifica l'API deve, in generale, essere responsabile della correzione
di tutto il codice del kernel che viene rotto per via della sua modifica.
Per una funzione ampiamente usata, questo compito può condurre letteralmente
a centinaia o migliaia di modifiche, molte delle quali sono in conflitto con
il lavoro svolto da altri sviluppatori. Non c'è bisogno di dire che questo
può essere un lavoro molto grosso, quindi è meglio essere sicuri che la
motivazione sia ben solida. Notate che lo strumento Coccinelle può fornire
un aiuto con modifiche estese dell'API.
Quando viene fatta una modifica API incompatibile, una persona dovrebbe,
quando possibile, assicurarsi che quel codice non aggiornato sia trovato
dal compilatore. Questo vi aiuterà ad essere sicuri d'avere trovato,
tutti gli usi di quell'interfaccia. Inoltre questo avviserà gli sviluppatori
di codice fuori dal kernel che c'è un cambiamento per il quale è necessario del
lavoro. Il supporto al codice fuori dal kernel non è qualcosa di cui gli
sviluppatori del kernel devono preoccuparsi, ma non dobbiamo nemmeno rendere
più difficile del necessario la vita agli sviluppatori di questo codice.
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
code 품질이 결정하는 project 성공
1-21견고하고 community 중심적인 설계 절차가 중요하지만, kernel 개발 project의 성패를 최종적으로 증명하는 것은 그 결과로 나온 code다. 다른 개발자는 설계 의도만이 아니라 실제 code를 검토하고, 그 code를 mainline tree에 merge할지 결정한다. 따라서 project가 궁극적으로 성공하는지는 code의 품질에 달려 있다.
이 문서는 coding 과정에서 kernel 개발자가 흔히 빠지는 함정을 먼저 살펴본다. 이어서 올바른 구현 방향과, 문제를 mainline에 보내기 전에 찾아내는 데 도움이 되는 도구를 설명한다.
견고한 설계와 community 협업도 중요하지만 mainline 포함 여부를 최종적으로 판단하는 대상은 실제 code입니다. 이 장은 구현 중 흔한 함정과 제출 전에 문제를 찾는 검사 도구를 함께 설명합니다.
.. include:: ../disclaimer-ita.rst
:Original: :ref:`Documentation/process/4.Coding.rst <development_coding>`
:Translator: Alessia Mantegazza <[email protected]>
.. _it_development_coding:
Scrivere codice corretto
========================
Nonostante ci sia molto da dire sul processo di creazione, sulla sua solidità
e sul suo orientamento alla comunità, la prova di ogni progetto di sviluppo
del kernel si trova nel codice stesso. È il codice che sarà esaminato dagli
altri sviluppatori ed inserito (o no) nel ramo principale. Quindi è la
qualità di questo codice che determinerà il successo finale del progetto.
Questa sezione esaminerà il processo di codifica. Inizieremo con uno sguardo
sulle diverse casistiche nelle quali gli sviluppatori kernel possono
sbagliare. Poi, l'attenzione si sposterà verso "il fare le cose
correttamente" e sugli strumenti che possono essere utili in questa missione.
style, abstraction, preprocessor, locking과 regression
22-240Kernel에는 오래전부터 Documentation/process/coding-style.rst에 정리된 표준 coding style이 있다. 과거에는 이 정책을 강제 규칙이 아니라 권고 정도로 취급한 시기가 길었고, 그 결과 현재 tree에도 지침을 따르지 않는 code가 상당량 남아 있다. 기존 code의 이런 모습은 개발자에게 서로 독립적인 두 가지 함정을 만든다.
첫 번째 함정은 kernel coding 표준이 중요하지 않으며 실제 review에서도 강제되지 않는다고 믿는 것이다. 현실은 반대다. 새 code가 표준에 맞게 작성되지 않았다면 kernel에 추가하기가 매우 어렵다. 많은 개발자는 내용 검토를 시작하기도 전에 먼저 형식을 고치라고 요청한다. Kernel처럼 거대한 code base에서 어느 영역이든 빠르게 이해하려면 일정한 형식이 필요하므로, 특이한 개인식 formatting을 허용할 여지가 없다.
회사에서 의무화한 style과 kernel style이 충돌할 때도 있다. 그 code를 kernel에 merge하려면 kernel style이 우선해야 한다. Code를 kernel에 넣는다는 것은 여러 측면에서 통제권 일부를 넘기는 일이며, code formatting을 결정하는 권한도 여기에 포함된다.
두 번째 함정은 이미 kernel 안에 있는 code가 coding style 수정부터 시급히 필요하다고 생각하는 것이다. 개발 절차를 익히거나 changelog에 이름을 남기기 위해 reformatting patch를 만들 수 있지만, community는 기능 변화가 없는 순수 style 수정은 대개 noise로 본다. 다른 이유로 code를 수정하면서 그 주변 style을 함께 바로잡는 것은 자연스럽지만, style 변경만을 목적으로 한 patch는 피하는 편이 좋다.
Coding style 문서를 절대로 어길 수 없는 법률처럼 읽어서도 안 된다. 예를 들어 80-column 제한에 맞추려고 줄을 나누었더니 훨씬 읽기 어려워진다면, 명확한 이유를 가지고 제한을 벗어날 수 있다.
clang-format은 규칙에 맞춰 code 일부를 빠르게 자동 정렬하고, 파일 전체에서 style 오류·오타·개선 후보를 찾는 데 쓸 수 있다. #include 정렬, 변수와 macro 정렬, text reflow 같은 작업에도 유용하다. 자세한 내용은 Documentation/dev-tools/clang-format.rst를 참고한다.
EditorConfig를 지원하는 editor라면 indentation이나 line ending 같은 기본 설정이 자동으로 적용된다. 추가 정보는 https://editorconfig.org/ 에서 확인할 수 있다.
Computer Science 교육에서는 flexibility와 information hiding을 위해 abstraction layer를 폭넓게 사용하라고 가르친다. 수백만 line의 code로 이루어진 kernel 역시 abstraction 없이 유지될 수 없다. 그러나 경험상 지나치거나 너무 이른 abstraction은 premature optimization만큼 해로울 수 있다. 필요한 수준까지만 추상화하고 그 이상은 만들지 않아야 한다.
예를 들어 어떤 함수의 인자를 모든 caller가 항상 0으로 넘긴다고 하자. 언젠가 flexibility가 필요할지 모른다는 이유로 그 인자를 남겨 둘 수 있다. 그러나 실제로 사용될 때까지 한 번도 실행되지 않은 구현은 눈에 띄지 않은 채 망가졌을 가능성이 크다. 또는 훗날 요구되는 flexibility의 형태가 처음 예상과 전혀 다를 수 있다. Kernel 개발자는 사용되지 않는 인자를 제거하는 patch를 흔히 제출하므로, 일반적으로 그런 인자는 처음부터 추가하지 않는 것이 맞다.
Hardware 접근을 감추는 abstraction layer, 특히 driver 대부분을 여러 operating system에서 공용으로 사용하려고 만든 계층은 좋지 않게 평가된다. 이런 계층은 code의 실제 동작을 가리고 performance penalty까지 만들 수 있으므로 Linux kernel에 들어갈 자리가 아니다.
반대로 다른 kernel subsystem에서 상당한 양의 code를 복사하고 있다면, 공통 부분을 별도 library로 분리하거나 더 높은 계층에서 기능을 제공하는 것이 맞는지 검토해야 한다. 동일한 code를 kernel 곳곳에 반복해서 복제하는 데에는 가치가 없다.
C preprocessor는 하나의 source file에 많은 flexibility를 효율적으로 넣는 수단처럼 보이기 때문에 일부 C programmer에게 강한 유혹이 된다. 하지만 preprocessor는 C 언어 자체가 아니다. 과도하게 사용하면 다른 사람이 읽기 어려워지고 compiler가 correctness를 검사하기도 어려워진다. Preprocessor 사용량이 지나치다면 code 정리가 필요하다는 신호인 경우가 거의 대부분이다.
#ifdef에 의한 conditional compilation은 강력하고 kernel 내부에서도 사용된다. 그렇다고 source 곳곳을 #ifdef block으로 채워서는 안 된다. 가능하면 #ifdef는 header file 안으로 제한한다. 조건에 따라 빠져야 하는 구현은, 해당 기능이 없을 때 빈 함수가 되는 작은 function으로 감쌀 수 있다. Compiler는 빈 함수 call을 제거하므로 결과 code는 더 깔끔하고 흐름도 따라가기 쉽다.
/* 조건부 구현은 header의 stub으로 격리하는 형태가 바람직하다. */
#ifdef CONFIG_FEATURE
void feature_run(struct device *dev);
#else
static inline void feature_run(struct device *dev)
{
}
#endif
위 code는 원문의 설명을 구체화한 예시이며 Linux v6.18.37 원문에 직접 포함된 snippet은 아니다.
C preprocessor macro에는 side effect를 가진 expression을 여러 번 평가할 수 있다는 위험과 type safety가 없다는 문제가 있다. Macro를 만들고 싶다면 먼저 inline function으로 바꿀 수 있는지 검토한다. 생성되는 machine code는 같을 수 있지만 inline function은 읽기 쉽고 인자를 중복 평가하지 않으며 compiler가 인자와 반환값의 type을 검사할 수 있다.
Inline function에도 자체적인 위험이 있다. Function call을 없애면 효율적일 것이라는 생각에 source file을 inline function으로 가득 채우기 쉽지만, 오히려 performance가 떨어질 수 있다. Function body가 모든 call site에 복제되면서 compiled kernel 크기가 불어나고, processor cache에 압력을 주어 실행 속도가 크게 느려질 수 있기 때문이다.
일반적으로 inline function은 아주 작고 드물게 사용해야 한다. Function call 자체의 비용은 그렇게 크지 않다. 다수의 inline function을 만드는 것은 premature optimization의 전형적인 사례다.
Kernel programmer가 cache effect를 무시하면 큰 대가를 치를 수 있다. 입문 data structure 수업에서 말하는 전통적인 time/space tradeoff는 현대 hardware에 그대로 적용되지 않는 경우가 많다. 더 큰 program은 cache 효율이 나빠져 더 느리게 실행되므로, 이 환경에서는 공간이 곧 시간이다.
최근 compiler는 특정 함수를 실제로 inline할지 스스로 결정하는 데 더 적극적이다. 따라서 inline keyword를 널리 붙이는 일은 과도할 뿐 아니라 아무 효과도 없을 수 있다.
2006년 5월 Devicescape networking stack이 GPL로 공개되어 mainline kernel에 들어갈 수 있게 되었을 때 큰 기대를 받았다. 당시 Linux wireless networking 지원은 부족하다는 평가를 받았고 이 stack이 상황을 개선할 것으로 보였다. 그러나 실제 mainline merge는 2007년 6월, Linux 2.6.22에서야 이루어졌다.
이 code에는 회사 내부에서 비공개로 개발된 흔적이 여러 곳에 있었고, 특히 multiprocessor system에서 동작하도록 설계되지 않았다는 큰 문제가 있었다. 현재 mac80211이라고 부르는 이 networking stack은 merge 전에 locking 체계를 사후에 추가해야 했다.
예전에는 multiprocessor가 만드는 concurrency 문제를 고려하지 않고 Linux kernel code를 작성할 수 있었다. 하지만 이 문서가 처음 작성될 당시에도 dual-core laptop은 이미 일상적인 장치였다. Single-processor system에서도 responsiveness를 높이는 작업은 kernel 내부 concurrency 수준을 올린다. Locking을 생각하지 않고 kernel code를 작성할 수 있던 시대는 오래전에 끝났다.
둘 이상의 thread가 동시에 접근할 수 있는 모든 resource, 즉 data structure와 hardware register 등은 lock으로 보호해야 한다. 새 code는 처음부터 이 요구를 반영해 작성해야 한다. 구현이 끝난 뒤 locking을 덧붙이는 일은 훨씬 어렵다. Kernel 개발자는 제공되는 locking primitive를 충분히 이해하고 상황에 맞는 도구를 골라야 하며, concurrency를 제대로 고려하지 않은 code는 mainline 진입 과정에서 큰 어려움을 겪는다.
기존 사용자에게 동작하던 무언가를 깨뜨리는 대신 큰 개선을 얻는 변경을 시도하고 싶을 수 있다. 이런 변경을 regression이라고 하며 mainline kernel에서는 매우 강하게 거부된다. 극히 일부 예외를 제외하면 regression을 제때 고치지 못할 경우 해당 변경은 revert된다. 가장 좋은 방법은 처음부터 regression을 만들지 않는 것이다.
한 system을 깨뜨리는 대신 열 system에 새 기능을 제공한다면 regression을 정당화할 수 있다는 주장도 있다. 그러나 2007년 7월 Linus는 이 생각을 다음과 같이 설명했다.
So we don't fix bugs by introducing new problems. That way lies
madness, and nobody ever knows if you actually make any real
progress at all. Is it two steps forwards, one step back, or one
step forward and two steps back?
즉, 새로운 문제를 도입하는 방식으로 bug를 고치지 않는다. 그렇게 하면 실제로 전진했는지조차 판단할 수 없다. 두 걸음 나아가고 한 걸음 물러난 것인지, 한 걸음 나아가고 두 걸음 물러난 것인지 알 수 없기 때문이다.
특히 허용되지 않는 regression은 user-space ABI를 바꾸는 일이다. Interface가 user space로 한번 export되면 사실상 영구히 지원해야 한다. 이 때문에 user-space interface 설계는 특히 어렵다. 호환되지 않는 방식으로 나중에 고칠 수 없으므로 처음부터 제대로 만들어야 하며, 충분한 검토와 명확한 문서, 넓은 범위의 review가 항상 필요하다.
새 code는 kernel coding style을 따라야 하지만 기능 변화 없이 기존 file 전체를 재정렬하는 patch는 review noise가 됩니다. `clang-format`과 EditorConfig는 반복 형식을 맞추는 도구이며 가독성을 해치는 기계적 규칙 적용은 피합니다.
사용되지 않는 flexibility와 운영체제 공용 hardware abstraction은 실제 동작을 가리고 유지비를 높일 수 있습니다. 공통 code의 진짜 중복은 library나 상위 subsystem 기능으로 모으되, 미래 요구를 추측한 interface는 필요해질 때 설계합니다.
`#ifdef`는 가능하면 header의 stub function으로 격리하고 macro보다 type-safe한 inline function을 우선합니다. 다만 큰 function을 무리하게 inline하면 text 크기와 cache pressure가 늘어 오히려 느려질 수 있습니다.
공유 data structure와 hardware register는 처음부터 concurrency와 locking을 설계해야 합니다. 완성 뒤에 SMP 지원을 덧붙이는 일은 어렵고, 기존 사용자 동작이나 user-space ABI를 깨는 regression은 특별한 사유가 없으면 revert 대상입니다.
원문이 강조하는 대표적인 위험을 구현 단계의 행동으로 연결했습니다.
Trappole
--------
Lo stile del codice
*******************
Il kernel ha da tempo delle norme sullo stile di codifica che sono descritte in
:ref:`Documentation/translations/it_IT/process/coding-style.rst <codingstyle>`.
Per la maggior parte del tempo, la politica descritta in quel file è stata
praticamente informativa. Ne risulta che ci sia una quantità sostanziale di
codice nel kernel che non rispetta le linee guida relative allo stile.
La presenza di quel codice conduce a due distinti pericoli per gli
sviluppatori kernel.
Il primo di questi è credere che gli standard di codifica del kernel
non sono importanti e possono non essere applicati. La verità è che
aggiungere nuovo codice al kernel è davvero difficile se questo non
rispetta le norme; molti sviluppatori richiederanno che il codice sia
riformulato prima che anche solo lo revisionino. Una base di codice larga
quanto il kernel richiede una certa uniformità, in modo da rendere possibile
per gli sviluppatori una comprensione veloce di ogni sua parte. Non ci sono,
quindi, più spazi per un codice formattato alla carlona.
Occasionalmente, lo stile di codifica del kernel andrà in conflitto con lo
stile richiesto da un datore di lavoro. In alcuni casi, lo stile del kernel
dovrà prevalere prima che il codice venga inserito. Mettere il codice
all'interno del kernel significa rinunciare a un certo grado di controllo
in differenti modi - incluso il controllo sul come formattare il codice.
L’altra trappola è quella di pensare che il codice già presente nel kernel
abbia urgentemente bisogno di essere sistemato. Gli sviluppatori potrebbero
iniziare a generare patch che correggono lo stile come modo per prendere
famigliarità con il processo, o come modo per inserire i propri nomi nei
changelog del kernel – o entrambe. La comunità di sviluppo vede un attività
di codifica puramente correttiva come "rumore"; queste attività riceveranno
una fredda accoglienza. Di conseguenza è meglio evitare questo tipo di patch.
Mentre si lavora su un pezzo di codice è normale correggerne anche lo stile,
ma le modifiche di stile non dovrebbero essere fatte fini a se stesse.
Il documento sullo stile del codice non dovrebbe essere letto come una legge
assoluta che non può mai essere trasgredita. Se c’è un a buona ragione
(per esempio, una linea che diviene poco leggibile se divisa per rientrare
nel limite di 80 colonne), fatelo e basta.
Notate che potete utilizzare lo strumento “clang-format” per aiutarvi con
le regole, per una riformattazione automatica e veloce del vostro codice
e per revisionare interi file per individuare errori nello stile di codifica,
refusi e possibili miglioramenti. Inoltre è utile anche per classificare gli
``#includes``, per allineare variabili/macro, per testi derivati ed altri
compiti del genere. Consultate il file
:ref:`Documentation/translations/it_IT/dev-tools/clang-format.rst <clangformat>`
per maggiori dettagli
Se utilizzate un programma compatibile con EditorConfig, allora alcune
configurazioni basilari come l'indentazione e la fine delle righe verranno
applicate automaticamente. Per maggiori informazioni consultate la pagina:
https://editorconfig.org/
Livelli di astrazione
*********************
I professori di Informatica insegnano ai propri studenti a fare ampio uso dei
livelli di astrazione nel nome della flessibilità e del nascondere informazioni.
Certo il kernel fa un grande uso dell'astrazione; nessun progetto con milioni
di righe di codice potrebbe fare altrimenti e sopravvivere. Ma l'esperienza
ha dimostrato che un'eccessiva o prematura astrazione può rivelarsi dannosa
al pari di una prematura ottimizzazione. L'astrazione dovrebbe essere usata
fino al livello necessario e non oltre.
Ad un livello base, considerate una funzione che ha un argomento che viene
sempre impostato a zero da tutti i chiamanti. Uno potrebbe mantenere
quell'argomento nell'eventualità qualcuno volesse sfruttare la flessibilità
offerta. In ogni caso, tuttavia, ci sono buone possibilità che il codice
che va ad implementare questo argomento aggiuntivo, sia stato rotto in maniera
sottile, in un modo che non è mai stato notato - perché non è mai stato usato.
Oppure, quando sorge la necessità di avere più flessibilità, questo argomento
non la fornisce in maniera soddisfacente. Gli sviluppatori di Kernel,
sottopongono costantemente patch che vanno a rimuovere gli argomenti
inutilizzate; anche se, in generale, non avrebbero dovuto essere aggiunti.
I livelli di astrazione che nascondono l'accesso all'hardware -
spesso per poter usare dei driver su diversi sistemi operativi - vengono
particolarmente disapprovati. Tali livelli oscurano il codice e possono
peggiorare le prestazioni; essi non appartengono al kernel Linux.
D'altro canto, se vi ritrovate a dover copiare una quantità significativa di
codice proveniente da un altro sottosistema del kernel, è tempo di chiedersi
se, in effetti, non avrebbe più senso togliere parte di quel codice e metterlo
in una libreria separata o di implementare quella funzionalità ad un livello
più elevato. Non c'è utilità nel replicare lo stesso codice per tutto
il kernel.
#ifdef e l'uso del preprocessore in generale
********************************************
Il preprocessore C sembra essere una fonte di attrazione per qualche
programmatore C, che ci vede una via per ottenere una grande flessibilità
all'interno di un file sorgente. Ma il preprocessore non è scritto in C,
e un suo massiccio impiego conduce a un codice che è molto più difficile
da leggere per gli altri e che rende più difficile il lavoro di verifica del
compilatore. L'uso eccessivo del preprocessore è praticamente sempre il segno
di un codice che necessita di un certo lavoro di pulizia.
La compilazione condizionata con #ifdef è, in effetti, un potente strumento,
ed esso viene usato all'interno del kernel. Ma esiste un piccolo desiderio:
quello di vedere il codice coperto solo da una leggera spolverata di
blocchi #ifdef. Come regola generale, quando possibile, l'uso di #ifdef
dovrebbe essere confinato nei file d'intestazione. Il codice compilato
condizionatamente può essere confinato a funzioni tali che, nel caso in cui
il codice non deve essere presente, diventano vuote. Il compilatore poi
ottimizzerà la chiamata alla funzione vuota rimuovendola. Il risultato è
un codice molto più pulito, più facile da seguire.
Le macro del preprocessore C presentano una serie di pericoli, inclusi
valutazioni multiple di espressioni che hanno effetti collaterali e non
garantiscono una sicurezza rispetto ai tipi. Se siete tentati dal definire
una macro, considerate l'idea di creare invece una funzione inline. Il codice
che ne risulterà sarà lo stesso, ma le funzioni inline sono più leggibili,
non considerano i propri argomenti più volte, e permettono al compilatore di
effettuare controlli sul tipo degli argomenti e del valore di ritorno.
Funzioni inline
***************
Comunque, anche le funzioni inline hanno i loro pericoli. I programmatori
potrebbero innamorarsi dell'efficienza percepita derivata dalla rimozione
di una chiamata a funzione. Queste funzioni, tuttavia, possono ridurre le
prestazioni. Dato che il loro codice viene replicato ovunque vi sia una
chiamata ad esse, si finisce per gonfiare le dimensioni del kernel compilato.
Questi, a turno, creano pressione sulla memoria cache del processore, e questo
può causare rallentamenti importanti. Le funzioni inline, di norma, dovrebbero
essere piccole e usate raramente. Il costo di una chiamata a funzione, dopo
tutto, non è così alto; la creazione di molte funzioni inline è il classico
esempio di un'ottimizzazione prematura.
In generale, i programmatori del kernel ignorano gli effetti della cache a
loro rischio e pericolo. Il classico compromesso tempo/spazio teorizzato
all'inizio delle lezioni sulle strutture dati spesso non si applica
all'hardware moderno. Lo spazio *è* tempo, in questo senso un programma
più grande sarà più lento rispetto ad uno più compatto.
I compilatori più recenti hanno preso un ruolo attivo nel decidere se
una data funzione deve essere resa inline oppure no. Quindi l'uso
indiscriminato della parola chiave "inline" potrebbe non essere non solo
eccessivo, ma anche irrilevante.
Sincronizzazione
****************
Nel maggio 2006, il sistema di rete "Devicescape" fu rilasciato in pompa magna
sotto la licenza GPL e reso disponibile per la sua inclusione nella ramo
principale del kernel. Questa donazione fu una notizia bene accolta;
il supporto per le reti senza fili era considerata, nel migliore dei casi,
al di sotto degli standard; il sistema Deviscape offrì la promessa di una
risoluzione a tale situazione. Tuttavia, questo codice non fu inserito nel
ramo principale fino al giugno del 2007 (2.6.22). Cosa accadde?
Quel codice mostrava numerosi segnali di uno sviluppo in azienda avvenuto
a porte chiuse. Ma in particolare, un grosso problema fu che non fu
progettato per girare in un sistema multiprocessore. Prima che questo
sistema di rete (ora chiamato mac80211) potesse essere inserito, fu necessario
un lavoro sugli schemi di sincronizzazione.
Una volta, il codice del kernel Linux poteva essere sviluppato senza pensare
ai problemi di concorrenza presenti nei sistemi multiprocessore. Ora,
comunque, questo documento è stato scritto su di un portatile dual-core.
Persino su sistemi a singolo processore, il lavoro svolto per incrementare
la capacità di risposta aumenterà il livello di concorrenza interno al kernel.
I giorni nei quali il codice poteva essere scritto senza pensare alla
sincronizzazione sono da passati tempo.
Ogni risorsa (strutture dati, registri hardware, etc.) ai quali si potrebbe
avere accesso simultaneo da più di un thread deve essere sincronizzato. Il
nuovo codice dovrebbe essere scritto avendo tale accortezza in testa;
riadattare la sincronizzazione a posteriori è un compito molto più difficile.
Gli sviluppatori del kernel dovrebbero prendersi il tempo di comprendere bene
le primitive di sincronizzazione, in modo da sceglier lo strumento corretto
per eseguire un compito. Il codice che presenta una mancanza di attenzione
alla concorrenza avrà un percorso difficile all'interno del ramo principale.
Regressioni
***********
Vale la pena menzionare un ultimo pericolo: potrebbe rivelarsi accattivante
l'idea di eseguire un cambiamento (che potrebbe portare a grandi
miglioramenti) che porterà ad alcune rotture per gli utenti esistenti.
Questa tipologia di cambiamento è chiamata "regressione", e le regressioni son
diventate mal viste nel ramo principale del kernel. Con alcune eccezioni,
i cambiamenti che causano regressioni saranno fermati se quest'ultime non
potranno essere corrette in tempo utile. È molto meglio quindi evitare
la regressione fin dall'inizio.
Spesso si è argomentato che una regressione può essere giustificata se essa
porta risolve più problemi di quanti non ne crei. Perché, dunque, non fare
un cambiamento se questo porta a nuove funzionalità a dieci sistemi per
ognuno dei quali esso determina una rottura? La migliore risposta a questa
domanda ci è stata fornita da Linus nel luglio 2007:
::
Dunque, noi non sistemiamo bachi introducendo nuovi problemi. Quella
via nasconde insidie, e nessuno può sapere del tutto se state facendo
dei progressi reali. Sono due passi avanti e uno indietro, oppure
un passo avanti e due indietro?
(http://lwn.net/Articles/243460/).
Una particolare tipologia di regressione mal vista consiste in una qualsiasi
sorta di modifica all'ABI dello spazio utente. Una volta che un'interfaccia
viene esportata verso lo spazio utente, dev'essere supportata all'infinito.
Questo fatto rende la creazione di interfacce per lo spazio utente
particolarmente complicato: dato che non possono venir cambiate introducendo
incompatibilità, esse devono essere fatte bene al primo colpo. Per questa
ragione sono sempre richieste: ampie riflessioni, documentazione chiara e
ampie revisioni dell'interfaccia verso lo spazio utente.
compiler와 자동 검사 도구
241-351오류 없는 code 작성은 아직 소수만이 도달할 수 있는 이상에 가깝다. 현실적인 목표는 mainline에 들어가기 전에 가능한 한 많은 오류를 찾아 고치는 것이다. Kernel 개발자는 잘 드러나지 않는 다양한 문제를 자동으로 찾는 도구를 마련해 왔다. Computer가 미리 찾은 문제는 나중에 사용자가 겪지 않아도 되는 문제이므로 가능한 모든 자동 검사 도구를 사용해야 한다.
첫 단계는 compiler warning을 무시하지 않는 것이다. 현대 gcc는 많은 잠재 오류를 찾아 경고할 수 있고, 그 경고는 실제 문제를 가리키는 경우가 많다. Review에 제출하는 code는 원칙적으로 compiler warning을 만들지 않아야 한다. Warning을 없앨 때는 근본 원인을 이해해야 하며, 원인을 해결하지 않은 채 메시지만 사라지게 하는 수정은 피한다.
make KCFLAGS=-W
기본값으로 활성화되지 않은 compiler warning까지 보려면 위와 같이 build한다. Kernel에는 debugging 기능을 켜는 configuration option도 많으며 대부분 kernel hacking submenu에 있다. 개발 또는 test용 kernel에서는 다음 option을 켜는 것이 좋다.
- FRAME_WARN: 지정 크기보다 큰 stack frame을 경고한다. 출력이 많아질 수 있지만 kernel의 다른 영역에서 나온 경고까지 모두 직접 해결할 필요는 없다.
- DEBUG_OBJECTS: kernel이 만드는 여러 object의 lifetime을 추적하고 operation 순서가 잘못되면 경고한다. 독자적인 복잡한 object를 생성하고 외부에 제공하는 subsystem을 추가한다면 object debugging infrastructure 지원도 고려한다.
- DEBUG_SLAB: memory allocation과 사용 과정에서 발생하는 다양한 오류를 찾는다. 대부분의 development kernel에서 사용해야 한다.
- DEBUG_SPINLOCK, DEBUG_ATOMIC_SLEEP, DEBUG_MUTEXES: 흔한 locking 오류를 다수 찾아낸다.
그 밖에도 debugging option이 많이 있다. 일부는 performance overhead가 커서 항상 켜 둘 수 없지만, 어떤 option이 있는지 학습하는 데 들인 시간은 대개 짧은 기간 안에 여러 배로 돌아온다.
더 무거운 debugging 도구 중 하나가 locking checker인 lockdep이다. Lockdep은 system의 모든 spinlock과 mutex 획득·해제, lock 사이의 획득 순서, 현재 interrupt 환경 등을 추적한다. 이를 바탕으로 lock이 항상 같은 순서로 잡히는지, 모든 경로에서 interrupt에 대한 전제가 일치하는지 검사한다. 드물게 발생할 수 있는 deadlock 시나리오를 배포 전에 자동으로 찾을 수 있으므로, 단순하지 않은 locking을 포함한 code는 제출 전에 lockdep을 켜고 실행해야 한다.
신중한 kernel programmer라면 memory allocation처럼 실패할 수 있는 operation의 return status를 확인한다. 하지만 그 결과로 작성한 failure recovery path는 실제로 한 번도 실행해 보지 않았을 가능성이 높다. Test되지 않은 code는 망가진 code일 가능성이 크므로 error-handling path도 의도적으로 실행해 보아야 한다.
Kernel의 fault injection framework는 특히 memory allocation 실패를 주입해 이 검증을 수행한다. 활성화하면 설정한 비율의 allocation을 실패시킬 수 있고, 실패를 특정 code range로 제한할 수도 있다. Fault injection 상태로 실행하면 상황이 나빠졌을 때 code가 실제로 어떻게 반응하는지 볼 수 있다. 사용법은 Documentation/fault-injection/fault-injection.rst에 설명되어 있다.
Sparse static analysis 도구는 user-space address와 kernel-space address의 혼동, big-endian 값과 little-endian 값의 혼합, bit flag 집합이 필요한 자리에 integer를 전달하는 문제 등을 찾아 경고한다. Distribution package에 없다면 https://sparse.wiki.kernel.org/index.php/Main_Page 에서 별도로 설치한 뒤 make 명령에 C=1을 추가하여 실행한다.
make C=1
Coccinelle은 다양한 잠재 coding 문제를 찾고 수정안까지 제안할 수 있다. Kernel tree의 scripts/coccinelle directory에는 여러 semantic patch가 포함되어 있으며 make coccicheck를 실행하면 이 patch들을 순회하면서 발견한 문제를 보고한다. 자세한 내용은 Documentation/dev-tools/coccinelle.rst를 참고한다.
make coccicheck
또 다른 종류의 portability 오류는 다른 architecture용으로 compile할 때 가장 잘 드러난다. S/390 system이나 Blackfin development board를 실제로 보유하지 않아도 cross compiler로 compilation 단계는 수행할 수 있다. x86 system용 cross compiler 모음은 https://www.kernel.org/pub/tools/crosstool/ 에 있다. 이를 설치하고 사용하는 데 시간을 들이면 나중에 곤란한 상황을 피할 수 있다.
compiler warning은 잠재 bug 신호이므로 원인을 이해하고 제거해야 합니다. `make KCFLAGS=-W`로 추가 warning을 확인하고 development kernel에서는 object lifetime, slab, spinlock, mutex와 atomic sleep 검사를 켭니다.
lockdep은 잠금 획득 순서와 IRQ 문맥을 추적해 드문 deadlock 가능성을 찾습니다. fault injection은 allocation 실패 같은 error path를 실제로 실행시키며, test하지 않은 복구 code가 예상대로 동작하는지 검증합니다.
sparse는 address space annotation과 type misuse를 검사하고 `make C=1`로 변경 file에 적용할 수 있습니다. Coccinelle의 semantic patch는 `make coccicheck`로 반복적인 API 오용을 찾으며 cross compile은 다른 architecture에서만 보이는 portability 문제를 드러냅니다.
Strumenti di verifica del codice
--------------------------------
Almeno per ora la scrittura di codice priva di errori resta un ideale
irraggiungibile ai più. Quello che speriamo di poter fare, tuttavia, è
trovare e correggere molti di questi errori prima che il codice entri nel
ramo principale del kernel. A tal scopo gli sviluppatori del kernel devono
mettere insieme una schiera impressionante di strumenti che possano
localizzare automaticamente un'ampia varietà di problemi. Qualsiasi problema
trovato dal computer è un problema che non affliggerà l'utente in seguito,
ne consegue che gli strumenti automatici dovrebbero essere impiegati ovunque
possibile.
Il primo passo consiste semplicemente nel fare attenzione agli avvertimenti
proveniente dal compilatore. Versioni moderne di gcc possono individuare
(e segnalare) un gran numero di potenziali errori. Molto spesso, questi
avvertimenti indicano problemi reali. Di regola, il codice inviato per la
revisione non dovrebbe produrre nessun avvertimento da parte del compilatore.
Per mettere a tacere gli avvertimenti, cercate di comprenderne le cause reali
e cercate di evitare le "riparazioni" che fan sparire l'avvertimento senza
però averne trovato la causa.
Tenete a mente che non tutti gli avvertimenti sono disabilitati di default.
Costruite il kernel con "make KCFLAGS=-W" per ottenerli tutti.
Il kernel fornisce differenti opzioni che abilitano funzionalità di debugging;
molti di queste sono trovano all'interno del sotto menu "kernel hacking".
La maggior parte di queste opzioni possono essere attivate per qualsiasi
kernel utilizzato per lo sviluppo o a scopo di test. In particolare dovreste
attivare:
- FRAME_WARN per ottenere degli avvertimenti su stack frame più
grandi di un dato valore. Il risultato generato da questi
avvertimenti può risultare verboso, ma non bisogna preoccuparsi per
gli avvertimenti provenienti da altre parti del kernel.
- DEBUG_OBJECTS aggiungerà un codice per tracciare il ciclo di vita di
diversi oggetti creati dal kernel e avvisa quando qualcosa viene eseguito
fuori controllo. Se state aggiungendo un sottosistema che crea (ed
esporta) oggetti complessi propri, considerate l'aggiunta di un supporto
al debugging dell'oggetto.
- DEBUG_SLAB può trovare svariati errori di uso e di allocazione di memoria;
esso dovrebbe esser usato dalla maggior parte dei kernel di sviluppo.
- DEBUG_SPINLOCK, DEBUG_ATOMIC_SLEEP, e DEBUG_MUTEXES troveranno un certo
numero di errori comuni di sincronizzazione.
Esistono ancora delle altre opzioni di debugging, di alcune di esse
discuteremo qui sotto. Alcune di esse hanno un forte impatto e non dovrebbero
essere usate tutte le volte. Ma qualche volta il tempo speso nell'capire
le opzioni disponibili porterà ad un risparmio di tempo nel breve termine.
Uno degli strumenti di debugging più tosti è il *locking checker*, o
"lockdep". Questo strumento traccerà qualsiasi acquisizione e rilascio di
ogni *lock* (spinlock o mutex) nel sistema, l'ordine con il quale i *lock*
sono acquisiti in relazione l'uno con l'altro, l'ambiente corrente di
interruzione, eccetera. Inoltre esso può assicurare che i *lock* vengano
acquisiti sempre nello stesso ordine, che le stesse assunzioni sulle
interruzioni si applichino in tutte le occasioni, e così via. In altre parole,
lockdep può scovare diversi scenari nei quali il sistema potrebbe, in rari
casi, trovarsi in stallo. Questa tipologia di problema può essere grave
(sia per gli sviluppatori che per gli utenti) in un sistema in uso; lockdep
permette di trovare tali problemi automaticamente e in anticipo.
In qualità di programmatore kernel diligente, senza dubbio, dovrete controllare
il valore di ritorno di ogni operazione (come l'allocazione della memoria)
poiché esso potrebbe fallire. Il nocciolo della questione è che i percorsi
di gestione degli errori, con grande probabilità, non sono mai stati
collaudati del tutto. Il codice collaudato tende ad essere codice bacato;
potrete quindi essere più a vostro agio con il vostro codice se tutti questi
percorsi fossero stati verificati un po' di volte.
Il kernel fornisce un framework per l'inserimento di fallimenti che fa
esattamente al caso, specialmente dove sono coinvolte allocazioni di memoria.
Con l'opzione per l'inserimento dei fallimenti abilitata, una certa percentuale
di allocazione di memoria sarà destinata al fallimento; questi fallimenti
possono essere ridotti ad uno specifico pezzo di codice. Procedere con
l'inserimento dei fallimenti attivo permette al programmatore di verificare
come il codice risponde quando le cose vanno male. Consultate:
Documentation/fault-injection/fault-injection.rst per avere maggiori
informazioni su come utilizzare questo strumento.
Altre tipologie di errori possono essere riscontrati con lo strumento di
analisi statica "sparse". Con Sparse, il programmatore può essere avvisato
circa la confusione tra gli indirizzi dello spazio utente e dello spazio
kernel, un miscuglio fra quantità big-endian e little-endian, il passaggio
di un valore intero dove ci sia aspetta un gruppo di flag, e così via.
Sparse deve essere installato separatamente (se il vostra distribuzione non
lo prevede, potete trovarlo su https://sparse.wiki.kernel.org/index.php/Main_Page);
può essere attivato sul codice aggiungendo "C=1" al comando make.
Lo strumento "Coccinelle" (http://coccinelle.lip6.fr/) è in grado di trovare
una vasta varietà di potenziali problemi di codifica; e può inoltre proporre
soluzioni per risolverli. Un buon numero di "patch semantiche" per il kernel
sono state preparate nella cartella scripts/coccinelle; utilizzando
"make coccicheck" esso percorrerà tali patch semantiche e farà rapporto su
qualsiasi problema trovato. Per maggiori informazioni, consultate
:ref:`Documentation/dev-tools/coccinelle.rst <devtools_coccinelle>`.
Altri errori di portabilità sono meglio scovati compilando il vostro codice
per altre architetture. Se non vi accade di avere un sistema S/390 o una
scheda di sviluppo Blackfin sotto mano, potete comunque continuare la fase
di compilazione. Un vasto numero di cross-compilatori per x86 possono
essere trovati al sito:
http://www.kernel.org/pub/tools/crosstool/
Il tempo impiegato nell'installare e usare questi compilatori sarà d'aiuto
nell'evitare situazioni imbarazzanti nel futuro.
patch와 interface 문서화
352-413Kernel 개발에서 documentation은 규칙이라기보다 예외였던 시기가 많았다. 그래도 충분한 문서는 새 code의 merge를 쉽게 하고 다른 개발자와 사용자의 작업을 돕는다. 이제는 문서 추가가 사실상 필수인 경우도 많다.
모든 patch의 첫 번째 documentation은 함께 제출하는 changelog다. Log entry에는 해결하려는 문제, 해결 방법의 형태, patch 작업에 참여한 사람, 관련 performance 영향, patch를 이해하는 데 필요한 기타 사항을 적어야 한다. 특히 그 patch를 적용할 가치가 왜 있는지 설명해야 한다. 놀랄 만큼 많은 개발자가 이 정보를 빠뜨린다.
새 sysfs 또는 /proc file을 포함하여 새로운 user-space interface를 추가하는 code에는 user-space 개발자가 interface 사용법과 의미를 알 수 있는 문서가 따라야 한다. 형식과 필수 정보는 Documentation/ABI/README에 설명되어 있다.
Documentation/admin-guide/kernel-parameters.rst는 kernel boot-time parameter 전체를 설명한다. 새 parameter를 추가하는 patch는 해당 문서에도 항목을 추가해야 한다. 새 configuration option에는 option의 의미와 사용자가 언제 선택해야 하는지를 명확히 설명하는 help text가 반드시 있어야 한다.
여러 subsystem의 internal API는 특별한 형식의 comment로 문서화되며 kernel-doc script가 이를 추출해 다양한 형식으로 만들 수 있다. Kerneldoc comment를 사용하는 subsystem에서 작업한다면 기존 comment를 함께 유지하고, 외부에 제공되는 function에는 필요에 따라 새 comment를 추가한다. 아직 이런 문서가 없는 영역에서도 미래를 위해 kerneldoc comment를 추가해 두는 것은 유용하며 초보 kernel 개발자가 시작하기 좋은 작업이기도 하다. Comment 형식과 kerneldoc template 생성법은 Documentation/doc-guide/에 있다.
기존 kernel code를 많이 읽다 보면 comment가 없는 경우가 흔하다는 것을 알 수 있다. 하지만 새 code에 대한 기대 수준은 과거보다 높아졌으므로 comment 없는 code는 merge하기 더 어렵다. 그렇다고 장황한 comment를 원하는 것은 아니다. Code 자체가 읽을 수 있어야 하고, comment는 미묘하거나 code만으로 드러나지 않는 부분을 설명해야 한다.
- Memory barrier를 사용한 곳에는 그 barrier가 왜 필요한지 설명하는 한 줄을 둔다.
- Data structure의 locking rule은 적절한 위치에 설명한다.
- 핵심 data structure에는 전체 역할과 불변 조건을 포함한 충분한 문서를 둔다.
- 서로 떨어진 code 조각 사이의 명확하지 않은 dependency를 밝혀 둔다.
- 겉보기에는 불필요해 보여 잘못된 cleanup을 유도할 수 있는 구현에는 왜 그 방식이어야 하는지 적는다.
모든 patch의 첫 문서는 changelog입니다. 해결할 문제, 선택한 해법, 참여자, 성능 영향과 mainline에 적용할 가치가 있는 이유를 reviewer가 code 밖에서도 이해할 수 있어야 합니다.
새 sysfs·procfs interface는 `Documentation/ABI/README` 형식으로, boot parameter와 Kconfig option은 대응 문서와 help text로 설명합니다. exported internal API에는 kernel-doc을 유지하고 comment는 locking rule, memory barrier 이유와 data structure 불변식처럼 code만으로 드러나지 않는 사실을 기록합니다.
Documentazione
--------------
La documentazione è spesso stata più un'eccezione che una regola nello
sviluppo del kernel. Nonostante questo, un'adeguata documentazione aiuterà
a facilitare l'inserimento di nuovo codice nel kernel, rende la vita più
facile per gli altri sviluppatori e sarà utile per i vostri utenti. In molti
casi, la documentazione è divenuta sostanzialmente obbligatoria.
La prima parte di documentazione per qualsiasi patch è il suo changelog.
Questi dovrebbero descrivere le problematiche risolte, la tipologia di
soluzione, le persone che lavorano alla patch, ogni effetto rilevante
sulle prestazioni e tutto ciò che può servire per la comprensione della
patch. Assicuratevi che il changelog dica *perché*, vale la pena aggiungere
la patch; un numero sorprendente di sviluppatori sbaglia nel fornire tale
informazione.
Qualsiasi codice che aggiunge una nuova interfaccia in spazio utente - inclusi
nuovi file in sysfs o /proc - dovrebbe includere la documentazione di tale
interfaccia così da permette agli sviluppatori dello spazio utente di sapere
con cosa stanno lavorando. Consultate: Documentation/ABI/README per avere una
descrizione di come questi documenti devono essere impostati e quali
informazioni devono essere fornite.
Il file :ref:`Documentation/translations/it_IT/admin-guide/kernel-parameters.rst <kernelparameters>`
descrive tutti i parametri di avvio del kernel. Ogni patch che aggiunga
nuovi parametri dovrebbe aggiungere nuove voci a questo file.
Ogni nuova configurazione deve essere accompagnata da un testo di supporto
che spieghi chiaramente le opzioni e spieghi quando l'utente potrebbe volerle
selezionare.
Per molti sottosistemi le informazioni sull'API interna sono documentate sotto
forma di commenti formattati in maniera particolare; questi commenti possono
essere estratti e formattati in differenti modi attraverso lo script
"kernel-doc". Se state lavorando all'interno di un sottosistema che ha
commenti kerneldoc dovreste mantenerli e aggiungerli, in maniera appropriata,
per le funzioni disponibili esternamente. Anche in aree che non sono molto
documentate, non c'è motivo per non aggiungere commenti kerneldoc per il
futuro; infatti, questa può essere un'attività utile per sviluppatori novizi
del kernel. Il formato di questi commenti, assieme alle informazione su come
creare modelli per kerneldoc, possono essere trovati in
:ref:`Documentation/translations/it_IT/doc-guide/ <doc_guide>`.
Chiunque legga un ammontare significativo di codice kernel noterà che, spesso,
i commenti si fanno maggiormente notare per la loro assenza. Ancora una volta,
le aspettative verso il nuovo codice sono più alte rispetto al passato;
inserire codice privo di commenti sarà più difficile. Detto ciò, va aggiunto
che non si desiderano commenti prolissi per il codice. Il codice dovrebbe
essere, di per sé, leggibile, con dei commenti che spieghino gli aspetti più
sottili.
Determinate cose dovrebbero essere sempre commentate. L'uso di barriere
di memoria dovrebbero essere accompagnate da una riga che spieghi perché sia
necessaria. Le regole di sincronizzazione per le strutture dati, generalmente,
necessitano di una spiegazioni da qualche parte. Le strutture dati più
importanti, in generale, hanno bisogno di una documentazione onnicomprensiva.
Le dipendenze che non sono ovvie tra bit separati di codice dovrebbero essere
indicate. Tutto ciò che potrebbe indurre un inserviente del codice a fare
una "pulizia" incorretta, ha bisogno di un commento che dica perché è stato
fatto in quel modo. E così via.
internal API 변경의 권한과 책임
414-450Kernel이 user space에 제공하는 binary interface는 극도로 심각한 상황이 아니면 깨뜨릴 수 없다. 반면 kernel internal programming interface는 매우 유동적이며 필요할 때 변경할 수 있다. Kernel API를 피해 우회 구현을 하고 있거나 필요한 기능이 없어 특정 API를 사용하지 못한다면, API 자체를 바꿔야 한다는 신호일 수 있다. Kernel 개발자는 그런 변경을 할 권한이 있다.
물론 조건이 있다. API 변경에는 충분한 근거가 필요하다. Internal API를 바꾸는 patch에는 무엇을 어떻게 바꾸며 왜 필요한지 설명을 붙여야 한다. 또한 더 큰 patch 안에 묻어 두지 말고 API 변경을 독립된 patch로 분리해야 한다.
Internal API를 바꾼 개발자는 일반적으로 그 변경으로 인해 kernel tree 안에서 깨지는 모든 code를 고칠 책임도 진다. 널리 사용되는 function이라면 수백 또는 수천 곳을 수정해야 할 수 있고, 그중 다수는 다른 개발자가 진행 중인 작업과 충돌할 가능성이 있다. 매우 큰 작업이 될 수 있으므로 변경 근거가 확실한지 먼저 확인해야 한다. 범위가 넓은 API 변경에는 Coccinelle이 도움을 줄 수 있다.
호환되지 않는 API 변경을 할 때는 가능하면 아직 갱신되지 않은 code가 compiler에 의해 검출되도록 설계한다. 그러면 tree 안의 모든 사용처를 찾았는지 확인할 수 있고, out-of-tree code 개발자도 대응해야 할 변경이 생겼음을 알 수 있다. Kernel 개발자가 out-of-tree code 지원을 책임질 필요는 없지만, 필요 이상으로 그들의 작업을 어렵게 만들 이유도 없다.
user-space binary interface와 달리 kernel internal API는 더 나은 설계를 위해 바꿀 수 있습니다. 그러나 이유와 변경 형태를 독립 patch로 설명하고 tree 안에서 깨지는 모든 caller를 함께 갱신해야 합니다.
넓은 API 변환은 Coccinelle로 기계적 수정과 누락 탐지를 도울 수 있습니다. 호환되지 않는 사용처가 compiler error로 드러나도록 설계하면 in-tree와 out-of-tree 개발자 모두 필요한 변경을 명확히 알 수 있습니다.
Cambiamenti interni dell'API
----------------------------
L'interfaccia binaria fornita dal kernel allo spazio utente non può essere
rotta tranne che in circostanze eccezionali. L'interfaccia di programmazione
interna al kernel, invece, è estremamente fluida e può essere modificata al
bisogno. Se vi trovate a dover lavorare attorno ad un'API del kernel o
semplicemente non state utilizzando una funzionalità offerta perché questa
non rispecchia i vostri bisogni, allora questo potrebbe essere un segno che
l'API ha bisogno di essere cambiata. In qualità di sviluppatore del kernel,
hai il potere di fare questo tipo di modifica.
Ci sono ovviamente alcuni punti da cogliere. I cambiamenti API possono essere
fatti, ma devono essere giustificati. Quindi ogni patch che porta ad una
modifica dell'API interna dovrebbe essere accompagnata da una descrizione
della modifica in sé e del perché essa è necessaria. Questo tipo di
cambiamenti dovrebbero, inoltre, essere fatti in una patch separata, invece di
essere sepolti all'interno di una patch più grande.
L'altro punto da cogliere consiste nel fatto che uno sviluppatore che
modifica l'API deve, in generale, essere responsabile della correzione
di tutto il codice del kernel che viene rotto per via della sua modifica.
Per una funzione ampiamente usata, questo compito può condurre letteralmente
a centinaia o migliaia di modifiche, molte delle quali sono in conflitto con
il lavoro svolto da altri sviluppatori. Non c'è bisogno di dire che questo
può essere un lavoro molto grosso, quindi è meglio essere sicuri che la
motivazione sia ben solida. Notate che lo strumento Coccinelle può fornire
un aiuto con modifiche estese dell'API.
Quando viene fatta una modifica API incompatibile, una persona dovrebbe,
quando possibile, assicurarsi che quel codice non aggiornato sia trovato
dal compilatore. Questo vi aiuterà ad essere sicuri d'avere trovato,
tutti gli usi di quell'interfaccia. Inoltre questo avviserà gli sviluppatori
di codice fuori dal kernel che c'è un cambiamento per il quale è necessario del
lavoro. Il supporto al codice fuori dal kernel non è qualcosa di cui gli
sviluppatori del kernel devono preoccuparsi, ma non dobbiamo nemmeno rendere
più difficile del necessario la vita agli sviluppatori di questo codice.
요약·해설
4.Coding.rst:1-450mainline 품질 기준에 맞는 code를 작성하기 위해 style, abstraction, preprocessor, concurrency와 user-space ABI에서 피해야 할 함정을 설명합니다.
compiler warning, lockdep, fault injection, sparse, Coccinelle, cross compile과 문서화 절차로 제출 전 오류를 찾는 방법도 다룹니다.