Introduzione
Il programma config non fa altro che creare l'occorrente per la
compilazione del kernel a partire da un file di configurazione.
Generalmente viene eseguito quando si vuole compilare il kernel nella
directory contenente i file di configurazione, che per l'architettura i386
è /sys/arch/i386/conf
# cd /sys/arch/i386/conf
# config ./GENERIC
# cd ../compile/GENERIC
# make depend && make
D'ora in poi ci riferiremo ad un kernel di nome GENERIC compilato per una
architettura i386.
Il comando config(8) creerà la directory /sys/arch/i386/compile/GENERIC/,
dove GENERIC è il nome del kernel che si sta compilando.
All'interno di tale directory tra i tanti file quelli principali sono il
Makefile e "ioconf.c" che tra l'altro contiene la struttura "cfdata", ma
questo lo approfondiremo dopo.
Per prima cosa config(8) leggerà il file di configurazione principale, nel
nostro esempio "GENERIC", che deve avere come prima riga la direttiva
"machine" che indica il tipo di architettura (ad es. "machine i386").
Poi sarà letto il file delle regole indipendente dall'architettura
"../../../conf/files" e infine il file delle regole relativo
all'architettura scelta, ad esempio "./files.i386"
Di seguito vedremo in dettaglio come vengono gestite da config(8) le varie
direttive presenti all'interno del file di configurazione e quale è la
funzione e la struttura dei file delle regole.
File di configurazione
Il file di configurazione conterrà, oltre alle indicazioni sul tipo di
macchina e processore, le informazioni relative all'hardware che si vuole
configurare, le opzioni del kernel e vari altri valori impostabili
(maxusers, etc.).
Lo statement "option" viene usato per settare le opzioni del kernel.
È possibile usare "option" nelle seguenti forme
option NOME
option NOME=valore
Queste opzioni sono passate al compilatore con il flag -D, ad esempio le
righe
option I686_CPU
option WSDISPLAY_DEFAULTSCREENS=6
all'interno del file di configurazione, diverranno al momento della
compilazione rispettivamente
-DI686_CPU
-DWSDISPLAY_DEFAULTSCREENS=6
Lo stesso vale per la direttiva
che diventa
Questo valore viene usato per le dimensioni di alcune strutture dati del
kernel.
I device, invece, sono dichiarati usando la sintassi "figlio at padre",
ad esempio
Alcuni device forniscono al proprio padre anche delle informazioni
necessarie al proprio funzionamento, tali informazioni sono dette
"locator"
Gli pseudodevice sono dichiarati con la parola chiave "pseudo-device" seguita
dal nome e dal numero di istanze di quel particolare device
pseudo-device crypto 1
pseudo-device wsmux 2
Il significato di queste dichiarazioni sarà spiegato in dettaglio nei
prossimi paragrafi.
I file generati da config(8) popoleranno la directory "../compile/GENERIC"
In tale directory sarà presente anche un link simbolico a
"../../../../arch/i386/include" di nome "machine" ed un link a ""machine"
di nome "i386" per permettere l'utilizzo di include del tipo
#include <machine/vparam.h>
Device e files
Innanzitutto è necessaria una breve parentesi sui device all'interno del
kernel di OpenBSD.
Il kernel gestisce i device come un albero gerarchico, dove ogni singolo
device si attacca al device padre. Non ci sono restrizioni sul numero di
figli che un device può avere, ed è possibile anche avere più padri.
In cima alla gerarchia c'è lo pseudo-device "mainbus" che risulta esso
stesso attaccato al nodo fittizio "root". Indicativamente avremo una
struttura del genere:
eisa--
/
/ /
root-----mainbus ----isa --mcd
\
\ /
pci --
\
Le direttive relative ai device sono gestite da config(8) con l'ausilio
dei file delle regole, denominati files.*, sparsi per il kernel.
config(8) usa tali file per creare il contenuto della directory di
compilazione.
I file delle regole contengono le informazioni necessarie per fare il
parsing del file di configurazione; in pratica i files.* forniscono, per
ogni device e pseudo-device, varie informazioni sulla loro gestione.
In particolare indicano a quale device o bus si devono attaccare, quali
attributi e locator sono presenti, quali file includere e quali header
creare.
Attributi e locator
Come abbiamo visto prima i locator servono a passare informazioni dal
figlio al padre, assumono come valore un intero o una stringa che viene
risolta a intero.
Oltre ai locator è possibile definire anche degli attributi, che indicano
le proprietà del device. All'interno dei file delle regole sono definiti
con la parola chiave "define" nella seguente forma
oppure se si tratta di un attributo di tipo attachment è possibile anche
indicare i suoi locator, ad esempio
define pckbcport { [irq = -1], [port = -1]}
Ogni attributo può definire uno o più locator. È possibile fare
riferimento agli attributi attraverso la direttiva "device".
Nel file di configurazione è possibile fare riferimento solo ai locator
indicati nel file delle regole, e solo a quelli, ad esempio se nel file
files.isa è indicato
device isa {[port = -1], [size = 0], [iomem = -1], [iosiz = 0],
[irq = -1], [drq = -1], [drq2 = -1]}
all'interno del file di configurazione è valida la seguente linea
fdc0 at isa? port 0x3f0 irq 6 drq 2
mentre non lo è quest'altra
fdc0 at isa? port 0x3f0 pluto ?
È possibile anche assegnare dei valori di default ai locator, in genere
si usa 0 per gli indirizzi e -1 per gli indici e i numeri di drive.
Se un device ha un "?" come valore per un locator allora config(8) userà
il valore di default. Inoltre se il locator è scritto tra [] può essere
completamente omesso nella definizione del device.
Il "?" riferito al padre sta a indicare che il figlio può legarsi a tutti
i padri di quel tipo istanziati, incluso i cloni.
Esempio e flag
Vediamo adesso un esempio reale di gestione di un device da parte del
kernel, prendiamo in considerazione il driver dei CD-ROM Mitsumi.
Nel file di configurazione del kernel per attivare il supporto a tale
periferica basta solo la seguente riga
mcd0 at isa? port 0x300 irq 10
dove "port" e "irq" sono due locator di "isa" definiti nel file files.isa
come visto in precedenza.
In "sys/dev/isa/files.isa" troviamo le seguenti righe:
device mcd: disk, opti
attach mcd at isa
file dev/isa/mcd.c mcd needs-flag
la prima linea comunica a config(8) l'esistenza di un driver chiamato
"mcd" con gli attributi "disk" e "opti". La seconda linea indica che
"mcd" si lega al bus "isa", mentre la terza riga dice che il file
"dev/isa/mcd.c" deve essere compilato nel kernel se tale device è
inserito nel file di configurazione e se le dipendenze, indicate dopo
il nome del file, sono soddisfatte.
"needs-flag" sta a indicare che deve essere creato il file "mcd.h"
nella directory di compilazione, che conterrà la riga
se il driver mcd deve essere presente nel kernel,
in caso contrario.
config(8) creerà un makefile che contiene regole di compilazione diverse
per ogni file, quelli con estensione .c useranno la regola generica
${NORMAL_C}.
Per usare, invece, una regola diversa da quella standard bisogna usare il
comando "compile-with" alla fine della riga "file".
Altre etichette, oltre a "need-flag" e "compile-with", che possono essere
usate con i file sono:
device-driver
|
equivale a compile-with"${DRIVER_C}"
|
config-dependent
|
il file è compilato con la regola ${NORMAL_C_C}
oppure con ${DRIVER_C_C}
|
needs-count
|
indica al momento della compilazione quante
istanze sono presenti.
Ad esempio una riga del tipo
file net/bpf.c bpfilter needs-count
genererà un header file "bpfilter.h" contenente
la riga
"#define NBPFILTER 8"
|
Pseudo-device
Gli pseudo-device sono definiti allo stesso modo dei device veri e
propri, con la differenza che non si possono usare i locator
pseudo-device nome: attributi
Avremo ad esempio nel file sys/conf/files
pseudo-device pf: ifnet
file net/pf.c pf needs-flag
file net/pf_norm.c pf
file net/pf_ioctl.c pf
file net/pf_table.c pf
file net/pf_osfp.c pf
anche in questo caso, come per i device, possiamo vedere gli attributi,
i file da includere, le dipendenze e la regola need-flag.
Device e cfdata
Le relazioni tra i driver e i bus, tra padri e figli, e numerose altre
informazioni sono contenute in un array di strutture "cfdata", di nome
"cfdata", all'interno del file "ioconf.c" nella directory di compilazione.
La struttura "cfdata" è definita nel file "/sys/sys/device.h"
struct cfdata {
struct cfattach *cf_attach;
struct cfdriver *cf_driver;
short cf_unit;
short cf_fstate;
int *cf_loc;
int cf_flags;
short *cf_parents;
int cf_locnames;
void (**cf_ivstubs)(void);
short cf_starunit1;
};
Brevemente abbiamo che "cf_fstate" può assumere i seguenti valori:
FSTATE_NOTFOUND, FSTATE_FOUND, FSTATE_STAR (clone), FSTATE_DNOTFOUND e
FSTATE_DSTAR (la "D" sta per "disabilitato").
"cf_ivstubs" gestisce le interruzioni, "cf_unit" e "cf_loc" rappresentano
rispettivamente il numero di unità e un puntatore al primo locator
specifico del device.
"cf_parents" è un puntatore ad un eventuale padre, "cf_flags" indica i
flag dal file di configurazione e "cf_startunit1" indica il primo numero
di unità disponibile.
Le strutture "cfattach" e "cfdriver" sono definite nello stesso file nel
seguente modo:
struct cfattach {
size_t ca_devsize;
cfmatch_t ca_match;
void (*ca_attach)(struct device *, struct device *, void *);
int (*ca_detach)(struct device *, int);
int (*ca_activate)(struct device *, enum devact);
void (*ca_zeroref)(struct device *);
};
struct cfdriver {
void **cd_devs;
char *cd_name;
enum devclass cd_class;
int cd_indirect;
int cd_ndevs;
};
"cf_attach" rappresenta l'interfaccia tra il driver e il sistema,
convenzionalmente per ogni device il nome di tale struttura è composto dal
nome del device seguito da "_ca", ad esempio "rl_pci_ca".
La struttura "cf_attach" fornisce le funzioni match, attach, detach e
activate per ogni device.
Inoltre il campo "ca_devsize" rappresenta la quantità di memoria che il
framework per l'autoconfigurazione deve allocare ad ogni nuova istanza del
driver. Tale dimensione si riferisce alla struttura "softc" del driver che
conterrà le variabili relative allo specifico driver. In questo caso il
nome è dato dal nome del device seguito da "_softc".
Ogni struttura "cfdata" fa riferimento al proprio driver attraverso un
puntatore a "struct cfdriver". In questo caso il nome della struttura è
ottenuto aggiungendo "_cd" al nome del device, "rl_cd".
Tutti i campi, tranne "cd_devs" e "cd_ndevs", possono solo essere letti
dal sistema e non modificati.
"cf_driver" indica il nome del device e la classe. "cf_devs" è un
puntatore all'array di driver del device istanziati, e "cd_ndevs" indica
le dimensioni di tale array.
I vari tipi di classe sono elencati in "sys/sys/device.h"
enum devclass {
DV_DULL,
DV_CPU,
DV_DISK,
DV_IFNET,
DV_TAPE,
DV_TTY
};
da notare la classe "dull" che conterrà tutti i device che non rientrano
nelle altre classi.
Una tipica entry di cfdata è la seguente
struct cfdata cfdata[] = {
...
/* 37: rl* at pci* dev -1 function -1 */
{&rl_pci_ca, &rl_cd, 0, STAR, loc+205, 0, pv+101, 30, 0, 0}
...
};
con "rl_pci_ca" e "rl_cd" che valgono rispettivamente
struct cfattach rl_pci_ca = {
sizeof(struct rl_softc), rl_pci_match, rl_pci_attach,
};
struct cfdriver rl_cd = {
0, "rl", DV_IFNET
};
Oltre a "cfdata" nel file "ioconf.c" ci sono informazioni relative agli
pseudo-device, ai locator, ai parent vector, etc.