View Single Post
Old 07-01-2005, 14:27   #10
ilsensine
Senior Member
 
L'Avatar di ilsensine
 
Iscritto dal: Apr 2000
Città: Roma
Messaggi: 15625
Esempio di interfaccia con lo userspace: mmap

Un mmap (memory map) consente di accedere direttamente da userspace a una zona di memoria del kernel (oppure memoria fisica, o anche memoria di i/o). Un driver che supporta l'mmap deve implementare la chiamata omonima delle file_operations.
Il modo classico per effettuare una mappatura è la funzione remap_page_range (in trasformazione nella più recente remap_pfn_range), che non tratterò (i driver sono pieni di esempi). Mostrerò invece una tecnica molto elegante (e semplice!), chiamata "nopage".
Per semplicità ho rimosso le altre file_operations, lasciando la sola mmap:
Codice:
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/mm.h>
#include <linux/vmalloc.h>
#include <asm/uaccess.h>

static char *buf;
static int bsize = 4*PAGE_SIZE;

static struct page *kmisc_vm_nopage(struct vm_area_struct *vma,
		unsigned long address, int *type);

static struct vm_operations_struct kmisc_vm_operations = {
	.nopage		= kmisc_vm_nopage,
};

static struct page *kmisc_vm_nopage(struct vm_area_struct *vma,
		unsigned long address, int *type)
{
	struct page *pg;
	unsigned long pg_base = vma->vm_pgoff<<PAGE_SHIFT;
	unsigned long ofs = pg_base+(address-vma->vm_start);

	ofs &= PAGE_MASK;
	printk(KERN_INFO "%s: page fault at %08lx [vma %08lx-%08lx pgoff %08lx]\n",
		__func__, address, vma->vm_start, vma->vm_end,
		vma->vm_pgoff<<PAGE_SHIFT);
	if (ofs>=bsize)
		return NOPAGE_SIGBUS;
	pg = vmalloc_to_page(buf+ofs);
	if (pg)
		get_page(pg);
	if (type)
		*type = VM_FAULT_MINOR;
	return pg;
}

static int kmisc_mmap(struct file *filp, struct vm_area_struct *vma)
{
	unsigned long size  = vma->vm_end - vma->vm_start;
	unsigned long pg_base = vma->vm_pgoff<<PAGE_SHIFT;

	if ((pg_base+size)>bsize)
		return -EINVAL;
	if (!(vma->vm_flags&VM_SHARED))
		return -EINVAL;
	vma->vm_flags |= VM_RESERVED;
	vma->vm_ops = &kmisc_vm_operations;
	return 0;
}

static struct file_operations kops = {
	.owner		= THIS_MODULE,
	.mmap		= kmisc_mmap,
};

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

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

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


module_init(kmisc_init);
module_exit(kmisc_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("ilsensine");
MODULE_DESCRIPTION("miscdevice module");
Quando un utente effettua la chiamata mmap, il kernel prepara una vma (virtual memory area) già pronta per contenere la mappatura alle pagine di memoria che vogliamo. La funzione che gestisce la nostra mmap ha questo prototipo:
Codice:
static int kmisc_mmap(struct file *filp, struct vm_area_struct *vma)
Della vma, ci interessano sostanzialmente questi campi:
- vma->vm_start, vma->vm_end: limiti (indirizzi _virtuali_!!) della regione
- vma->vm_pgoff: offset di pagina (il parametro "offset" della syscall mmap, diviso per la dimensione di pagina).
In questo esempio la funzione kmisc_mmap fa veramente poco, anzi nulla: controlla la dimensione della regione, controlla che la mappatura sia "condivisa" (non può essere altrimenti, stiamo condividendo memoria tra kernel e user space; non possiamo gestire una MAP_PRIVATE qui), imposta il flag VM_RESERVED (informa il sistema di gestione della vm che non deve mettere in swapout questa vma) e imposta le vm_operations per la vma. Nessuna mappatura fisica viene creata tra la memoria di sistema e la vma del processo. Non ancora.
La prima volta che il processo tenterà di accedere fisicamente a questa vma, non essendoci ancora nessuna pagina di memoria mappata, verrà generato un page fault. Il kernel controllerà quindi se per questa vma esiste il metodo "nopage", e lo invocherà per chiedere l'assegnazione della pagina di memoria richiesta. Il kernel vuole solo la pagina, penserà lui al resto (gestione mmu, ecc.)
Alla funzione
Codice:
static struct page *kmisc_vm_nopage(struct vm_area_struct *vma,
		unsigned long address, int *type)
viene passata la vma di interesse e l'indirizzo al quale è avvenuto il page fault. Il terzo parametro serve per restituire il tipo di page fault (minor - la pagina è in memoria e deve essere solo rimappata - o major - è stato necessario i/o da un dispositivo di storage per recuperare la pagina).
Se il gestore nopage non può reperire la pagina, restituirà NOPAGE_SIGBUS (sostanzialmente NULL) e il programma si beccherà un SIGBUS.

Per testare la mmap di quero modulo si può usare il seguente programma, che legge il byte indicato nella forma (offset, posizione), e lo incrementa:
Codice:
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>

#define fail(x) do { \
	perror(x); \
	exit(-1); \
} while(0)

int main(int argc, char **argv)
{
	unsigned long ofs;
	unsigned long pos;
	unsigned long pg = getpagesize();
	int fd;
	void *map;
	unsigned char *bitmap;

	if (argc<3) {
		fprintf(stderr, "%s <offset> <byte pos>\n", argv[0]);
		return -1;
	}
	ofs = atoi(argv[1]);
	pos = atoi(argv[2]);
	if (ofs&(pg-1)) {
		fprintf(stderr, "L'offset deve essere allineato alla pagina\n");
		return -1;
	}
	fd = open("/dev/misc/kmisc", O_RDWR);
	if (fd<0)
		fail("open");
	map = mmap(NULL, pos+1, PROT_READ|PROT_WRITE, MAP_SHARED, fd, ofs);
	if (map==MAP_FAILED)
		fail("mmap");
	close(fd);
	bitmap = map;
	fprintf(stderr, "Vecchio valore: %02x\n", bitmap[pos]);
	bitmap[pos]++;
	fprintf(stderr, "Nuovo valore: %02x\n", bitmap[pos]);
	munmap(map, pos+1);
	return 0;
}
Se reintroducete le funzioni read/write, potrete effettuare un dump (con cat o dd) del device e analizzare dove sono finite le modifiche effettuate dal programma di test, al variare di offset+posizione.
__________________
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 15:53.
ilsensine è offline