欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 美景 > Linux系统驱动(十九)块设备驱动

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

2025/5/14 11:34:50 来源:https://blog.csdn.net/weixin_44254079/article/details/141214367  浏览:    关键词:Linux系统驱动(十九)块设备驱动

文章目录

  • 一、块设备驱动简介
    • (一)简介
    • (二)块设备驱动相关概念
  • 二、块设备驱动
    • (一)框架图
      • 1. 虚拟文件系统(VFS)
      • 2. Disk Cache:硬盘的高速缓存
      • 3. 映射层(mapping layer)
      • 4. Generic Block Layer:通用块层
      • 5. I/O Scheduler Layer :I/O调度层
      • 6. 块设备驱动层
    • (二)块设备驱动框架
  • 三、块设备驱动API
    • (一)结构体对象
      • 1. reque_queue和request及bio的关系
    • (二)结构体对象初始化API
  • 四、代码示例

一、块设备驱动简介

(一)简介

系统能够随机访问固定大小(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:分区的个数返回值:成功返回分配到的内存的首地址,失败返回NULLint 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_vecvoid* b_buf = page_address(bvec.bv_page) + bvec.bv_offset; //将页地址转换为线性地址(内地址)rq_data_dir(rq))   //从request获取本次读写的方向  WRITE 1   READ 0dev_addr+(rq->__sector *512) //磁盘设备的地址3.注册、注销void add_disk(struct gendisk *disk)//注册void del_gendisk(struct gendisk *disk)//注销

四、代码示例


版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词