struct gendisk
struct block_device_operations
struct request
struct request_queue
struct bio
, submit_bio
Dispozitivele de tip bloc se caracterizează prin accesul aleator la date organizate în blocuri de dimensiune fixă. Exemple de astfel de dispozitive sunt hard disk drive-urile, CD-ROM drive-urile, RAM disk-urile etc. Viteza dispozitivelor de tip bloc este, în general, mult mai ridicată decât a celor de tip caracter, iar performanța acestora este, de asemenea, importantă. Acesta este motivul pentru care nucleul Linux tratează diferit cele două tipuri de dispozitive (dispune de un API specializat).
Lucrul cu dispozitive de tip bloc este, astfel, mai complicat decât lucrul cu cele de tip caracter. Dispozitivele de tip caracter au o singură poziție curentă, în timp ce dispozitivele de tip bloc trebuie să se poată mișca la orice poziție din dispozitiv pentru a asigura accesul aleator la date. Pentru a simplifica lucrul cu dispozitivele de tip bloc, nucleul Linux pune la dispoziția programatorului un întreg subsistem denumit subsistemul block I/O (sau block layer).
Din perspectiva nucleului, cea mai mică unitate logică de adresare este blocul. Cu toate că dispozitivul fizic poate fi adresat la nivel de sector, nucleul efectuează toate operațiile cu discuri folosind blocuri. Întrucât cea mai mică unitate de adresare fizică este sectorul, dimensiunea blocului trebuie să fie un multiplu al dimensiunii sectorului. În plus, dimensiunea blocului trebuie să fie o putere a lui 2 și nu poate depăși dimensiunea unei pagini. Dimensiunea blocului poate varia în funcție de sistemul de fișiere folosit, cele mai frecvente valori fiind 512 bytes, 1 kilobyte și 4 kilobytes.
Pentru înregistrare se folosește funcția register_blkdev 1).
Pentru deînregistrarea unui dispozitiv de tip bloc se folosește funcția unregister_blkdev.
În versiunea 4.9 a kernel-ului Linux, apelul funcției register_blkdev este opțional. Singurele operații efectuate de această funcție sunt alocarea dinamică a unui major (dacă este apelată cu valoarea 0
pentru argumentul major
) și crearea unei intrări în /proc/devices
. În versiunile viitoare de kernel este posibil să fie eliminată; cu toate acestea majoritatea driverelor încă o apelează.
Ca de obicei, apelul funcției de înregistrare se realizează în funcția de inițializare a modulului, iar apelul funcției de deînregistrare în funcția de ieșire a modulului. Un scenariu obișnuit este prezentat în continuare:
#include <linux/fs.h> #define MY_BLOCK_MAJOR 240 #define MY_BLKDEV_NAME "mybdev" static int my_block_init(void) { int status; status = register_blkdev(MY_BLOCK_MAJOR, MY_BLKDEV_NAME); if (status < 0) { printk(KERN_ERR "unable to register mybdev block device\n"); return -EBUSY; } //... } static void my_block_exit(void) { //... unregister_blkdev(MY_BLOCK_MAJOR, MY_BLKDEV_NAME); }
Cu toate că funcția register_blkdev obține un major, nu pune la dispoziția sistemului un dispozitiv (disc). Pentru crearea și utilizarea de dispozitive de tip bloc (discuri), se folosește o interfață specializată definită în linux/genhd.h.
Funcțiile utile definite în linux/genhd.h sunt cele de înregistrare/alocare a unui disc, de adăugare a acestuia în sistem și de deînregistrare/dezalocare a discului.
Funcția alloc_disk este folosită pentru alocarea unui disc, iar funcția del_gendisk este utilizată pentru dezalocarea acestuia. Adăugarea discului în sistem se realizează cu ajutorul funcției add_disk.
Funcțiile alloc_disk și add_disk se folosesc, de obicei, în funcția de inițializare a modulului, iar funcția del_gendisk în funcția de ieșire a modulului.
#include <linux/fs.h> #include <linux/genhd.h> #define MY_BLOCK_MINORS 1 static struct my_block_dev { struct gendisk *gd; //... } dev; static int create_block_device(struct my_block_dev *dev) { dev->gd = alloc_disk(MY_BLOCK_MINORS); //... add_disk(dev->gd); } static int my_block_init(void) { //... create_block_device(&dev); } static void delete_block_device(struct my_block_dev *dev) { if (dev->gd) del_gendisk(dev->gd); //... } static void my_block_exit(void) { delete_block_device(&dev); //... }
Ca și la dispozitivele de tip caracter, se recomandă folosirea unei structuri de tipul my_block_dev
în care să se regăsească elemente importante ce descriu dispozitivul de tip bloc.
Trebuie reținut faptul că imediat după apelul funcției add_disk (de fapt chiar încă din timpul apelului) discul este activ și metodele sale pot fi apelate la orice moment de timp. Ca urmare, această funcție nu trebuie apelată înainte ca driverul să fie complet inițializat și gata să răspundă cererilor adresate discului înregistrat.
Se observă că structura de bază în lucrul cu dispozitive de tip bloc (discuri) este structura struct gendisk.
După un apel del_gendisk este posibil ca structura struct gendisk să continue să existe (și operațiile asupra dispozitivului să fie apelate în continuare), în cazul în care există utilizatori ai acesteia (s-a apelat o operație open
asupra dispozitivului, dar încă nu a fost apelată operația release
asociată). O soluție este păstrarea numărului de utilizatori ai dispozitivului și apelarea funcției del_gendisk numai atunci când nu există utilizatori ai acestuia.
Structura struct gendisk reține informațiile referitoare la un disc. După cum s-a afirmat și mai sus, o astfel de structură se obține în urma apelului alloc_disk și trebuie completată înainte de a fi transmisă funcției add_disk.
Structura struct gendisk are următoarele câmpuri importante:
major
, first_minor
, minors
, care descriu identificatorii folosiți de disc; un disc trebuie să aibă cel puțin un minor; dacă discul permite operația de partiționare, trebuie alocat un minor pentru fiecare partiție posibilădisk_name
, care reprezintă numele discului, așa cum apare în /proc/partitions
și în sysfs
(/sys/block
)fops
, care reprezintă operațiile asociate disculuiqueue
, care reprezintă coada de cereri 2)capacity
, care reprezintă capacitatea discului în sectoare de 512 octeți; se inițializează folosind funcția set_capacityprivate_data
, care reprezintă un pointer către datele privateUn exemplu de completare a unei structuri struct gendisk este prezentat în continuare:
#include <linux/genhd.h> #include <linux/fs.h> #include <linux/blkdev.h> #define NR_SECTORS 1024 #define KERNEL_SECTOR_SIZE 512 static struct my_block_dev { //... spinlock_t lock; /* For mutual exclusion */ struct request_queue *queue; /* The device request queue */ struct gendisk *gd; /* The gendisk structure */ //... } dev; static int create_block_device(struct my_block_dev *dev) { ... /* Initialize the gendisk structure */ dev->gd = alloc_disk(MY_BLOCK_MINORS); if (!dev->gd) { printk (KERN_NOTICE "alloc_disk failure\n"); return -ENOMEM; } dev->gd->major = MY_BLOCK_MAJOR; dev->gd->first_minor = 0; dev->gd->fops = &my_block_ops; dev->gd->queue = dev->queue; dev->gd->private_data = dev; snprintf (dev->gd->disk_name, 32, "myblock"); set_capacity(dev->gd, NR_SECTORS); add_disk(dev->gd); return 0; } static int my_block_init(void) { int status; //... status = create_block_device(&dev); if (status < 0) return status; //... } static void delete_block_device(struct my_block_dev *dev) { if (dev->gd) { del_gendisk(dev->gd); } //... } static void my_block_exit(void) { delete_block_device(&dev); //... }
După cum s-a precizat, kernel-ul consideră un disc ca fiind un vector de sectoare cu dimensiunea 512 octeți. În realitate, dispozitivele pot avea altă dimensiune a sectorului. Pentru a lucra cu aceste dispozitive, trebuie informat kernel-ul asupra dimensiunii reale a unui sector, și pentru toate operațiile trebuie făcute conversiile necesare.
Pentru a informa kernel-ul asupra dimensiunii sectorului dispozitivului, trebuie setat un parametru al cozii de cereri, imediat după ce coada este alocată folosind funcția blk_queue_logical_block_size. Toate cererile generate de kernel vor fi multiplu de această dimensiune a sectorului și vor fi aliniate corespunzător. Totuși, comunicația între dispozitiv și driver se va realiza în continuare în sectoare de 512 octeți, astfel încât trebuie făcută conversia de fiecare dată (un exemplu de astfel de conversie este la apelul funcției set_capacity în codul de mai sus).
La fel cum pentru un dispozitiv de tip caracter trebuiau completate operațiile din file_operations, și pentru un dispozitiv de tip bloc trebuie completate operațiile din block_device_operations. Asocierea operațiilor se realizează prin intermediul câmpului fops
din structura struct gendisk.
Câteva din câmpurile structurii block_device_operations sunt prezentate în continuare:
struct block_device_operations { int (*open) (struct block_device *, fmode_t); int (*release) (struct gendisk *, fmode_t); int (*locked_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long); int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long); int (*compat_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long); int (*direct_access) (struct block_device *, sector_t, void **, unsigned long *); int (*media_changed) (struct gendisk *); int (*revalidate_disk) (struct gendisk *); int (*getgeo)(struct block_device *, struct hd_geometry *); struct module *owner; }
Operațiile open
și release
sunt apelate direct din user space de către utilitare de partiționare, creare de sisteme de fișiere sau verificare de sisteme de fișiere. La o operație mount, se apelează open
direct din kernel space, identificatorul de fișier fiind reținut de către kernel. Un driver de tip bloc nu poate face diferența între apelurile open
din user space și cele din kernel space.
Un exemplu de utilizare a celor două funcții este prezentat în continuare:
#include <linux/fs.h> #include <linux/genhd.h> static struct my_block_dev { //... struct gendisk * gd; //... } dev; static int my_block_open(struct block_device *bdev, fmode_t mode) { //... return 0; } static void my_block_release(struct gendisk *gd, fmode_t mode) { //... } struct block_device_operations my_block_ops = { .owner = THIS_MODULE, .open = my_block_open, .release = my_block_release }; static int create_block_device(struct my_block_dev *dev) { //.... dev->gd->fops = &my_block_ops; dev->gd->private_data = dev; //... }
Este de remarcat că nu există operațiile read
și write
. Aceste operații sunt efectuate de funcția request
asociată cu coada de cereri a discului.
Driverele de tip bloc folosesc cozi de cereri pentru a păstra cererile block I/O care urmează să fie procesate. O coadă de cereri este reprezentată de structura struct request_queue. Coada de cereri este formată dintr-o listă dublu înlănțuită de cereri și informația de control asociată. Cererile sunt adăugate la coadă de cod kernel de nivel mai înalt (de exemplu, sistemele de fișiere). Cât timp coada de cereri nu este vidă, driver-ul asociat cozii va trebui să extragă prima cerere din coadă și să o transmită dispozitivului de tip bloc asociat. Fiecare element din lista de cereri este o cerere reprezentată de tipul struct request.
Cozile de cereri implementează o interfață care permite utilizarea mai multor planificatoare I/O (I/O schedulers). Un planificator trebuie să sorteze cererile și să le prezinte driver-ului într-o ordine care să maximizeze performanța. De asemenea, planificatorul se ocupă de combinarea cererilor adiacente (care se referă la sectoare adiacente de pe disc).
O coadă de cereri este creată cu ajutorul funcției blk_init_queue și este ștearsă cu ajutorul funcției blk_cleanup_queue.
Un exemplu de folosire a acestor funcții este următorul:
#include <linux/fs.h> #include <linux/genhd.h> #include <linux/blkdev.h> static struct my_block_dev { //... struct request_queue *queue; //... } dev; static void my_block_request(struct request_queue *q); //... static int create_block_device(struct my_block_dev *dev) { /* Initialize the I/O queue */ spin_lock_init(&dev->lock); dev->queue = blk_init_queue(my_block_request, &dev->lock); if (dev->queue == NULL) goto out_err; blk_queue_logical_block_size(dev->queue, KERNEL_SECTOR_SIZE); dev->queue->queuedata = dev; //... out_err: return -ENOMEM; } static int my_block_init(void) { int status; //... status = create_block_device(&dev); if (status < 0) return status; //... } static void delete_block_device(struct block_dev *dev) { //... if (dev->queue) blk_cleanup_queue(dev->queue); } static void my_block_exit(void) { delete_block_device(&dev); //... }
Funcția blk_init_queue primește ca prim argument un pointer la funcția de prelucrare a cererilor pentru dispozitiv (de tipul request_fn_proc). În exemplul de mai sus, funcția este my_block_request
. Parametrul lock
este un spinlock (inițializat de către driver) pe care kernel-ul îl deține în timpul apelului funcției request
pentru a asigura accesul exclusiv la coada de cereri. Acest spinlock se poate folosi și în alte funcții ale driver-ului, pentru a proteja accesul la date partajate cu funcția request
.
Ca parte a inițializării cozii de cereri se poate configura câmpul queuedata
, care este echivalent cu câmpul private_data
din alte structuri.
Funcția de tipul request_fn_proc este utilizată pentru tratarea cererilor de lucru cu dispozitivul de tip bloc. Această funcție este echivalentul funcțiilor de citire și scriere întâlnite la dispozitivele de tip caracter. Funcția primește ca argument coada de cereri asociată dispozitivului și poate folosi diverse funcții pentru prelucrarea cererilor din coadă.
Funcțiile utilizate pentru prelucrarea cererilor din coadă, descrise mai jos, sunt:
Înainte de a apela aceste funcții, trebuie obținut spinlock-ul asociat cozii. Dacă aceste funcții sunt apelate în funcția de tip request_fn_proc, spinlock-ul este deja deținut.
O cerere pentru un dispozitiv de tip bloc este descrisă de structura struct request.
Câmpurile structurii struct request includ:
cmd_flags
, o serie de flag-uri printre care și direcția (citire sau scriere); pentru a afla direcția se folosește macrodefiniția rq_data_dir, care returnează 0 pentru o cerere de citire și 1 pentru o cerere de scriere pe dispozitiv;__sector
, primul sector al cererii de transfer; dacă sectorul dispozitivului are altă dimensiune, trebuie facută conversia corespunzătoare; pentru accesarea acestui câmp se folosește macro-ul blk_rq_pos;__data_len
, numărul total de octeți de transferat; pentru accesarea acestui câmp se folosește macro-ul blk_rq_bytes;bio
, o listă dinamică de structuri bio care reprezintă un set de bufere asociate cu cererea; acest câmp se accesează cu ajutorul macrodefiniției rq_for_each_segment pentru cazul în care există mai multe buffere sau cu macrodefiniția bio_data pentru cazul în care există un singur bufer asociat; bio_data
intoarce adresa buferului asociat cererii Cererile de citire/scriere sunt create de nivelurile de cod superioare subsistemului de I/O din nucleu. De obicei, subsistemul care creează cereri pentru dispozitive de tip bloc este subsistemul de gestiune a fișierelor. Subsistemul de I/O acționează ca intermediar între subsistemul de gestiune a fișierelor și driverul de dispozitiv de tip bloc. Principalele operații care intră în responsabilitatea subsistemului de I/O sunt adăugarea cererilor în coada de cereri a dispozitivului de tip bloc specific și sortarea și comasarea cererilor (sorting and merging) din considerente de performanță.
Când driverul a terminat de transferat toate sectoarele dintr-o cerere către/dinspre dispozitiv, trebuie să informeze subsistemul de I/O prin apelarea funcției blk_end_request. Dacă lock-ul aferent cozii de cereri este deja obținut, se poate folosi funcția __blk_end_request.
În situația în care driverul dorește să încheie cererea chiar dacă nu a transferat toate sectoarele aferente acesteia, poate apela respectiv funcțiile blk_end_request_all sau __blk_end_request_all. Funcția __blk_end_request_all se apelează dacă lock-ul aferent cozii de cereri este deja obținut.
Partea centrală a unui driver de tip bloc este metoda de tip request_fn_proc. În exemplele anterioare, funcția care satisfăcea acest rol era my_block_request
. După cum s-a precizat și în secțiunea Crearea și ștergerea cozii de cereri, această funcție este atașată driverului prin apelarea blk_init_queue.
Această metodă este apelată atunci când kernel-ul consideră că driverul trebuie să proceseze cereri de I/O. Metoda trebuie să pornească procesarea cererilor din coadă, dar nu este obligată să le și termine, cererile putând fi terminate din alte părți ale driverului.
Parametrul lock
, transmis la crearea unei cozi de cereri, reprezintă un spinlock pe care kernel-ul îl deține atunci când execută metoda request
. Din acest motiv, metoda request
rulează în context atomic și trebuie să respecte regulile pentru cod atomic (nu trebuie să apeleze funcții care pot duce la sleep etc.). Acest lock asigură și faptul că nu vor fi adăugate în coada alte cereri pentru device în timp ce se execută metoda request.
Apelarea funcției de prelucrare a cozii de cereri este asincronă relativ la acțiunile oricărui proces din userspace și nu trebuie făcute presupuneri privind procesul în contextul căruia rulează. De asemenea nu trebuie presupus că buffer-ul oferit de o cerere este din kernelspace sau userspace, orice operație care accesează userspace-ul fiind eronata.
În continuare este prezentată una dintre cele mai simple metode de tip request_fn_proc:
static void my_block_request(struct request_queue *q) { struct request *rq; struct my_block_dev *dev = q->queuedata; while (1) { rq = blk_fetch_request(q); if (rq == NULL) break; if (blk_rq_is_passthrough(rq)) { printk (KERN_NOTICE "Skip non-fs request\n"); __blk_end_request_all(rq, -EIO); continue; } /* do work */ ... __blk_end_request_all(rq, 0); } }
Funcția my_block_request
conține un ciclu while de parcurgere a cererilor din coada de cereri transmisă ca argument. Operațiile realizate în cadrul acestui ciclu sunt:
blk_fetch_request
obține primul element din coada de cereri și pornește cererea.NULL
, s-a ajuns la sfârșitul cozii de cereri (nu mai este nici o cerere de prelucrat) și se iese din funcție.-EIO
.Fiecare structură struct request reprezintă o cerere block I/O, dar poate proveni din combinarea mai multor cereri independente de la un nivel mai înalt. Sectoarele ce trebuie transferate pentru o cerere pot fi dispersate în memoria principală, dar întotdeauna corespund unui set de sectoare consecutive de pe dispozitiv. Cererea este reprezentată ca o mulțime de segmente, fiecare corespunzând unui buffer din memorie. Kernel-ul poate combina cereri care se referă la sectoare adiacente, dar nu va combina cereri de scriere cu cereri de citire într-o singură structură struct request.
O structură struct request este implementată ca o listă înlănțuită de structuri bio împreună cu informații care permit driver-ului să-și rețină poziția curentă în timp ce procesează cererea.
Structura bio este o descriere low-level a unei porțiuni dintr-o cerere block I/O.
struct bio { //... struct gendisk *bi_disk; unsigned int bi_opf; /* bottom bits req flags, top bits REQ_OP. Use accessors. */ //... struct bio_vec *bi_io_vec; /* the actual vec list */ //... struct bvec_iter bi_iter; /... void *bi_private; //... };
La rândul ei, structura bio conține un vector bi_io_vec
de tipul struct bio_vec. Acesta este format din paginile individuale din memoria fizică ce trebuie transferate, offsetul în cadrul paginii și dimensiunea buferului. Pentru a parcurge o structura bio
, trebuie parcurs acest vector de structuri struct bio_vec și transferate datele din fiecare pagină fizică. Pentru a simplifica parcurgerea vectorului se folosește struct bvec_iter. Această structură menține informații despre câte bufere și sectoare au fost consumate în timpul parcurgerii. Tipul cererii este encodat în câmpul bi_opf
, pentru determinarea acestuia folosiți funcția bio_data_dir.
Pentru crearea unei structuri bio se pot folosi două funcții:
Ambele funcții întorc o nouă structură bio.
De obicei o structură bio este creată de nivelurile superioare ale kernel-ului (de obicei sistemul de fișiere). O structură astfel creată este apoi transmisă subsistemului de I/O care adună mai multe structuri bio într-o cerere.
Pentru transmiterea unei structuri bio către driverul dispozitivului I/O asociat se folosește funcția submit_bio. Funcția primește ca argument o structură bio inițializată care va fi adăugată unei cereri din coada de cereri a unui dispozitiv I/O. Din acea coadă de cereri va putea fi prelucrată de driverul dispozitivului I/O cu o funcție specializată.
Transmiterea unei structuri bio unui driver are ca efect adăugarea acesteia într-o cerere din coada de cereri de unde va fi ulterior prelucrată. Astfel, în momentul în care funcția submit_bio se întoarce, nu se garantează încheierea prelucrării structurii. Dacă se dorește așteptarea prelucrării cererii se va folosi funcția submit_bio_wait.
Pentru a fi notificați atunci când se încheie prelucrarea unei structuri bio (atunci când nu folosim submit_bio_wait
), va trebui folosit câmpul bi_end_io al structurii. În acest câmp se precizează funcția care va fi apelată la încheierea prelucrării structurii bio. Pentru a pasa informații către funcție se poate folosi câmpul bi_private al structurii.
După ce o structură bio a fost alocată și înainte de a fi transmisă, trebuie inițializată.
Inițializarea structurii presupune completarea câmpurilor importante. După cum s-a precizat anterior, câmpul bi_end_io este folosit pentru a preciza funcția apelată la încheierea prelucrării structurii. Câmpul bi_private este folosit pentru a stoca date utile ce pot fi accesate în funcția bi_end_io.
Câmpul bi_opf specifică tipul operației. Folosiți bio_set_op_attrs pentru a inițializa tipul operației.
struct bio *bio = bio_alloc(GFP_NOIO, 1); //... bio->bi_disk = bdev->bd_disk; bio->bi_iter.bi_sector = sector; bio_set_op_attrs(bio, REQ_OP_READ, 0); bio_add_page(bio, page, size, offset); //...
În extrasul de mai sus se specifică dispozitivul de tip bloc către care va fi trimis bio-ul, sectorul de început, operația (REQ_OP_READ
or REQ_OP_WRITE
) și conținutul. Conținutul unui bio este un bufer descris prin: o pagină fizică, offset-ul în pagină și dimensiunea buferului. O pagină poate fi alocată folosind apelul alloc_page.
size
al apelului bio_add_page trebuie să fie multiplu de dimensiunea sectorului dispozitivului.
Pentru folosirea conținutului unei structuri bio, paginile de suport ale structurii trebuie mapate în spațiul de adresă nucleu de unde vor putea fi accesate. Pentru mapare/demapare se folosesc macrourile kmap_atomic și kunmap_atomic 3).
Un exemplu tipic de folosire este:
static void my_block_transfer(struct my_block_dev *dev, size_t start, size_t len, char *buffer, int dir); static int my_xfer_bio(struct my_block_dev *dev, struct bio *bio) { struct bio_vec bvec; struct bvec_iter i; int dir = bio_data_dir(bio); /* Do each segment independently. */ bio_for_each_segment(bvec, bio, i) { sector_t sector = i.bi_sector; char *buffer = kmap_atomic(bvec.bv_page); unsigned long offset = bvec.bv_offset; size_t len = bvec.bv_len; /* process mapped buffer */ my_block_transfer(dev, sector, len, buffer + offset, dir); kunmap_atomic(buffer); } return 0; }
După cum se observă din exemplul de mai sus, parcurgerea unui bio
presupune iterarea prin toate segmentele acestuia. Un segment (bio_vec) este definit de pagina adresei fizice, offset-ul în pagină și dimensiunea acestuia.
Pentru a simplifica procesarea unui bio se folosește macrodefiniția bio_for_each_segment. Aceasta va itera prin toate segmentele și de asemenea va actualiza informații globale, stocate într-un iterator (bvec_iter), cum ar fi sectorul curent precum și alte informații interne (indexul în vectorul de segmente, numărul de octeți rămași de procesat, etc.).
Se pot stoca informații în buffer-ul mapat sau se pot extrage informații.
În cazul în care se folosesc cozile de cereri și se dorește prelucrarea cererilor la nivel de structură bio
, se va folosi macrodefiniția rq_for_each_segment în locul bio_for_each_segment. Această macrodefiniție parcurge fiecare segment din fiecare structură bio
a unei cereri struct request
și actualizează o structură struct req_iterator. Structura struct req_iterator conține structura curentă bio
și iteratorul ce parcurge segmentele acestuia. Un exemplu tipic de folosire este:
struct bio_vec bvec; struct req_iterator iter; rq_for_each_segment(bvec, req, iter) { sector_t sector = iter.iter.bi_sector; char *buffer = kmap_atomic(bvec.bv_page); unsigned long offset = bvec.bv_offset; size_t len = bvec.bv_len; int dir = bio_data_dir(iter.bio); my_block_transfer(dev, sector, len, buffer + offset, dir); kunmap_atomic(buffer); }
După ce un subsistem al nucleului folosește o structură bio va trebui să elibereze referința către aceasta. Acest lucru se realizează cu ajutorul funcției bio_put.
Cu ajutorul funcției blk_init_queue se putea specifica o funcție care să fie folosită pentru prelucrarea cererilor transmise driverului. Funcția primea ca argument coada de cereri și realiza prelucrări la nivel de structuri request.
Dacă, din motive de flexibilitate, se dorește specificarea unei funcții care să realizeze prelucrări la nivel de bio, trebuie folosită funcția blk_queue_make_request în conjuncție cu funcția blk_alloc_queue. Mai jos este prezentat un exemplu tipic de inițializare a unei funcții de prelucrare la nivel de bio:
// semnatura functiei de tratare static void my_make_request(struct request_queue *q, struct bio *bio); // ... // crearea cozii dev->queue = blk_alloc_queue (GFP_KERNEL); if (dev->queue == NULL) { printk (KERN_ERR "cannot allocate block device queue\n"); return -ENOMEM; } // inregistrarea functiei de tratare blk_queue_make_request (dev->queue, my_make_request); dev->queue->queuedata = dev;
Funcția my_make_request
este de tipul make_request_fn. Un exemplu de folosire a unei astfel de funcții se găsește în drivers/md/md.c.
În cazul în care se folosește această metodă, nu se mai folosesc cozile de cereri, fiecare cerere fiind reprezentă de o structură bio
. Astfel, transferul de date se reduce la parcurgerea fiecărei structuri bio
după cum a fost prezentat mai sus și semnalarea terminării prelucrării acesteia cu ajutorul funcției bio_endio.