Sendmail Copertina Rete

Articoli


Sysctl
Ristampato con il permesso del Linux Journal

La chiamata di sistema sysctl è un'interessante funzionalità offerta dal kernel Linux, ed è unica nel mondo Unix. Tale chiamata di sistema permette di accedere ai parametri di configurazione fine del kernel, ed è strettamente collegata al filesystem /proc: l'interfaccia "a file" di /proc permette infatti di svolgere gli stessi compiti disponibili tramite la chiamata di sistema. sysctl è stata introdotta nella versione 1.3.57 del kernel, e da allora è sempre stata supportata. Questo articolo spiega come usare sysctl con qualsiasi kernel compreso tra 2.0.0 e 2.1.35 (il più recente nel momento in cui scrivo).

Introduzione

Gli amministratori di sistema che gestiscono sistemi Unix hanno spesso bisogno di ottimizzare alcuni parametri di basso livello perché si adattino al meglio ai loro bisogni; nella maggior parte dei casi, questa personalizzazione del sistema obbliga a ricompilare il kernel e riavviare il calcolatore. Svolgere questi due compiti richiede molto tempo, e occorre una buona abilità e un po' di fortuna perché l'operazione vada a buon fine. Gli autori di Linux hanno scelto un approccio diverso, e hanno deciso di implementare parametri variabili per tutti i valori di configurazione, invece di costanti definite durante la compilazione: la configurazione di questi parametri durante il funzionamento del computer è poi stata resa possibile sfruttando il filesystem /proc. La struttura interna di sysctl non è solo stata progettata per leggere e modificare i parametri di configurazione, ma anche per gestire un insieme dinamico di tali parametri; in altre parole, chi scrive dei moduli del kernel può aggiungere nuove voci all'albero di variabili gestite da sysctl e permettere la configurazione delle caratteristiche del proprio driver durante il suo funzionamento.

L'interfaccia /proc al controllo del sistema

La maggior parte degli utenti Linux si abitua velocemente all'idea che sta sotto /proc. In breve, il filesystem può essere considereto una specie di finestra sulle strutture interne del kernel: i suoi file sono punti di accesso alle informazioni contenute nel kernel. Tali informazioni vengono di solito scambiate con l'esterno in forma testuale al fine facilitare l'uso interattivo, anche se alcuni file generano dati binari per poter essere usati da applicazioni specifiche. Il tipico esempio di file binario in /proc è /proc/kcore, un file di "core" che rappresenta lo stato attuale del kernel e permette di invocare "gdb /usr/src/linux/vmlinux /proc/kcore" per sbirciare nel proprio kernel. Ovviamente, se si compila vmlinux con l'opzione -g si ottengono maggiori informazioni quando si usa gdb su /proc/kcore.

Quasi tutti i file che vivono in /proc sono file in sola lettura: scriverci sopra non ha alcun effetto. Questo per esempio vale per i file /proc/interrupts, /proc/ioports, /proc/net/route e tutti gli altri file informativi. La directory /proc/sys invece si comporta in modo diverso: essa è la radice di un albero di file scrivibili strettamente connessi con il controllo delle caratteristiche del sistema. Ogni subdirectory in /proc/sys si occupa di un sottosistema del kernel, come per esempio net e vm, mentre la sottodirectory kernel contiene parametri che interessano l'intero sistema, come il nome del calcolatore.

Ogni file gestito da sysctl contiene valori numerici o stringa: a volte un solo valore e a volte un vettore di valori. Per esempio, questo è il conlenuto di alcuni di tali file La schermata seguente si riferisce alla versione 2.1.32 del kernel.


morgana.root# pwd
/proc/sys
morgana.root# grep . kernel/*
kernel/ctrl-alt-del:0
kernel/domainname:systemy.it
kernel/file-max:1024
kernel/file-nr:128
kernel/hostname:morgana
kernel/inode-max:3072
kernel/inode-nr:384     263
kernel/osrelease:2.1.32
kernel/ostype:Linux
kernel/panic:0
kernel/printk:6 4       1       7
kernel/securelevel:0
kernel/version:#9 Mon Apr 7 23:08:18 MET DST 1997

È interessante notare che less non riesce a leggere i file di /proc (almeno non la versione che ho io), perché tali file appaiono di lunghezza zero alla chiamata di sistema stat, usata da less per controllare la lunghezza del file prima di leggerlo. Questa inaccuratezza in stat è intenzionale, non si tratta di un baco: rappresenta un risparmio di tempo-uomo (nell'implementazione del codice) e di memoria (necessaria per portare in giro il codice). Le informazioni restituite da stat per la maggior parte dei file in /proc non sono importanti, poiché cat, grep e gli altri programmi funzionano egregiamente. Se davvero si ha bisogno di usare less su un file appartenente a /proc, si può sempre ricorrere a "cat file | less".

Se si vuole modificare un parametro di sistema, tutto quello che occorre fare è scrivere il nuovo valore nel file corretto all'interno di /proc/sys. Se il file contiene un vettore di valori, questi vengono sovrascritti in ordine. Vediamo come esempio il file kernel/printk (ma si noti che questo file è stato introdotto solo a partire dalla versione 2.1.32 del kernel). I quattro numeri che si trovano in /proc/sys/kernel/printk controllano il livello di ``prolissità'' della funzione printk del kernel Linux, e il primo numero rappresenta console_loglevel. I messaggi stampati dal kernel con priorità minore o uguale a console_loglevel vengono stampati sulla console di sistema (il terminale testo corrente, a meno che non sia stato scelto un terminale specifico per ricevere tali messaggi). Il parametro comuqnue non agisce sull'operato di klogd, che riceve in ogni caso tutti i messaggi. I comandi seguenti mostrano come cambiare console_loglevel:


morgana.root# cat kernel/printk
6       4       1       7
morgana.root# echo 8 > kernel/printk
morgana.root# cat kernel/printk
8       4       1       7

Un livello pari a 8 corrisponde ai messaggi di debug: normalmente questi messaggi non vengono stampati sulla console, mentre il comando echo precedente cambia il comportamento di printk in modo che tutti i messaggi appaiano sulla console, anche quelli di debug.

Allo stesso modo, si può cambiare l'hostname della propria macchina scrivendo il nuovo valore in /proc/sys/kernel/hostname. Questa può essere una funzionalità interessante nelle situazioni in cui non si ha il comando hostname a disposizione.

Uso della chiamata di sistema

Nonostante il filesystem /proc sia una gran bella trovata, a volte non è disponibile. Il filesystem non è vitale per la funzionalità del sistema, e ci sono casi in cui si può decidere di non includerlo nell'immagine del kernel, o semplicemente di non montarlo. Quando si compila un kernel per un sistema embedded, per esempio, poter risparmiare 40 o 50 kilobyte può essere un'opzione interessante; se poi si è molto preoccupati per la sicurezza del proprio sistema si può decidere di nascondere le informazioni relative al sistema stesso lasciando /proc smontato.

L'interfaccia alla configurazione di sistema tramite chiamata di sistema (cioè la chiamata sysctl, è un modo alternativo per accedere ai valori dei parametri configurabili e per cambiarli. Un vantaggio della chiamata di sistema rispetto all'uso di /proc è la sua maggiore velocità rispetto all'uso di script di shell, in quanto non bisogna fare né forkexec, nè la ricerca nell'albero dei file. In ogni caso però, a meno che non si stia usando una macchina molto vecchia, i guadagni in performance sono trascurabili.

Per usare la chiamata di sistema, bisogna includere l'header <sys/sysclt.h> nel proprio codice. Questo header dichiara la funzione nel modo seguente:


int sysctl (int *name, int nlen, void *oldval,
        size_t *oldlenp, void *newval, size_t newlen);

Se la libreria C che si sta usando non è aggiornata, la funzione non apparirà negli header di sistema, e non sarà definita nella libreria. Non so dire esattamente quando è stata introdotta tale funzione di libreria, ma posso assicurare che libc-5.0 non la dichiarava, mentre essa è presente in libc-5.3. Se si ha una vecchia versione della libreria, la chiamata di sistema deve essere invocata direttamente usando codice simile a questo:


#include <linux/unistd.h>
#include <linux/sysctl.h>

_syscall1(int, _sysctl, struct __sysctl_args *, args);
/* now "_sysctl(struct __sysctl_args *args)" can be called */

Come si vede, la chiamata di sistema riceve un solo argomento invece di sei, portando quindi ad una potenziale incompatibilità nei prototipi delle due funzioni (la chiamata di sistema e la funzione di libreria). Questa incompatibilità è stata risolta aggiungendo un carattere di sottolineatura in testa al nome della chiamata di sistema. Perciò, la chiamata di sistema è _sysctl e riceve un solo argomento, mentre la funzione di libreria si chiama sysctl e riceve sei argomenti. Il codice di esempio introdotto in questo articolo usa la funzione di libreria.

Gli argomenti della funzione hanno il seguente significato:

Cerchiamo adesso di scrivere del codice C per accedere ai quattro parametri di /proc/sys/kernel/printk. Il ``nome'' del file è KERN_PRINTK, all'interno della directory CTL_KERN. Il codice seguente è il programma completo per accedere a questi valori, e prende il nome di pkparms.c. Questo sorgente, come gli altri introdotti in questo articolo, è diponibile nell'archivio dei sorgenti.


#include <stdio.h>
#include <stdlib.h>
#include <sys/sysctl.h>
#include <linux/sysctl.h>

int main(int argc, char **argv)
{
        int name[] = {CTL_KERN, KERN_PRINTK};
        int namelen = 2;
        int oldval[8];  /* 4 would suffice */
        size_t len = sizeof(oldval);
        int i, error;


        error = sysctl (name, namelen, (void *)oldval, &len,
                NULL /* newval */, 0 /* newlen */);
        if (error) {
                fprintf(stderr,"%s: sysctl(): %s\n",
                        argv[0],strerror(errno));
                exit(1);
        }
        printf("len is %i bytes\n", len);
        for (i = 0; i < len/(sizeof(int)); i++)
                printf("%i\t", oldval[i]);
        printf("\n");
        exit(0);
}

Cambiare i valori di sysctl non è molto diverso dal leggerli: basta usare newval e newlen. Un programma simile a pkparms.c può essere usato per cambiare il console_loglevel, il primo numero in kernel/printk. La mia versione di tale programma si chiama setlevel.c, e contiene questo codice:


        int newval[1];
        int newlen = sizeof(newval);

        /* assign newval[0] */

        error = sysctl (name, namelen, NULL /* oldval */, 0 /* len */,
                newval, newlen);

Come si vede dal codice, il programma sovrascrive solo i primi "sizeof(int)" byte della voce del kernel, ma questo è proprio quello che si desidera. Se si compilano pkparms.c e setlevel.c si ricordi che i parametri di printk non vengono esportati tramite sysctl nella versione 2.0 del kernel. I due programmi non compileranno nemmeno sotto Linux-2.0 perché il simbolo KERN_PRINTK non viene definito dagli header del kernel. Se invece si compila uno di questi programmi con gli header di Linux-2.1 e si fa girare sotto la 2.0 si riceverà un messaggio di errore relativo all'invocazione di sysctl.

Un esempio d'uso dei due programmi appare qui sotto:


morgana.root# ./pkparms
len is 16 bytes
6       4       1       7
morgana.root# cat /proc/sys/kernel/printk
6       4       1       7
morgana.root# ./setlevel 8
morgana.root# ./pkparms
len is 16 bytes
8       4       1       7

Se non si ha acesso a Linux-2.1 non bisogna comunque disperarsi: i file mostrati sopra sono solo degli esempi e lo stesso codice può essere usato per accedere a qualunque altra voce di sysctl con minime modifiche.

Nello stesso file di sorgenti si trova anche il programma hname.c, che implementa una semplice versione di "hostname", usando la voce kernel/hostname gestita da sysctl. Tale sorgente funziona sia con Linux-2.0 che con Linux-2.1 ed è anche in grado di invocare la chiamata di sistema direttamente se la libreria di cui si dispone è abbastanza vecchia. Questo perché io faccio andare Linux-2.0 su un calcolatore basato su libc-5.0.

Un'occhiata ad alcune voci di sysctl

Nonostante si tratti di una risorsa di basso livello, i parametri configurabili del kernel sono una cosa molto interessante con cui giocare, e possono aiutare ad ottimizzare la performance del sistema per i diversi usi che si possono fare di una macchina Linux.

La lista seguente è una panoramica incompleta delle directory kernel e vm che si trovano in /proc/sys. Le informazioni seguenti valgono per tutte le versioni 2.0 del kernel e per le 2.1 fino almeno a 2.1.35.

L'interfaccia di programmazione: aggiunta di elementi nuovi

Chi scrive moduli del kernel può facilmente aggiungere parametri configurabili a /proc/sys usando l'apposita interfaccia di programmazione per estendere l'albero di controllo. Le funzioni seguenti possono essere invocate dai moduli a questo fine:


struct ctl_table_header * register_sysctl_table(ctl_table * table,
                                                int insert_at_head);
void unregister_sysctl_table(struct ctl_table_header * table);

La prima delle due viene usata per registrare una ``tabella'' di voci di controllo, e ritorna un puntatore che funge da identificatore di tale tabella. Tale puntatore deve essere passato alla seconda funzione per eliminare la propria tabella di voci dall'albero. L'argomento insert_at_head specifica se la nuova tabella deve essere inserita prima o dopo quelle esistenti, la questione può essere ignorata specificando 0 (non in testa) senza che questo causi alcun problema.

La domanda che sorge spontanea è: cosa rappresenta il tipo ctl_table? Si tratta di una struttura composta dei seguenti campi:

Mi rendo conto che la precedente lista può scoraggiare molti lettori, perciò non mostrerò qui i prototipi per le funzioni di gestione (proc_handler e ctl_handler) e passerò direttamente a mostrare del codice di esempio. Scrivere codice è molto più facile che comprenderlo, perché si può sempre iniziare copiando le parti di altri sorgenti. Il risultato di questo procedimento di copia e adattamento cadranno sotto la licenza GPL, ma personalmente non vedo alcuno svantaggio in ciò.

Cerchiamo allora di scrivere un modulo con due parametri interi, accessibili tramite sysctl. Il modulo effettua un ciclo di attesa per la durata di alcuni tick del timer di sistema, e dorme per un altro intervallo; i due parametri si chiamano ontime e offtime e controllano la durata di questi due intervalli, in numero di tick (centesimi di secondo sul PC e Sparc, millesimi su Alpha). Certo questo modulo svolge una funzione stupida, ma si tratta del codice più semplice che potessi immaginare che non dipenda dalla piattaforma hardware usata.

I parametri saranno visibili in /proc/sys/kernel/busy, una nuova directory dell'albero di controllo. Per ottenere questo dobbiamo registrare un albero come quello mostrato in figura.


La figura è anche disponibile in postscript come sysctlbusy.ps.


La directory kernel non verrà creata dalla funzione register_sysctl_table, perché esiste già, mentre la subdirectory busy dovrà essere creata. Allo stesso modo busy verrà cancellata quando il modulo viene rimosso, ma kernel no perché conterrà ancora dei file attivi. L'interfaccia usata da sysctl permette in questo modo di aggiungere dei file a tutte le directory di /proc/sys.

Il lavoro relativo a sysctl viene svolto da queste linee di codice in busy.c:


#define KERN_BUSY 434 /* a random number, high enough */
enum {BUSY_ON=1, BUSY_OFF};

int busy_ontime = 0;   /* loop 0 ticks */
int busy_offtime = HZ; /* every second */

/* two integer items (files) */
static ctl_table busy_table[] = {
        {BUSY_ON, "ontime", &busy_ontime, sizeof(int), 0644,
        NULL, &proc_dointvec, &sysctl_intvec, /* fill with 0's */},
        {BUSY_ON, "offtime", &busy_offtime, sizeof(int), 0644,
        NULL, &proc_dointvec, &sysctl_intvec, /* fill with 0's */},
        {0}
        };

/* a directory */
static ctl_table busy_kern_table[] = {
        {KERN_BUSY, "busy", NULL, 0, 0555, busy_table},
        {0}
        };

/* the parent directory */
static ctl_table busy_root_table[] = {
        {CTL_KERN, "kernel", NULL, 0, 0555, busy_kern_table},
        {0}
        };

static struct ctl_table_header *busy_table_header;

int init_module(void) 
{
        busy_table_header = register_sysctl_table(busy_root_table, 0);
        if (!busy_table_header)
                return -ENOMEM;
        busy_loop();
        return 0;
}

void cleanup_module(void)
{
        unregister_sysctl_table(busy_table_header);
}

Come si vede, in questo modulo si lascia tutto il lavoro duro alle funzioni proc_dointvec e sysctl_intvec. Questi due gestori vengono esportati solo a partide dalla versione 2.1.8 del kernel, perciò occorrerà copiare il relativo codice nel proprio modulo o implementare funzionalità simili qualora si voglia usare sysctl all'interno di moduli che girino con la version 2.0 del kernel.

Non mostrerò qui il codice relativo ai cicli di attesa effettuati dal modulo, perché tale codice non contiene niente di interessante relativo all'argomento qui trattato. Il sorgente completo del modulo busy è distribuito con gli altri sorgenti e ciascuno può provare il modulo a casa propria. Questo codice è stato provato con il kernel 2.0 e 2.1, su Intel, Alpha e Sparc.

Per saperne di più

Nonostante l'utilità della chiamata sysctl, è difficile trovare documentazione al riguardo. Questo non è un problema per i programmatori di sistema, che sono abituati a sbirciare i sorgenti, dai quali si può estrarre un sacco di informazione.

I punti di accesso principali alla struttura interna di sysctl nel kernel sono kernel/sysctl.c e net/sysctl_net.c. La maggior parte delle voci nelle tabelle di controllo agiscono su numeri interi, stringhe o vettori di interi, per cui si potrà usare il campo data come chiave di ricerca da usare con grep per cercare le informazioni nei sorgenti. Non ci sono a mio avviso vie più dirette per raccogliere informazioni.

Come esempio, vediamo qui come trovare il significato del file ip_log_martians, che appare in /proc/sys/net/ipv4 dalla versione 2.1.15 in poi.

Il file sysctl_net.c si riferisce alla struttura ipv4_table per quello che riguarda net/ipv4, la quale è esportata da sysctl_net_ipv4.c. Quest'ultimo file contiene questa voce all'interno della tabella:


        {NET_IPV4_LOG_MARTIANS, "ip_log_martians",
         &ipv4_config.log_martians, sizeof(int), 0644, NULL,
         &proc_dointvec},

Per sapere il significato del file perciò basta cercare il campo ipv4config.log_martians all'interno dei sorgenti. Si scoprirà che tale valore viene usare per controllare la stampa diagnostica attraveso printk dei pacchetti erronei ricevuti dal calcolatore, quelli con mittente inesistente, quindi "marziano".

Sfortunatamente, molti amministratori di sistema non sono programmatori e hanno bisogno di fonti di informazione alternative. Per fortuna talvolta gli sviluppatori del kernel scrivono documentazione per divagare dallo scrivere codice; questi documenti vengono poi distribuiti insieme al sorgente del kernel. La cattiva notizia è che sysctl è un progetto abbastanza recente, perciò la documentazione allegata è scarsa.

Il file Documentation/networking/Configurable contiene una breve introduzione alla chiamata di sistema sysctl (molto più breve di questo articolo), e include un puntatore al file net/TUNABLE, il quale è una grossa lista di parametri configurabile nel sottosistema di rete del kernel. La descrizione di ciascuna voce non è troppo comprensibile ai lettori non specializzati, ma questo tipo di utenti di solito non potrebbe comunque trarre vantaggio dalla possibilità di controllare questi parametri. Non conosco al momento atre fonti di informazione al riguardo del controllo di sistema, a parte il codice sorgente.

di Alessandro Rubini


Sendmail Copertina Rete