Wydajniejsza obsługa blokowych operacji weścia-wyjścia (struktura bio)

Problem z odczytywaniem danych z urządzeń blokowych (dyski, cd-rom, pamieć flash) polega na tym, że dane nie są odczytywane sekwensyjnie tak jak z urządzeń znakowych. Możliwe jest odczytywanie i zapisywanie w dowolne miejsce urządzenia.


Linux <= 2.4

Struktura buffer_head zdefiniowana w <linux/buffer_head.h>

Buffer_head bezpośrednio mapuje blok na dysku z buforem w pamięci. Jest w jądrach wcześniejszych niż 2.6 podstawową jednostką przechowującą dane potrzebne do manipulowania operacjami I/O.

Wadą tego rozwiązania jest fakt, że duże operacje I/O muszą być rozbijane przez jądro i część informacji jest powtarzana w każdym buforze co jest nieefektywne i uciążliwe. Dlatego powstała struktura bio dużo elastyczniejsza niż buffer_head.


Linux >= 2.6

Struktura bio zdefiniowana w <linux/bio.h>
 struct bio {
         sector_t                bi_sector;
         struct bio              *bi_next;       /* request queue link */
         struct block_device     *bi_bdev;
         unsigned long           bi_flags;       /* status, command, etc */
         unsigned long           bi_rw;          /* bottom bits READ/WRITE,
                                                  * top bits priority
                                                  */
         unsigned short          bi_vcnt;        /* how many bio_vec's */
         unsigned short          bi_idx;         /* current index into bvl_vec */
 
         /* Number of segments in this BIO after
          * physical address coalescing is performed.
          */
         unsigned short          bi_phys_segments;
 
         /* Number of segments after physical and DMA remapping
          * hardware coalescing is performed.
          */
         unsigned short          bi_hw_segments;
 
         unsigned int            bi_size;        /* residual I/O count */
         unsigned int            bi_max_vecs;    /* max bvl_vecs we can hold */

         struct bio_vec          *bi_io_vec;     /* the actual vec list */
         bio_end_io_t            *bi_end_io;
         atomic_t                bi_cnt;         /* pin count */
         void                    *bi_private;
         bio_destructor_t        *bi_destructor; /* destructor */
 };

Struktura bio nie jest skomplikowana. Główne jej elementy to:

Struktura bio_vect ma bardzo prostą definicję:

 struct bio_vec {
	struct page	*bv_page;
	unsigned int	bv_len;
	unsigned int	bv_offset;
 };

Struktura bio śledzi bufor z danymi za pomocą wskaźnika struct page co pociąga za sobą:

Na pierwszy rzut oka struktura bio wydaje się trudniejsza do obsługi niż stary buffer_head, który od razu zapewniał adres do pojedyńczego kawałka danych. Praca z bio nie jest jednak skomplikowana.


Jak się posługiwać bio?

Początkowy sektor dla całego bio znajduje się w polu bi_sector. Całkowity rozmiar operacji w polu bi_size (w bajtach). Można zdobyć rozmiar operacji w sektorach za pomocą:

    bio_sectors(struct bio *bio);

Makro:

    int bio_data_dir(struct bio *bio);
zwraca READ lub WRITE w zależności od typu operacji.

Jeśli chcemy coś zrobić z całą tablicą bio_vec przyda mam się makro:

    bio_for_each_segment(bvec, bio, segno) {
	/* Do something with this segment */
    }
W pętli segno przechowuje aktualną pozycję w tablicy a bvec jest wskażnikim na aktualną strukturę bio_vec.

Przy kończeniu operacji I/O wykorzystuje się funkcję:

    void bio_endio(struct bio *bio, unsigned int nbytes, int error);


Literatura:
Więcej informacji można znaleźć w <linux/bio.h> i block/biodoc.txt w katalogu z dokumentacją jądra.


Powrót

Autor: Mariusz Zawadzki, mz209441@zodiac.mimuw.edu.pl