块设备驱动第一课

来源:互联网 发布:小说语音阅读软件 编辑:程序博客网 时间:2024/06/08 04:45

概述

与字符设备不同,块设备一开始加载就已经打开。用户对他的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 requesttypedef void (request_fn_proc) (struct request_queue *q);
//不经过I/O调度处理的struct biotypedef 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");

实例二:

0 0
原创粉丝点击