块设备驱动第一课

概述

与字符设备不同,块设备一开始加载就已经打开。用户对他的open操作只是在已经打开的设备上
找一个索引节点(即找到文件存储的位置)。不用写自动生成设备文件的人相关函数调用,他天生
会自动生成设备文件,生成的设备文件保存在/dev/block/ 目录下。

1.竞态:

几个进程同时读写时会不会有冲突呢?大文件拷贝的过程中又有一个小文件需要拷贝时,
是先等大文件拷贝完在开始小文件的拷贝吗?要是大文件有1T要拷贝1个小时,是否还是等其拷贝
完成再启动下个文件的拷贝?
解决以上问题,内核自有一套调度算法,通过各种调度策略(如电梯算法)将块设备的读写任务
规划成合理的的任务块并将这些任务块排成一个队列,我们称其为“请求队列”。
对于驱动开发者,无需关心这些调度算法,我们只要从请求请队列中取“请求”就行了,

描述传输属性的bio_vec,向上封装成bio,再经过规划变成request,再经过调度算法把多个请
求排序变成request_queue.

函数接口

1.出处与定义

include/linux/genhd.h
struct gendisk {

    int major;          //主设备号
    int first_minor;    //第一个次设备号
    int minors;         //最大分区数        
    char disk_name[DISK_NAME_LEN]; //设备名,设备文件基本名(设备文件自动生成时会用到)  
    struct disk_part_tbl __rcu *part_tbl;//分区表(一般写死)
    struct hd_struct part0;              //磁盘容量
    const struct block_device_operations *fops;  //操作方法集
    struct request_queue *queue; //请求队列,把运用层的读写函数放此队列中,驱动会一一进行响应
    void *private_data;     //私有数据
    ...
};

2.设备号:

/*
* 功能:申请设备号,动态或静态
* 输入参数:unsigned int:主设备号:0:动态  大于0:静态指定
*         const char *:设备名字
* 返回值:成功:主设备号 失败:负数
*/
int register_blkdev(unsigned int, const char *);

/*
* 功能:释放设备号
* 输入参数:unsigned int:主设备号
*         const char *:设备名字
* 返回值:none
*/
 void unregister_blkdev(unsigned int, const char *);

3.容量:

/*
* 功能:得到磁盘容量
* 输入参数:struct gendisk *disk:磁盘对象
* 返回值:磁盘总扇区数
*/
sector_t get_capacity(struct gendisk *disk)
/*
* 功能:设置磁盘容量
* 输入参数:struct gendisk *disk:磁盘对象
*         sector_t size; 扇区数
* 返回值:none
*/
void set_capacity(struct gendisk *disk, sector_t size)

4.操作方法集:


struct block_device_operations {
    int (*open) (struct block_device *, fmode_t);
    void (*release) (struct gendisk *, fmode_t);
    int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
    int (*media_changed) (struct gendisk *);//不要紧
    int (*revalidate_disk) (struct gendisk *);//一般不用
    int (*getgeo)(struct block_device *, struct hd_geometry *);//几何信息
    struct module *owner;//THIS_MODULE
    ...
};

5.请求队列:

<include/linux/blkdev.h>

struct request_queue {
    request_fn_proc     *request_fn;  //
    make_request_fn     *make_request_fn;
    ...
}
//经过I/O调度处理的请求struct request
typedef void (request_fn_proc) (struct request_queue *q);
//不经过I/O调度处理的struct bio
typedef void (make_request_fn) (struct request_queue *q, struct bio *bio);
<include/linux/bio.h>
struct bio {
    //块传输控制的各种属性
    ...
}
没有经过I/O调度的包
struct bio {
    unsigned long       bi_flags;   /* status, command, etc */
    unsigned long       bi_rw;      /* 方向bottom bits READ/WRITE,                    
    struct bvec_iter    bi_iter;    //外存首地址
    unsigned short      bi_max_vecs;//最大的内存块
    atomic_t        bi_cnt;     /*当前内存块 pin count */
    struct bio_vec      *bi_io_vec; //内存块
    ...
};
<include/linux/bvec.h>

struct bio_vec {
    struct page *bv_page;    //页指针
    unsigned int    bv_len;  //传输的字节数
    unsigned int    bv_offset;//偏移位置
};
//       I/O调度后的数据包
struct request {
    u64 cmd_flags;      //读写方向
    unsigned int __data_len;    /* total data len */
    sector_t __sector;      /* 外存首地址sector cursor */
    struct bio *bio;        //从bio找到内存大小及读写区域
    int errors;
    ...
};

请求的操作函数

/*
* 功能:创建并初始化请求队列,用于需要调度的机械硬盘等设备
* 输入参数:funcp:      处理请求队列的回调函数
*         spin_lock:  自旋锁
* 返回值:none
*/
void blk_init_queue(funcp,spin_lock);
/*
* 功能:创建伪请求队列,用于不需要调度的固态硬盘等设备
* 输入参数:flg:      权限,一般用GFP_KERNEL
* 返回值:none
*/
void blk_alloc_queue(flg);
/*
* 功能:给伪请求队列绑定处理函数,用于不需要调度的固态硬盘等设备
* 输入参数:rq_queue:      权限,一般用GFP_KERNEL
*         rq_queue:       处理伪请求队列的回调函数
* 返回值:none
*/
void blk_queue_make_request(rq_queue, func);
·/*
* 功能:清空请求队列
* 输入参数:struct request_queue *q:请求队列
* 返回值:none
*/
void blk_cleanup_queue(struct request_queue *q)
/*
* 功能:从请求队列中提取出一个请求
* 输入参数:struct request_queue *q:请求队列
* 返回值:得到的请求
*/
struct request *blk_fetch_request(struct request_queue *q);
/*
* 功能:从请求中得到要要读写的外存位置
* 输入参数:struct request_queue *q:请求
* 返回值:扇区
*/
sector_t blk_rq_pos(const struct request *rq)
/*
* 功能:从请求中得到要读写的字节数
* 输入参数:struct request_queue *q:请求
* 返回值:字节数
*/
int blk_rq_cur_bytes(const struct request *rq)


unsigned int blk_rq_cur_sectors(const struct request *rq)
/*
* 功能:从请求中得到读写方向
* 输入参数:struct request_queue *q:请求
* 返回值:1(WRITE宏):写  0(READ宏):读
*/
int rq_data_dir(struct request *rq)
/*
 * 功能:从bio中得到内存首地址
 * 参数:struct bio *bio - bio
 * 返回值:内存首地址
 */
void *bio_data(struct bio *bio);

gendisk相关的操作函数

/*
 * 功能:分配并初始化gendisk
 * 参数:struct bio *bio :最大分区数
 * 返回值:gendisk 
 */
struct gendisk *alloc_disk(int max_secter)
/*
 * 功能:释放gendisk
 * 参数:struct gendisk *disk: gendisk
 * 返回值:none
 */
void put_disk(struct gendisk *disk)
/*
 * 功能:注册gendisk
 * 参数:struct bio *bio :最大分区数
 * 返回值:
*/
add_disk();
/*
 * 功能:注销gendisk
 * 参数:struct bio *bio :最大分区数
 * 返回值:
 */
 add_disk();

范例

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>

#include <linux/fs.h>
#include <linux/genhd.h>
#include <linux/hdreg.h>
#include <linux/blkdev.h>

#include <linux/spinlock.h>

#include <asm/current.h>
#include <linux/sched.h>

#include <linux/vmalloc.h>

static struct gendisk *blkdev = NULL;
static spinlock_t lock;

#define BLKNAME "blkdev"

#define SECTOR 512
#define SECTNR (1024*2*10)

static char *addrp = NULL;

static int blkdev_open(struct block_device *dev, fmode_t mode)
{
    //get command and pid
    printk(KERN_INFO "(%s:pid=%d), %s : %s : %d\n",
        current->comm, current->pid, __FILE__, __func__, __LINE__);
    return 0;
}

static int blkdev_release(struct gendisk *genhd, fmode_t mode)
{
    //get command and pid
    printk(KERN_INFO "(%s:pid=%d), %s : %s : %d\n",
        current->comm, current->pid, __FILE__, __func__, __LINE__);

    return 0;
}

/*对块设备进行分区操作*/
static int blkdev_getgeo(struct block_device *blkdev, struct hd_geometry *geo)
{
    unsigned long size;

    //get command and pid
    printk(KERN_INFO "(%s:pid=%d), %s : %s : %d\n",
        current->comm, current->pid, __FILE__, __func__, __LINE__);


    size = SECTNR*SECTOR;

    geo->cylinders = (size & ~0x3f) >> 6;//柱面数
    geo->heads   = 4;                     //头
    geo->sectors = 16;                    //扇区
    geo->start   = 0;                     //

    return 0;
}

/*对请求队列中的任务块进行处理*/
static void rw_proc(struct request_queue *q)
{
    struct request *rq = NULL;

    unsigned long offset;
    int len;
    char *buffer = NULL;

    while(1){
        if(!rq){
            //从请求队列中提取请求
            rq = blk_fetch_request(q);
            if(NULL == rq)
                break;
        }

        if (rq->cmd_type != REQ_TYPE_FS){
            printk (KERN_ERR "Skip non-fs request\n");
            // 全部任务的完成函数,即处理队列中任务的过程中遇到错误就跳出while循环
            __blk_end_request_all(rq, -EIO);
            break;
        }

        //提取出要读写的外存的偏移量
        offset = blk_rq_pos(rq) << 9;
        //实际读写大小
        len = blk_rq_cur_bytes(rq);
        //获得内存首地址
        buffer = bio_data(rq->bio);

        // 读写超出范围
        if((offset+len) > SECTNR*SECTOR){
            printk (KERN_ERR "no space\n");
            // complate function
            __blk_end_request_all(rq, -EIO);
            break;
        }

        if(WRITE == rq_data_dir(rq)){//获得读写方向
            memcpy(addrp+offset, buffer, len);
        }else{
            memcpy(buffer, addrp+offset, len);
        }

        if(!__blk_end_request_cur(rq, 0)){//当前任务的完成函数,出错就返回0
            rq = NULL;
        }
    }
}

static struct block_device_operations ops = {//块设备的操作方法集
    .owner  = THIS_MODULE,
    .open   = blkdev_open,
    .release= blkdev_release,
    .getgeo = blkdev_getgeo,
};


/*模块初始化*/
static int __init blkdev_init(void)
{
    int ret;

    //get command and pid
    printk(KERN_INFO "(%s:pid=%d), %s : %s : %d - entry\n",
        current->comm, current->pid, __FILE__, __func__, __LINE__);

    //1. 分配 gendisk 并初始化
    blkdev = alloc_disk(3);
    if(NULL == blkdev){
        return -ENOMEM;
    }

    //2. 为块设备申请一个主设备号(0表动态)
    blkdev->major = register_blkdev(0, BLKNAME);
    if(0 > blkdev->major){
        ret = blkdev->major;
        goto ERR_STEP;
    }
    blkdev->first_minor = 0;

    snprintf(blkdev->disk_name, DISK_NAME_LEN, "%sa", BLKNAME);

    set_capacity(blkdev, SECTNR);//设置容量

    blkdev->fops = &ops;//指定操作方法集

    spin_lock_init(&lock);//初始化一个自旋锁
    blkdev->queue = blk_init_queue(rw_proc, &lock);//初始化请求队列
    if(!blkdev->queue){
        ret = -ENOMEM;
        goto ERR_STEP1;
    }
    blk_queue_logical_block_size(blkdev->queue, SECTOR);

    addrp = vmalloc(SECTOR*SECTNR);//申请一片较大的内存空间,用来模拟磁盘(一字节为单位)
    if(!addrp){
        ret = -ENOMEM;
        goto ERR_STEP1;
    }

    //3. 注册 gendisk
    add_disk(blkdev);

    //get command and pid
    printk(KERN_INFO "(%s:pid=%d), %s : %s : %d - ok\n",
        current->comm, current->pid, __FILE__, __func__, __LINE__);
    return 0;   

ERR_STEP1:
    unregister_blkdev(blkdev->major, BLKNAME);  //释放主设备号

ERR_STEP:
    put_disk(blkdev);//释放gendisk
    return ret;
}

static void __exit blkdev_exit(void)
{
    //1. free resource
    unregister_blkdev(blkdev->major, BLKNAME);//释放主设备号
    blk_cleanup_queue(blkdev->queue);   //清空请求队列
    put_disk(blkdev);//释放gendisk

    vfree(addrp);   //释放申请的内存空间

    //2. unregist gendisk
    del_gendisk(blkdev);    //注销gendisk

    //get command and pid
    printk(KERN_INFO "(%s:pid=%d), %s : %s : %d - leave\n",
        current->comm, current->pid, __FILE__, __func__, __LINE__);
}

module_init(blkdev_init);
module_exit(blkdev_exit);

MODULE_LICENSE("GPL");

实例二:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xxgui1992

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值