块设备驱动

块设备的基本概念

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


  • 块设备驱动是基于扇区(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");
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,125评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,293评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,054评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,077评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,096评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,062评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,988评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,817评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,266评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,486评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,646评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,375评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,974评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,621评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,642评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,538评论 2 352

推荐阅读更多精彩内容