CONFIG OVVERO IL CONCEPIMENTO DEL KERNEL DI OPENBSD =================================================== Gianluigi Spagnuolo, Kirash Abstract ======== In questo documento vedremo cosa fa il comando config(8) quando viene eseguito durante la compilazione del kernel di OpenBSD. In particolare vedremo come vengono interpretate le varie direttive del file di configurazione e come viene gestito l'hardware. Il testo che segue è valido, quasi totalmente, per tutti i sistemi basati su 4.4BSD 1. 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 struttuta "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. 2. 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. E' 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 maxusers 32 che diventa -DMAXUSERS=32 Qyesto 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 bios0 at mainbus0 Alcuni device forniscono al proprio padre anche delle informazioni necessarie al proprio funzionamento, tali informazioni sono dette "locator" it0 at isa? port 0x290 I 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 3. 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. 3.1. 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 define attributo 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. E' 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 ? E' possibile anche assegnare dei valori di defaulta 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. 3.2. 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 #define NMCD 1 se il driver mcd deve essere presente nel kernel, #define NMCD 0 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 3.3. 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. 4. 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. 5. Riferimenti ============== - man config(8) - man files.conf(5) - man autoconf(9) - S.J.Leffer, M.J.Karels - "Building Berkeley UNIX Kernels with Config" 4.3BSD UNIX System Manager's Manual