[快速上手Linux设备驱动]之块设备驱动流程详解一

walfred已经在[快速上手Linux设备驱动]之我看字符设备驱动一

文中详细讲解了linux下字符设备驱动,并紧接着用四篇文章描述了Linux的设备模型,分别是总线、设备、驱动以及是类子系统。为什么要在现在才开始

讲解块设备驱动呢,这里面是有原因,当初walfred自己学习时,是先看的块设备驱动然后才是linux设备模型,导致理解上面有了点偏差,现在我先将

linux设备模型抛出之后,再叙述下linux下的块设备驱动。

[快速上手Linux设备驱动]之块设备驱动流程分两篇文章讲解,分别是:

1、[快速上手Linux设备驱动]之块设备驱动流程详解一 即是本文,主要讲解块设备去字符设备的区别以及块设备驱动中牵涉到的几个重要的结构体。

2、[快速上手Linux设备驱动]之块设备驱动流程详解二 讲述了一个块设备驱动的模板

1块设备与字符设备的区别

1.1从字面上理解,块设备和字符设备最大的区别在于读写数据的基本单元不同。块设备读写数据的基本单元为块,例如磁盘通常为一个sector(扇区),而字符设备的基本单元为字节。所以Linux中块设备驱动往往为磁盘设备的驱动,但是由于磁盘设备的IO性能与CPU相比很差,因此,块设备的数据流往往会引入文件系统的Cache机制。

1.2从实现角度来看,Linux为块设备和字符设备提供了两套机制。字符设备实现的比较简单,内核例程和用户态API一一对应,用户层的Read函数直接对应了内核中的Read例程,这种映射关系由字符设备的file_operations维护。块设备接口相对于字符设备复杂,read、write API没有直接到块设备层,而是直接到文件系统层,然后再由文件系统层发起读写请求。

2相关结构体

2.1 block_device_operations

与字符设备驱动程序一样,块设备驱动程序也包含一个在中定义的block_device_operations结构,其定义如下所示。

struct block_device_operations

{

int (*open) (struct inode *, struct file *);

int (*release) (struct inode *, struct file *);

int (*ioctl) (struct inode *, struct file *, unsigned, unsigned long);

long (*unlocked_ioctl) (struct file *, unsigned, unsigned long);

long (*compat_ioctl) (struct file *, unsigned, unsigned long);

int (*direct_access) (struct block_device *, sector_t, unsigned long *);

int (*media_changed) (struct gendisk *);

int (*revalidate_disk) (struct gendisk *);

int (*getgeo)(struct block_device *, struct hd_geometry *);

struct module *owner;

};

从该结构的定义中,可以看出块设备并不提供read()、write()等函数接口。对块设备的读写请求都是以异步方式发送到设备相关的request队列之中。

关于block_device_operations,walfred曾在[快速上手Linux设备驱动]之一切皆是文件思想一文中有详细叙述。

2.2 gendisk

一个块设备物理实体由一个gendisk结构体来表示(在中定义),每个gendisk可以支持多个分区。

每个gendisk中包含了本物理实体的全部信息以及操作函数接口。整个块设备的注册过程是围绕gendisk来展开的。在驱动程序中需要初始化的gendisk的一些成员如下所示。

struct gendisk

{

int major;            /*主设备号*/

int first_minor;    /*第一个次设备号*/

int minors;          /*次设备号个数,一个块设备至少需要使用一个次设备号,而且块设

备的每个分区都需要一个次设备号,因此这个成员等于1,则表明该块

设备是不可被分区的,否则可以包含minors – 1个分区。*/

char disk_name[32];        /*块设备名称,在/proc/partions中显示*/

struct hd_struct **part;    /*分区表*/

struct block_device_operations *fops;        /*块设备操作接口,与字符设备的

file_operations结构对应*/

struct request_queue *queue;    /* I/O请求队列*/

void *private_data;        /*指向驱动程序私有数据*/

sector_t capacity;    /*块设备可包含的扇区数*/

…… /*其他省略*/

};

2.3 request_queue和request

request和request_queue结构体:Linux块设备驱动中,使用request结构体来表征等待进行的IO请求;并用request_queue来表征一个块IO请求队列.两个结构体的定义如下:

request结构体

struct request{

struct list_head queuelist;

unsigned long flags;

sector_t sector;/*要传输的下一个扇区*/

unsigned long nr_sectors;/*要传送的扇区数目*/

unsigned int current_nr_sector;/*当前要传送的扇区*/

sector_t hard_sector;/*要完成的下一个扇区*/

unsigned long hard_nr_sectors;/*要被完成的扇区数目*/

unsigned int hard_cur_sectors;/*当前要被完成的扇区数目*/

struct bio* bio;/*请求的bio结构体的链表*/

struct bio* biotail;/*请求的bio结构体的链表尾*/

/*请求在屋里内存中占据的不连续的段的数目*/

unsigned short nr_phys_segments;

unsigned short nr_hw_segments;

int tag;

char* buffer;/*传送的缓冲区,内核的虚拟地址*/

int ref_count;/*引用计数*/

...

};

说明:

request结构体的主要成员包括:

sector_t hard_sector;/*要完成的下一个扇区*/

unsigned long hard_nr_sectors;/*要被完成的扇区数目*/

unsigned int hard_cur_sectors;/*当前要被完成的扇区数目*/

/*

*上述三个成员依次是第一个尚未传输的扇区,尚待完成的扇区数,当前IO操作中待完成的扇区数

*但驱动中一般不会用到他们.而是下面的一组成员.

*/

sector_t sector;/*要传输的下一个扇区*/

unsigned long nr_sectors;/*要传送的扇区数目*/

unsigned int current_nr_sector;/*当前要传送的扇区*/

/*

*这三个成员,以字节为单位.如果硬件的扇区大小不是512字节.如字节,则在开始对硬件进行操作之

*前,应先用4来除起始扇区号.前三个成员,与后三个成员的关系可以理解为"副本".

*/

关于unsigned short nr_phys_segments:该成员表示相邻的页被合并后,这个请求在物理内存中的段的数目.如果该设备支持SG(分散/聚合,scatter/gather),可根据该字段申请sizeof(scatterlist*) nr_phys_segments的内存,并使用下面的函数进行DMA映射:

int blk_rq_map_sg(request_queue_t* q, struct request* rq, struct scatterlist *sg);

该函数与dma_map_sg()类似,返回scatterlist列表入口的数量.

关于struct list_head queuelist:该成员用于链接这个请求到请求队列的链表结构,函数blkdev_ dequeue_request()可用于从队列中移除请求.宏rq_data_dir(struct request* req)可获得数据传送方向.返回0表示从设备读取,否则表示写向设备.

2.4 request_queue请求队列

struct request_queue{

...

/*自旋锁,保护队列结构体*/

spinlock_t __queue_lock;

spinlock_t* queue_lock;

struct kobject kobj;/*队列kobject*/

/*队列设置*/

unsigned long nr_requests;/*最大的请求数量*/

unsigned int  nr_congestion_on;

unsigned int  nr_congestion_off;

unsigned int  nr_batching;

unsigned short max_sectors;/*最大扇区数*/

unsigned short max_hw_sectors;

unsigned short max_phys_sectors;/*最大的段数*/

unsigned short max_hw_segments;

unsigned short hardsect_size;/*硬件扇区尺寸*/

unsigned int max_segment_size;/*最大的段尺寸*/

unsigned long seg_boundary_mask;/*段边界掩码*/

unsigned int dma_alignment;/*DMA传送内存对齐限制*/

struct blk_queue_tag* queue_tags;

atomic_t refcnt;/*引用计数*/

unsigned int in_flight;

unsigned int sg_timeout;

unsigned int sg_reserved_size;

int node;

struct list_head drain_list;

struct request* flush_rq;

unsigned char ordered;

};

说明:请求队列跟踪等候的块IO请求,它存储用于描述这个设备能够支持的请求的类型信息,他们的最大大小,多少不同的段可以进入一个请求,硬件扇区大小,对齐要求等参数.其结果是:如果请求队列被配置正确了,它不会交给该设备一个不能处理的请求.

请求队列还要实现一个插入接口,这个接口允许使用多个IO调度器,IO调度器以最优性能的方式向驱动提交IO请求.大部分IO调度器是积累批量的IO请求,并将其排列为递增/递减的块索引顺序后,提交给驱动.另外,IO调度器还负责合并邻近的请求,当一个新的IO请求被提交给调度器后,它会在队列里搜寻包含邻近的扇区的请求.如果找到一个,并且请求合理,调度器会将这两个请求合并.

2.5块I/O

通常一个bio对应一个IO请求.IO调度算法可将连续的bio合并成一个请求.所以一个请求包含多个bio.

struct bio{

sector_t bi_sector;/*要传送的第一个扇区*/

struct bio* bi_next;/*下一个bio*/

struct block_device* bi_bdev;

unsigned long bi_flags;

/*如果是一个写请求,最低有效位被置位,可使用bio_data_dir(bio)宏来获取读写方向*/

unsigned long bi_rw;/*地位表示R/W方向,高位表示优先级*/

unsigned short bi_vcnt;/*bio_vec数量*/

unsigned short bi_idx; /*当前bvl_vec索引*/

unsigned short bi_phys_segments;/*不相邻的物理段的数目*/

unsigned short bi_hw_segments;/*物理合并和DMA remap合并后不相邻的物理扇区*/

unsigned int bi_size;

/*被传送的数据大小(byte),用bio_sector(bio)获取扇区为单位的大小*/

/*为了明了最大的hw尺寸,考虑bio中第一个和最后一个虚拟的可合并的段的尺寸*/

unsigned int bi_hw_front_size;

unsigned int bi_hw_back_size;

unsigned int bi_max_vecs;/*能持有的最大bvl_vecs数*/

struct bio_vec* bio_io_vec;/*实际的vec列表*/

bio_end_io_t* bio_end_io;

atomic_t bi_cnt;

void* bi_private;

bio_destructor_t* bi_destructor;

};

//结构体包含三个成员

struct bio_vec{

struct page* bv_page;//页指针

unsigned int bv_len;//传送的字节数

unsigned int bv_offset;//偏移位置

};

/*一般不直接访问bio的bio_vec成员,而使用bio_for_each_segment()宏进行操作.

*该宏循环遍历整个bio中的每个段.

*/

#define __bio_for_each_segment(bvl, bio, i, start_idx)\

for(

bvl = bio_iovec_idx((bio),(start_idx)),i = (start_idx);\

i bi_vcnt;\

bvl++, i++\

)

#define bio_for_each_segment(bvl, bio, i)\

__bio_for_each_segment(bvl, bio, i, (bio)->bi_idx)

在内核中,提供了一组函数(宏)用于操作bio:

int bio_data_dir(struct bio* bio);

该函数用于获得数据传送方向.

struct page* bio_page(struct bio* bio);

该函数用于获得目前的页指针.

int bio_offset(struct bio* bio);

该函数返回操作对应的当前页的页内偏移,通常块IO操作本身就是页对齐的.

int bio_cur_sectors(struct bio* bio);

该函数返回当前bio_vec要传输的扇区数.

char* bio_data(struct bio* bio);

该函数返回数据缓冲区的内核虚拟地址.

char* bvec_kmap_irq(struct bio_vec* bvec, unsigned long* offset);

该函数也返回一个内核虚拟地址此地址可用于存取被给定的bio_vec入口指向的数据缓冲区.同时会屏蔽中断并返回一个原子kmap,因此,在此函数调用之前,驱动不应该是睡眠状态.

void bvec_kunmap_irq(char* buffer, unsigned long flags);

该函数撤销函数bvec_kmap_irq()创建的内存映射.

char* bio_kmap_irq(struct bio* bio, unsigned long* flags);

该函数是对bvec_kmap_irq函数的封装,它返回给定的比偶的当前bio_vec入口的映射.

char* __bio_kmap_atomic(struct bio* bio, int i, enum km_type type);

该函数是通过kmap_atomic()获得返回给定bio的第i个缓冲区的虚拟地址.

void __bio_kunmap_atomic(char* addr, enum km_type type);

该函数返还由函数__bio_kmap_atomic()获得的内核虚拟地址给系统.

void bio_get(struct bio* bio);

void bio_put(struct bio* bio);

上面两个函数分别完成对bio的引用和引用释放.

下图可以体现出bio/request/request_queue/bio_vec四个结构体之间的关系.

1

linux中流设备_[快速上手Linux设备驱动]之块设备驱动流程详解一相关推荐

  1. python蝴蝶代码_快速数论变换(NTT)及蝴蝶操作构造详解

    快速数论变换 本文不会从头开始介绍NTT算法,所以需要先了解FFT:永远在你身后:快速傅里叶变换(FFT)求解多项式乘法​zhuanlan.zhihu.com 除此之外,先简单的铺垫一些数论的概念同余 ...

  2. 珍爱网java高级等通知?_珍爱网独家JAVA开发工程师面试题及流程详解

    社招的愉快面试经历: 直接是技术总监做面试官.我参加过acm,还是转专业,而且辅修法学,参加过创业大赛也实际创业过,所以面试官对我的简历还是很感兴趣的.首先是自我介绍,我说了自己的学习情况,并且说明自 ...

  3. 快速上手Linux核心命令(一):核心命令简介

    Linux核心命令系列文章目录 快速上手Linux核心命令(一):核心命令简介 快速上手Linux核心命令(二):关机.重启 快速上手Linux核心命令(三):文件和目录操作命令 快速上手Linux核 ...

  4. 十六、Linux驱动之块设备驱动

    1. 基本概念 块设备是Linux三大设备之一,其驱动模型主要针对磁盘,Flash等存储类设备,块设备(blockdevice)是一种具有一定结构的随机存取设备,对这种设备的读写是按块(所以叫块设备) ...

  5. Linux驱动开发|块设备驱动

    块设备驱动 块设备驱动是 Linux 三大驱动类型之一,块设备驱动比字符设备驱动复杂得多,不同类型的存储设备又对应不同的驱动子系统,下面介绍块设备驱动框架及使用 一.块设备介绍 块设备是针对存储设备的 ...

  6. Linux驱动开发---块设备驱动

    块设备驱动(Linux kernel 4.9.x) 主要结构 gendisk结构体:表示一个独立的磁盘设备(或分区) 1.1 定义如下: struct gendisk {/* major, first ...

  7. linux read函数_浅谈Linux内核IO体系之磁盘IO

    前言 Linux I/O体系是Linux内核的重要组成部分,主要包含网络IO.磁盘IO等.基本所有的技术栈都需要与IO打交道,分布式存储系统更是如此.本文主要简单分析一下磁盘IO,看看一个IO请求从发 ...

  8. a33 linux 硬解码_全志A33 linux led驱动编程(附实测参考代码)

    开发平台 开发平台 * 芯灵思SinlinxA33开发板 嵌入式linux 开发板交流 QQ:641395230 #实验原理 在芯灵思开发板上,没有led灯模块,只能通过引脚电平观察: 这里我选择LS ...

  9. 【正点原子Linux连载】第三十二章 U-Boot启动流程详解 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

最新文章

  1. Java Programming Test Question 3
  2. SQL Server R2 地图报表制作(四)
  3. JUC多线程:线程池的创建及工作原理 和 Executor 框架
  4. CentOS6安裝Cacti
  5. 安装Openface,实现人脸比对
  6. Oracle存储过程及调用
  7. 日本富士通正考虑将PC业务并入联想
  8. python查找字符串数量_python如何实现从字符串中找出字符1的位置以及个数的示例...
  9. nodejs libararies
  10. SWFUpload flash上传控件
  11. ionic ionc-item去掉下划线
  12. ThinkPHP6 隐藏身份证中间8位
  13. 教你如何有效防止DDos攻击?
  14. 给宝宝补钙的健康新钙念
  15. 基于分区表的物化视图快速刷新以及维护
  16. Android面试老生常谈的 View 事件分发机制,看这一篇就够了
  17. LINUX支持exfat格式U盘
  18. 基于java校园新闻管理系统数据库模块的论文_校园新闻管理系统设计与实现 大学论文.doc...
  19. ubuntu12.04安装QQ2012教程
  20. 用计算机来算出情人节,关于情人节的说说短句子 有关情人节的爱情句子

热门文章

  1. linux 如何自定义安装路径,Linux下安装mysql并自定义数据的存储路径
  2. SpringCloud feign、hystrix、zuul超时配置
  3. 源码编译打包_Atlas 2.1.0 实践(1)—— 编译Atlas
  4. 使用Elasticsearch和C#理解和实现CRUD APP的初学者教程——第2部分
  5. 化身阿凡达,国外小哥开源 AI 实时变脸工具 Avatarify
  6. Windows 10 移动版正式结束支持
  7. 介绍 SQL Server 的安全配置
  8. 微软解释为什么 Rust 是系统编程的最佳选择
  9. python取字符串一部分_python,如何获取字符串中的子字符串,部分字符串
  10. bigdecimal除法保留4位小数_小猿圈分享-MySQL保留几位小数的4种方法