요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
.. include:: ../disclaimer-ita.rst
.. note:: Per leggere la documentazione originale in inglese:
:ref:`Documentation/kernel-hacking/hacking.rst <kernel_hacking_hack>`
:Original: :ref:`Documentation/kernel-hacking/hacking.rst <kernel_hacking_hack>`
:Translator: Federico Vaga <[email protected]>
.. _it_kernel_hacking_hack:
=================================================
L'inaffidabile guida all'hacking del kernel Linux
=================================================
:Author: Rusty Russell
Introduzione
============
Benvenuto, gentile lettore, alla notevole ed inaffidabile guida all'hacking
del kernel Linux ad opera di Rusty. Questo documento descrive le procedure
più usate ed i concetti necessari per scrivere codice per il kernel: lo scopo
è di fornire ai programmatori C più esperti un manuale di base per sviluppo.
Eviterò dettagli implementativi: per questo abbiamo il codice,
ed ignorerò intere parti di alcune procedure.
Prima di leggere questa guida, sappiate che non ho mai voluto scriverla,
essendo esageratamente sotto qualificato, ma ho sempre voluto leggere
qualcosa di simile, e quindi questa era l'unica via. Spero che possa
crescere e diventare un compendio di buone pratiche, punti di partenza
e generiche informazioni.
Gli attori
==========
In qualsiasi momento ognuna delle CPU di un sistema può essere:
- non associata ad alcun processo, servendo un'interruzione hardware;
- non associata ad alcun processo, servendo un softirq o tasklet;
- in esecuzione nello spazio kernel, associata ad un processo
(contesto utente);
- in esecuzione di un processo nello spazio utente;
Esiste un ordine fra questi casi. Gli ultimi due possono avvicendarsi (preempt)
l'un l'altro, ma a parte questo esiste una gerarchia rigida: ognuno di questi
può avvicendarsi solo ad uno di quelli sottostanti. Per esempio, mentre un
softirq è in esecuzione su d'una CPU, nessun altro softirq può avvicendarsi
nell'esecuzione, ma un'interruzione hardware può. Ciò nonostante, le altre CPU
del sistema operano indipendentemente.
Più avanti vedremo alcuni modi in cui dal contesto utente è possibile bloccare
le interruzioni, così da impedirne davvero il diritto di prelazione.
Contesto utente
---------------
Ci si trova nel contesto utente quando si arriva da una chiamata di sistema
od altre eccezioni: come nello spazio utente, altre procedure più importanti,
o le interruzioni, possono far valere il proprio diritto di prelazione sul
vostro processo. Potete sospendere l'esecuzione chiamando :c:func:`schedule()`.
.. note::
Si è sempre in contesto utente quando un modulo viene caricato o rimosso,
e durante le operazioni nello strato dei dispositivi a blocchi
(*block layer*).
Nel contesto utente, il puntatore ``current`` (il quale indica il processo al
momento in esecuzione) è valido, e :c:func:`in_interrupt()`
(``include/linux/preempt.h``) è falsa.
.. warning::
Attenzione che se avete la prelazione o i softirq disabilitati (vedere
di seguito), :c:func:`in_interrupt()` ritornerà un falso positivo.
Interruzioni hardware (Hard IRQs)
---------------------------------
Temporizzatori, schede di rete e tastiere sono esempi di vero hardware
che possono produrre interruzioni in un qualsiasi momento. Il kernel esegue
i gestori d'interruzione che prestano un servizio all'hardware. Il kernel
garantisce che questi gestori non vengano mai interrotti: se una stessa
interruzione arriva, questa verrà accodata (o scartata).
Dato che durante la loro esecuzione le interruzioni vengono disabilitate,
i gestori d'interruzioni devono essere veloci: spesso si limitano
esclusivamente a notificare la presa in carico dell'interruzione,
programmare una 'interruzione software' per l'esecuzione e quindi terminare.
Potete dire d'essere in una interruzione hardware perché in_hardirq()
ritorna vero.
.. warning::
Attenzione, questa ritornerà un falso positivo se le interruzioni
sono disabilitate (vedere di seguito).
Contesto d'interruzione software: softirq e tasklet
---------------------------------------------------
Quando una chiamata di sistema sta per tornare allo spazio utente,
oppure un gestore d'interruzioni termina, qualsiasi 'interruzione software'
marcata come pendente (solitamente da un'interruzione hardware) viene
eseguita (``kernel/softirq.c``).
La maggior parte del lavoro utile alla gestione di un'interruzione avviene qui.
All'inizio della transizione ai sistemi multiprocessore, c'erano solo i
cosiddetti 'bottom half' (BH), i quali non traevano alcun vantaggio da questi
sistemi. Non appena abbandonammo i computer raffazzonati con fiammiferi e
cicche, abbandonammo anche questa limitazione e migrammo alle interruzioni
software 'softirqs'.
Il file ``include/linux/interrupt.h`` elenca i differenti tipi di 'softirq'.
Un tipo di softirq molto importante è il timer (``include/linux/timer.h``):
potete programmarlo per far si che esegua funzioni dopo un determinato
periodo di tempo.
Dato che i softirq possono essere eseguiti simultaneamente su più di un
processore, spesso diventa estenuante l'averci a che fare. Per questa ragione,
i tasklet (``include/linux/interrupt.h``) vengo usati più di frequente:
possono essere registrati dinamicamente (il che significa che potete averne
quanti ne volete), e garantiscono che un qualsiasi tasklet verrà eseguito
solo su un processore alla volta, sebbene diversi tasklet possono essere
eseguiti simultaneamente.
.. warning::
Il nome 'tasklet' è ingannevole: non hanno niente a che fare
con i 'processi' ('tasks').
Potete determinate se siete in un softirq (o tasklet) utilizzando la
macro :c:func:`in_softirq()` (``include/linux/preempt.h``).
.. warning::
State attenti che questa macro ritornerà un falso positivo
se :ref:`bottom half lock <it_local_bh_disable>` è bloccato.
Alcune regole basilari
======================
Nessuna protezione della memoria
Se corrompete la memoria, che sia in contesto utente o d'interruzione,
la macchina si pianterà. Siete sicuri che quello che volete fare
non possa essere fatto nello spazio utente?
Nessun numero in virgola mobile o MMX
Il contesto della FPU non è salvato; anche se siete in contesto utente
lo stato dell'FPU probabilmente non corrisponde a quello del processo
corrente: vi incasinerete con lo stato di qualche altro processo. Se
volete davvero usare la virgola mobile, allora dovrete salvare e recuperare
lo stato dell'FPU (ed evitare cambi di contesto). Generalmente è una
cattiva idea; usate l'aritmetica a virgola fissa.
Un limite rigido dello stack
A seconda della configurazione del kernel lo stack è fra 3K e 6K per la
maggior parte delle architetture a 32-bit; è di 14K per la maggior
parte di quelle a 64-bit; e spesso è condiviso con le interruzioni,
per cui non si può usare.
Evitare profonde ricorsioni ad enormi array locali nello stack
(allocateli dinamicamente).
Il kernel Linux è portabile
Quindi mantenetelo tale. Il vostro codice dovrebbe essere a 64-bit ed
indipendente dall'ordine dei byte (endianess) di un processore. Inoltre,
dovreste minimizzare il codice specifico per un processore; per esempio
il codice assembly dovrebbe essere incapsulato in modo pulito e minimizzato
per facilitarne la migrazione. Generalmente questo codice dovrebbe essere
limitato alla parte di kernel specifica per un'architettura.
ioctl: non scrivere nuove chiamate di sistema
=============================================
Una chiamata di sistema, generalmente, è scritta così::
asmlinkage long sys_mycall(int arg)
{
return 0;
}
Primo, nella maggior parte dei casi non volete creare nuove chiamate di
sistema.
Create un dispositivo a caratteri ed implementate l'appropriata chiamata ioctl.
Questo meccanismo è molto più flessibile delle chiamate di sistema: esso non
dev'essere dichiarato in tutte le architetture nei file
``include/asm/unistd.h`` e ``arch/kernel/entry.S``; inoltre, è improbabile
che questo venga accettato da Linus.
Se tutto quello che il vostro codice fa è leggere o scrivere alcuni parametri,
considerate l'implementazione di un'interfaccia :c:func:`sysfs()`.
All'interno di una ioctl vi trovate nel contesto utente di un processo. Quando
avviene un errore dovete ritornare un valore negativo di errno (consultate
``include/uapi/asm-generic/errno-base.h``,
``include/uapi/asm-generic/errno.h`` e ``include/linux/errno.h``), altrimenti
ritornate 0.
Dopo aver dormito dovreste verificare se ci sono stati dei segnali: il modo
Unix/Linux di gestire un segnale è di uscire temporaneamente dalla chiamata
di sistema con l'errore ``-ERESTARTSYS``. La chiamata di sistema ritornerà
al contesto utente, eseguirà il gestore del segnale e poi la vostra chiamata
di sistema riprenderà (a meno che l'utente non l'abbia disabilitata). Quindi,
dovreste essere pronti per continuare l'esecuzione, per esempio nel mezzo
della manipolazione di una struttura dati.
::
if (signal_pending(current))
return -ERESTARTSYS;
Se dovete eseguire dei calcoli molto lunghi: pensate allo spazio utente.
Se **davvero** volete farlo nel kernel ricordatevi di verificare periodicamente
se dovete *lasciare* il processore (ricordatevi che, per ogni processore, c'è
un sistema multi-processo senza diritto di prelazione).
Esempio::
cond_resched(); /* Will sleep */
Una breve nota sulla progettazione delle interfacce: il motto dei sistemi
UNIX è "fornite meccanismi e non politiche"
La ricetta per uno stallo
=========================
Non è permesso invocare una procedura che potrebbe dormire, fanno eccezione
i seguenti casi:
- Siete in un contesto utente.
- Non trattenete alcun spinlock.
- Avete abilitato le interruzioni (in realtà, Andy Kleen dice che
lo schedulatore le abiliterà per voi, ma probabilmente questo non è quello
che volete).
Da tener presente che alcune funzioni potrebbero dormire implicitamente:
le più comuni sono quelle per l'accesso allo spazio utente (\*_user) e
quelle per l'allocazione della memoria senza l'opzione ``GFP_ATOMIC``
Dovreste sempre compilare il kernel con l'opzione ``CONFIG_DEBUG_ATOMIC_SLEEP``
attiva, questa vi avviserà se infrangete una di queste regole.
Se **infrangete** le regole, allora potreste bloccare il vostro scatolotto.
Veramente.
Alcune delle procedure più comuni
=================================
:c:func:`printk()`
------------------
Definita in ``include/linux/printk.h``
:c:func:`printk()` fornisce messaggi alla console, dmesg, e al demone syslog.
Essa è utile per il debugging o per la notifica di errori; può essere
utilizzata anche all'interno del contesto d'interruzione, ma usatela con
cautela: una macchina che ha la propria console inondata da messaggi diventa
inutilizzabile. La funzione utilizza un formato stringa quasi compatibile con
la printf ANSI C, e la concatenazione di una stringa C come primo argomento
per indicare la "priorità"::
printk(KERN_INFO "i = %u\n", i);
Consultate ``include/linux/kern_levels.h`` per gli altri valori ``KERN_``;
questi sono interpretati da syslog come livelli. Un caso speciale:
per stampare un indirizzo IP usate::
__be32 ipaddress;
printk(KERN_INFO "my ip: %pI4\n", &ipaddress);
:c:func:`printk()` utilizza un buffer interno di 1K e non s'accorge di
eventuali sforamenti. Accertatevi che vi basti.
.. note::
Saprete di essere un vero hacker del kernel quando inizierete a digitare
nei vostri programmi utenti le printf come se fossero printk :)
.. note::
Un'altra nota a parte: la versione originale di Unix 6 aveva un commento
sopra alla funzione printf: "Printf non dovrebbe essere usata per il
chiacchiericcio". Dovreste seguire questo consiglio.
:c:func:`copy_to_user()` / :c:func:`copy_from_user()` / :c:func:`get_user()` / :c:func:`put_user()`
---------------------------------------------------------------------------------------------------
Definite in ``include/linux/uaccess.h`` / ``asm/uaccess.h``
**[DORMONO]**
:c:func:`put_user()` e :c:func:`get_user()` sono usate per ricevere ed
impostare singoli valori (come int, char, o long) da e verso lo spazio utente.
Un puntatore nello spazio utente non dovrebbe mai essere dereferenziato: i dati
dovrebbero essere copiati usando suddette procedure. Entrambe ritornano
``-EFAULT`` oppure 0.
:c:func:`copy_to_user()` e :c:func:`copy_from_user()` sono più generiche:
esse copiano una quantità arbitraria di dati da e verso lo spazio utente.
.. warning::
Al contrario di:c:func:`put_user()` e :c:func:`get_user()`, queste
funzioni ritornano la quantità di dati copiati (0 è comunque un successo).
[Sì, questa interfaccia mi imbarazza. La battaglia torna in auge anno
dopo anno. --RR]
Le funzioni potrebbero dormire implicitamente. Queste non dovrebbero mai essere
invocate fuori dal contesto utente (non ha senso), con le interruzioni
disabilitate, o con uno spinlock trattenuto.
:c:func:`kmalloc()`/:c:func:`kfree()`
-------------------------------------
Definite in ``include/linux/slab.h``
**[POTREBBERO DORMIRE: LEGGI SOTTO]**
Queste procedure sono utilizzate per la richiesta dinamica di un puntatore ad
un pezzo di memoria allineato, esattamente come malloc e free nello spazio
utente, ma :c:func:`kmalloc()` ha un argomento aggiuntivo per indicare alcune
opzioni. Le opzioni più importanti sono:
``GFP_KERNEL``
Potrebbe dormire per librarare della memoria. L'opzione fornisce il modo
più affidabile per allocare memoria, ma il suo uso è strettamente limitato
allo spazio utente.
``GFP_ATOMIC``
Non dorme. Meno affidabile di ``GFP_KERNEL``, ma può essere usata in un
contesto d'interruzione. Dovreste avere **davvero** una buona strategia
per la gestione degli errori in caso di mancanza di memoria.
``GFP_DMA``
Alloca memoria per il DMA sul bus ISA nello spazio d'indirizzamento
inferiore ai 16MB. Se non sapete cos'è allora non vi serve.
Molto inaffidabile.
Se vedete un messaggio d'avviso per una funzione dormiente che viene chiamata
da un contesto errato, allora probabilmente avete usato una funzione
d'allocazione dormiente da un contesto d'interruzione senza ``GFP_ATOMIC``.
Dovreste correggerlo. Sbrigatevi, non cincischiate.
Se allocate almeno ``PAGE_SIZE``(``asm/page.h`` o ``asm/page_types.h``) byte,
considerate l'uso di :c:func:`__get_free_pages()` (``include/linux/gfp.h``).
Accetta un argomento che definisce l'ordine (0 per per la dimensione di una
pagine, 1 per una doppia pagina, 2 per quattro pagine, eccetra) e le stesse
opzioni d'allocazione viste precedentemente.
Se state allocando un numero di byte notevolemnte superiore ad una pagina
potete usare :c:func:`vmalloc()`. Essa allocherà memoria virtuale all'interno
dello spazio kernel. Questo è un blocco di memoria fisica non contiguo, ma
la MMU vi darà l'impressione che lo sia (quindi, sarà contiguo solo dal punto
di vista dei processori, non dal punto di vista dei driver dei dispositivi
esterni).
Se per qualche strana ragione avete davvero bisogno di una grossa quantità di
memoria fisica contigua, avete un problema: Linux non ha un buon supporto per
questo caso d'uso perché, dopo un po' di tempo, la frammentazione della memoria
rende l'operazione difficile. Il modo migliore per allocare un simile blocco
all'inizio dell'avvio del sistema è attraverso la procedura
:c:func:`alloc_bootmem()`.
Prima di inventare la vostra cache per gli oggetti più usati, considerate
l'uso di una cache slab disponibile in ``include/linux/slab.h``.
:c:macro:`current`
-------------------
Definita in ``include/asm/current.h``
Questa variabile globale (in realtà una macro) contiene un puntatore alla
struttura del processo corrente, quindi è valido solo dal contesto utente.
Per esempio, quando un processo esegue una chiamata di sistema, questo
punterà alla struttura dati del processo chiamate.
Nel contesto d'interruzione in suo valore **non è NULL**.
:c:func:`mdelay()`/:c:func:`udelay()`
-------------------------------------
Definite in ``include/asm/delay.h`` / ``include/linux/delay.h``
Le funzioni :c:func:`udelay()` e :c:func:`ndelay()` possono essere utilizzate
per brevi pause. Non usate grandi valori perché rischiate d'avere un
overflow - in questo contesto la funzione :c:func:`mdelay()` è utile,
oppure considerate :c:func:`msleep()`.
:c:func:`cpu_to_be32()`/:c:func:`be32_to_cpu()`/:c:func:`cpu_to_le32()`/:c:func:`le32_to_cpu()`
-----------------------------------------------------------------------------------------------
Definite in ``include/asm/byteorder.h``
La famiglia di funzioni :c:func:`cpu_to_be32()` (dove "32" può essere
sostituito da 64 o 16, e "be" con "le") forniscono un modo generico
per fare conversioni sull'ordine dei byte (endianess): esse ritornano
il valore convertito. Tutte le varianti supportano anche il processo inverso:
:c:func:`be32_to_cpu()`, eccetera.
Queste funzioni hanno principalmente due varianti: la variante per
puntatori, come :c:func:`cpu_to_be32p()`, che prende un puntatore
ad un tipo, e ritorna il valore convertito. L'altra variante per
la famiglia di conversioni "in-situ", come :c:func:`cpu_to_be32s()`,
che convertono il valore puntato da un puntatore, e ritornano void.
:c:func:`local_irq_save()`/:c:func:`local_irq_restore()`
--------------------------------------------------------
Definite in ``include/linux/irqflags.h``
Queste funzioni abilitano e disabilitano le interruzioni hardware
sul processore locale. Entrambe sono rientranti; esse salvano lo stato
precedente nel proprio argomento ``unsigned long flags``. Se sapete
che le interruzioni sono abilite, potete semplicemente utilizzare
:c:func:`local_irq_disable()` e :c:func:`local_irq_enable()`.
.. _it_local_bh_disable:
:c:func:`local_bh_disable()`/:c:func:`local_bh_enable()`
--------------------------------------------------------
Definite in ``include/linux/bottom_half.h``
Queste funzioni abilitano e disabilitano le interruzioni software
sul processore locale. Entrambe sono rientranti; se le interruzioni
software erano già state disabilitate in precedenza, rimarranno
disabilitate anche dopo aver invocato questa coppia di funzioni.
Lo scopo è di prevenire l'esecuzione di softirq e tasklet sul processore
attuale.
:c:func:`smp_processor_id()`
----------------------------
Definita in ``include/linux/smp.h``
:c:func:`get_cpu()` nega il diritto di prelazione (quindi non potete essere
spostati su un altro processore all'improvviso) e ritorna il numero
del processore attuale, fra 0 e ``NR_CPUS``. Da notare che non è detto
che la numerazione dei processori sia continua. Quando avete terminato,
ritornate allo stato precedente con :c:func:`put_cpu()`.
Se sapete che non dovete essere interrotti da altri processi (per esempio,
se siete in un contesto d'interruzione, o il diritto di prelazione
è disabilitato) potete utilizzare smp_processor_id().
``__init``/``__exit``/``__initdata``
------------------------------------
Definite in ``include/linux/init.h``
Dopo l'avvio, il kernel libera una sezione speciale; le funzioni marcate
con ``__init`` e le strutture dati marcate con ``__initdata`` vengono
eliminate dopo il completamento dell'avvio: in modo simile i moduli eliminano
questa memoria dopo l'inizializzazione. ``__exit`` viene utilizzato per
dichiarare che una funzione verrà utilizzata solo in fase di rimozione:
la detta funzione verrà eliminata quando il file che la contiene non è
compilato come modulo. Guardate l'header file per informazioni. Da notare che
non ha senso avere una funzione marcata come ``__init`` e al tempo stesso
esportata ai moduli utilizzando :c:func:`EXPORT_SYMBOL()` o
:c:func:`EXPORT_SYMBOL_GPL()` - non funzionerà.
:c:func:`__initcall()`/:c:func:`module_init()`
----------------------------------------------
Definite in ``include/linux/init.h`` / ``include/linux/module.h``
Molte parti del kernel funzionano bene come moduli (componenti del kernel
caricabili dinamicamente). L'utilizzo delle macro :c:func:`module_init()`
e :c:func:`module_exit()` semplifica la scrittura di codice che può funzionare
sia come modulo, sia come parte del kernel, senza l'ausilio di #ifdef.
La macro :c:func:`module_init()` definisce quale funzione dev'essere
chiamata quando il modulo viene inserito (se il file è stato compilato come
tale), o in fase di avvio : se il file non è stato compilato come modulo la
macro :c:func:`module_init()` diventa equivalente a :c:func:`__initcall()`,
la quale, tramite qualche magia del linker, s'assicura che la funzione venga
chiamata durante l'avvio.
La funzione può ritornare un numero d'errore negativo per scatenare un
fallimento del caricamento (sfortunatamente, questo non ha effetto se il
modulo è compilato come parte integrante del kernel). Questa funzione è chiamata
in contesto utente con le interruzioni abilitate, quindi potrebbe dormire.
:c:func:`module_exit()`
-----------------------
Definita in ``include/linux/module.h``
Questa macro definisce la funzione che dev'essere chiamata al momento della
rimozione (o mai, nel caso in cui il file sia parte integrante del kernel).
Essa verrà chiamata solo quando il contatore d'uso del modulo raggiunge lo
zero. Questa funzione può anche dormire, ma non può fallire: tutto dev'essere
ripulito prima che la funzione ritorni.
Da notare che questa macro è opzionale: se non presente, il modulo non sarà
removibile (a meno che non usiate 'rmmod -f' ).
:c:func:`try_module_get()`/:c:func:`module_put()`
-------------------------------------------------
Definite in ``include/linux/module.h``
Queste funzioni maneggiano il contatore d'uso del modulo per proteggerlo dalla
rimozione (in aggiunta, un modulo non può essere rimosso se un altro modulo
utilizzo uno dei sui simboli esportati: vedere di seguito). Prima di eseguire
codice del modulo, dovreste chiamare :c:func:`try_module_get()` su quel modulo:
se fallisce significa che il modulo è stato rimosso e dovete agire come se
non fosse presente. Altrimenti, potete accedere al modulo in sicurezza, e
chiamare :c:func:`module_put()` quando avete finito.
La maggior parte delle strutture registrabili hanno un campo owner
(proprietario), come nella struttura
:c:type:`struct file_operations <file_operations>`.
Impostate questo campo al valore della macro ``THIS_MODULE``.
Code d'attesa ``include/linux/wait.h``
======================================
**[DORMONO]**
Una coda d'attesa è usata per aspettare che qualcuno vi attivi quando una
certa condizione s'avvera. Per evitare corse critiche, devono essere usate
con cautela. Dichiarate una :c:type:`wait_queue_head_t`, e poi i processi
che vogliono attendere il verificarsi di quella condizione dichiareranno
una :c:type:`wait_queue_entry_t` facendo riferimento a loro stessi, poi
metteranno questa in coda.
Dichiarazione
-------------
Potere dichiarare una ``wait_queue_head_t`` utilizzando la macro
:c:func:`DECLARE_WAIT_QUEUE_HEAD()` oppure utilizzando la procedura
:c:func:`init_waitqueue_head()` nel vostro codice d'inizializzazione.
Accodamento
-----------
Mettersi in una coda d'attesa è piuttosto complesso, perché dovete
mettervi in coda prima di verificare la condizione. Esiste una macro
a questo scopo: :c:func:`wait_event_interruptible()` (``include/linux/wait.h``).
Il primo argomento è la testa della coda d'attesa, e il secondo è
un'espressione che dev'essere valutata; la macro ritorna 0 quando questa
espressione è vera, altrimenti ``-ERESTARTSYS`` se è stato ricevuto un segnale.
La versione :c:func:`wait_event()` ignora i segnali.
Svegliare una procedura in coda
-------------------------------
Chiamate :c:func:`wake_up()` (``include/linux/wait.h``); questa attiverà tutti
i processi in coda. Ad eccezione se uno di questi è impostato come
``TASK_EXCLUSIVE``, in questo caso i rimanenti non verranno svegliati.
Nello stesso header file esistono altre varianti di questa funzione.
Operazioni atomiche
===================
Certe operazioni sono garantite come atomiche su tutte le piattaforme.
Il primo gruppo di operazioni utilizza :c:type:`atomic_t`
(``include/asm/atomic.h``); questo contiene un intero con segno (minimo 32bit),
e dovete utilizzare queste funzione per modificare o leggere variabili di tipo
:c:type:`atomic_t`. :c:func:`atomic_read()` e :c:func:`atomic_set()` leggono ed
impostano il contatore, :c:func:`atomic_add()`, :c:func:`atomic_sub()`,
:c:func:`atomic_inc()`, :c:func:`atomic_dec()`, e
:c:func:`atomic_dec_and_test()` (ritorna vero se raggiunge zero dopo essere
stata decrementata).
Sì. Ritorna vero (ovvero != 0) se la variabile atomica è zero.
Da notare che queste funzioni sono più lente rispetto alla normale aritmetica,
e quindi non dovrebbero essere usate a sproposito.
Il secondo gruppo di operazioni atomiche sono definite in
``include/linux/bitops.h`` ed agiscono sui bit d'una variabile di tipo
``unsigned long``. Queste operazioni prendono come argomento un puntatore
alla variabile, e un numero di bit dove 0 è quello meno significativo.
:c:func:`set_bit()`, :c:func:`clear_bit()` e :c:func:`change_bit()`
impostano, cancellano, ed invertono il bit indicato.
:c:func:`test_and_set_bit()`, :c:func:`test_and_clear_bit()` e
:c:func:`test_and_change_bit()` fanno la stessa cosa, ad eccezione che
ritornano vero se il bit era impostato; queste sono particolarmente
utili quando si vuole impostare atomicamente dei flag.
Con queste operazioni è possibile utilizzare indici di bit che eccedono
il valore ``BITS_PER_LONG``. Il comportamento è strano sulle piattaforme
big-endian quindi è meglio evitarlo.
Simboli
=======
All'interno del kernel, si seguono le normali regole del linker (ovvero,
a meno che un simbolo non venga dichiarato con visibilita limitata ad un
file con la parola chiave ``static``, esso può essere utilizzato in qualsiasi
parte del kernel). Nonostante ciò, per i moduli, esiste una tabella dei
simboli esportati che limita i punti di accesso al kernel. Anche i moduli
possono esportare simboli.
:c:func:`EXPORT_SYMBOL()`
-------------------------
Definita in ``include/linux/export.h``
Questo è il classico metodo per esportare un simbolo: i moduli caricati
dinamicamente potranno utilizzare normalmente il simbolo.
:c:func:`EXPORT_SYMBOL_GPL()`
-----------------------------
Definita in ``include/linux/export.h``
Essa è simile a :c:func:`EXPORT_SYMBOL()` ad eccezione del fatto che i
simboli esportati con :c:func:`EXPORT_SYMBOL_GPL()` possono essere
utilizzati solo dai moduli che hanno dichiarato una licenza compatibile
con la GPL attraverso :c:func:`MODULE_LICENSE()`. Questo implica che la
funzione esportata è considerata interna, e non una vera e propria interfaccia.
Alcuni manutentori e sviluppatori potrebbero comunque richiedere
:c:func:`EXPORT_SYMBOL_GPL()` quando si aggiungono nuove funzionalità o
interfacce.
:c:func:`EXPORT_SYMBOL_NS()`
----------------------------
Definita in ``include/linux/export.h``
Questa è una variate di `EXPORT_SYMBOL()` che permette di specificare uno
spazio dei nomi. Lo spazio dei nomi è documentato in
Documentation/translations/it_IT/core-api/symbol-namespaces.rst.
:c:func:`EXPORT_SYMBOL_NS_GPL()`
--------------------------------
Definita in ``include/linux/export.h``
Questa è una variate di `EXPORT_SYMBOL_GPL()` che permette di specificare uno
spazio dei nomi. Lo spazio dei nomi è documentato in
Documentation/translations/it_IT/core-api/symbol-namespaces.rst.
Procedure e convenzioni
=======================
Liste doppiamente concatenate ``include/linux/list.h``
------------------------------------------------------
Un tempo negli header del kernel c'erano tre gruppi di funzioni per
le liste concatenate, ma questa è stata la vincente. Se non avete particolari
necessità per una semplice lista concatenata, allora questa è una buona scelta.
In particolare, :c:func:`list_for_each_entry()` è utile.
Convenzione dei valori di ritorno
---------------------------------
Per codice chiamato in contesto utente, è molto comune sfidare le convenzioni
C e ritornare 0 in caso di successo, ed un codice di errore negativo
(eg. ``-EFAULT``) nei casi fallimentari. Questo potrebbe essere controintuitivo
a prima vista, ma è abbastanza diffuso nel kernel.
Utilizzate :c:func:`ERR_PTR()` (``include/linux/err.h``) per codificare
un numero d'errore negativo in un puntatore, e :c:func:`IS_ERR()` e
:c:func:`PTR_ERR()` per recuperarlo di nuovo: così si evita d'avere un
puntatore dedicato per il numero d'errore. Da brividi, ma in senso positivo.
Rompere la compilazione
-----------------------
Linus e gli altri sviluppatori a volte cambiano i nomi delle funzioni e
delle strutture nei kernel in sviluppo; questo non è solo per tenere
tutti sulle spine: questo riflette cambiamenti fondamentati (eg. la funzione
non può più essere chiamata con le funzioni attive, o fa controlli aggiuntivi,
o non fa più controlli che venivano fatti in precedenza). Solitamente a questo
s'accompagna un'adeguata e completa nota sulla lista di discussone
più adatta; cercate negli archivi. Solitamente eseguire una semplice
sostituzione su tutto un file rendere le cose **peggiori**.
Inizializzazione dei campi d'una struttura
------------------------------------------
Il metodo preferito per l'inizializzazione delle strutture è quello
di utilizzare gli inizializzatori designati, come definiti nello
standard ISO C99, eg::
static struct block_device_operations opt_fops = {
.open = opt_open,
.release = opt_release,
.ioctl = opt_ioctl,
.check_media_change = opt_media_change,
};
Questo rende più facile la ricerca con grep, e rende più chiaro quale campo
viene impostato. Dovreste fare così perché si mostra meglio.
Estensioni GNU
--------------
Le estensioni GNU sono esplicitamente permesse nel kernel Linux. Da notare
che alcune delle più complesse non sono ben supportate, per via dello scarso
sviluppo, ma le seguenti sono da considerarsi la norma (per maggiori dettagli,
leggete la sezione "C Extensions" nella pagina info di GCC - Sì, davvero
la pagina info, la pagina man è solo un breve riassunto delle cose nella
pagina info).
- Funzioni inline
- Istruzioni in espressioni (ie. il costrutto ({ and }) ).
- Dichiarate attributi di una funzione / variabile / tipo
(__attribute__)
- typeof
- Array con lunghezza zero
- Macro varargs
- Aritmentica sui puntatori void
- Inizializzatori non costanti
- Istruzioni assembler (non al di fuori di 'arch/' e 'include/asm/')
- Nomi delle funzioni come stringhe (__func__).
- __builtin_constant_p()
Siate sospettosi quando utilizzate long long nel kernel, il codice generato
da gcc è orribile ed anche peggio: le divisioni e le moltiplicazioni non
funzionano sulle piattaforme i386 perché le rispettive funzioni di runtime
di GCC non sono incluse nell'ambiente del kernel.
C++
---
Solitamente utilizzare il C++ nel kernel è una cattiva idea perché
il kernel non fornisce il necessario ambiente di runtime e gli header file
non sono stati verificati. Rimane comunque possibile, ma non consigliato.
Se davvero volete usarlo, almeno evitate le eccezioni.
NUMif
-----
Viene generalmente considerato più pulito l'uso delle macro negli header file
(o all'inizio dei file .c) per astrarre funzioni piuttosto che utlizzare
l'istruzione di pre-processore \`#if' all'interno del codice sorgente.
Mettere le vostre cose nel kernel
=================================
Al fine d'avere le vostre cose in ordine per l'inclusione ufficiale, o
anche per avere patch pulite, c'è del lavoro amministrativo da fare:
- Trovare chi è responsabile del codice che state modificando. Guardare in cima
ai file sorgenti, all'interno del file ``MAINTAINERS``, ed alla fine
di tutti nel file ``CREDITS``. Dovreste coordinarvi con queste persone
per evitare di duplicare gli sforzi, o provare qualcosa che è già stato
rigettato.
Assicuratevi di mettere il vostro nome ed indirizzo email in cima a
tutti i file che create o che maneggiate significativamente. Questo è
il primo posto dove le persone guarderanno quando troveranno un baco,
o quando **loro** vorranno fare una modifica.
- Solitamente vorrete un'opzione di configurazione per la vostra modifica
al kernel. Modificate ``Kconfig`` nella cartella giusta. Il linguaggio
Config è facile con copia ed incolla, e c'è una completa documentazione
nel file ``Documentation/kbuild/kconfig-language.rst``.
Nella descrizione della vostra opzione, assicuratevi di parlare sia agli
utenti esperti sia agli utente che non sanno nulla del vostro lavoro.
Menzionate qui le incompatibilità ed i problemi. Chiaramente la
descrizione deve terminare con “if in doubt, say N” (se siete in dubbio,
dite N) (oppure, occasionalmente, \`Y'); questo è per le persone che non
hanno idea di che cosa voi stiate parlando.
- Modificate il file ``Makefile``: le variabili CONFIG sono esportate qui,
quindi potete solitamente aggiungere una riga come la seguete
"obj-$(CONFIG_xxx) += xxx.o". La sintassi è documentata nel file
``Documentation/kbuild/makefiles.rst``.
- Aggiungete voi stessi in ``CREDITS`` se credete di aver fatto qualcosa di
notevole, solitamente qualcosa che supera il singolo file (comunque il vostro
nome dovrebbe essere all'inizio dei file sorgenti). ``MAINTAINERS`` significa
che volete essere consultati quando vengono fatte delle modifiche ad un
sottosistema, e quando ci sono dei bachi; questo implica molto di più di un
semplice impegno su una parte del codice.
- Infine, non dimenticatevi di leggere
``Documentation/process/submitting-patches.rst``.
Trucchetti del kernel
=====================
Dopo una rapida occhiata al codice, questi sono i preferiti. Sentitevi liberi
di aggiungerne altri.
``arch/x86/include/asm/delay.h``::
#define ndelay(n) (__builtin_constant_p(n) ? \
((n) > 20000 ? __bad_ndelay() : __const_udelay((n) * 5ul)) : \
__ndelay(n))
``include/linux/fs.h``::
/*
* Kernel pointers have redundant information, so we can use a
* scheme where we can return either an error code or a dentry
* pointer with the same return value.
*
* This should be a per-architecture thing, to allow different
* error and pointer decisions.
*/
#define ERR_PTR(err) ((void *)((long)(err)))
#define PTR_ERR(ptr) ((long)(ptr))
#define IS_ERR(ptr) ((unsigned long)(ptr) > (unsigned long)(-1000))
``arch/x86/include/asm/uaccess_32.h:``::
#define copy_to_user(to,from,n) \
(__builtin_constant_p(n) ? \
__constant_copy_to_user((to),(from),(n)) : \
__generic_copy_to_user((to),(from),(n)))
``arch/sparc/kernel/head.S:``::
/*
* Sun people can't spell worth damn. "compatability" indeed.
* At least we *know* we can't spell, and use a spell-checker.
*/
/* Uh, actually Linus it is I who cannot spell. Too much murky
* Sparc assembly will do this to ya.
*/
C_LABEL(cputypvar):
.asciz "compatibility"
/* Tested on SS-5, SS-10. Probably someone at Sun applied a spell-checker. */
.align 4
C_LABEL(cputypvar_sun4m):
.asciz "compatible"
``arch/sparc/lib/checksum.S:``::
/* Sun, you just can't beat me, you just can't. Stop trying,
* give up. I'm serious, I am going to kick the living shit
* out of you, game over, lights out.
*/
Ringraziamenti
==============
Ringrazio Andi Kleen per le sue idee, le risposte alle mie domande,
le correzioni dei miei errori, l'aggiunta di contenuti, eccetera.
Philipp Rumpf per l'ortografia e per aver reso più chiaro il testo, e
per alcuni eccellenti punti tutt'altro che ovvi. Werner Almesberger
per avermi fornito un ottimo riassunto di :c:func:`disable_irq()`,
e Jes Sorensen e Andrea Arcangeli per le precisazioni. Michael Elizabeth
Chastain per aver verificato ed aggiunto la sezione configurazione.
Telsa Gwynne per avermi insegnato DocBook.
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
번역 정보와 안내서의 목적
1-31이 이탈리아어 번역은 공통 면책 고지 `../disclaimer-ita.rst`를 포함하며, 공식 영어 원문 `Documentation/kernel-hacking/hacking.rst`의 `kernel_hacking_hack` 참조를 명시합니다. 번역자는 Federico Vaga이고 원 저자는 Rusty Russell입니다.
`_it_kernel_hacking_hack`은 이탈리아어 페이지의 앵커입니다. 제목은 ‘신뢰하기 어려운 Linux 커널 해킹 안내서’로, 표현은 유머러스하지만 내용은 커널 코드 작성에 필요한 일반 절차와 요구 사항을 설명합니다.
대상 독자는 경험 있는 C 프로그래머입니다. 목표는 Linux 커널 개발의 입문서를 제공하는 것이며, 세부 구현은 실제 소스 코드에 맡기고 유용한 절차 전체를 망라하려 하지는 않습니다.
저자는 자신이 충분한 자격을 갖추지 못했다고 농담하면서도 이런 문서를 읽고 싶었기 때문에 직접 썼다고 설명합니다. 문서가 모범 사례, 공통 출발점, 여러 실용 정보를 모은 개요서로 성장하기를 바랍니다.
번역본과 공식 원문의 관계 및 작성자를 정리합니다.
경험 있는 C 개발자가 구현 세부로 들어가기 전에 공통 원칙을 익히도록 구성됩니다.
.. include:: ../disclaimer-ita.rst
.. note:: Per leggere la documentazione originale in inglese:
:ref:`Documentation/kernel-hacking/hacking.rst <kernel_hacking_hack>`
:Original: :ref:`Documentation/kernel-hacking/hacking.rst <kernel_hacking_hack>`
:Translator: Federico Vaga <[email protected]>
.. _it_kernel_hacking_hack:
=================================================
L'inaffidabile guida all'hacking del kernel Linux
=================================================
:Author: Rusty Russell
Introduzione
============
Benvenuto, gentile lettore, alla notevole ed inaffidabile guida all'hacking
del kernel Linux ad opera di Rusty. Questo documento descrive le procedure
più usate ed i concetti necessari per scrivere codice per il kernel: lo scopo
è di fornire ai programmatori C più esperti un manuale di base per sviluppo.
Eviterò dettagli implementativi: per questo abbiamo il codice,
ed ignorerò intere parti di alcune procedure.
Prima di leggere questa guida, sappiate che non ho mai voluto scriverla,
essendo esageratamente sotto qualificato, ma ho sempre voluto leggere
qualcosa di simile, e quindi questa era l'unica via. Spero che possa
crescere e diventare un compendio di buone pratiche, punti di partenza
e generiche informazioni.
CPU 실행 컨텍스트와 선점 계층
32-141어느 순간이든 시스템의 각 CPU는 네 상태 중 하나에 있을 수 있습니다. 프로세스와 무관하게 하드웨어 인터럽트를 처리하거나, 프로세스와 무관하게 softirq 또는 tasklet을 처리하거나, 프로세스에 연결된 채 커널 공간에서 실행하거나, 사용자 공간 프로세스를 실행합니다.
이 상태에는 선점 계층이 있습니다. 사용자 공간 실행과 프로세스에 연결된 커널 실행은 서로 전환될 수 있지만, 그 위에서는 아래 단계가 위 단계에 의해서만 선점되는 엄격한 순서가 적용됩니다.
예를 들어 한 CPU에서 softirq가 실행되는 동안 다른 softirq가 그것을 선점하지는 않지만 하드웨어 인터럽트는 선점할 수 있습니다. 다른 CPU들은 이 CPU와 독립적으로 실행되므로 SMP 동시성은 별도로 고려해야 합니다.
사용자 컨텍스트는 시스템 호출이나 다른 트랩에서 커널로 들어온 상태입니다. 더 중요한 작업이나 인터럽트에 선점될 수 있고, `schedule()`을 호출해 잠들 수 있습니다.
모듈을 로드하거나 제거할 때와 블록 장치 계층에서 작업할 때는 항상 사용자 컨텍스트입니다. 이 컨텍스트에서는 현재 실행 프로세스를 가리키는 `current`가 유효하고 `in_interrupt()`는 거짓입니다.
다만 선점이나 softirq를 비활성화한 상태에서는 `in_interrupt()`가 거짓 양성으로 참을 반환할 수 있으므로, 이 값 하나만으로 호출 가능 문맥을 단정해서는 안 됩니다.
타이머, 네트워크 카드, 키보드 같은 실제 하드웨어는 언제든 인터럽트를 만들 수 있습니다. 커널은 하드웨어를 서비스하는 인터럽트 핸들러를 실행하며, 같은 인터럽트가 다시 오면 큐에 넣거나 버려서 동일 핸들러가 스스로 선점되지 않도록 보장합니다.
핸들러 실행 중에는 인터럽트가 비활성화되므로 핸들러는 빨라야 합니다. 보통 인터럽트를 인수했다는 사실만 기록하고 소프트웨어 인터럽트 처리를 예약한 뒤 즉시 끝냅니다.
하드웨어 인터럽트 컨텍스트는 `in_hardirq()`가 참인지로 확인할 수 있습니다. 그러나 인터럽트를 수동으로 비활성화했을 때도 거짓 양성이 생길 수 있다는 경고가 붙습니다.
시스템 호출이 사용자 공간으로 돌아가기 직전이나 인터럽트 핸들러가 끝날 때, 대기 중으로 표시된 소프트웨어 인터럽트가 `kernel/softirq.c`에서 실행됩니다. 실제 인터럽트 처리 작업의 많은 부분이 여기서 이루어집니다.
과거의 bottom half(BH)는 멀티프로세서 장점을 활용하지 못했습니다. 이후 softirq로 옮겨 갔으며, 종류는 `include/linux/interrupt.h`에 정의됩니다.
중요한 softirq 종류 중 하나는 `include/linux/timer.h`의 타이머입니다. 일정 시간이 지난 뒤 함수를 실행하도록 예약할 수 있습니다.
softirq는 여러 프로세서에서 동시에 실행될 수 있어 다루기 어렵습니다. 그래서 동적으로 등록할 수 있고 동일 tasklet 하나가 한 번에 한 CPU에서만 실행되도록 보장하는 tasklet을 흔히 사용합니다. 서로 다른 tasklet은 동시에 실행될 수 있습니다.
`tasklet`이라는 이름은 misleading하며 프로세스인 task와는 관련이 없습니다. `in_softirq()`로 softirq 또는 tasklet 컨텍스트를 확인할 수 있지만, `local_bh_disable()`로 bottom half를 막았을 때는 거짓 양성이 생길 수 있습니다.
아래쪽 상태는 위쪽 상태에 의해 선점될 수 있으며 다른 CPU는 독립적으로 동작합니다.
상태 비활성화 때문에 참이 되면 실제 IRQ 실행과 구분해야 합니다.
프로세스에 연결된 사용자 컨텍스트에서만 일반적인 수면 호출을 사용할 수 있습니다.
핸들러는 최소 작업만 하고 나머지를 softirq 또는 tasklet로 넘깁니다.
한 tasklet 인스턴스는 직렬화되지만 서로 다른 tasklet은 병렬 실행될 수 있습니다.
Gli attori
==========
In qualsiasi momento ognuna delle CPU di un sistema può essere:
- non associata ad alcun processo, servendo un'interruzione hardware;
- non associata ad alcun processo, servendo un softirq o tasklet;
- in esecuzione nello spazio kernel, associata ad un processo
(contesto utente);
- in esecuzione di un processo nello spazio utente;
Esiste un ordine fra questi casi. Gli ultimi due possono avvicendarsi (preempt)
l'un l'altro, ma a parte questo esiste una gerarchia rigida: ognuno di questi
può avvicendarsi solo ad uno di quelli sottostanti. Per esempio, mentre un
softirq è in esecuzione su d'una CPU, nessun altro softirq può avvicendarsi
nell'esecuzione, ma un'interruzione hardware può. Ciò nonostante, le altre CPU
del sistema operano indipendentemente.
Più avanti vedremo alcuni modi in cui dal contesto utente è possibile bloccare
le interruzioni, così da impedirne davvero il diritto di prelazione.
Contesto utente
---------------
Ci si trova nel contesto utente quando si arriva da una chiamata di sistema
od altre eccezioni: come nello spazio utente, altre procedure più importanti,
o le interruzioni, possono far valere il proprio diritto di prelazione sul
vostro processo. Potete sospendere l'esecuzione chiamando :c:func:`schedule()`.
.. note::
Si è sempre in contesto utente quando un modulo viene caricato o rimosso,
e durante le operazioni nello strato dei dispositivi a blocchi
(*block layer*).
Nel contesto utente, il puntatore ``current`` (il quale indica il processo al
momento in esecuzione) è valido, e :c:func:`in_interrupt()`
(``include/linux/preempt.h``) è falsa.
.. warning::
Attenzione che se avete la prelazione o i softirq disabilitati (vedere
di seguito), :c:func:`in_interrupt()` ritornerà un falso positivo.
Interruzioni hardware (Hard IRQs)
---------------------------------
Temporizzatori, schede di rete e tastiere sono esempi di vero hardware
che possono produrre interruzioni in un qualsiasi momento. Il kernel esegue
i gestori d'interruzione che prestano un servizio all'hardware. Il kernel
garantisce che questi gestori non vengano mai interrotti: se una stessa
interruzione arriva, questa verrà accodata (o scartata).
Dato che durante la loro esecuzione le interruzioni vengono disabilitate,
i gestori d'interruzioni devono essere veloci: spesso si limitano
esclusivamente a notificare la presa in carico dell'interruzione,
programmare una 'interruzione software' per l'esecuzione e quindi terminare.
Potete dire d'essere in una interruzione hardware perché in_hardirq()
ritorna vero.
.. warning::
Attenzione, questa ritornerà un falso positivo se le interruzioni
sono disabilitate (vedere di seguito).
Contesto d'interruzione software: softirq e tasklet
---------------------------------------------------
Quando una chiamata di sistema sta per tornare allo spazio utente,
oppure un gestore d'interruzioni termina, qualsiasi 'interruzione software'
marcata come pendente (solitamente da un'interruzione hardware) viene
eseguita (``kernel/softirq.c``).
La maggior parte del lavoro utile alla gestione di un'interruzione avviene qui.
All'inizio della transizione ai sistemi multiprocessore, c'erano solo i
cosiddetti 'bottom half' (BH), i quali non traevano alcun vantaggio da questi
sistemi. Non appena abbandonammo i computer raffazzonati con fiammiferi e
cicche, abbandonammo anche questa limitazione e migrammo alle interruzioni
software 'softirqs'.
Il file ``include/linux/interrupt.h`` elenca i differenti tipi di 'softirq'.
Un tipo di softirq molto importante è il timer (``include/linux/timer.h``):
potete programmarlo per far si che esegua funzioni dopo un determinato
periodo di tempo.
Dato che i softirq possono essere eseguiti simultaneamente su più di un
processore, spesso diventa estenuante l'averci a che fare. Per questa ragione,
i tasklet (``include/linux/interrupt.h``) vengo usati più di frequente:
possono essere registrati dinamicamente (il che significa che potete averne
quanti ne volete), e garantiscono che un qualsiasi tasklet verrà eseguito
solo su un processore alla volta, sebbene diversi tasklet possono essere
eseguiti simultaneamente.
.. warning::
Il nome 'tasklet' è ingannevole: non hanno niente a che fare
con i 'processi' ('tasks').
Potete determinate se siete in un softirq (o tasklet) utilizzando la
macro :c:func:`in_softirq()` (``include/linux/preempt.h``).
.. warning::
State attenti che questa macro ritornerà un falso positivo
se :ref:`bottom half lock <it_local_bh_disable>` è bloccato.
기본 제약과 ioctl 인터페이스
142-224커널에는 사용자 공간과 같은 메모리 보호가 없습니다. 사용자 컨텍스트든 인터럽트 컨텍스트든 메모리를 손상시키면 시스템 전체가 멈출 수 있으므로, 작업을 사용자 공간에서 수행할 수 없는지 먼저 검토해야 합니다.
FPU 컨텍스트는 저장되지 않으며 현재 FPU 상태가 현재 프로세스의 것이라고 보장할 수 없습니다. 부동소수점이나 MMX를 함부로 사용하면 다른 프로세스 상태를 망칠 수 있으므로 일반적으로 고정소수점 연산을 사용합니다.
정말 부동소수점이 필요하면 FPU 상태 저장·복원과 컨텍스트 전환 방지를 직접 책임져야 하므로 좋은 선택이 아닙니다.
커널 스택은 엄격히 제한됩니다. 원문은 구성에 따라 대부분의 32비트 아키텍처에서 3K~6K, 대부분의 64비트 아키텍처에서 14K라고 설명하며 인터럽트와 공유될 수도 있다고 경고합니다.
깊은 재귀와 큰 지역 배열은 피하고 필요한 메모리는 동적으로 할당해야 합니다. 스택에 여유가 있다고 가정하는 설계는 이식성과 안정성을 해칩니다.
Linux 커널은 이식 가능해야 합니다. 코드는 64비트와 바이트 순서에 독립적이어야 하고, 프로세서별 코드와 어셈블리는 최소화해 깔끔하게 감싸며 보통 아키텍처 전용 영역에 둡니다.
원문은 일반적인 시스템 호출 골격으로 `asmlinkage long sys_mycall(int arg)` 예제를 제시하지만, 대부분의 경우 새 시스템 호출을 만들지 말라고 권합니다.
Una chiamata di sistema, generalmente, è scritta così::
asmlinkage long sys_mycall(int arg)
{
return 0;
}
대신 문자 장치를 만들고 적절한 `ioctl`을 구현하는 편이 더 유연합니다. 새 시스템 호출은 모든 아키텍처의 `include/asm/unistd.h`와 `arch/kernel/entry.S`에 선언해야 하며 상류 수용 가능성도 낮습니다.
코드가 몇 개의 매개변수를 읽거나 쓰기만 한다면 `sysfs()` 인터페이스도 고려합니다.
ioctl 안에서는 프로세스의 사용자 컨텍스트에 있습니다. 오류 시 `include/uapi/asm-generic/errno-base.h`, `include/uapi/asm-generic/errno.h`, `include/linux/errno.h`에 정의된 음수 errno를 반환하고 성공 시 0을 반환합니다.
잠든 뒤에는 대기 중 신호를 확인해야 합니다. Unix/Linux 방식은 `-ERESTARTSYS`로 시스템 호출을 잠시 빠져나가 사용자 공간의 신호 핸들러를 실행한 다음, 사용자가 재시작을 막지 않았다면 호출을 다시 시작하는 것입니다.
따라서 자료구조 조작 중간에서도 안전하게 재개할 수 있도록 상태를 설계해야 합니다. 원문은 `signal_pending(current)`가 참이면 `-ERESTARTSYS`를 반환하는 예를 제공합니다.
::
if (signal_pending(current))
return -ERESTARTSYS;
아주 긴 계산은 사용자 공간에서 하는 것이 좋습니다. 정말 커널에서 해야 한다면 CPU를 양보해야 하는지 주기적으로 확인하고 `cond_resched()` 같은 지점에서 잠들 수 있게 해야 합니다.
Esempio::
cond_resched(); /* Will sleep */
인터페이스 설계의 짧은 원칙은 UNIX의 표어인 ‘정책이 아니라 메커니즘을 제공하라’입니다. 커널 인터페이스는 특정 사용자 공간 정책을 강제하기보다 필요한 기본 기능을 제공해야 합니다.
위반 시 시스템 전체에 영향을 주는 핵심 제한입니다.
사용자 컨텍스트 인터페이스의 대표 반환값입니다.
새 시스템 호출은 마지막 선택지이며 단순 설정은 더 작은 인터페이스로 표현합니다.
Alcune regole basilari
======================
Nessuna protezione della memoria
Se corrompete la memoria, che sia in contesto utente o d'interruzione,
la macchina si pianterà. Siete sicuri che quello che volete fare
non possa essere fatto nello spazio utente?
Nessun numero in virgola mobile o MMX
Il contesto della FPU non è salvato; anche se siete in contesto utente
lo stato dell'FPU probabilmente non corrisponde a quello del processo
corrente: vi incasinerete con lo stato di qualche altro processo. Se
volete davvero usare la virgola mobile, allora dovrete salvare e recuperare
lo stato dell'FPU (ed evitare cambi di contesto). Generalmente è una
cattiva idea; usate l'aritmetica a virgola fissa.
Un limite rigido dello stack
A seconda della configurazione del kernel lo stack è fra 3K e 6K per la
maggior parte delle architetture a 32-bit; è di 14K per la maggior
parte di quelle a 64-bit; e spesso è condiviso con le interruzioni,
per cui non si può usare.
Evitare profonde ricorsioni ad enormi array locali nello stack
(allocateli dinamicamente).
Il kernel Linux è portabile
Quindi mantenetelo tale. Il vostro codice dovrebbe essere a 64-bit ed
indipendente dall'ordine dei byte (endianess) di un processore. Inoltre,
dovreste minimizzare il codice specifico per un processore; per esempio
il codice assembly dovrebbe essere incapsulato in modo pulito e minimizzato
per facilitarne la migrazione. Generalmente questo codice dovrebbe essere
limitato alla parte di kernel specifica per un'architettura.
ioctl: non scrivere nuove chiamate di sistema
=============================================
Una chiamata di sistema, generalmente, è scritta così::
asmlinkage long sys_mycall(int arg)
{
return 0;
}
Primo, nella maggior parte dei casi non volete creare nuove chiamate di
sistema.
Create un dispositivo a caratteri ed implementate l'appropriata chiamata ioctl.
Questo meccanismo è molto più flessibile delle chiamate di sistema: esso non
dev'essere dichiarato in tutte le architetture nei file
``include/asm/unistd.h`` e ``arch/kernel/entry.S``; inoltre, è improbabile
che questo venga accettato da Linus.
Se tutto quello che il vostro codice fa è leggere o scrivere alcuni parametri,
considerate l'implementazione di un'interfaccia :c:func:`sysfs()`.
All'interno di una ioctl vi trovate nel contesto utente di un processo. Quando
avviene un errore dovete ritornare un valore negativo di errno (consultate
``include/uapi/asm-generic/errno-base.h``,
``include/uapi/asm-generic/errno.h`` e ``include/linux/errno.h``), altrimenti
ritornate 0.
Dopo aver dormito dovreste verificare se ci sono stati dei segnali: il modo
Unix/Linux di gestire un segnale è di uscire temporaneamente dalla chiamata
di sistema con l'errore ``-ERESTARTSYS``. La chiamata di sistema ritornerà
al contesto utente, eseguirà il gestore del segnale e poi la vostra chiamata
di sistema riprenderà (a meno che l'utente non l'abbia disabilitata). Quindi,
dovreste essere pronti per continuare l'esecuzione, per esempio nel mezzo
della manipolazione di una struttura dati.
::
if (signal_pending(current))
return -ERESTARTSYS;
Se dovete eseguire dei calcoli molto lunghi: pensate allo spazio utente.
Se **davvero** volete farlo nel kernel ricordatevi di verificare periodicamente
se dovete *lasciare* il processore (ricordatevi che, per ogni processore, c'è
un sistema multi-processo senza diritto di prelazione).
Esempio::
cond_resched(); /* Will sleep */
Una breve nota sulla progettazione delle interfacce: il motto dei sistemi
UNIX è "fornite meccanismi e non politiche"
수면 규칙, printk, 사용자 메모리 접근
225-316잠들 수 있는 루틴은 사용자 컨텍스트이고, spinlock을 보유하지 않았고, 인터럽트가 활성화된 경우에만 호출할 수 있습니다. 이 세 조건을 어기면 교착이나 시스템 정지가 발생할 수 있습니다.
일부 함수는 암묵적으로 잠듭니다. 대표적으로 `*_user` 사용자 공간 접근 함수와 `GFP_ATOMIC` 없이 수행하는 메모리 할당이 있습니다.
`CONFIG_DEBUG_ATOMIC_SLEEP`을 활성화해 잘못된 컨텍스트에서 잠드는 호출을 탐지해야 합니다. 원문은 이 규칙을 어기면 실제로 시스템을 멈출 수 있다고 거듭 강조합니다.
`printk()`는 `include/linux/printk.h`에 정의되며 콘솔, dmesg, syslog 데몬에 메시지를 제공합니다. 디버깅과 오류 알림에 유용하고 인터럽트 컨텍스트에서도 쓸 수 있지만, 콘솔을 메시지로 범람시키면 시스템이 사실상 사용할 수 없게 됩니다.
형식 문자열은 ANSI C `printf`와 거의 호환되며 첫 문자열에 `KERN_INFO` 같은 우선순위를 연결합니다. 다른 `KERN_` 값은 `include/linux/kern_levels.h`에 있고 syslog가 수준으로 해석합니다.
per indicare la "priorità"::
printk(KERN_INFO "i = %u\n", i);
Consultate ``include/linux/kern_levels.h`` per gli altri valori ``KERN_``;
questi sono interpretati da syslog come livelli. Un caso speciale:
per stampare un indirizzo IP usate::
__be32 ipaddress;
printk(KERN_INFO "my ip: %pI4\n", &ipaddress);
IPv4 주소는 `__be32` 값을 `%pI4`와 주소 포인터로 출력합니다. 원문은 `printk()` 내부 버퍼가 1K이고 넘침을 알아채지 못한다고 하므로 메시지 크기를 제한해야 한다고 경고합니다.
`put_user()`와 `get_user()`는 `include/linux/uaccess.h` 또는 `asm/uaccess.h`의 사용자 접근 함수이며 잠들 수 있습니다. int, char, long 같은 단일 값을 사용자 공간에서 읽거나 사용자 공간에 씁니다.
사용자 공간 포인터를 직접 역참조해서는 안 됩니다. 이 루틴을 통해 데이터를 복사해야 하며 `put_user()`와 `get_user()`는 `-EFAULT` 또는 0을 반환합니다.
`copy_to_user()`와 `copy_from_user()`는 임의 길이 데이터를 복사합니다. 단일 값 함수와 달리 복사하지 못한 데이터 양을 반환하므로 0이 성공입니다.
이 사용자 접근 함수는 암묵적으로 잠들 수 있으므로 사용자 컨텍스트 밖, 인터럽트 비활성 상태, spinlock 보유 상태에서 호출해서는 안 됩니다.
세 조건이 모두 충족되어야 수면 가능 루틴을 호출할 수 있습니다.
함수 집합마다 성공 판정 방식이 다릅니다.
printk도 사용할 수 있지만 핸들러에서는 빠른 기록과 지연 처리를 우선합니다.
La ricetta per uno stallo
=========================
Non è permesso invocare una procedura che potrebbe dormire, fanno eccezione
i seguenti casi:
- Siete in un contesto utente.
- Non trattenete alcun spinlock.
- Avete abilitato le interruzioni (in realtà, Andy Kleen dice che
lo schedulatore le abiliterà per voi, ma probabilmente questo non è quello
che volete).
Da tener presente che alcune funzioni potrebbero dormire implicitamente:
le più comuni sono quelle per l'accesso allo spazio utente (\*_user) e
quelle per l'allocazione della memoria senza l'opzione ``GFP_ATOMIC``
Dovreste sempre compilare il kernel con l'opzione ``CONFIG_DEBUG_ATOMIC_SLEEP``
attiva, questa vi avviserà se infrangete una di queste regole.
Se **infrangete** le regole, allora potreste bloccare il vostro scatolotto.
Veramente.
Alcune delle procedure più comuni
=================================
:c:func:`printk()`
------------------
Definita in ``include/linux/printk.h``
:c:func:`printk()` fornisce messaggi alla console, dmesg, e al demone syslog.
Essa è utile per il debugging o per la notifica di errori; può essere
utilizzata anche all'interno del contesto d'interruzione, ma usatela con
cautela: una macchina che ha la propria console inondata da messaggi diventa
inutilizzabile. La funzione utilizza un formato stringa quasi compatibile con
la printf ANSI C, e la concatenazione di una stringa C come primo argomento
per indicare la "priorità"::
printk(KERN_INFO "i = %u\n", i);
Consultate ``include/linux/kern_levels.h`` per gli altri valori ``KERN_``;
questi sono interpretati da syslog come livelli. Un caso speciale:
per stampare un indirizzo IP usate::
__be32 ipaddress;
printk(KERN_INFO "my ip: %pI4\n", &ipaddress);
:c:func:`printk()` utilizza un buffer interno di 1K e non s'accorge di
eventuali sforamenti. Accertatevi che vi basti.
.. note::
Saprete di essere un vero hacker del kernel quando inizierete a digitare
nei vostri programmi utenti le printf come se fossero printk :)
.. note::
Un'altra nota a parte: la versione originale di Unix 6 aveva un commento
sopra alla funzione printf: "Printf non dovrebbe essere usata per il
chiacchiericcio". Dovreste seguire questo consiglio.
:c:func:`copy_to_user()` / :c:func:`copy_from_user()` / :c:func:`get_user()` / :c:func:`put_user()`
---------------------------------------------------------------------------------------------------
Definite in ``include/linux/uaccess.h`` / ``asm/uaccess.h``
**[DORMONO]**
:c:func:`put_user()` e :c:func:`get_user()` sono usate per ricevere ed
impostare singoli valori (come int, char, o long) da e verso lo spazio utente.
Un puntatore nello spazio utente non dovrebbe mai essere dereferenziato: i dati
dovrebbero essere copiati usando suddette procedure. Entrambe ritornano
``-EFAULT`` oppure 0.
:c:func:`copy_to_user()` e :c:func:`copy_from_user()` sono più generiche:
esse copiano una quantità arbitraria di dati da e verso lo spazio utente.
.. warning::
Al contrario di:c:func:`put_user()` e :c:func:`get_user()`, queste
funzioni ritornano la quantità di dati copiati (0 è comunque un successo).
[Sì, questa interfaccia mi imbarazza. La battaglia torna in auge anno
dopo anno. --RR]
Le funzioni potrebbero dormire implicitamente. Queste non dovrebbero mai essere
invocate fuori dal contesto utente (non ha senso), con le interruzioni
disabilitate, o con uno spinlock trattenuto.
메모리 할당, 지연, 바이트 순서, IRQ와 CPU
317-450`kmalloc()`과 `kfree()`는 `include/linux/slab.h`에 정의되며 사용자 공간의 malloc/free처럼 정렬된 동적 메모리 블록을 다룹니다. `kmalloc()`에는 할당 동작을 정하는 추가 플래그가 있습니다.
`GFP_KERNEL`은 메모리를 확보하기 위해 잠들 수 있고 가장 신뢰도 높은 일반 할당이지만 사용자 컨텍스트에서만 사용할 수 있습니다.
`GFP_ATOMIC`은 잠들지 않아 인터럽트 컨텍스트에서 사용할 수 있지만 `GFP_KERNEL`보다 실패하기 쉽습니다. 메모리 부족 때의 확실한 오류 처리 전략이 필요합니다.
`GFP_DMA`는 ISA 버스 DMA용으로 16MB 아래 주소 공간에서 메모리를 할당하며 매우 신뢰하기 어렵습니다. 필요성을 정확히 아는 장치 코드에서만 사용합니다.
잘못된 컨텍스트의 수면 함수 경고가 나오면 인터럽트 컨텍스트에서 `GFP_ATOMIC` 없이 수면 가능한 할당을 호출했는지 확인하고 즉시 수정해야 합니다.
`PAGE_SIZE` 이상을 할당할 때는 `include/linux/gfp.h`의 `__get_free_pages()`를 고려합니다. order 0은 한 페이지, 1은 두 페이지, 2는 네 페이지처럼 2의 거듭제곱 페이지 수를 뜻하며 같은 할당 플래그를 받습니다.
한 페이지보다 훨씬 큰 메모리는 `vmalloc()`로 커널 가상 주소 공간에 할당할 수 있습니다. 물리적으로는 비연속이지만 MMU가 CPU에 연속으로 보이게 하므로 외부 장치 DMA 관점에서는 연속 메모리가 아닙니다.
큰 물리 연속 메모리는 시스템 가동 후 단편화 때문에 확보하기 어렵습니다. 원문은 부팅 초기에 `alloc_bootmem()`을 사용하는 방법을 언급하며, 이 오래된 API 이름도 로컬 원문 그대로 보존합니다.
자주 쓰는 객체를 위해 자체 캐시를 만들기 전에 `include/linux/slab.h`의 slab 캐시 사용을 고려합니다.
`current`는 `include/asm/current.h`에 정의된 매크로로 현재 프로세스 구조체를 가리킵니다. 사용자 컨텍스트에서만 의미가 유효하며 인터럽트 컨텍스트에서도 값 자체가 NULL이 되는 것은 아닙니다.
`udelay()`와 `ndelay()`는 짧은 지연에 사용합니다. 큰 값은 오버플로 위험이 있으므로 `mdelay()`를 쓰거나 잠들 수 있는 상황이면 `msleep()`을 고려합니다.
`cpu_to_be32()` 계열은 `include/asm/byteorder.h`에서 CPU 바이트 순서와 big-endian 또는 little-endian 값 사이를 변환합니다. 32 대신 16·64, be 대신 le를 쓰는 변형과 `be32_to_cpu()` 같은 역변환이 있습니다.
포인터 변형인 `cpu_to_be32p()`는 포인터가 가리키는 값을 읽어 변환값을 반환합니다. in-place 변형인 `cpu_to_be32s()`는 포인터 대상 자체를 변환하고 void를 반환합니다.
`local_irq_save()`와 `local_irq_restore()`는 `include/linux/irqflags.h`에서 현재 CPU의 하드웨어 인터럽트를 비활성화·복원합니다. 재진입 가능하며 `unsigned long flags`에 이전 상태를 저장합니다.
인터럽트가 활성화되어 있다는 것을 확실히 알면 `local_irq_disable()`과 `local_irq_enable()`을 사용할 수 있습니다.
`local_bh_disable()`과 `local_bh_enable()`은 `include/linux/bottom_half.h`에서 현재 CPU의 softirq와 tasklet 실행을 막고 복원합니다. 중첩 가능하며 이전부터 비활성 상태였다면 쌍을 호출한 뒤에도 비활성 상태를 유지합니다.
`get_cpu()`는 선점을 막아 다른 CPU로 이동하지 못하게 하고 현재 CPU 번호를 반환합니다. 번호 범위는 0부터 `NR_CPUS` 사이지만 연속이라는 보장은 없으며 끝나면 `put_cpu()`로 이전 상태를 복원합니다.
이미 인터럽트 컨텍스트이거나 선점이 비활성화되어 다른 프로세스가 선점할 수 없다는 사실을 알면 `smp_processor_id()`를 직접 사용할 수 있습니다.
호출 컨텍스트와 연속성 요구에 따라 할당 방법을 고릅니다.
busy wait와 수면 지연을 구분합니다.
선점 상태를 안전하게 관리해야 CPU 번호가 유효합니다.
값, 포인터, in-place API를 목적에 맞게 구분합니다.
:c:func:`kmalloc()`/:c:func:`kfree()`
-------------------------------------
Definite in ``include/linux/slab.h``
**[POTREBBERO DORMIRE: LEGGI SOTTO]**
Queste procedure sono utilizzate per la richiesta dinamica di un puntatore ad
un pezzo di memoria allineato, esattamente come malloc e free nello spazio
utente, ma :c:func:`kmalloc()` ha un argomento aggiuntivo per indicare alcune
opzioni. Le opzioni più importanti sono:
``GFP_KERNEL``
Potrebbe dormire per librarare della memoria. L'opzione fornisce il modo
più affidabile per allocare memoria, ma il suo uso è strettamente limitato
allo spazio utente.
``GFP_ATOMIC``
Non dorme. Meno affidabile di ``GFP_KERNEL``, ma può essere usata in un
contesto d'interruzione. Dovreste avere **davvero** una buona strategia
per la gestione degli errori in caso di mancanza di memoria.
``GFP_DMA``
Alloca memoria per il DMA sul bus ISA nello spazio d'indirizzamento
inferiore ai 16MB. Se non sapete cos'è allora non vi serve.
Molto inaffidabile.
Se vedete un messaggio d'avviso per una funzione dormiente che viene chiamata
da un contesto errato, allora probabilmente avete usato una funzione
d'allocazione dormiente da un contesto d'interruzione senza ``GFP_ATOMIC``.
Dovreste correggerlo. Sbrigatevi, non cincischiate.
Se allocate almeno ``PAGE_SIZE``(``asm/page.h`` o ``asm/page_types.h``) byte,
considerate l'uso di :c:func:`__get_free_pages()` (``include/linux/gfp.h``).
Accetta un argomento che definisce l'ordine (0 per per la dimensione di una
pagine, 1 per una doppia pagina, 2 per quattro pagine, eccetra) e le stesse
opzioni d'allocazione viste precedentemente.
Se state allocando un numero di byte notevolemnte superiore ad una pagina
potete usare :c:func:`vmalloc()`. Essa allocherà memoria virtuale all'interno
dello spazio kernel. Questo è un blocco di memoria fisica non contiguo, ma
la MMU vi darà l'impressione che lo sia (quindi, sarà contiguo solo dal punto
di vista dei processori, non dal punto di vista dei driver dei dispositivi
esterni).
Se per qualche strana ragione avete davvero bisogno di una grossa quantità di
memoria fisica contigua, avete un problema: Linux non ha un buon supporto per
questo caso d'uso perché, dopo un po' di tempo, la frammentazione della memoria
rende l'operazione difficile. Il modo migliore per allocare un simile blocco
all'inizio dell'avvio del sistema è attraverso la procedura
:c:func:`alloc_bootmem()`.
Prima di inventare la vostra cache per gli oggetti più usati, considerate
l'uso di una cache slab disponibile in ``include/linux/slab.h``.
:c:macro:`current`
-------------------
Definita in ``include/asm/current.h``
Questa variabile globale (in realtà una macro) contiene un puntatore alla
struttura del processo corrente, quindi è valido solo dal contesto utente.
Per esempio, quando un processo esegue una chiamata di sistema, questo
punterà alla struttura dati del processo chiamate.
Nel contesto d'interruzione in suo valore **non è NULL**.
:c:func:`mdelay()`/:c:func:`udelay()`
-------------------------------------
Definite in ``include/asm/delay.h`` / ``include/linux/delay.h``
Le funzioni :c:func:`udelay()` e :c:func:`ndelay()` possono essere utilizzate
per brevi pause. Non usate grandi valori perché rischiate d'avere un
overflow - in questo contesto la funzione :c:func:`mdelay()` è utile,
oppure considerate :c:func:`msleep()`.
:c:func:`cpu_to_be32()`/:c:func:`be32_to_cpu()`/:c:func:`cpu_to_le32()`/:c:func:`le32_to_cpu()`
-----------------------------------------------------------------------------------------------
Definite in ``include/asm/byteorder.h``
La famiglia di funzioni :c:func:`cpu_to_be32()` (dove "32" può essere
sostituito da 64 o 16, e "be" con "le") forniscono un modo generico
per fare conversioni sull'ordine dei byte (endianess): esse ritornano
il valore convertito. Tutte le varianti supportano anche il processo inverso:
:c:func:`be32_to_cpu()`, eccetera.
Queste funzioni hanno principalmente due varianti: la variante per
puntatori, come :c:func:`cpu_to_be32p()`, che prende un puntatore
ad un tipo, e ritorna il valore convertito. L'altra variante per
la famiglia di conversioni "in-situ", come :c:func:`cpu_to_be32s()`,
che convertono il valore puntato da un puntatore, e ritornano void.
:c:func:`local_irq_save()`/:c:func:`local_irq_restore()`
--------------------------------------------------------
Definite in ``include/linux/irqflags.h``
Queste funzioni abilitano e disabilitano le interruzioni hardware
sul processore locale. Entrambe sono rientranti; esse salvano lo stato
precedente nel proprio argomento ``unsigned long flags``. Se sapete
che le interruzioni sono abilite, potete semplicemente utilizzare
:c:func:`local_irq_disable()` e :c:func:`local_irq_enable()`.
.. _it_local_bh_disable:
:c:func:`local_bh_disable()`/:c:func:`local_bh_enable()`
--------------------------------------------------------
Definite in ``include/linux/bottom_half.h``
Queste funzioni abilitano e disabilitano le interruzioni software
sul processore locale. Entrambe sono rientranti; se le interruzioni
software erano già state disabilitate in precedenza, rimarranno
disabilitate anche dopo aver invocato questa coppia di funzioni.
Lo scopo è di prevenire l'esecuzione di softirq e tasklet sul processore
attuale.
:c:func:`smp_processor_id()`
----------------------------
Definita in ``include/linux/smp.h``
:c:func:`get_cpu()` nega il diritto di prelazione (quindi non potete essere
spostati su un altro processore all'improvviso) e ritorna il numero
del processore attuale, fra 0 e ``NR_CPUS``. Da notare che non è detto
che la numerazione dei processori sia continua. Quando avete terminato,
ritornate allo stato precedente con :c:func:`put_cpu()`.
Se sapete che non dovete essere interrotti da altri processi (per esempio,
se siete in un contesto d'interruzione, o il diritto di prelazione
è disabilitato) potete utilizzare smp_processor_id().
초기화 섹션과 모듈 생명주기
451-525`__init`, `__exit`, `__initdata`는 `include/linux/init.h`에 정의됩니다. 부팅이 끝나면 커널은 특별 초기화 섹션을 해제하므로 `__init` 함수와 `__initdata` 자료구조는 더 이상 남지 않습니다.
모듈도 초기화가 끝난 뒤 같은 종류의 메모리를 해제합니다. `__exit`는 제거 때만 사용하는 함수임을 나타내며, 파일이 모듈로 빌드되지 않으면 그 함수를 버릴 수 있게 합니다.
`__init`로 표시한 함수를 `EXPORT_SYMBOL()` 또는 `EXPORT_SYMBOL_GPL()`로 모듈에 내보내는 것은 의미가 없습니다. 초기화 뒤 함수 메모리가 사라지므로 동작하지 않습니다.
`module_init()`과 `module_exit()`는 `include/linux/init.h` 및 `include/linux/module.h`에서 같은 코드를 동적 모듈과 내장 커널 구성 양쪽에 사용할 수 있게 합니다.
`module_init()`은 모듈 삽입 때 호출할 함수를 지정합니다. 내장 빌드에서는 `__initcall()`과 같아져 링커를 통해 부팅 중 해당 함수를 호출합니다.
초기화 함수는 음수 오류를 반환해 모듈 로드를 실패시킬 수 있지만 내장 커널 구성에서는 같은 효과가 없습니다. 사용자 컨텍스트, 인터럽트 활성 상태에서 호출되므로 잠들 수 있습니다.
`module_exit()`는 모듈 제거 때 호출할 함수를 지정하며 내장 구성에서는 호출되지 않습니다. 사용 횟수가 0일 때만 실행되고 잠들 수 있지만 실패할 수는 없으므로 반환 전에 모든 자원을 정리해야 합니다.
`module_exit()`가 없으면 일반적으로 모듈을 제거할 수 없습니다. 원문은 강제 제거 `rmmod -f`를 예외로 언급합니다.
`try_module_get()`과 `module_put()`은 모듈 사용 카운터를 관리해 실행 중 제거를 막습니다. 모듈 코드를 실행하기 전에 `try_module_get()`이 성공해야 하며, 끝나면 `module_put()`을 호출합니다.
다른 모듈이 내보낸 심볼을 사용 중인 경우에도 모듈은 제거되지 않습니다. 등록 가능한 많은 구조체에는 `struct file_operations`처럼 `owner` 필드가 있으며 `THIS_MODULE`로 설정해야 합니다.
코드와 데이터가 필요한 생명주기를 빌드 시스템에 알립니다.
초기화와 제거의 오류·수면 규칙이 다릅니다.
참조를 획득한 동안 모듈 제거를 막습니다.
``__init``/``__exit``/``__initdata``
------------------------------------
Definite in ``include/linux/init.h``
Dopo l'avvio, il kernel libera una sezione speciale; le funzioni marcate
con ``__init`` e le strutture dati marcate con ``__initdata`` vengono
eliminate dopo il completamento dell'avvio: in modo simile i moduli eliminano
questa memoria dopo l'inizializzazione. ``__exit`` viene utilizzato per
dichiarare che una funzione verrà utilizzata solo in fase di rimozione:
la detta funzione verrà eliminata quando il file che la contiene non è
compilato come modulo. Guardate l'header file per informazioni. Da notare che
non ha senso avere una funzione marcata come ``__init`` e al tempo stesso
esportata ai moduli utilizzando :c:func:`EXPORT_SYMBOL()` o
:c:func:`EXPORT_SYMBOL_GPL()` - non funzionerà.
:c:func:`__initcall()`/:c:func:`module_init()`
----------------------------------------------
Definite in ``include/linux/init.h`` / ``include/linux/module.h``
Molte parti del kernel funzionano bene come moduli (componenti del kernel
caricabili dinamicamente). L'utilizzo delle macro :c:func:`module_init()`
e :c:func:`module_exit()` semplifica la scrittura di codice che può funzionare
sia come modulo, sia come parte del kernel, senza l'ausilio di #ifdef.
La macro :c:func:`module_init()` definisce quale funzione dev'essere
chiamata quando il modulo viene inserito (se il file è stato compilato come
tale), o in fase di avvio : se il file non è stato compilato come modulo la
macro :c:func:`module_init()` diventa equivalente a :c:func:`__initcall()`,
la quale, tramite qualche magia del linker, s'assicura che la funzione venga
chiamata durante l'avvio.
La funzione può ritornare un numero d'errore negativo per scatenare un
fallimento del caricamento (sfortunatamente, questo non ha effetto se il
modulo è compilato come parte integrante del kernel). Questa funzione è chiamata
in contesto utente con le interruzioni abilitate, quindi potrebbe dormire.
:c:func:`module_exit()`
-----------------------
Definita in ``include/linux/module.h``
Questa macro definisce la funzione che dev'essere chiamata al momento della
rimozione (o mai, nel caso in cui il file sia parte integrante del kernel).
Essa verrà chiamata solo quando il contatore d'uso del modulo raggiunge lo
zero. Questa funzione può anche dormire, ma non può fallire: tutto dev'essere
ripulito prima che la funzione ritorni.
Da notare che questa macro è opzionale: se non presente, il modulo non sarà
removibile (a meno che non usiate 'rmmod -f' ).
:c:func:`try_module_get()`/:c:func:`module_put()`
-------------------------------------------------
Definite in ``include/linux/module.h``
Queste funzioni maneggiano il contatore d'uso del modulo per proteggerlo dalla
rimozione (in aggiunta, un modulo non può essere rimosso se un altro modulo
utilizzo uno dei sui simboli esportati: vedere di seguito). Prima di eseguire
codice del modulo, dovreste chiamare :c:func:`try_module_get()` su quel modulo:
se fallisce significa che il modulo è stato rimosso e dovete agire come se
non fosse presente. Altrimenti, potete accedere al modulo in sicurezza, e
chiamare :c:func:`module_put()` quando avete finito.
La maggior parte delle strutture registrabili hanno un campo owner
(proprietario), come nella struttura
:c:type:`struct file_operations <file_operations>`.
Impostate questo campo al valore della macro ``THIS_MODULE``.
대기 큐와 원자 연산
526-596`include/linux/wait.h`의 대기 큐는 특정 조건이 참이 될 때 누군가 깨워 주기를 기다리는 데 사용하며 잠드는 기능입니다. 경쟁 상태를 피하도록 순서를 주의해야 합니다.
먼저 `wait_queue_head_t`를 선언합니다. 기다리는 프로세스는 자신을 가리키는 `wait_queue_entry_t`를 준비해 큐에 넣습니다.
대기 큐 머리는 `DECLARE_WAIT_QUEUE_HEAD()`로 정적으로 선언하거나 초기화 코드에서 `init_waitqueue_head()`로 설정합니다.
큐에 들어가는 과정에서는 조건을 검사하기 전에 먼저 자신을 큐에 넣어야 합니다. `wait_event_interruptible()`은 첫 인자로 큐 머리, 둘째 인자로 평가할 조건식을 받습니다.
조건식이 참이면 0을 반환하고, 기다리는 동안 신호를 받으면 `-ERESTARTSYS`를 반환합니다. `wait_event()` 변형은 신호를 무시합니다.
조건을 만든 쪽은 `wake_up()`을 호출해 큐의 프로세스를 깨웁니다. 일반적으로 모두 깨우지만 `TASK_EXCLUSIVE` 항목이 있으면 그 뒤의 나머지는 깨우지 않습니다. 같은 헤더에 여러 변형이 있습니다.
원자 연산의 첫 집합은 `include/asm/atomic.h`의 `atomic_t`를 사용합니다. 최소 32비트 부호 있는 정수를 담으며 반드시 전용 함수를 통해 읽고 수정합니다.
`atomic_read()`, `atomic_set()`은 읽기와 설정, `atomic_add()`, `atomic_sub()`, `atomic_inc()`, `atomic_dec()`은 산술 연산을 수행합니다. `atomic_dec_and_test()`는 감소 후 0이 되면 참을 반환합니다.
원자 함수는 일반 산술보다 느리므로 필요하지 않은 곳에 남용하면 안 됩니다.
두 번째 원자 연산 집합은 `include/linux/bitops.h`에 있고 `unsigned long`의 비트를 다룹니다. 변수 포인터와 최하위 비트를 0으로 하는 비트 번호를 받습니다.
`set_bit()`, `clear_bit()`, `change_bit()`는 지정 비트를 설정·해제·반전합니다. `test_and_set_bit()`, `test_and_clear_bit()`, `test_and_change_bit()`은 같은 동작을 하면서 이전 비트가 설정되어 있었는지 반환해 원자적 플래그 조작에 유용합니다.
`BITS_PER_LONG`을 넘는 비트 인덱스도 기술적으로 쓸 수 있지만 big-endian 플랫폼에서 동작이 이상할 수 있으므로 피해야 합니다.
등록을 조건 검사보다 먼저 해야 깨우기 이벤트를 놓치지 않습니다.
신호 처리 여부에 따라 사용할 변형을 고릅니다.
test_and 계열은 변경 전 상태도 반환합니다.
대기자는 조건식과 신호를 구분하고 생산자는 상태 변경 뒤 깨웁니다.
Code d'attesa ``include/linux/wait.h``
======================================
**[DORMONO]**
Una coda d'attesa è usata per aspettare che qualcuno vi attivi quando una
certa condizione s'avvera. Per evitare corse critiche, devono essere usate
con cautela. Dichiarate una :c:type:`wait_queue_head_t`, e poi i processi
che vogliono attendere il verificarsi di quella condizione dichiareranno
una :c:type:`wait_queue_entry_t` facendo riferimento a loro stessi, poi
metteranno questa in coda.
Dichiarazione
-------------
Potere dichiarare una ``wait_queue_head_t`` utilizzando la macro
:c:func:`DECLARE_WAIT_QUEUE_HEAD()` oppure utilizzando la procedura
:c:func:`init_waitqueue_head()` nel vostro codice d'inizializzazione.
Accodamento
-----------
Mettersi in una coda d'attesa è piuttosto complesso, perché dovete
mettervi in coda prima di verificare la condizione. Esiste una macro
a questo scopo: :c:func:`wait_event_interruptible()` (``include/linux/wait.h``).
Il primo argomento è la testa della coda d'attesa, e il secondo è
un'espressione che dev'essere valutata; la macro ritorna 0 quando questa
espressione è vera, altrimenti ``-ERESTARTSYS`` se è stato ricevuto un segnale.
La versione :c:func:`wait_event()` ignora i segnali.
Svegliare una procedura in coda
-------------------------------
Chiamate :c:func:`wake_up()` (``include/linux/wait.h``); questa attiverà tutti
i processi in coda. Ad eccezione se uno di questi è impostato come
``TASK_EXCLUSIVE``, in questo caso i rimanenti non verranno svegliati.
Nello stesso header file esistono altre varianti di questa funzione.
Operazioni atomiche
===================
Certe operazioni sono garantite come atomiche su tutte le piattaforme.
Il primo gruppo di operazioni utilizza :c:type:`atomic_t`
(``include/asm/atomic.h``); questo contiene un intero con segno (minimo 32bit),
e dovete utilizzare queste funzione per modificare o leggere variabili di tipo
:c:type:`atomic_t`. :c:func:`atomic_read()` e :c:func:`atomic_set()` leggono ed
impostano il contatore, :c:func:`atomic_add()`, :c:func:`atomic_sub()`,
:c:func:`atomic_inc()`, :c:func:`atomic_dec()`, e
:c:func:`atomic_dec_and_test()` (ritorna vero se raggiunge zero dopo essere
stata decrementata).
Sì. Ritorna vero (ovvero != 0) se la variabile atomica è zero.
Da notare che queste funzioni sono più lente rispetto alla normale aritmetica,
e quindi non dovrebbero essere usate a sproposito.
Il secondo gruppo di operazioni atomiche sono definite in
``include/linux/bitops.h`` ed agiscono sui bit d'una variabile di tipo
``unsigned long``. Queste operazioni prendono come argomento un puntatore
alla variabile, e un numero di bit dove 0 è quello meno significativo.
:c:func:`set_bit()`, :c:func:`clear_bit()` e :c:func:`change_bit()`
impostano, cancellano, ed invertono il bit indicato.
:c:func:`test_and_set_bit()`, :c:func:`test_and_clear_bit()` e
:c:func:`test_and_change_bit()` fanno la stessa cosa, ad eccezione che
ritornano vero se il bit era impostato; queste sono particolarmente
utili quando si vuole impostare atomicamente dei flag.
Con queste operazioni è possibile utilizzare indici di bit che eccedono
il valore ``BITS_PER_LONG``. Il comportamento è strano sulle piattaforme
big-endian quindi è meglio evitarlo.
커널과 모듈의 심볼 내보내기
597-646커널 내부에서는 일반 링커 규칙을 따릅니다. `static`으로 파일 내부 가시성을 제한하지 않은 심볼은 커널 다른 부분에서 사용할 수 있습니다.
동적 모듈에는 별도의 내보낸 심볼 테이블이 있어 커널 진입점을 제한합니다. 모듈 자신도 심볼을 내보낼 수 있습니다.
`EXPORT_SYMBOL()`은 `include/linux/export.h`에 정의된 일반 내보내기 방법이며 로드된 모듈이 해당 심볼을 사용할 수 있게 합니다.
`EXPORT_SYMBOL_GPL()`은 `MODULE_LICENSE()`로 GPL 호환 라이선스를 선언한 모듈만 심볼을 사용할 수 있게 합니다. 이런 심볼은 안정된 외부 인터페이스라기보다 내부 함수로 간주됩니다.
일부 유지관리자와 개발자는 새 기능이나 인터페이스를 추가할 때도 `EXPORT_SYMBOL_GPL()` 사용을 요구할 수 있습니다.
`EXPORT_SYMBOL_NS()`는 `EXPORT_SYMBOL()`에 네임스페이스를 지정하는 변형이고, `EXPORT_SYMBOL_NS_GPL()`은 GPL 제한 변형에 네임스페이스를 지정합니다.
심볼 네임스페이스는 `Documentation/translations/it_IT/core-api/symbol-namespaces.rst`에 설명되어 있습니다. 원문의 이 경로를 그대로 보존합니다.
라이선스 제한과 네임스페이스 사용 여부를 구분합니다.
내보내기 종류와 모듈 라이선스 선언을 함께 확인합니다.
Simboli
=======
All'interno del kernel, si seguono le normali regole del linker (ovvero,
a meno che un simbolo non venga dichiarato con visibilita limitata ad un
file con la parola chiave ``static``, esso può essere utilizzato in qualsiasi
parte del kernel). Nonostante ciò, per i moduli, esiste una tabella dei
simboli esportati che limita i punti di accesso al kernel. Anche i moduli
possono esportare simboli.
:c:func:`EXPORT_SYMBOL()`
-------------------------
Definita in ``include/linux/export.h``
Questo è il classico metodo per esportare un simbolo: i moduli caricati
dinamicamente potranno utilizzare normalmente il simbolo.
:c:func:`EXPORT_SYMBOL_GPL()`
-----------------------------
Definita in ``include/linux/export.h``
Essa è simile a :c:func:`EXPORT_SYMBOL()` ad eccezione del fatto che i
simboli esportati con :c:func:`EXPORT_SYMBOL_GPL()` possono essere
utilizzati solo dai moduli che hanno dichiarato una licenza compatibile
con la GPL attraverso :c:func:`MODULE_LICENSE()`. Questo implica che la
funzione esportata è considerata interna, e non una vera e propria interfaccia.
Alcuni manutentori e sviluppatori potrebbero comunque richiedere
:c:func:`EXPORT_SYMBOL_GPL()` quando si aggiungono nuove funzionalità o
interfacce.
:c:func:`EXPORT_SYMBOL_NS()`
----------------------------
Definita in ``include/linux/export.h``
Questa è una variate di `EXPORT_SYMBOL()` che permette di specificare uno
spazio dei nomi. Lo spazio dei nomi è documentato in
Documentation/translations/it_IT/core-api/symbol-namespaces.rst.
:c:func:`EXPORT_SYMBOL_NS_GPL()`
--------------------------------
Definita in ``include/linux/export.h``
Questa è una variate di `EXPORT_SYMBOL_GPL()` che permette di specificare uno
spazio dei nomi. Lo spazio dei nomi è documentato in
Documentation/translations/it_IT/core-api/symbol-namespaces.rst.
자료구조와 코딩 관례
647-753단순한 이중 연결 리스트가 필요하면 `include/linux/list.h`의 공통 리스트 구현을 사용합니다. 특별한 요구가 없다면 자체 구현보다 이 선택이 적절하며 `list_for_each_entry()`가 특히 유용합니다.
사용자 컨텍스트에서 호출되는 커널 코드는 C의 흔한 관례와 달리 성공 시 0, 실패 시 `-EFAULT` 같은 음수 오류 코드를 반환하는 경우가 많습니다.
`include/linux/err.h`의 `ERR_PTR()`는 음수 오류를 포인터 값으로 인코딩합니다. `IS_ERR()`로 오류 포인터인지 확인하고 `PTR_ERR()`로 오류 번호를 되찾아 별도 오류 출력 포인터를 피할 수 있습니다.
개발 중인 커널에서는 함수나 구조체 이름이 바뀔 수 있습니다. 이는 호출 가능 컨텍스트, 추가 검사, 제거된 검사처럼 근본 동작이 바뀌었음을 반영하는 경우가 많습니다.
관련 메일링 리스트와 아카이브에서 변경 설명을 찾아야 합니다. 파일 전체에 단순 문자열 치환을 적용하면 의미 변화를 놓쳐 상황을 더 나쁘게 만들 수 있습니다.
구조체는 ISO C99 지정 초기화자를 사용하는 것이 권장됩니다. 필드 이름을 명시하면 grep으로 찾기 쉽고 어떤 필드를 설정하는지 분명합니다.
Il metodo preferito per l'inizializzazione delle strutture è quello
di utilizzare gli inizializzatori designati, come definiti nello
standard ISO C99, eg::
static struct block_device_operations opt_fops = {
.open = opt_open,
.release = opt_release,
.ioctl = opt_ioctl,
.check_media_change = opt_media_change,
};
Linux 커널은 GNU C 확장을 명시적으로 허용합니다. 자세한 내용은 GCC info 페이지의 ‘C Extensions’를 참조하며, man 페이지는 info 내용의 짧은 요약일 뿐입니다.
일반적으로 쓰이는 확장에는 inline 함수, statement expression `({ ... })`, `__attribute__`, `typeof`, 길이 0 배열, 가변 인자 매크로, void 포인터 산술, 비상수 초기화자, `__func__`, `__builtin_constant_p()`가 있습니다.
어셈블러 문장은 허용되지만 원문은 `arch/`와 `include/asm/` 밖에서 사용하지 말라고 제한합니다.
커널에서 `long long` 사용은 주의해야 합니다. GCC 생성 코드가 좋지 않을 수 있고 i386에서는 필요한 런타임 함수가 커널 환경에 없어 나눗셈과 곱셈이 동작하지 않을 수 있다고 원문이 경고합니다.
C++는 필요한 런타임 환경이 없고 커널 헤더도 검증되지 않아 일반적으로 나쁜 선택입니다. 기술적으로 가능하더라도 권장하지 않으며, 꼭 쓴다면 최소한 예외를 피해야 합니다.
`NUMif` 절은 소스 코드 중간에 전처리기 `#if`를 흩뿌리기보다 헤더나 `.c` 파일 시작 부분의 매크로로 기능 차이를 추상화하는 편이 더 깔끔하다고 권합니다.
값과 포인터 양쪽에서 오류를 음수 errno로 표현합니다.
오류를 포인터 반환 하나에 함께 싣는 절차입니다.
커널에서 흔히 보는 확장을 목적별로 묶습니다.
컴파일 오류를 기계적 치환으로 숨기지 않고 의미 변화를 조사합니다.
Procedure e convenzioni
=======================
Liste doppiamente concatenate ``include/linux/list.h``
------------------------------------------------------
Un tempo negli header del kernel c'erano tre gruppi di funzioni per
le liste concatenate, ma questa è stata la vincente. Se non avete particolari
necessità per una semplice lista concatenata, allora questa è una buona scelta.
In particolare, :c:func:`list_for_each_entry()` è utile.
Convenzione dei valori di ritorno
---------------------------------
Per codice chiamato in contesto utente, è molto comune sfidare le convenzioni
C e ritornare 0 in caso di successo, ed un codice di errore negativo
(eg. ``-EFAULT``) nei casi fallimentari. Questo potrebbe essere controintuitivo
a prima vista, ma è abbastanza diffuso nel kernel.
Utilizzate :c:func:`ERR_PTR()` (``include/linux/err.h``) per codificare
un numero d'errore negativo in un puntatore, e :c:func:`IS_ERR()` e
:c:func:`PTR_ERR()` per recuperarlo di nuovo: così si evita d'avere un
puntatore dedicato per il numero d'errore. Da brividi, ma in senso positivo.
Rompere la compilazione
-----------------------
Linus e gli altri sviluppatori a volte cambiano i nomi delle funzioni e
delle strutture nei kernel in sviluppo; questo non è solo per tenere
tutti sulle spine: questo riflette cambiamenti fondamentati (eg. la funzione
non può più essere chiamata con le funzioni attive, o fa controlli aggiuntivi,
o non fa più controlli che venivano fatti in precedenza). Solitamente a questo
s'accompagna un'adeguata e completa nota sulla lista di discussone
più adatta; cercate negli archivi. Solitamente eseguire una semplice
sostituzione su tutto un file rendere le cose **peggiori**.
Inizializzazione dei campi d'una struttura
------------------------------------------
Il metodo preferito per l'inizializzazione delle strutture è quello
di utilizzare gli inizializzatori designati, come definiti nello
standard ISO C99, eg::
static struct block_device_operations opt_fops = {
.open = opt_open,
.release = opt_release,
.ioctl = opt_ioctl,
.check_media_change = opt_media_change,
};
Questo rende più facile la ricerca con grep, e rende più chiaro quale campo
viene impostato. Dovreste fare così perché si mostra meglio.
Estensioni GNU
--------------
Le estensioni GNU sono esplicitamente permesse nel kernel Linux. Da notare
che alcune delle più complesse non sono ben supportate, per via dello scarso
sviluppo, ma le seguenti sono da considerarsi la norma (per maggiori dettagli,
leggete la sezione "C Extensions" nella pagina info di GCC - Sì, davvero
la pagina info, la pagina man è solo un breve riassunto delle cose nella
pagina info).
- Funzioni inline
- Istruzioni in espressioni (ie. il costrutto ({ and }) ).
- Dichiarate attributi di una funzione / variabile / tipo
(__attribute__)
- typeof
- Array con lunghezza zero
- Macro varargs
- Aritmentica sui puntatori void
- Inizializzatori non costanti
- Istruzioni assembler (non al di fuori di 'arch/' e 'include/asm/')
- Nomi delle funzioni come stringhe (__func__).
- __builtin_constant_p()
Siate sospettosi quando utilizzate long long nel kernel, il codice generato
da gcc è orribile ed anche peggio: le divisioni e le moltiplicazioni non
funzionano sulle piattaforme i386 perché le rispettive funzioni di runtime
di GCC non sono incluse nell'ambiente del kernel.
C++
---
Solitamente utilizzare il C++ nel kernel è una cattiva idea perché
il kernel non fornisce il necessario ambiente di runtime e gli header file
non sono stati verificati. Rimane comunque possibile, ma non consigliato.
Se davvero volete usarlo, almeno evitate le eccezioni.
NUMif
-----
Viene generalmente considerato più pulito l'uso delle macro negli header file
(o all'inizio dei file .c) per astrarre funzioni piuttosto che utlizzare
l'istruzione di pre-processore \`#if' all'interno del codice sorgente.
커널에 변경을 넣기 위한 준비
754-797공식 포함을 목표로 하거나 깔끔한 패치를 만들려면 코드 작성 외의 관리 작업도 필요합니다.
먼저 수정하는 코드의 책임자를 찾습니다. 소스 파일 상단, `MAINTAINERS`, 마지막 수단으로 `CREDITS`를 확인하고, 노력 중복이나 과거에 거절된 접근을 피하도록 관련자와 조율합니다.
새로 만들거나 크게 다룬 파일 상단에는 이름과 이메일 주소를 적습니다. 버그를 발견하거나 후속 변경을 하려는 사람이 가장 먼저 보는 위치입니다.
커널 변경에는 보통 구성 옵션이 필요합니다. 알맞은 디렉터리의 `Kconfig`를 수정하고 `Documentation/kbuild/kconfig-language.rst`의 언어 문서를 참조합니다.
구성 옵션 설명은 전문 사용자와 해당 기능을 모르는 사용자 모두에게 말해야 합니다. 호환성 문제와 위험을 언급하고, 선택을 모르는 사람을 위해 일반적으로 ‘if in doubt, say N’ 또는 드물게 Y로 끝냅니다.
`Makefile`도 수정합니다. CONFIG 변수가 내보내지므로 보통 `obj-$(CONFIG_xxx) += xxx.o` 같은 줄을 추가하며 문법은 `Documentation/kbuild/makefiles.rst`에 설명되어 있습니다.
한 파일을 넘는 주목할 만한 기여라면 `CREDITS` 추가를 고려합니다. `MAINTAINERS` 등록은 단순 공로 표시보다 훨씬 큰 약속으로, 하위 시스템 변경과 버그 때 연락받고 책임지겠다는 뜻입니다.
마지막으로 `Documentation/process/submitting-patches.rst`를 읽어 패치 제출 규칙을 따라야 합니다.
변경을 패치로 내기 전에 확인할 관리 파일과 문서입니다.
기술 구현과 유지관리 책임을 함께 준비합니다.
Mettere le vostre cose nel kernel
=================================
Al fine d'avere le vostre cose in ordine per l'inclusione ufficiale, o
anche per avere patch pulite, c'è del lavoro amministrativo da fare:
- Trovare chi è responsabile del codice che state modificando. Guardare in cima
ai file sorgenti, all'interno del file ``MAINTAINERS``, ed alla fine
di tutti nel file ``CREDITS``. Dovreste coordinarvi con queste persone
per evitare di duplicare gli sforzi, o provare qualcosa che è già stato
rigettato.
Assicuratevi di mettere il vostro nome ed indirizzo email in cima a
tutti i file che create o che maneggiate significativamente. Questo è
il primo posto dove le persone guarderanno quando troveranno un baco,
o quando **loro** vorranno fare una modifica.
- Solitamente vorrete un'opzione di configurazione per la vostra modifica
al kernel. Modificate ``Kconfig`` nella cartella giusta. Il linguaggio
Config è facile con copia ed incolla, e c'è una completa documentazione
nel file ``Documentation/kbuild/kconfig-language.rst``.
Nella descrizione della vostra opzione, assicuratevi di parlare sia agli
utenti esperti sia agli utente che non sanno nulla del vostro lavoro.
Menzionate qui le incompatibilità ed i problemi. Chiaramente la
descrizione deve terminare con “if in doubt, say N” (se siete in dubbio,
dite N) (oppure, occasionalmente, \`Y'); questo è per le persone che non
hanno idea di che cosa voi stiate parlando.
- Modificate il file ``Makefile``: le variabili CONFIG sono esportate qui,
quindi potete solitamente aggiungere una riga come la seguete
"obj-$(CONFIG_xxx) += xxx.o". La sintassi è documentata nel file
``Documentation/kbuild/makefiles.rst``.
- Aggiungete voi stessi in ``CREDITS`` se credete di aver fatto qualcosa di
notevole, solitamente qualcosa che supera il singolo file (comunque il vostro
nome dovrebbe essere all'inizio dei file sorgenti). ``MAINTAINERS`` significa
che volete essere consultati quando vengono fatte delle modifiche ad un
sottosistema, e quando ci sono dei bachi; questo implica molto di più di un
semplice impegno su una parte del codice.
- Infine, non dimenticatevi di leggere
``Documentation/process/submitting-patches.rst``.
커널 코드 예제 모음
798-859‘커널의 요령’ 절은 소스에서 발견한 흥미로운 예를 모으며 다른 예의 추가도 환영합니다. 주석과 매크로는 역사적 원문이므로 표현을 정제하지 않고 코드 그대로 보존합니다.
`arch/x86/include/asm/delay.h`의 `ndelay(n)`은 `__builtin_constant_p(n)`으로 상수 여부를 컴파일 시 판별합니다. 상수이면 범위를 검사해 `__const_udelay()`를 사용하고, 아니면 `__ndelay(n)`로 보냅니다.
``arch/x86/include/asm/delay.h``::
#define ndelay(n) (__builtin_constant_p(n) ? \
((n) > 20000 ? __bad_ndelay() : __const_udelay((n) * 5ul)) : \
__ndelay(n))
`include/linux/fs.h`의 예는 커널 포인터 주소 공간의 여유를 이용해 오류 코드와 dentry 포인터를 같은 반환값에 담는 `ERR_PTR`, `PTR_ERR`, `IS_ERR` 매크로를 보여 줍니다.
``include/linux/fs.h``::
/*
* Kernel pointers have redundant information, so we can use a
* scheme where we can return either an error code or a dentry
* pointer with the same return value.
*
* This should be a per-architecture thing, to allow different
* error and pointer decisions.
*/
#define ERR_PTR(err) ((void *)((long)(err)))
#define PTR_ERR(ptr) ((long)(ptr))
#define IS_ERR(ptr) ((unsigned long)(ptr) > (unsigned long)(-1000))
`arch/x86/include/asm/uaccess_32.h`의 `copy_to_user` 매크로도 복사 길이가 컴파일 상수인지 검사해 상수 전용 구현과 일반 구현 중 하나를 선택합니다.
``arch/x86/include/asm/uaccess_32.h:``::
#define copy_to_user(to,from,n) \
(__builtin_constant_p(n) ? \
__constant_copy_to_user((to),(from),(n)) : \
__generic_copy_to_user((to),(from),(n)))
`arch/sparc/kernel/head.S`와 `arch/sparc/lib/checksum.S` 예는 개발자 사이의 유머러스한 주석을 보여 줍니다. 이 주석은 API 지침이 아니라 역사적 코드 예시이며 원문 전체를 그대로 유지합니다.
``arch/sparc/kernel/head.S:``::
/*
* Sun people can't spell worth damn. "compatability" indeed.
* At least we *know* we can't spell, and use a spell-checker.
*/
/* Uh, actually Linus it is I who cannot spell. Too much murky
* Sparc assembly will do this to ya.
*/
C_LABEL(cputypvar):
.asciz "compatibility"
/* Tested on SS-5, SS-10. Probably someone at Sun applied a spell-checker. */
.align 4
C_LABEL(cputypvar_sun4m):
.asciz "compatible"
``arch/sparc/lib/checksum.S:``::
/* Sun, you just can't beat me, you just can't. Stop trying,
* give up. I'm serious, I am going to kick the living shit
* out of you, game over, lights out.
*/
각 소스 조각이 보여 주는 커널 구현 패턴입니다.
컴파일러가 상수임을 알 때 전용 경로를 선택합니다.
Trucchetti del kernel
=====================
Dopo una rapida occhiata al codice, questi sono i preferiti. Sentitevi liberi
di aggiungerne altri.
``arch/x86/include/asm/delay.h``::
#define ndelay(n) (__builtin_constant_p(n) ? \
((n) > 20000 ? __bad_ndelay() : __const_udelay((n) * 5ul)) : \
__ndelay(n))
``include/linux/fs.h``::
/*
* Kernel pointers have redundant information, so we can use a
* scheme where we can return either an error code or a dentry
* pointer with the same return value.
*
* This should be a per-architecture thing, to allow different
* error and pointer decisions.
*/
#define ERR_PTR(err) ((void *)((long)(err)))
#define PTR_ERR(ptr) ((long)(ptr))
#define IS_ERR(ptr) ((unsigned long)(ptr) > (unsigned long)(-1000))
``arch/x86/include/asm/uaccess_32.h:``::
#define copy_to_user(to,from,n) \
(__builtin_constant_p(n) ? \
__constant_copy_to_user((to),(from),(n)) : \
__generic_copy_to_user((to),(from),(n)))
``arch/sparc/kernel/head.S:``::
/*
* Sun people can't spell worth damn. "compatability" indeed.
* At least we *know* we can't spell, and use a spell-checker.
*/
/* Uh, actually Linus it is I who cannot spell. Too much murky
* Sparc assembly will do this to ya.
*/
C_LABEL(cputypvar):
.asciz "compatibility"
/* Tested on SS-5, SS-10. Probably someone at Sun applied a spell-checker. */
.align 4
C_LABEL(cputypvar_sun4m):
.asciz "compatible"
``arch/sparc/lib/checksum.S:``::
/* Sun, you just can't beat me, you just can't. Stop trying,
* give up. I'm serious, I am going to kick the living shit
* out of you, game over, lights out.
*/
감사의 글
860-870저자는 아이디어, 질문 답변, 오류 수정, 내용 추가에 기여한 Andi Kleen에게 감사합니다.
Philipp Rumpf는 맞춤법과 문장 명료성을 개선하고 쉽게 떠올리기 어려운 훌륭한 지적을 제공했습니다.
Werner Almesberger는 `disable_irq()`에 관한 좋은 요약을 제공했고, Jes Sorensen과 Andrea Arcangeli는 세부 사항을 명확히 했습니다.
Michael Elizabeth Chastain은 구성 섹션을 검증하고 내용을 추가했으며, Telsa Gwynne은 저자에게 DocBook을 가르쳤습니다.
감사의 글에 기록된 도움을 정리합니다.
Ringraziamenti
==============
Ringrazio Andi Kleen per le sue idee, le risposte alle mie domande,
le correzioni dei miei errori, l'aggiunta di contenuti, eccetera.
Philipp Rumpf per l'ortografia e per aver reso più chiaro il testo, e
per alcuni eccellenti punti tutt'altro che ovvi. Werner Almesberger
per avermi fornito un ottimo riassunto di :c:func:`disable_irq()`,
e Jes Sorensen e Andrea Arcangeli per le precisazioni. Michael Elizabeth
Chastain per aver verificato ed aggiunto la sezione configurazione.
Telsa Gwynne per avermi insegnato DocBook.
요약·해설
hacking.rst:1-870이 안내서는 CPU 실행 컨텍스트와 선점 계층을 출발점으로, 잠들 수 있는 함수의 제약, 사용자 메모리 접근, 할당 플래그, IRQ·bottom half 제어, 모듈 생명주기와 동기화 기본기를 연결합니다.
API 이름과 일부 권고는 역사적 문서 특성을 갖지만, 로컬 Linux v6.18.37 이탈리아어 원문의 870줄을 변경하지 않고 번역했습니다. 실제 코드에서는 현재 하위 시스템 문서와 구현을 함께 확인해야 합니다.