Linux系统驱动(十九)块设备驱动

一、块设备驱动简介

(一)简介

系统能够随机访问固定大小(1block–512byte)数据片的设备被称之为块设备。

  • 注:块设备的访问方式是随机的,即可以直接访问设备上的任何块,而不需要按照顺序读取或写入

块设备文件一般都是以安装(挂载)文件系统的方式使用,这也是块设备通常的访问方式。

  • 注:为了使用块设备存储数据,我们需要在其上安装(或挂载)一个文件系统。文件系统是组织和存储文件的一种方式,它定义了如何存储数据、如何命名文件以及如何组织文件的层次结构。安装(挂载)文件系统是将块设备与目录树中的某个点(挂载点)关联起来的过程,之后该目录下的所有文件和目录都将被存储在块设备上

扇区是块设备的基本存储单元,扇区大小一般是2的整数倍,最常见的大小是512字节。所有对块设备的读写操作都是基于扇区进行的。因此,扇区的大小直接决定了块设备能够处理的最小数据量。
扇区是设备的最小可寻址单位,所以块不能比扇区还小,只能数倍与扇区大小。
内核对块大小的要求是:必须是扇区大小的整数倍,并且小于页面的大小,所以块的大小通常是512字节、1K或者4K。

(二)块设备驱动相关概念

磁头:一个磁盘有多少个面就有多少个磁头
磁道:在一个磁头上可以有很多环,这些环就叫做磁道
扇区:磁道上访问数据的最小的单位就是扇区,一个扇区的大小就是512字节

1block = 512字节 1024字节 2048字节 4096字节
1扇区 = 512字节

块设备的能存储的数据 = 磁头 * 磁道 * 扇区 * 512

机械硬盘存放文件时,同一个文件有可能存放在不同的盘片的不同面,且数据可能会分成多个块,并且是无序的存放。因此在读取时,为了减少切换磁头的频率,会先无序的从磁盘读出该文件所有块,然后再对读到内存中的块进行排序后返回给用户空间

  • 注:磁盘擅长连续的读数据而非跳跃的读数据

二、块设备驱动

(一)框架图

在这里插入图片描述

1. 虚拟文件系统(VFS)

隐藏了各种硬件的具体细节,为用户操作不同的硬件提供了一个统一的接口。屏蔽各个文件系统的差异,其基于不同的文件系统格式,比如EXT,FAT等。用户程序对设备的操作都通过VFS来完成,在VFS上面就是诸如open、close、write和read的函数API。

windows文件系统:ntfs
ubuntu文件系统:ext4

2. Disk Cache:硬盘的高速缓存

用户缓存最近访问的文件数据,如果能在高速缓存中找到,就不必去访问硬盘,毕竟硬盘的访问速度慢很多。

3. 映射层(mapping layer)

这一层主要用于确定文件系统的block size,然后计算所请求的数据包含多少个block。同时调用具体文件系统函数来访问文件的inode,确定所请求的数据在磁盘上面的逻辑地址。

4. Generic Block Layer:通用块层

Linux内核把块设备看做是由若干个扇区组成的数据空间,上层的读写请求在通用块层被构造成一个或多个bio结构
在硬盘上连续存储的每个空间会对应一个BIO结构体

5. I/O Scheduler Layer :I/O调度层

负责将通用块层的块I/O操作进行(电梯调度算法)调度、插入、暂存、排序、合并、分发等操作,对磁盘的操作更为高效。负责将通用块层的块I/O操作进行
将连续的BIO(可能属于不同进程)合并成一个request加入到request队列,因此一个request中有一个或多个BIO结构体

6. 块设备驱动层

在块系统架构的最底层,由块设备驱动根据排序好的请求,对硬件进行数据访问。

(二)块设备驱动框架

user:
 open     read    write    close
-------------------(io请求)-----------------------------------
kernel	|中间层: (block_device)
        |   read读1500数据在物理磁盘上三段不连续,构造3个bio结构体
     |	将用户的io请求转化成BIO(block,input ,output),
     |	在物理内存上连续的bio会被合成request,这个request
     |	会被放到内核的一个队列上。
     |---------------------------------------------------------
     |driver:gendisk
     | 1.分配对象
     | 2.对象初始化
     | 3.初始化一个队列  head----request(read)----request(write)---...
     | //4.硬盘设备的初始化
     | 5.注册、注销
------------------------------------------------------------------  
haredware :   分配的内存(模拟真实的设备)(1M)
  • 补充:反汇编命令 objdump

三、块设备驱动API

(一)结构体对象

1. gendisk的结构体对象
    struct gendisk {   
        int major;   			//块设备的主设备号
        int first_minor; 		//起始的次设备号
        int minors; 			//设备的个数,分区的个数
        char disk_name[DISK_NAME_LEN]; //磁盘的名字
        struct disk_part_tbl  *part_tbl;//磁盘的分区表的首地址
        struct hd_struct part0;	//part0分区的描述
        const struct block_device_operations *fops;//块设备的操作方法结构体
        struct request_queue *queue;//队列
        void *private_data; 	//私有数据
    };

2. hd_struct分区的结构体:part0分区的描述
    struct hd_struct {
        sector_t start_sect; 	//起始的扇区号
        sector_t nr_sects;   	//扇区的个数                        
        int  partno;        	//分区号
    };

    //块设备的操作方法结构体
    struct block_device_operations {
        int (*open) (struct block_device *, fmode_t);
        int (*release) (struct gendisk *, fmode_t);
        int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
        int (*getgeo)(struct block_device *, struct hd_geometry *); 
        //设置磁盘的磁头,磁道,扇区的个数的
    };
    
3. hd_geometry结构体
	struct hd_geometry {
    	  unsigned char heads;
    	  unsigned char sectors;
    	  unsigned short cylinders;
    	  unsigned long start;
	};
----------------------------------------------------------------
4. request_queue结构体
	struct  request_queue 
	{
    	/*双向链表数据结构,将所有加入到队列的IO请求组建成一个双向链表*/
    	struct  list_head  queue_head; 
    	struct list_head    requeue_list; //request队列
    	spinlock_t      requeue_lock;     //队列自旋锁
    	unsigned long     nr_requests;     /* 最大的请求数量 */
    	unsigned long     queue_flags;/*当前请求队列的状QUEUE_FLAG_STOPPED*/ 
	};
5. request结构体
	struct  request
	{
	    struct list_head queuelist;/* 请求对象中的链表元素*/
	    struct request_queue *q; /* 指向存放当前请求的请求队列*/
	    unsigned int __data_len; /* 当前请求要求数据传输的总的数据量 */
	    sector_t __sector;         /* 当前请求要求数据传输的块设备的起始扇区 */
	    struct bio *bio;  /* bio对象所携带的信息转存至请求对象中*/
	    struct bio *biotail; /* bio链表*/
	};
	//通常一个request请求可以包含多个bio,一个bio对应一个I/O请求  
6. bio结构体
	struct bio {  
	    struct bio *bi_next;  /* 指向当前bio的下一个对象*/ 
	    unsigned long  bi_flags;   /* 状态、命令等 */ 
	    unsigned long bi_rw;   /* 表示READ/WRITE*/ 
	    struct block_device *bi_bdev;    /* 与请求相关联的块设备对象指针*/ 
	    unsigned short bi_vcnt;  /* bi_io_vec数组中元素个数 */ 
	    unsigned short bi_idx;  /* 当前处理的bi_io_vec数组元素索引 */
	    unsigned int bi_size;  /* 本次传输需要传输的数据总量,byte(扇区大小整数倍) */ 
	    struct bio_vec *bi_io_vec;/* 指向一个IO向量的数组,数组中的内各元素对应一个物理页的page对象 */
	  };
7. bio_vec结构体
	struct bio_vec {  
	    struct page  *bv_page; //指向用于数据传输的页面所对应的struct page对象
	    unsigned int bv_len;   //表示当前要传输的数据大小  
	    unsigned int bv_offset;//表示数据在页面内的偏移量 
	};

1. reque_queue和request及bio的关系

在这里插入图片描述
新版本的linux块设备驱动会有多个request_queue,一般是有几个核就有几个request队列;
每个request_queue上有多个request节点,以链表形式链在一起;
每个request节点又包含多个bio结构体(可能属于不同的进程,但是必定是在物理上连续存储的);
每个bio结构体又包含多个bio_vec结构体,因为bio结构体在内存上可能又分成了多个块,bio_vec结构体中存放了页号,偏移量和长度
在这里插入图片描述
因此,可以根据bio_vec结构体中的信息获得内存中的线性地址;
然后,根据request节点的起始扇区号,加上dev_addr,得到要操作的物理内存地址;

在这里插入图片描述

(二)结构体对象初始化API

1. 初始化结构体
  	struct gendisk *mydisk;

    struct gendisk *alloc_disk(int minors)
    //void put_disk(struct gendisk *disk)
    //归还引用计数
    功能:分配gendisk的内存,然后完成必要的初始化
    参数:
        @minors:分区的个数
    返回值:成功返回分配到的内存的首地址,失败返回NULL


    int register_blkdev(unsigned int major, const char *name)
    //void unregister_blkdev(unsigned int major, const char *name)
    功能:申请设备设备驱动的主设备号
    参数:
        @major : 0:自动申请
                  >0 :静态指定
        @name  :名字  cat /proc/devices
    返回值: 
            major=0 ;成功返回主设备号,失败返回错误码
            major>0 :成功返回0 ,失败返回错误码

    void set_capacity(struct gendisk *disk, sector_t size)
    功能:设置磁盘的容量

    struct request_queue *blk_mq_init_sq_queue(struct blk_mq_tag_set *set,
     const struct blk_mq_ops *ops,unsigned int queue_depth,unsigned int set_flags)
    //void blk_cleanup_queue(struct request_queue *q)
    功能:用于在给定队列深度的情况下使用mq ops设置队列的助手,以及通过mq ops标志传递的助手
    参数:
        @被初始化的tag对象,tag被上层使用,里面包含硬件队列的个数,队列的操作方法结构体,标志位等
     @放入到tag中的操作方法结构体
     @ tag中指定支持的队列深度
     @将tag中队列的处理标志位,例如BLK_MQ_F_SHOULD_MERGE, BLK_MQ_F_BLOCKING等
    返回值:成功返回队列指针,失败返回错误码指针 
2. 队列处理相关函数
	blk_mq_start_request(rq); //开始处理队列
	blk_mq_end_request(rq, BLK_STS_OK); //结束队列处理
	rq_for_each_segment(bvec, rq, iter) //从request->bio_vec
	void* b_buf = page_address(bvec.bv_page) + bvec.bv_offset; //将页地址转换为线性地址(内地址)
	rq_data_dir(rq))   //从request获取本次读写的方向  WRITE 1   READ 0
	dev_addr+(rq->__sector *512) //磁盘设备的地址

3.注册、注销
    void add_disk(struct gendisk *disk)
    //注册
    void del_gendisk(struct gendisk *disk)
    //注销

四、代码示例


  • 12
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值