块设备驱动

块设备的基本概念

  • 页、段、块、扇区之间的关系图如下:


  • 块设备驱动是基于扇区(sector)来访问底层物理磁盘,基于块(block)来访问上层文件系统。
  • 扇区一般是2的n次方大小,典型为512B,内核也要求块是2的n次方大小,且块大小通常为扇区大小的整数倍,并且块大小要小于页面大小,典型大小为512B、1K或4K。

块设备在linux中的结构

块设备与字符设备的区别

块设备驱动程序的特点

1.块设备接口相对复杂,不如字符设备明晰易用
2.块设备驱动程序对整个系统的性能影响较大,速度和效率是设计块设备驱动程要重点考虑的问题
3.系统中使用缓冲区与访问请求的优化管理(合并与重新排序)来提高系统性能

块设备驱动的实现步骤

  • 确定主设备号和次设备号
  • 确定设备名称
  • 创建设备文件
  • 实现块设备驱动程序
    1.实现block_device_operations结构体
    2.实现request(请求)
    3.实现初始化函数,注册块设备
    4.实现销毁函数,取消块设备

几个关键的数据结构

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;
};

gendisk结构体

struct gendisk {    
    int major;          /* major number of driver */    
    int first_minor;    
    int minors;                     /* maximum number of minors, =1 for   disks that can't be partitioned. */
    char disk_name[32];     /* name of major driver */
    struct hd_struct **part;    /* [indexed by minor] */    
    int part_uevent_suppress;   
    struct block_device_operations *fops;   
    struct request_queue *queue;    
    void *private_data; 
    sector_t capacity;
    int flags;  
    struct device *driverfs_dev;    
    struct kobject kobj;
    struct kobject *holder_dir; 
    struct kobject *slave_dir;  
    struct timer_rand_state *random;    
    int policy; 
    atomic_t sync_io;       /* RAID */  
    unsigned long stamp;    
    int in_flight;
    #ifdef  CONFIG_SMP  
    struct disk_stats *dkstats;
    #else   
    struct disk_stats dkstats;
    #endif
}
  • 每一个块设备物理实体由一个gendisk结构体来表示
  • 每个gendisk可以支持若干个分区
  • 每个gendisk中包含本物理实体的全部信息及操作函数接口
  • 整个块设备的注册过程都是围绕gendisk来开展的
  • 各种设备操作
    struct block_device_operations *fops;
  • 管理设备I/O请求
    struct request_queue *queue;
  • sector_t capacity
    以512字节为一个扇区时,该驱动器可包含的扇区数,驱动程序不能直接设置该成员,而要将扇区数传递给set_capacity
  • 内核扇区和硬件扇区的转换
    内核总是认为扇区大小是512字节,而硬件扇区却不都是512字节,所以要相应的转换。
  • 将硬件扇区数转换成内核扇区数
    sectors = nsectors*(hardsect_size/KERNEL_SECTOR_SIZE);
    nsecotrs:硬件实际的扇区数
    hardsect_size:硬件扇区大小
    KERNEL_SECTOR_SIZE:内核扇区大小512字节
    sectors:对应于内核扇区大小的扇区总数
  • 通过set_capacity()可以设置硬件存储器的实际容量
    set_capacity(struct gendisk *gd, sector_t sectors);

请求与请求队列

  • 用户对块设备上文件的操作会转化为一个“请求”,请求由结构体request表示,它定义在文件<linux/blkdev.h>中;
  • request(请求)结构的主要成员
....
sector_t sector; //还没有传输的第一个扇区
unsigned long nr_sectors;//等待传输扇区的总量
unsigned int current_nr_sectors; //当前Bio中剩余的扇区数
struct bio *bio //请求的bio结构链表,需要使用rq_for_each_bio访问
char *buffer//用来查找需要传输的缓冲区
....
  • 请求队列(request_queue)
    • 块设备将它们挂起的块I/O请求保存在请求队列中,该队列由reques_queue结构体表示
    • 块设备的请求队列指包含块设备I/O请求的序列。
    • 创建和初始化请求队列的函数是:
      request_queue_t *blk_init_queue(request_fn_proc *request, spinlock_t *lock);
    • 释放和删除请求队列
      void blk_cleanup_queue(request_queue_t *);
    • 返回下一个需要处理的请求指针
      struct request *blk_fetch_request(request_queue_t *queue);
    • 将请求从队列中删除
      void blkdev_dequeue_request(struct request *req);
    • 将删除的请求再次加入到队列
      void elv_requeue_request(request_queue_t *queue,struct request *req);
    • 设置请求参数
void blk_queue_max_sectors(request_queue_t *queue,
                    unsigned short max);
void blk_queue_max_phys_segments(request_queue_t *queue,
                       unsigned short max);
void blk_queue_max_hw_segments(request_queue_t *queue,
                        unsigned short max);
void blk_queue_max_segment_size(request_queue_t *queue,
                    unsigned int max);
void blk_queue_hardsect_size(request_queue_t *queue,
                        unsigned short max);

块设备驱动的注册与注销

  • 块设备驱动注册函数
    int register_blkdev(unsigned int major, const char *name);
    这个函数不是真正的注册函数,他实际起到的作用只是获取一个设备号。
  • 块设备驱动注销函数
    int unregister_blkdev(unsigned int major, const char *name);
  • 块设备驱动初始化函数主要工作
    • 分配、初始化请求队列,绑定请求队列和请求函数
    • 分配、初始化gendisk,给gendisk的major、fops、queue等成员赋值,最后添加gendisk。
    • 注册块设备驱动
static int ramblock_init(void)
{
    ramblock_disk = alloc_disk(16); /* 16为次设备号个数: 分区个数+1 */

    /* 设置队列 */
    ramblock_queue = blk_init_queue(do_ramblock_request, &ramblock_lock);
    ramblock_disk->queue = ramblock_queue;
         major = register_blkdev(0, "ramblock");  /* cat /proc/devices */   
    ramblock_disk->major       = major;
    ramblock_disk->first_minor = 0;
    sprintf(ramblock_disk->disk_name, "ramblock");
    ramblock_disk->fops        = &ramblock_fops;
    set_capacity(ramblock_disk, RAMBLOCK_SIZE / 512);

    ramblock_buf = kzalloc(RAMBLOCK_SIZE, GFP_KERNEL);

    /*  注册 */
    add_disk(ramblock_disk);

    return 0;
}
  • 块设备驱动退出函数主要工作
    • 清除请求队列;
    • 删除gendisk和对gendisk的引用;
    • 删除对块设备的引用,注销块设备驱动;
static void ramblock_exit(void)
{
    unregister_blkdev(major, "ramblock");
    del_gendisk(ramblock_disk);
    put_disk(ramblock_disk);
    blk_cleanup_queue(ramblock_queue);

    kfree(ramblock_buf);
}

块设备的I/O请求处理

  • 块设备驱动对块设备进行读、写主要通过request来实现。
    块设备驱动请求函数的原型为:
    void request(request_queue_t *queue);
  • 块设备驱动程序处理请求的过程


static void do_ramblock_request(struct request_queue *q)
{
    static int r_cnt = 0;
    static int w_cnt = 0;
    struct request *req;
    
    req = blk_fetch_request(q); /*取出一个请求*/
    while (req) {
        unsigned long offset = blk_rq_pos(req)<<9; /* 请求里的起始地址 */
        unsigned long len = blk_rq_cur_bytes(req);/*要操作数据的大小*/

        if (rq_data_dir(req) == READ)   /*判断是否是读操作*/
        {
            memcpy(req->buffer, ramblock_buf+offset, len);
        }
        else
        {
            memcpy(ramblock_buf+offset, req->buffer, len);
        }       
        if(!__blk_end_request_cur(req, 0))  /*完成当前请求*/
            req = blk_fetch_request(q); /*取下一个请求*/
    }
}                                                                 

代码实战

#include <linux/module.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/timer.h>
#include <linux/genhd.h>
#include <linux/hdreg.h>
#include <linux/ioport.h>
#include <linux/init.h>
#include <linux/wait.h>
#include <linux/blkdev.h>
#include <linux/blkpg.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/gfp.h>
#include <linux/slab.h>

#include <asm/system.h>
#include <asm/uaccess.h>
#include <asm/dma.h>

#define MYDISK_SIZE (1024*1024) //
#define MYDISK_NAME "ram_sice"

static DEFINE_SPINLOCK(mydisk_lock);

static const struct block_device_operations mydisk_fops = {
    .owner  = THIS_MODULE,
};

static struct gendisk *my_disk;
static struct request_queue *my_queue;
static unsigned char *ram_buffer;
static int major;

static void myblock_do_request(struct request_queue * q)
{
    struct request *req;
    req = blk_fetch_request(q);
    while (req) {
        unsigned long offset = blk_rq_pos(req) * 512;
        unsigned long len  = blk_rq_cur_bytes(req);
        if (rq_data_dir(req) == READ){
            memcpy(req->buffer, ram_buffer+offset, len);
            printk("### read \n");
        }
        else{
            printk("### write offset %x\n",offset);
            memcpy(ram_buffer+offset, req->buffer, len);
        }
        if (!__blk_end_request_cur(req, 0))
            req = blk_fetch_request(q);
    }

}

static int my_ram_init(void)
{
    major = register_blkdev(major, MYDISK_NAME);
    my_disk = alloc_disk(8);
    
    my_queue = blk_init_queue(myblock_do_request, &mydisk_lock);

    my_disk->queue = my_queue;

    my_disk->major = major;
    my_disk->first_minor = 0;
    sprintf(my_disk->disk_name, MYDISK_NAME);
    my_disk->fops = &mydisk_fops;
    set_capacity(my_disk, MYDISK_SIZE/512);
    ram_buffer = kzalloc(MYDISK_SIZE, GFP_KERNEL);

    printk("ram_buffer = %x\n",ram_buffer);
    add_disk(my_disk);
    
    return 0;
}

static void my_ram_exit(void)
{
    unregister_blkdev(major, MYDISK_NAME);
    del_gendisk(my_disk);
    put_disk(my_disk);
    blk_cleanup_queue(my_queue);
    kfree(ram_buffer);
}

module_init(my_ram_init);
module_exit(my_ram_exit);
MODULE_LICENSE("GPL");
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容