Initialisierung
Der Beispiel-Treiber heisst sbd (=simple block device). Die
Initialisierungsfunktion heisst sbd_init(). Die Aufgabe dieser Funktion
ist es die block Operationen aufzusetzen und diese dem System verfügbar
zu machen. Im ersten Schritt muss eine interne Datenstruktur für den Disk
Treiber aufgesetzt werden:
static struct sbd_device {
unsigned long size;
spinlock_t lock;
u8 *data;
struct gendisk *gd;
} Device;
Dabei ist size die Grösse des Disk in Bytes, data ist ein Array
in dem Daten abgelegt werden, lock ist der spinlock für
Wechselseitigen Ausschluss und gd ist die Kernel Representation des
Geräts. Die Geräte Initialisierung ist recht einfach: sie besteht im
Allozieren des Speichers und dem Initialisieren des spinlock.
Device.size = nsectors*hardsect_size;
spin_lock_init(&Device.lock);
Device.data = vmalloc(Device.size);
if (Device.data == NULL)
return -ENOMEM;
Dabei sind nsectors und hardsect_size die Geometrieparameter des
Disks. Nachdem diese Datenstrukturen aufgesetzt sind kann der Treiber
registriert werden:
major_num = register_blkdev(major_num, "sbd");
if (major_num <= 0) {
printk(KERN_WARNING "sbd: unable to get major number\n");
goto out;
}
Man beachte, dass keine operations Struktur übergeben wird. Die
Funktion register_blkdev wird möglicherweise in Zukunft eliminiert. Die
einzige Aufgabe die diese Funktion noch ausübt ist die Zuweisung einer
major Nummer und das Sichtbarmachen des Geräts in
/proc/devices.
Generic Disk
Die Funktionalität die früher in register_blkdev vorhanden, war ist nun
im generic disk (gendisk) enthalten.
Im ersten Schritt wird eine gendisk Datenstruktur für sbd
vorbereitet:
Device.gd = alloc_disk(16);
if (! Device.gd)
goto out_unregister;
Mit der Funktion alloc_disk(16) werden 16 minor Nummern
reserviert für dieses Gerät. Das bedeutet, dass 15 (1..15) Partitions
unterstützt
werden können. Da diese Funktion das Reservieren von Speicher verlangt, muss
der return value überprüft werden.
Die Initialisierung des gendisk erfolgt wie folgt:
Device.gd->major = major_num;
Device.gd->first_minor = 0;
Device.gd->fops = &sbd_ops;
Device.gd->private_data = &Device;
strcpy (Device.gd->disk_name, "sbd0");
set_capacity(Device.gd, nsectors*(hardsect_size/KERNEL_SECTOR_SIZE));
Das fops Feld zeigt auf eine block_device_operations
Datenstruktur. Das Feld private_data kann frei benützt werden. In
diesem Beispiel wird darin ein Zeiger auf die sbd_device abgelegt. Mit
der Funktion set_capacity wird dem Kernel die Grösse des Disk
mitgeteilt. Der Kernel kann mit Sektorgrössen von über 512 Bytes arbeiten, aber
intern arbeitet er immer mit 512 Bytes.
static struct request_queue *Queue;
/* ... */
Queue = blk_init_queue(sbd_request, &Device.lock);
if (Queue == NULL)
goto out;
blk_queue_hardsect_size(Queue, hardsect_size);
Device.gd->queue = Queue;
Der Parameter sbd_request stellt die Request Funktion dar. Falls die
Sektorgrösse nicht 512 Bytes beträgt muss dies dem Kernel mit der Funktion
blk_queue_hardsect_size() mitgeteilt werden. Am Schluss muss die
Adresse der Request Queue in der gendisk Struktur abgelegt werden.
Zum Abschluss muss das Blockgerät ins aktuelle System eingefügt werden:
add_disk(Device.gd);Da
add_disk() Zugriffe auf das Gerät vornehmen kann, muss das Gerät zu
diesem Zeitpunkt verfügbar sein.
request method
Das Herz des Treibers ist die request method.
static void sbd_request(request_queue_t *q)
{
struct request *req;
while ((req = elv_next_request(q)) != NULL) {
if (! blk_fs_request(req)) {
end_request(req, 0);
continue;
}
sbd_transfer(&Device, req->sector, req->current_nr_sectors,
req->buffer, rq_data_dir(req));
end_request(req, 1);
}
}
Der Device.lock wird von der request method gehalten. Um die
erste Anforderung aus der Queue zu erhalten wird die Funktion
elv_next_request() verwendet. Ein return value von null
bedeutet die Queue ist leer. Es können unterschiedliche Anforderungen in der
Request Queue sein, ein return value von nicht null bedeutet
einen normalen Dateisystem Request. Mit der Funktion end_request() wird
die request queue Verarbeitung abgeschlossen.
ioctl()
In dieser Treiberfunktion wird das Lesen der Diskgeometrie implementiert:
int sbd_ioctl (struct inode *inode, struct file *filp,
unsigned int cmd, unsigned long arg)
{
long size;
struct hd_geometry geo;
switch(cmd) {
/*
* The only command we need to interpret is HDIO_GETGEO, since
* we can't partition the drive otherwise. We have no real
* geometry, of course, so make something up.
*/
case HDIO_GETGEO:
size = Device.size*(hardsect_size/KERNEL_SECTOR_SIZE);
geo.cylinders = (size & ~0x3f) >> 6;
geo.heads = 4;
geo.sectors = 16;
geo.start = 4;
if (copy_to_user((void *) arg, &geo, sizeof(geo)))
return -EFAULT;
return 0;
}
return -ENOTTY; /* unknown command */
}
Shutdown
Beim Entladen des Moduls wird diese Funktion aufgerufen:
static void __exit sbd_exit(void)
{
del_gendisk(Device.gd);
put_disk(Device.gd);
unregister_blkdev(major_num, "sbd");
blk_cleanup_queue(Queue);
vfree(Device.data);
}
Mit del_gendisk() wird der generic Disk gelöscht. Mit
put_disk() wird die Referenz auf die gendisk freigegeben.