[Tar] [About] [Copertina] [indice] |
Articoli
Traduzione di Andrea Cisternino acister@sunfe3.fe.infn.it
Questo è l'ultimo di una serie di cinque articoli sui character device drivers. In questa parte finale Georg si occupa dei devices per i quali è possibile il memory mapping, iniziando con una descrizione generale del sistema di memory management di Linux. di Georg v. Zezschwitz
Anche se pochi driver implementano la tecnica del memory mapping, essa ci fornisce interessanti punti di vista sul funzionamento di Linux. Introdurrò la gestione della memoria e le sue caratteristiche, permettendoci così di giocare con la console, di aggiungere il memory mapping ai drivers e di "piantare" il sistema...
Sin dai giorni del 80386, il mondo Intel supporta una tecnica chiamata indirizzamento virtuale. Proveniendo dallo Z80 e dal mondo 68000, la prima cosa che ho associato a questo termine è stata ``è possibile allocare più memoria della RAM fisica, poiché alcuni indirizzi verranno associati a parti del disco rigido''.
Per essere più accademico: ogni indirizzo usato dal programma per
accedere alla memoria (non importa se codice o dati) sarà
tradotto---o in un indirizzo fisico nella RAM, o in una eccezione
che sarà gestita dal sistema per fornire la memoria richiesta.
Alcune volte, tuttavia, l'accesso ad una particolare locazione di memoria
virtuale rivela che il programma non si sta comportando correttamente---in
questo caso, il sistema operativo genererà una ``vera'' eccezione
(normalmente il segnale 11, SIGSEGV
).
L'unità più piccola utilizzata da questo sistema di
traduzione è la pagina, che è di 4 kB
sull'architettura Intel e di 8 kB sull'Alpha (è definita in
asm/page.h
).
Quando si cerca di comprendere il sistema di traduzione dell'indirizzo si entra in una confusione di page table descriptors, segment descriptors, page table flags e diversi spazi di indirizzamento. Per ora diciamo che l'indirizzo logico (o virtuale) è quello usato dal programma; esso viene trasformato tramite le page-tables in un indirizzo fisico (o un page-fault). La Linux Kernel Hacker's Guide impiega una ventina di pagine per spiegare questo meccanismo, e non credo che sia possibile diminuirle ulteriormente, anche perché l'implementazione Intel è leggermente più complicata.
Per comprendere meglio l'inizializzazione, l'utilizzo e la tecnica
sottostante l'uso delle pagine in Linux, specialmente per i processori
Intel, dovete leggere la Linux Kernel Hacker's Guide. Essa
è liberamente disponibile nella directory
/pub/linux/docs/LDP
di ogni mirror di sunsite.unc.edu. Anche se
il libro è vecchio, niente è cambiato nel funzionamento del
i386, ed altri processori sono simili (in particolare il Pentium è
praticamente uguale ad un 386). Una versione un po' più aggiornata
del libro si può trovare su Web, all'indirizzo
http://www.redhat.com:8080/HyperNews/get/khg.html.
Se volete conoscere meglio la gestione delle pagine, potete leggere la KHG adesso o credere a questa breve descrizione.
do_no_page()
, in
mm/memory.c
. Il numero di page faults per ogni processo è
memorizzato nel campo maj_flt
di struct task_struct
.
do_wp_page()
e contata
in min_flt
di struct task_struct
).
COW
nei sorgenti del
kernel). Se, per esempio, un processo esegue una fork()
, il
processo figlio condividerà il segmento dati con il padre, ma
entrambe saranno protetti dalla scrittura: le pagine sono condivise in
lettura. Non appena uno dei due processi effettua una operazione di
scrittura sulla pagina, essa viene copiata ed il processo può
continuare l'operazione su questa nuova pagina. Il processo che non
ha scritto continuerà ad usare la pagina originale per la quale
sarà stato decrementato un contatore (``share count'') che tiene
conto del numero di processi che condividono la pagina stessa. Se lo share
count è già uno al momento della scrittura la pagina non
viene copiata ed essa viene semplicemente marcata come scrivibile al
momento del minor fault. Il sistema del copy-on-write minimizza l'uso di
memoria.
high_memory
(il più alto indirizzo della RAM
fisica, definito in asm/pgtable.h
). L'intervallo fra 640 kB e
1024 kB non è usato da Linux, e viene identificato come
``reserved'' nella struttura mem_map
. Questi indirizzi sono i
``384k reserved'' che appaiono nel primo messaggio del kernel dopo il
calcolo dei BogoMips.
La prima assunzione che si deve fare quando si pensa di mappare in
memoria un device è quella di poter definire una posizione esatta
ed una lunghezza per quel device. Naturalmente è possibile contare
l'ennesimo carattere in arrivo dalla porta seriale ad es., ma il paradigma
su cui si basa la mmap()
si applica molto più chiaramente
a device che abbiano una dimensione ben definita. Anche perché
mmap()
si basa sulla costruzione di page-tables, e quindi solo
dati che vivono in un indizzo fisico di memoria possono essere mappati.
Un character ``device'' impiegato ogni volta che usate la svgalib o il
server X è /dev/mem
. Questo device rappresenta la vostra
memoria fisica. Il server X e la svgalib lo usano per mappare i
buffer video della scheda grafica nel proprio spazio di indirizzamento.
Una volta (sono così vecchio?) la gente scriveva in BASIC giochi come Tetris che dovevano funzionare da una console di tipo testo. Piuttosto che usare i lenti comandi del BASIC i programmatori scrivevano direttamente nella memoria video. Questo è esattamente come usare il memory-mapping.
Per creare un piccolo esempio di utilizzo della mmap()
, ho
scritto un piccolo programma chiamato nasty. Come potete sapete, la
scrittura araba è da destra verso sinistra. Anche se non credo che
qualcuno possa preferire questo stile con le normali lettere dell'alfabeto
latino, il programma seguente vi dà un idea di questo stile. Nasty
gira esclusivamente su architettura Intel con scheda grafica VGA, in
quanto usa esplicitamente gli indirizzi VGA. Questo limite non si applica
a mmap()
in generale, solo a questo esempio.
Se farete mai girare questo programma, fatelo come root
(altrimenti non avrete accesso a /dev/mem
), fatelo dalla console
(non vedrete nulla se lo lanciate sotto X) e accertatevi di avere una
scheda VGA o EGA.
/*
* nasty.c - flips right and left on the VGA console.
* "Arabic" display
*/
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
int main(int argc, char **argv)
{
FILE *fh;
short *vid_addr, temp;
int x, y, ofs;
fh = fopen("/dev/mem", "r+");
vid_addr = (short *) mmap(
/* where to map to: don't mind */
NULL,
/* how many bytes ? */
0x4000,
/* want to read and write */
PROT_READ | PROT_WRITE,
/* no copy on write */
MAP_SHARED,
/* handle to /dev/mem */
fileno(fh),
/* hopefully the Text-buffer :-) */
0xB8000);
if (vid_addr)
for (y = 0; y < 100; y++)
for (x = 0; x < 40; x++) {
ofs = y * 80;
temp = vid_addr[ofs + x];
vid_addr[ofs + x] = vid_addr[ofs + 79 - x];
vid_addr[ofs + 79 - x] = temp;
}
munmap((caddr_t) vid_addr, 0x4000);
fclose(fh);
return 0;
}
mmap()
Cosa è possibile cambiare nella chiamata alla mmap()
vista prima?
È possibile cambiare i privilegi per le pagine mappate modificando
uno dei flag PROT
chiedendo di poter scrivere, leggere o eseguire
(PROT_READ
, PROT_WRITE
, e PROT_EXEC
) i dati mappati nel programma.
Rimpiazzando MAP_SHARED
con MAP_PRIVATE
verrà
settato il flag Copy-On-Write delle pagine interessate permettendovi di
scrivere nel text buffer senza però modificarlo. Le eventuali
modifiche saranno apportate alla vostra copia privata delle pagine.
Cambiando il parametro offset
è possibile adattare
nasty
alle schede monocromatiche Hercules (usando
0xB0000
invece di 0xB8000
), o ottenere risultati
inaspettati, anche far piantare la macchina (usando un indirizzo casuale).
La mmap()
può essere ``applicata'' anche ad un file su
disco, invece cha alla memoria di sistema, convertendone il contenuto in
stile ``arabo'' (ma verificate che le dimensioni mappate non siano
superiori alla lunghezza del file). Non preoccupatevi se la vostra pagina
del manuale dice che mmap()
è una funzione di BSD---La
questione con Linux ormai è ``chi scrive la documentazione''
piuttosto che ``chi implementa cosa''...
Invece che il NULL
utilizzato come primo parametro è
possibile specificare un indirizzo preciso (nello spazio di indirizzamento
del vostro processo) a partire dal quale volete mappare le pagine fisiche
richieste. Nelle versioni di Linux più recenti questa richiesta
viene ignorata se non viene contemporaneamente utilizzato il flag
MAP_FIXED
. In questo caso Linux eliminerà qualsiasi
mappatura precedente per l'indirizzo in questione, rimpiazzandola con
quella richiesta. Se utilizzerete questa possibilità, dovete essere
sicuri che l'indirizzo specificato sia quello del primo byte di una pagina
((addr & PAGE_MASK) == addr
).
Concludendo, abbiamo visto uno degli usi preferiti della
mmap()
--- specialmente quando avrete a che fare con piccole
porzioni di file di grandi dimensioni come i database, troverete utile---e
più veloce--- mappare l'intero file in memoria per poterlo leggere
e scrivere come se fosse memoria fisica, lasciando agli algoritmi di
gestione dei buffer di Linux tutti i problemi di caching. L'accesso al
file sarà molto più rapido che l'uso delle classiche
fread()
e fwrite()
.
La persona che si dovrà occupare di tutte queste utili
caratteristiche è il vostro povero programmatore di device driver.
Mentre il supporto per la mmap()
sui file è cura del
kernel (dei singoli filesystem in realtà), il sistema di memory
mapping per altri device deve essere supportato direttamente dai driver,
rendendo disponibile una apposita funzione nella struttura fops
che è stata descritta in un precedente articolo.
Come prima cosa daremo un'occhiata ad una delle poche ``vere''
implementazioni di questo supporto, basando la nostra discussione sul
driver per il device /dev/mem
. Quindi vi mostrerò una
particolare implementazione utile per schede tipo frame-grabber, schede di
I/O analogico-digitale per laboratorio e, probabilmente, anche per altre
periferiche.
La funzione mmap()
è fondamentalmente basata sulla
do_mmap()
, definita nel file mm/mmap.c del kernel. La
do_mmap()
fà due cose importanti:
mmap()
. In più,
controlla che non si superi il limite massimo indirizzabile (4GB sulle
macchine Intel) oltre ad effettuare altre verifiche sui parametri.
struct vm_area_struct
per la
nuova ``fetta'' di memoria virtuale. Ogni processo può possedere
diverse di queste strutture, chiamate VMA (Virtual Memory Area).
vm_area_struct
a quelle normalmente
possedute dal processo. Le strutture VMA sono gestite dal kernel ed
ordinate in un albero bilanciato per permettere un accesso veloce.
I campi della struct vm_area_struct
sono definiti in
linux/mm.h
. I contenuti ed il numero di VMA per un dato processo
possono essere osservati guardando i file
/proc/
pid/maps
di qualsiasi processo in
esecuzione, ove pid è il process id di quel particolare
processo. Vediamo il contenuto di maps
per il programma
nasty visto in precedenza e compilato in formato ELF. Durante
l'esecuzione del programma il file maps
assomiglierà a
quanto segue (senza commenti):
# /dev/sdb2: nasty CSS
08000000-08001000 rwxp 00000000 08:12 36890
# /dev/sdb2: nasty DSS
08001000-08002000 rw-p 00000000 08:12 36890
# BSS for nasty
08002000-08008000 rwxp 00000000 00:00 0
# /dev/sda2: /lib/ld-linux.so.1.7.3 CSS
40000000-40005000 r-xp 00000000 08:02 38908
# /dev/sda2: /lib/ld-linux.so.1.7.3 DSS
40005000-40006000 rw-p 00004000 08:02 38908
# BSS for ld-linux.so
40006000-40007000 rw-p 00000000 00:00 0
# /dev/sda2: /lib/libc.so.5.2.18 CSS
40009000-4007f000 rwxp 00000000 08:02 38778
# /dev/sda2: /lib/libc.so.5.2.18 DSS
4007f000-40084000 rw-p 00075000 08:02 38778
# BSS for libc.so
40084000-400b6000 rw-p 00000000 00:00 0
# /dev/sda2: /dev/mem (our mmap)
400b6000-400c6000 rw-s 000b8000 08:02 32767
# the user stack
bfffe000-c0000000 rwxp fffff000 00:00 0
I primi due campi di ogni linea, separati da un trattino,
rappresentano l'indirizzo al quale sono mappati i dati. Il campo
successivo mostra le protezioni per le pagine in questione (r
significa leggibile, w
scrivibile, p
private ed
s
shared). Il quarto campo indica a quale offset all'interno del
file è effettuato il mapping mentre i successivi due campi sono
rispettivamente il device fisico e l'inode del file. Il numero di device
rappresenta un disco (hard disk o altro) montato (ad es. 03:01 è
/dev/hda1, 08:01 è /dev/sda1). Per conoscere il file associato ad
un certo inode il metodo più semplice (ma anche il più
lento) è:
% cd your_mount_point
% find . -inum your_inode_number -print
Nel cercare di comprendere il file maps
ed i commenti,
ricordate che Linux distingue tra ``Code Storage Segment'' (CSS
),
a volte chiamato ``text'' segment; ``Data Storage Segment''
(DSS
), contenenti dati inizializzati; e ``Block Storage Segment''
(BSS
), area generica per variabili non inizializzate e
normalmente azzerata alla partenza. Poichè non devono essere
caricati da disco valori iniziali per le variabili del BSS, tutti i campi
di questo tipo in maps
non hanno né numero di device
né inode (un major number ``0'' equivale a NODEV
). Questo
ci mostra un altro possibile impiego della mmap()
: passando
MAP_ANONYMOUS
come file handle è possibile richiedere
blocchi di memoria libera per il programma (alcune versioni di
malloc()
ottengono memoria dal sistema in questo modo).
Quando la do_mmap()
chiama la funzione del vostro driver per
il mmapping, una VMA per la nuova regione di memoria è già
stata creata, ma non ancora inserita nella struttura task_struct
(definita in linux/sched.h
) del processo.
La funzione del device driver deve implementare la seguente interfaccia:
int skel_mmap (struct inode *inode,
struct file *file,
struct vm_area_struct *vma)
vma->vm_start
conterrà l'indirizzo nello user space a
partire dal quale deve essere mappata la memoria. vma->vm_end
contiene il primo indirizzo non valido alla fine dell'area cosicché
la differenza fra i due campi corrisponde all'argomento length
della chiamata alla mmap()
. Il campo vma->vm_offset
è l'offset all'interno del file a partire da quale il kernel
mapperà la memoria ed è identico all'argomento
offset
della mmap()
.
Studiamo ora come il driver per /dev/mem
implementa il memory
mapping. Troverete il codice nella funzione mmap_mem()
del file
drivers/char/mem.c
. Se vi siete aspettati qualcosa di complesso
rimarrete delusi: essa chiama semplicemente remap_page_range()
(in mm/memory.c
). Se veramente volete comprendere il
funzionamento di questa funzione dovete leggere le relative venti pagine
della Kernel Hacker's Guide. In breve, vengono prima creati i page
descriptors per lo spazio di indirizzamento del processo e quindi vengono
inizializzati con link alla memoria fisica. Notate che la struttura
vm_area_struct
è utilizzata per il memory management,
mentre i page descriptor sono direttamente interpretati dalla CPU
nel generare l'indirizzo fisico.
Se la remap_page_range()
ritorna zero, dicendo così che
nessun errore è avvenuto, il vostro mmap-handler dovrebbe fare lo
stesso. In questo caso la do_mmap()
ritornerà l'indirizzo
a partire dal quale sono state mappate le pagine. Qualsiasi altro valore
restituito verrà considerato un errore.
È impossibile fornire esempi di codice per ogni possibile applicazione del memory mapping di character devices. Alessandro ed io ci siamo occupati di un problema concreto nel caso di una scheda per laboratorio dotata di RAM, CPU e naturalmente convertitori ADC e DAC, ingressi ed uscite digitali, generatori di clock ed altro.
Per prima cosa descriverò il funzionamento dell'interfaccia per meglio comprendere i problemi che un programmatore deve affrontare nello scrivere device drivers.
La scheda di cui ci siamo occupati può campionare i suoi ingressi
direttamente nella sua memoria in maniera continua. Lo stato di questa
operazione può essere investigato dall'esterno tramite una porta di
I/O chiamata ``character channel''. Questo canale accetta un flusso di
caratteri ASCII che rappresentano i comandi. I processi interagiscono con
la scheda attraverso questo canale usando le classiche funzioni
read()
e write()
.
I reali trasferimenti di dati sono effettuati in maniera indipendente dal
funzionamento del character channel: inviando su quest'ultimo un comando
come TOHOST interface address, length, host
address
, la scheda genererà un interrupt dicendo al PC che
desidera trasferire, in DMA, un blocco di dati in un certo indirizzo della
memoria centrale. Ma dove dobbiamo mettere questi dati? Abbiamo deciso di
non complicare la semplice interfaccia a caratteri con i traferimenti di
dati. In più, poiché l'utente può inviare qualsiasi
comando alla scheda, non abbiamo fatto assunzioni sull'ordinamento ed il
significato dei dati.
Per queste ragioni abbiamo deciso lasciare il pieno controllo all'utente,
permettendogli di richiedere al sistema regioni di memoria accessibili al
DMA e di mapparle nel proprio spazio di indirizzamento. Ogni richiesta di
DMA proveniente dalla scheda verrà controllata confrontandola con
queste aree. In altre parole abbiamo implementato qualcosa di simile ad
una skel_malloc()
od una skel_free()
per mezzo di
chiamate a comandi ioctl()
, disabilitando contemporaneamente ogni
trasferimento verso altre regioni per rendere sicuro il tutto.
Vi chiederete perché non abbiamo usato direttamente la
mmap()
per questo lavoro. Principalmente perché non era
possibile realizzare l'equivalente munmap()
. Il driver infatti
non riesce a sapere quando termina l'operazione di memory mapping. Linux
fà tutto da solo: rimuove la vm_area_struct
e le page
tables decrementando il numero di utilizzatori nel caso di pagine shared.
kmalloc()
, essi devono essere liberati con la kfree()
ma
Linux non ci permetterà di farlo quando automaticamente
eliminerà il mapping. Ma se non esiste più il mapping per i
buffer, essi non sono più necessari. Perciò abbiamo
implementato una skel_malloc()
che alloca i buffer mappandoli
contemporaneamente nello user space, ed una skel_free()
che
rilascia i buffer eliminando il mapping (dopo aver controllato che non
stia avvenendo un trasferimento in DMA).
Avremmo potuto implementare il sistema di memory mapping nella libreria di
funzioni che accompagna il driver con lo stesso sistema usato nel
programma nasty visto prima. Ma, per valide ragioni,
/dev/mem
è accessibile solo da root, mentre i programmi
che usano il device devono poter essere eseguiti da qualsiasi utente.
Abbiamo usato due trucchi nel nostro codice. Per prima cosa modifichiamo
direttamente l'array mem_map
comunicando a Linux l'utilizzo ed i
permessi delle nostre pagine di memoria fisica. L'array mem_map
(mm/memory.c
) di strutture mem_map_t
(linux/mm.h
), ed è usato per mantenere informazioni su
tutta la memoria fisica.
Per ogni pagina allocata settiamo il flag PG_reserved
. Questo
è un metodo piuttosto sporco, ma ragiunge il suo scopo con tutte le
versioni di Linux (almeno a partire dalla 1.2.x): Linux tiene le sue mani
lontane dalle nostre pagine! Le considera come RAM video o una ROM o
qualsiasi cosa non possa swappare o utilizzare come memoria libera. Lo
stesso array mem_map
usa questo trucco per proteggersi da
processi troppo avidi di memoria.
Il secondo trucco che abbiamo utilizzato è quello di generare
velocemente uno pseudo-file che assomiglia in qualche modo ad un
/dev/mem
già aperto. La mancanza di una
mmap_mem()
nel kernel ci ha fatto riscrivere una funzione simile
prendendo come esempio quella del driver per /dev/mem
. Questa
semplice funzione è quindi utilizzata con
remap_page_range()
. Questo sistema non è sicuramente il
più ``pulito'' ma funziona. Si sarebbe potuta ottenenere la stessa
funzionalità in maniera più corretta ma con codice
più complesso.
Oltre a ciò, viene tenuta traccia dei buffer DMA allocati dalla
nostra skel_malloc()
inserendoli in liste per poter verificare se
le richieste di DMA coinvolgono aree di memoria valide. Le liste sono
usate anche per rilasciare i buffer quando il programma chiude il device
senza chiamare precedentemente skel_free()
. dma_desc
è il tipo di queste liste, come mostrato dalle seguenti linee di
codice. Esse mostrano le nostre skel_malloc()
e
skel_free()
implementate tramite chiamate a ioctl()
:
/* =============================================
* SKEL_MALLOC
*
* The user desires a(nother) dma-buffer, that
* is allocated by kmalloc (GFP_DMA) (continous
* and in lower 16 MB).
* The allocated buffer is mapped into
* user-space by
* a) a pseudo-file as you would get it when
* opening /dev/mem
* b) the buffer-pages tagged as "reserved"
* in memmap
* c) calling the normal entry point for
* mmap-calls "do_mmap" with our pseudo-file
*
* 0 or < 0 means an error occured, otherwise
* the user space address is returned.
* This is the main basis of the Skel_Malloc
* Library-Call
*/
/* ---------------------------------------------
* Ma's little helper replaces the mmap
* file_operation for /dev/mem which is declared
* static in Linux and has to be rebuilt by us.
* But ain't that much work; we better drop more
* comments before they exceed the code in length.
*/
static int
skel_mmap_mem (struct inode *inode,
struct file *file,
struct vm_area_struct *vma)
{
if (remap_page_range (vma->vm_start,
vma->vm_offset,
vma->vm_end - vma->vm_start,
vma->vm_page_prot))
return -EAGAIN;
vma->vm_inode = NULL;
return 0;
}
static unsigned long
skel_malloc (struct file *file,
unsigned long size)
{
unsigned long pAdr, uAdr;
dma_desc *dpi;
skel_file_info *fip;
struct file_operations fops;
struct file memfile;
/* Our helpful pseudo-file only ... */
fops.mmap = skel_mmap_mem;
/* ... support mmap */
memfile.f_op = &fops;
/* and is read'n write */
memfile.f_mode = 0x3;
fip = (skel_file_info *) (file->private_data);
if (!fip)
return 0;
dpi = kmalloc (sizeof (dma_desc), GFP_KERNEL);
if (!dpi)
return 0;
PDEBUG ("skel: Size requested: %ld\n", size);
if (size <= PAGE_SIZE / 2)
size = PAGE_SIZE - 0x10;
if (size > 0x1FFF0)
return 0;
pAdr = (unsigned long) kmalloc (size,
GFP_DMA | GFP_BUFFER);
if (!pAdr) {
printk ("skel: Trying to get %ld bytes"
"buffer failed - no mem\n", size);
kfree_s (dpi, sizeof (dma_desc));
return 0;
}
for (uAdr = pAdr & PAGE_MASK;
uAdr < pAdr + size;
uAdr += PAGE_SIZE)
#if LINUX_VERSION_CODE < 0x01031D
/* before 1.3.29 */
mem_map[MAP_NR (uAdr)].reserved |=
MAP_PAGE_RESERVED;
#elseif /* LINUX_VERSION_CODE < 0x01033A */
/* before 1.3.58 */
mem_map[MAP_NR (uAdr)].reserved = 1;
#else
/* most recent versions */
mem_map_reserve (MAP_NR (uAdr));
#endif
uAdr = do_mmap (&memfile, 0,
(size + ~PAGE_MASK) & PAGE_MASK,
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_SHARED, pAdr & PAGE_MASK);
if ((signed long) uAdr <= 0) {
printk ("skel: A pity - "
"do_mmap returned %lX\n", uAdr);
kfree_s (dpi, sizeof (dma_desc));
kfree_s ((void *) pAdr, size);
return uAdr;
}
PDEBUG ("skel: Mapped physical %lX to %lX\n",
pAdr, uAdr);
uAdr |= pAdr & ~PAGE_MASK;
dpi->dma_adr = pAdr;
dpi->user_adr = uAdr;
dpi->dma_size = size;
dpi->next = fip->dmabuf_info;
fip->dmabuf_info = dpi;
return uAdr;
}
/* =============================================
* SKEL_FREE
*
* Releases memory previously allocated by
* skel_malloc
*/
static int
skel_free (struct file *file,
unsigned long ptr)
{
dma_desc *dpi, **dpil;
skel_file_info *fip;
fip = (skel_file_info *) (file->private_data);
if (!fip)
return 0;
dpil = &(fip->dmabuf_info);
for (dpi = fip->dmabuf_info;
dpi; dpi = dpi->next) {
if (dpi->user_adr == ptr)
break;
dpil = &(dpi->next);
}
if (!dpi)
return -EINVAL;
PDEBUG ("skel: Releasing %lX bytes at %lX\n",
dpi->dma_size, dpi->dma_adr);
do_munmap (ptr & PAGE_MASK,
(dpi->dma_size + (~PAGE_MASK)) & PAGE_MASK);
ptr = dpi->dma_adr;
do {
#if LINUX_VERSION_CODE < 0x01031D
/* before 1.3.29 */
mem_map[MAP_NR (ptr)] &= ~MAP_PAGE_RESERVED;
#elseif /* LINUX_VERSION_CODE \ 0x01033A */
/* before 1.3.58 */
mem_map[MAP_NR (ptr)].reserved = 0;
#else
mem_map_unreserve (MAP_NR (ptr));
#endif
ptr += PAGE_SIZE;
}
while (ptr < dpi->dma_adr + dpi->dma_size);
*dpil = dpi->next;
kfree_s ((void *) dpi->dma_adr, dpi->dma_size);
kfree_s (dpi, sizeof (dma_desc));
return 0;
}
La tecnologia evolve, ma le idee spesso rimangono le stesse. Nel
vecchio mondo ISA le periferiche avevano i loro buffer ``alla fine dello
spazio di indirizzamento'': sopra i 640 kB. Molte schede PCI moderne fanno
la stessa cosa, ma oggi questa frase và riferita allo spazio di
indirizzamento a 32-bit (ad es. indirizzi come 0xF0100000
).
Se volete avere accesso a buffer posti a questi indirizzi dovrete usare la
vremap()
definita in mm/vmalloc.c
per rimappare le
pagine di questa memoria fisica nel vostro spaizio di indirizzamento.
Documentation/IO-mapping.txt
Lo stesso
Linus afferma che il nome vremap()
non è del tutto
corretto. Nei kernel della serie 2.1.x la funzione è stata
rinominata più correttamente in ioremap()
. Nello
stesso file è possibile trovare altre interessanti
informazioni sull'uso della memoria.
vremap()
lavora in modo simile alla funzione mmap()
vista in nasty, ma è molto più semplice:
void * vremap (unsigned long offset, unsigned long size);
Dovete semplicemente passare l'indirizzo fisico del buffer e la sua
dimensione. Ricordate che vengono sempre mappate pagine quindi
offset
e size
devono essere multipli della dimensione di
una pagina. Se il buffer è più piccolo di una pagina,
mappate tutta la pagina e fate attenzione a non usare indirizzi non
validi.
Non ho provato personalmente questa funzione, e non sono sicuro se i
trucchi visti precedentemente per mappare i buffer in user space
funzionano con memoria sul bus PCI. Se vorrete provare, dovrete
sicuramente eliminare la ``brutale'' manipolazione dell'array
mem_map
: esso è solo per normale memoria fisica.
Provate a rimpiazzare le kmalloc()
e kfree()
con le
corrispondenti vremap()
e vfree()
ed effettuate un
ulteriore mapping (questa volta in user space) con la do_mmap()
.
Come avrete capito, siamo giunti al termine di questa serie di articoli. Ora spetta a voi andare spavaldamente dove nessun Linuxer è mai andato prima...
Buona fortuna!
Georg è un ventisettenne appassionato di Linux che ama l'hacking notturno e odia le scadenze.
[Tar] [About] [Copertina] [indice] |