View Single Post
Old 07-01-2005, 12:47   #8
ilsensine
Senior Member
 
L'Avatar di ilsensine
 
Iscritto dal: Apr 2000
Città: Roma
Messaggi: 15625
Esempio di interfaccia con lo userspace: read e write

Ora che abbiamo visto come creare un nodo di comunicazione con i programmi user space, implementiamo delle semplici operazioni di lettura/scrittura. L'idea è di allocare una certa quantità di memoria, e utilizzarla per scrivere e leggere da userspace. Il dispositivo quindi si comporterà come un file di dimensione fissa.
Dico subito che questo driver ha una grossolana race condition, che userò come spunto in seguito per introdurre un esempio di lock.
Codice:
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <asm/uaccess.h>

static char *buf;
static int bsize = PAGE_SIZE;

ssize_t kmisc_read(struct file *filp, char __user *dst, size_t count, loff_t *off)
{
	ssize_t ret = 0;
	printk(KERN_INFO "%s: reading up to %d bytes at offset %lld\n", __func__,
		count, *off);
	if ((*off+count)>bsize)
		count = bsize-*off;
	if (count) {
		if (copy_to_user(dst, buf+*off, count))
			ret = -EFAULT;
		else {
			ret = count;
			*off += count;
		}
	}
	return ret;
}

ssize_t kmisc_write(struct file *filp, const char __user *src, size_t count, loff_t *off)
{
	ssize_t ret = 0;
	if ((*off+count)>bsize) {
		printk(KERN_WARNING "%s: discarding %lld bytes on %d\n", __func__,
			(*off+count)-bsize, count);
		count = bsize-*off;
	}
	printk(KERN_INFO "%s: writing %d bytes at offset %lld\n", __func__,
		count, *off);
	if (count) {
		if (copy_from_user(buf+*off, src, count))
			ret = -EFAULT;
		else {
			ret = count;
			*off += count;
		}
	}
	return ret;
}

static struct file_operations kops = {
	.owner		= THIS_MODULE,
	.read		= kmisc_read,
	.write		= kmisc_write,
};

static struct miscdevice kmisc = {
	.minor 	= MISC_DYNAMIC_MINOR,
	.name 	= "kmisc",
	.fops	= &kops
};

static int __init kmisc_init(void)
{
	int ret;
	buf = kzalloc(bsize, GFP_KERNEL);
	if (!buf)
		return -ENOMEM;
	ret = misc_register(&kmisc);
	if (!ret)
		printk(KERN_INFO "kmisc registered on minor %d\n", kmisc.minor);
	else
		kfree(buf);
	return ret;
}

static void __exit kmisc_exit(void)
{
	misc_deregister(&kmisc);
	kfree(buf);
}


module_init(kmisc_init);
module_exit(kmisc_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("ilsensine");
MODULE_DESCRIPTION("miscdevice module");
Ho rimosso i metodi open/release in quanto non dobbiamo fare cose particolari in quel contesto. Ho aggiunto i metodi read/write. Questi metodi non forniscono più il parametro inode; se necessario, può essere ottenuto da filp->f_dentry->d_inode. Entrambi i metodi restituiscono il numero di byte letti/scritti, se non si verificano errori.
La parte più importante del codice è come vengono letti e scritti i valori da userspace. I buffer userspace forniti alle funzioni hanno l'attributo __user, una macro che viene espansa in una direttiva per il compilatore che sostanzialmente significa "sevizia a sangue lo sviluppatore se tenta di accedere direttamente a questo puntatore". Accedere allo spazio di memoria utente è una operazione molto delicata, per due principali ragioni:
- sicurezza: l'utente può fornire alla read un puntatore puntante alla memoria del kernel; occorre controllare che appartenga al suo spazio di indirizzamento
- gestione della memoria: il buffer indicato dall'utente può non essere fisicamente in memoria: potrebbe essere finito in swap, oppure essere una pagina COW. In questo caso tentare di accedere a quella zona provoca un page fault, che va gestito.
Di queste operazioni se ne occupano le funzioni di accesso alla memori autente: copy_to_user, copy_from_user, get_user, put_user. Queste funzioni restituiscono 0 se l'operazione è andata a buon fine (notate che può essere necessario gestire un page fault, quindi queste funzioni possono causare un context switch verso altre parti del kernel senza preavviso), oppure un valore non nullo se si è verificato un errore (ad es. indirizzo non valido o riferito a memoria non mappata; in questo caso occorre restituire l'errore -EFAULT -- notate che questo _non_ causa un segmentation fault del programma).
Per ultimo, notate che la posizione attuale del file è presente nel parametro loff_t *off: proprio qui ho messo una bella race, in quanto tutte le funzioni lo utilizzano e modificano allegramente senza alcuna sicurezza che qualche altro thread del programma utente stia facendo lo stesso.

Potete testare questo modulo scrivendo qualcosa tramite echo, e rileggendola tramite cat.
__________________
0: or %edi, %ecx; adc %eax, (%edx); popf; je 0b-22; pop %ebx; fadds 0x56(%ecx); lds 0x56(%ebx), %esp; mov %al, %al
andeqs pc, r1, #147456; blpl 0xff8dd280; ldrgtb r4, [r6, #-472]; addgt r5, r8, r3, ror #12

Ultima modifica di ilsensine : 23-02-2006 alle 16:49.
ilsensine è offline