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.
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.
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.
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);