Una delle novità del kernel 2.6 è il filesystem sysfs, progettato come interfaccia verso lo spazio utente delle strutture kobject, progettate per semplificare comuni operazioni di gestione degli oggetti e renderle immuni da corse critiche.
Nonostante un utilizzo canonico del filesystem richieda una struttura rigorosa dei file creati e una standardizzazione nella presentazione del loro contenuto, è possibile prendere confidenza con le strutture dati associate a sysfs anche senza rispettare tutte le regole. Il codice descritto è stato utilizzato con Linux-2.6.6 per esportare attraverso sysfs un parametro intero di un modulo, il cui valore puo` variare solo in un intervallo predefinito; gli estremi dell'intervallo sono anch'essi esportati attraverso sysfs, ma in sola lettura.
Non è la prima volta che il kernel offre un meccanismo centralizzato
per semplificare l'esportazione di parametri verso lo spazio utente.
Il meccanismo sysctl, acessibile attraverso una chiamata di sistema
apposita oppure tramite l'albero di directory /proc/sys/, era già
disponibile in Linux-2.0, e svolgeva il proprio lavoro in maniera
accettabile. Una descrizione di sysctl si trova, per esempio, su
http://www.linux.it/kerneldocs/sysctl
.
Quello che sysctl non offre è una strutturazione ordinata ed scalabile dei driver; l'immunità da corse critiche; una gestione "ad oggetti" delle entità registrate. Con strutturazione scalabile si intende un'organizzazione delle informazioni che non collassi al crescere delle unita` di informazione trattate.
L'interfaccia sysctl è ancora disponibile immutata nel kernel 2.6;
e` ancora possibile usarla e sperimentarne i limiti; in particolare si
puo` facilmente generare una corsa critica da cui scaturisce un errore
del kernel. I comandi che seguono fanno uso di un modulo
smallsysctl, che è disponibile, con gli altri sorgenti di questo
articolo, sul CD redazionale allegato alla rivista ma anxhe all'indirizzo
http://www.linux.it/kerneldocs/sysfs/src.tar.gz
.
L'errore (un
burla$ insmod smallsysctl.ko
burla% echo 1 2 3 4 > /proc/sys/dev/smallsysctl/ints
burla% cat /proc/sys/dev/smallsysctl/ints
1 2 3 4
burla$ (sleep 10; cat) < /proc/sys/dev/smallsysctl/ints
Segmentation fault
burla$ cat /proc/sys/dev/smallsysctl/ints
Segmentation fault
Oops
del kernel che continuerà a ripresentarsi fino
al riavvio della macchina) deriva dall'aver rimosso il modulo
smallsysctl, da un altra sessione, durante l'esecuzione di sleep 10
,
mentre il file ints
era aperto.
Anche se almeno uno dei due utenti deve essere l'amministratore di sistema, risulta chiaro come occorra pensare a un meccanismo piu` affidabile.
struct kobject
La struttura kobject
, definita in <linux/kobject.h>
, può
essere considerata una classe prototipale dalla quale si possono
derivare altre classi che ne ereditano i metodi (usando la
terminologia della programmazione orientata agli oggetti). Ogni
istanza della classe è dotata di un nome, di un contatore dei
riferimenti attivi, di una posizione tra gli altri oggetti del sistema
in una struttura ad albero, di alcuni metodi prefefiniti e di un tipo
(struct kobj_type
). Il "tipo" serve a dichiarare il distruttore
dell'oggetto, una serie di attributi e i metodi per leggere e scrivere
tali attributi attravero sysfs
La struttura kobject
viene comunemente usata come elemento di una
struttura più articolata. Per esempio, il file di esmepio int.c
,
riprodotto in questo articolo, dichiara nelle prime righe una la
struttura kint
, una classe derivata da kobject
, contenente tre
numeri interi.
La riga successiva nel sorgente usa la macro container_of
per
recuperare il puntatore a kint
a partire dal puntatore alla
struttura kobject
ivi contenuta. La macro, definita in
<linux/kernel.h>
, funziona correttamente anche quando la struttura
interna non è il primo campo della struttura che la contiene (come
rappresentato in figura 1).
Le funzioni int_init e int_exit usano i metodi predefiniti per l'inizializzazione dell'oggetto (init, set_name). Secondo le convenzioni del codice di Linux, un nuovo riferimento all'oggetto viene creato dal metodo get e viene rimosso dal metodo put; poiché il contatore dei riferimenti all'oggetto viene posto a 1 da kobject_init, nel codice appare solo kobject_put, nella funziona di uscita.
Il distruttore dell'oggetto, in questo caso la funzione kint_release, viene invocato solo quando l'oggetto non è più referenziato da altro codice; se l'oggetto è associato ad un file e questo file è aperto il contatore dei riferimenti non può essere zero, perche` ad ogni apertura di un file associato all'oggetto il kernel invoca il suo metodo get.
Gestire l'interazione tra i riferimenti a kobject
e i riferimenti
ad un modulo in corso di rimozione non è immediato; per una
discussione del problema e del codice rimando agli articoli di
Jonathan Corbet (si veda il riquadro 2). Nel modulo di esempio usato
in questa sede, la procedura di uscita del modulo semplicemente
aspetta che l'oggetto venga distrutto prima di ritornare, poiche` al
suo ritorno il chiamante distruggera` il modulo. È una soluzione
subottimale ma sufficiente per impedire l'errore visto in precedenza
con sysctl.
struct attribute
Ogni tipo di oggetti ha un insieme predefinito di attributi, ai quali
ogni istanza di oggetto può aggiungerne altri. Il modulo di esempio,
che gestendo una sola istanza di oggetto, usa solo gli attributi
predefiniti; il tipo ktype_int
, definito in questo modulo,
dichiara tre attributi, corrispondenti ai tre numeri interi che
fanno parte della struttura kint
.
Le strutture struct attribute
vengono gestite come struct kobject
:
sono pensate per essere parte di una struttura più ampia e
per questo definiscono solo un nome e una maschera di permessi di
accesso, due parametri usati per instaziare l'attributo in sysfs.
Nel programma di esempio la truttura più ampia che contiene
struct attribute
e` stata chiamata kint_attribute
; essa definisce un
campo index
per identificare l'attributo nel vettore che fa parte
di struct kint
e i due metodi show e store.
I due metodi vengono usati per comunicare con lo spazio utente: show mostra il valore dell'attributo in un file, mentre store serve a memorizzare un nuovo valore dell'attributo quando l'utente scrive il file associato.
Il collegamento tra i metodi associati ai singoli attributi di un
oggetto e il virtual file system sono le sysfs_ops dichiarate
nella struttura kobj_type
. L'utilizzo dei nomi show e store
sia per le operazioni generiche sull'oggetto sia per quelle specifiche
su ciascun attributo è la prassi per i programmatori del kernel,
quindi l'esempio int.c segue la stessa convenzione.
Appoggiandosi su questa infrastruttura di oggetti e attributi, sysfs esporta verso lo spazio utente l'albero degli oggetti e dei loro attributi sotto forma di file. Il filesystem implementa la sua struttura file_operations (introdotta da Daniele Bellucci il mese scorso nel riquadro degli approfondimenti) come ponte tra le chiamate di sistema effettuate dai processi e i metodi show e store implementati da ciascun oggetto.
Se sysfs non è ancora montato nell'albero dei file della macchina,
si può montare, dopo aver creato la directory /sys
, con il comando:
Si noti che a partire dalla versione 2.6.6 il codice relativo a
sysfs può essere disabilitato al momento della configurazione del
kernel, mentre nelle versioni precedenti era sempre presente
nell'immagine compilata del sistema. Ha senso eliminare sysfs solo
quando si lavora in ambienti limitati come i dispositivi embedded;
chi avesse disabilitato sysfs nel proprio PC e` invitato a
ricompilare il kernel.
mount -t sysfs none /sys
La registrazione di un oggetto nell'albero dei file viene effettuata dalla funzione kobject_add, che int.c chiama nella procedura di inizializzazione; la rimozione è ottenuta tramite kobject_del.
Ogni oggetto viene rappresentato in sysfs come una directory; avrete probabilmente notato, infatti, come molte directory dell'albero non contengono file (per esempio, sulla mia macchina /sys/devices/system contiene dieci subdirectory e nessun file). La posizione nell'albero della directory relativa ad ogni oggetto dipende da come l'oggetto e` stato inserito nell'infrastruttura di sistema.
Poiche` nel modulo di esempio non abbiamo inserito l'oggetto
nell'albero degli oggetti di sistema, la directory
sample-int-range
, (nome di kint->kobj
), appare direttamente
sotto la radice di /sys
.
I file regolari che appaiono all'interno di sysfs rappresentano gli
attributi, ma il sistema non esporta implicitamente tutti gli
attributi degli oggetti che vengono registrati; per questo motivo
int_init chiama la procedura sysfscreatefile una volta per ogni
attributo. Quando si vuole rimuovere un attributo dal filessytem, la
procedura da chiamare e` sysfs_remove_file. Per simmetria, il
modulo int
invoca sysfs_remove_file nella sua procedura di
pulizia, anche se gli attributi vengono automaticamente rimossi quando
kobject_del rimuove l'oggetto dal filesystem.
I tre attributi esportati da int.c sono tre numeri interi, associati
a tre file ASCII nella directory sample-int-range
. I tre file
rappresenntano valore minimo, valore corrente e valorme massimo di un
parametro intero modificabile dall'utente. I file associati ai minimo
e al massimo (chiamati min
e max
) sono in sola-lettura mentre
il file relativo al valore corrente, (chiamato val
) e`
scrivibile. I permessi di accesso sono stati indicati tramite il campo
mode di struct attribute
.
La lettura di un attributo (metodo show) viene effettuata semplicemente con sprintf, per convertire in ASCII il valore binario del numero intero richiesto.
Il buffer passato al metodo show e` sempre di dimensione
PAGE_SIZE
, perche` sysfs, come tmpfs alloca le pagine dei file
direttamente nella cosiddetta page cache. Una pagina e`
solitamente 4kB o piu`, ma il valore dipende dalla piattaforma
hardware su cui sta girando il sistema. Per sicurezza si consiglia di
usare snprintf per riempire il buffer, ma nel nostro esempio
sprintf e` accettabile perche` la rappresentazione ASCII di una
variabile intera e` sicuramente inferiore ad una pagina.
La scrittura (metodo store) si appoggia su sscanf e confronta il valore passato con il minimo e il massimo; a tal fine l'implementazione di store sa che i due estremi dell'intervallo accettabile di valori sono consecutivi al valore corrente in memoria, all'interno in un vettore di interi.
Se un metodo ritorna il valore negativo -EINVAL
, l'errore viene
riportato al processo nello spazio utente. Questo succede, qualora il
valore passato non ricade nell'intervallo accettabile oppure non e` la
rappresentazione testuale di un numero intero (sono pero` consentiti
caratteri di spazio prima e dopo il numero).
burla$ grep . /sys/sample-int-range/*
/sys/sample-int-range/max:5
/sys/sample-int-range/min:0
/sys/sample-int-range/val:1
burla$ /bin/echo 2 > /sys/sample-int-range/val
burla$ /bin/echo 20 > /sys/sample-int-range/val
/bin/echo: write error: Invalid argument
burla$ /bin/echo 3tigri > /sys/sample-int-range/val
/bin/echo: write error: Invalid argument
burla$ cat /sys/sample-int-range/val
2
Questa introduzione a sysfs rimane molto alla superficie dell'infrastruttura a oggetti che permea la versione 2.6 del kernel, un'infrastruttura che permette tra l'altro rimpiazzare devfs nello spazio utente (come gia` riportato da Daniele Bellucci il mese scorso), come pure di gestire l'inserimento e la rimozione a caldo delle periferiche. Una trattazione approfondita dovrebbe toccare molte altre strutture dati ed estendersi su molte piu` pagine, diventando sicuramente tediosa. Invito chi e` interessato ad approfondire ad usare i riferimenti riportati nel riquadro 2; riferimenti che sono per l'appunto molto piu` ampi di questo articolo.
container_of
int.c
/* Copyright (c) 2004 Alessandro Rubini. GNU GPL 2 or later */
#include <linux/config.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
MODULE_LICENSE("GPL");
/* the toplevel kint structure */
struct kint {
struct kobject kobj;
int numbers[3]; /* min, val, max */
};
/* macros to extract the container structure from the generic one */
#define to_kint(obj) container_of(obj, struct kint, kobj)
#define to_int_attr(attr) container_of(attr, struct kint_attribute, attr)
/* Functions to read and write one signed integer in a range */
ssize_t kint_one_show(int *what, char *buf)
{
return sprintf(buf, "%i\n", *what);
}
ssize_t kint_one_store(int *what, const char *buf, size_t count)
{
int result;
char s[4];
if (sscanf(buf, "%i %2s", &result, s) != 1)
return -EINVAL;
if (result < what[-1] || result > what[1])
return -EINVAL;
*what = result;
return strlen(buf);
}
/* This customized attribute allows to read min/max and read/write val */
struct kint_attribute {
struct attribute attr;
int index;
ssize_t (*show)(int *, char *);
ssize_t (*store)(int *, const char *, size_t count);
};
struct kint_attribute kint_attr_min = {
.attr = {.name = "min", .mode = S_IRUGO },
.index = 0,
.show = kint_one_show
};
struct kint_attribute kint_attr_val = {
.attr = {.name = "val", .mode = S_IRUGO | S_IWUGO },
.index = 1,
.show = kint_one_show,
.store = kint_one_store
};
struct kint_attribute kint_attr_max = {
.attr = {.name = "max", .mode = S_IRUGO },
.index = 2,
.show = kint_one_show
};
struct attribute * kint_default_attrs[] = {
&kint_attr_min.attr,
&kint_attr_val.attr,
&kint_attr_max.attr,
NULL
};
/* top level show/store functions, called by sysfs */
ssize_t kint_show(struct kobject * kobj, struct attribute * attr, char * buf)
{
struct kint *ki = to_kint(kobj);
struct kint_attribute *ka = to_int_attr(attr);
ssize_t ret = 0;
if (ka->show)
ret = ka->show(&ki->numbers[ka->index], buf);
return ret;
}
ssize_t kint_store(struct kobject * kobj, struct attribute * attr,
const char * buf, size_t count)
{
struct kint *ki = to_kint(kobj);
struct kint_attribute *ka = to_int_attr(attr);
ssize_t ret = 0;
if (ka->store)
ret = ka->store(&ki->numbers[ka->index], buf, count);
return ret;
}
struct sysfs_ops kint_sysfs_ops = {
.show = kint_show,
.store = kint_store,
};
/* our object, dynamically allocated */
struct kint *kint;
/* our release method */
void kint_release(struct kobject *kobj)
{
struct kint *ki = to_kint(kobj);
kfree(ki);
kint = NULL; /* no doubt it's this one */
}
/* the ktype is referenced by kobject */
struct kobj_type ktype_int = {
.release = kint_release,
.sysfs_ops = &kint_sysfs_ops,
.default_attrs = kint_default_attrs,
};
/* module initialization and cleanup */
int int_init(void)
{
int i, ret;
kint = kmalloc(sizeof(*kint), GFP_KERNEL);
if (!kint) return -ENOMEM;
memset(kint, 0, sizeof(*kint));
kobject_init(&kint->kobj);
kint->numbers[0] = 0;
kint->numbers[1] = 1;
kint->numbers[2] = 5;
kint->kobj.ktype = &ktype_int;
ret = kobject_set_name(&kint->kobj, "sample-int-range");
if (!ret) ret = kobject_add(&kint->kobj);
if (!ret) {
for (i=0; i<3; i++)
sysfs_create_file(&kint->kobj,
kint_default_attrs[i]);
}
if (ret)
kfree(kint);
return ret;
}
void int_exit(void)
{
int i;
for (i=0; i<3; i++)
sysfs_remove_file(&kint->kobj,
kint_default_attrs[i]);
kobject_del(&kint->kobj);
kobject_put(&kint->kobj);
/* wait untile the kobj is freed */
while (kint) {
schedule_timeout(HZ/2);
}
return;
}
module_init(int_init);
module_exit(int_exit);
Una discussione più approfondita di struct kobject
, sysfs,
e del modello di driver (driver model) si trova nei file della
documentazione del kernel:
Documentation/kobject.txt
descrive strutture kobject
e la struttura
in cui si inseriscono (struct kset
e altro);
Documentation/filesystems/sysfs.txt
descrive l'interfaccia di sysfs,
gli attributi e i tipi di attributi predefiniti;
Documentation/driver-model
descrive in vari file il driver model
La documentazione di Jonathan Corbet, http://lwn.net/Kernel/
, è
come sempre molto interessante e ben scritta.