zoukankan      html  css  js  c++  java
  • 20150310 块设备驱动程序

    20150310 块设备驱动程序

    2015-03-10 李海沿

        接下来我们来实现块设备驱动程序。

    一、块设备结构体

    1. file_operations 结构体

    和字符设备驱动中file_operations 结构体类似,块设备驱动中也有一个

    block_device_operations 结构体,它的声明位于/include/linux 录下的fs.h 文件中,它是对

    块操作的集合。

    struct block_device_operations{

    int(*open)(struct inode *, struct file*); //打开设备

    int(*release)(struct inode *, struct file*); //关闭设备

    //实现ioctl 系统调用

    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; //指向拥有这个结构体模块的指针,通常被初始化位THIS_MODULE

    };

    与字符驱动不同的是在这个结构体中缺少了read()和write()函数,那是因为块设备的I/O 子系统中,这些操作都是有request 函数进行处理。

    request 函数的原型如下:

    void request(request_queue_t *queue);

    2.gendisk结构体

    gendisk 结构体的定义位于/include/linux 目录下的genhd.h 文件中,如下所示。

    struct gendisk {

    /*

    *这三个成员的定义依次是:主设备号、第一个次设备号,次设备号。一个驱动中至有一个次设备号,

    *如果驱动器是一个可被分区,那么每一个分区都将分配一个次设号。

    */

    int major;

    int first_minor;

    int minors;

    //这个数组用以存储驱动设备的名字

    char disk_name[DISK_NAME_LEN];

    char *(*devnode)(struct gendisk *gd, umode_t *mode);

    unsigned int events;

    unsigned int async_events;

    struct disk_part_tbl __rcu *part_tbl;

    struct hd_struct part0;

    //这个结构体用以设置驱动中的各种设备操作

    const struct block_device_operations *fops;

    //Linux 内核使用这个结构体为设备管理I/O 请求,具体详解见request_queue 结构。

    struct request_queue *queue;

    void *private_data;

    int flags;

    struct device *driverfs_dev;

    struct kobject *slave_dir;

    struct timer_rand_state *random;

    atomic_t sync_io;

    struct disk_events *ev;

    #ifdef CONFIG_BLK_DEV_INTEGRITY

    struct blk_integrity *integrity;

    #endif

    int node_id;

    };

    gendisk 结构体是是动态分配,但是驱动程序自己不能动态分配该结构,而是通过调用

    alloc_disk()函数进行动态分配。

    struct gendisk *alloc_disk(int minors);

    其中minors 是该磁盘使用的次设备号。

    但是分配了gendisk 结构并不意味着该磁盘就对系统可用,使用之前得初始化结构体并

    且调用add_disk()函数。

    //初始化结构体

    struct request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)

    //添加分区

    void add_disk(struct gendisk *gd)

    如果不再需要这个磁盘,则对其进行卸载。

    //删除分区

    Void del_gendisk(struct gendisk *gd)

    void blk_cleanup_queue(struct request_queue *q)

    3.bio结构体

    bio 结构体的定义位于/include/linux 目录下的linux_blk_types.h 文件中。

    struct bio {

    //需要传输的第一个(512byte)扇区

    sector_t bi_sector;

    struct bio *bi_next;

    struct block_device *bi_bdev;

    unsigned long bi_flags;

    unsigned long bi_rw;

    unsigned short bi_vcnt;

    unsigned short bi_idx;

    //BIO 中所包含的物理段数目

    unsigned int bi_phys_segments;

    //所传输的数据大小(byte 为单位)

    unsigned int bi_size;

    unsigned int bi_seg_front_size;

    unsigned int bi_seg_back_size;

    unsigned int bi_max_vecs;

    atomic_t bi_cnt;

    struct bio_vec *bi_io_vec;

    bio_end_io_t *bi_end_io;

    void *bi_private;

    #ifdef CONFIG_BLK_CGROUP

    struct io_context *bi_ioc;

    struct cgroup_subsys_state *bi_css;

    #endif

    #if defined(CONFIG_BLK_DEV_INTEGRITY)

    struct bio_integrity_payload *bi_integrity;

    #endif

    bio_destructor_t *bi_destructor;

    struct bio_vec bi_inline_vecs[0];

    };

    bio 结构体包含了驱动程序执行请求的所有信息。既描述了磁盘的位置,又描述了内存

    的位置,是上层内核与下层驱动的连接纽带。

    struct bio_vec *bi_io_vec;

    bio_vec 结构体的声明为:结构bio_vec 代表了内存中的一个数据段,数据段用页、偏移和长度描述

    struct bio_vec {

    struct page *bv_page; /*数据段所在的页*/

    unsigned short bv_len; /*数据段的长度*/

    unsigned short bv_offset; /*数据段页内偏移*/

    };

    4. requeset 结构体

    request 结构体代表了挂起的I/O 请求,每个请求用一个结构request 实例描述,存放

    在请求队列链表中,由电梯算法进行排序,每个请求包含一个或多个结构bio 实例。requeest

    结构体声明位于/include/linux 目录下的blkdev.h 文件中。

    struct request {

    struct list_head queuelist;

    struct call_single_data csd;

    struct request_queue *q;

    unsigned int cmd_flags;

    enum rq_cmd_type_bits cmd_type;

    unsigned long atomic_flags;

    int cpu;

    unsigned int __data_len;

    sector_t __sector;

    struct bio *bio;

    struct bio *biotail;

    struct hlist_node hash;

    union {

    struct rb_node rb_node;

    void *completion_data;

    };

    union {

    struct {

    struct io_cq *icq;

    void *priv[2];

    } elv;

    struct {

    unsigned int seq;

    struct list_head list;

    rq_end_io_fn *saved_end_io;

    } flush;

    };

    struct gendisk *rq_disk;

    struct hd_struct *part;

    unsigned long start_time;

    #ifdef CONFIG_BLK_CGROUP

    struct request_list *rl;

    unsigned long long start_time_ns;

    unsigned long long io_start_time_ns;

    #endif

    unsigned short nr_phys_segments;

    #if defined(CONFIG_BLK_DEV_INTEGRITY)

    unsigned short nr_integrity_segments;

    #endif

    unsigned short ioprio;

    int ref_count;

    void *special;

    char *buffer;

    int tag;

    int errors;

    unsigned char __cmd[BLK_MAX_CDB];

    unsigned char *cmd;

    unsigned short cmd_len;

    unsigned int extra_len;

    unsigned int sense_len;

    unsigned int resid_len;

    void *sense;

    unsigned long deadline;

    struct list_head timeout_list;

    unsigned int timeout;

    int retries;

    rq_end_io_fn *end_io;

    void *end_io_data;

    struct request *next_rq;

    };

    5. request_queue 结构体

    每个块设备都有一个请求队列,每个请求队列单独执行I/O 调度,请求队列是由请求结

    构实例链接成的双向链表,链表以及整个队列的信息用request_queue 结构体描述,称为请

    求队列对象结构或请求队列结构。request_queue 结构体声明位于/include/linux 目录下的

    blkdev.h 文件中。

    struct request_queue{

        /*

         * Together with queue_head for cacheline sharing

         */

        struct list_head    queue_head;

        struct request        *last_merge;

        struct elevator_queue    *elevator;

        /*

         * the queue request freelist, one for reads and one for writes

         */

        struct request_list    rq;

        request_fn_proc        *request_fn;

        make_request_fn        *make_request_fn;

        prep_rq_fn        *prep_rq_fn;

        unplug_fn        *unplug_fn;

        prepare_discard_fn    *prepare_discard_fn;

        merge_bvec_fn        *merge_bvec_fn;

        prepare_flush_fn    *prepare_flush_fn;

        softirq_done_fn        *softirq_done_fn;

        rq_timed_out_fn        *rq_timed_out_fn;

        dma_drain_needed_fn    *dma_drain_needed;

        lld_busy_fn        *lld_busy_fn;

        /*

         * Dispatch queue sorting

         */

        sector_t        end_sector;

        struct request        *boundary_rq;

        /*

         * Auto-unplugging state

         */

        struct timer_list    unplug_timer;

        int            unplug_thresh;    /* After this many requests */

        unsigned long        unplug_delay;    /* After this many jiffies */

        struct work_struct    unplug_work;

        struct backing_dev_info    backing_dev_info;

        /*

         * The queue owner gets to use this for whatever they like.

         * ll_rw_blk doesn't touch it.

         */

        void            *queuedata;

        /*

         * queue needs bounce pages for pages above this limit

         */

        gfp_t            bounce_gfp;

        /*

         * various queue flags, see QUEUE_* below

         */

        unsigned long        queue_flags;

        /*

         * protects queue structures from reentrancy. ->__queue_lock should

         * _never_ be used directly, it is queue private. always use

         * ->queue_lock.

         */

        spinlock_t        __queue_lock;

        spinlock_t        *queue_lock;

        /*

         * queue kobject

         */

        struct kobject kobj;

        /*

         * queue settings

         */

        unsigned long        nr_requests;    /* Max # of requests */

        unsigned int        nr_congestion_on;

        unsigned int        nr_congestion_off;

        unsigned int        nr_batching;

        void            *dma_drain_buffer;

        unsigned int        dma_drain_size;

        unsigned int        dma_pad_mask;

        unsigned int        dma_alignment;

        struct blk_queue_tag    *queue_tags;

        struct list_head    tag_busy_list;

        unsigned int        nr_sorted;

        unsigned int        in_flight[2];

        unsigned int        rq_timeout;

        struct timer_list    timeout;

        struct list_head    timeout_list;

        struct queue_limits    limits;

        /*

         * sg stuff

         */

        unsigned int        sg_timeout;

        unsigned int        sg_reserved_size;

        int            node;

    #ifdef CONFIG_BLK_DEV_IO_TRACE

        struct blk_trace    *blk_trace;

    #endif

        /*

         * reserved for flush operations

         */

        unsigned int        ordered, next_ordered, ordseq;

        int            orderr, ordcolor;

        struct request        pre_flush_rq, bar_rq, post_flush_rq;

        struct request        *orig_bar_rq;

        struct mutex        sysfs_lock;

    #if defined(CONFIG_BLK_DEV_BSG)

        struct bsg_class_device bsg_dev;

    #endif

    };

    二、程序分析

    1.定义各种结构体指针

    2.实现fileoperation结构体

    如图所示,block_getgeo函数中的功能是伪装磁盘的磁头信息,磁头个数,柱面,容量等信息,是为了支持fdisk进行分区。

    3.在初始化函数中

    如图所示:

    首先分配一个动态分配一个gendisk结构体,然后初始化各种设置,分配内存,最后试用add_disk注册。

    4.实现读写函数

    由于块设备没字符设备中所谓的read和write函数,所以我们的读写函数都是在do_block_request函数中实现的。

    5.在exit函数中

    最后就是在exit函数中释放我们前面的申请的内存等资源

    6.编译命令

    Insmod blk.ko

    ll /dev/blkdev

    mkfs.ext3 /dev/blkdev

    fdisk /dev/blkdev

    附驱动源程序:

      1 /* blkdev.c */
      2 #include <linux/genhd.h>
      3 #include <linux/init.h>
      4 #include <linux/module.h>
      5 #include <linux/blkdev.h>
      6 #include <linux/types.h>
      7 #include <linux/fs.h>
      8 #include <linux/hdreg.h> //geo
      9 #include <linux/vmalloc.h>
     10 
     11 #define BLKDEV_SIZE  2*1024*1024
     12 
     13 /* 定义一个指向请求队列的结构体指针 */
     14 static struct request_queue *blkdev_queue;
     15 /* 定义一个指向独立分区(磁盘)的结构体指针 */
     16 static struct gendisk *blkdev_disk;
     17 /* 定义一个自旋锁 */
     18 static DEFINE_SPINLOCK(blkdev_lock);
     19 /* 主设备号 */
     20 static int blkdev_major;
     21 static unsigned char blkdev_data[BLKDEV_SIZE];
     22 static unsigned long lock_buf;  //保存分配内存的指针
     23 
     24 //由于没有老式的磁头等信息,但是为了支持fdisk分区工具,我们必须伪装有
     25 static int block_getgeo(struct block_device *bdev, struct hd_geometry *geo){
     26      //容量: heads * cvlinders * 512
     27      geo->heads     = (unsigned char)2;    //磁头个数 ,2面
     28      geo->cylinders     = (unsigned short)32;    //柱面, 32环
     29      geo->sectors = (unsigned char)BLKDEV_SIZE / 2 / 32 / 512; //容量
     30      return 0;
     31 }
     32 /*
     33  * 块设备操作的集合
     34  */
     35 struct block_device_operations blkdev_fops = {
     36     .owner = THIS_MODULE,
     37     .getgeo = block_getgeo,
     38 };
     39 
     40 static void do_block_request(struct request_queue *q){
     41     struct request *req;
     42     static int r_cnt=0;
     43     static int w_cnt=0;
     44 
     45     while((req = blk_fetch_request(q)) != NULL){
     46         /*数据传输*/
     47         unsigned long offset = blk_rq_pos(req) * 512; //
     48         unsigned long len = blk_rq_sectors(req) * 512;//长度
     49         /*
     50          * 结束一个队列请求,第二个参数表示请求处理结果
     51          * 成功的话设定为1,失败的话设定为0 或者错误号
     52         */
     53         /*
     54          * rq_data_dir()函数返回该请求的方向:读还是写
     55          */
     56         if(rq_data_dir(req) == READ){
     57             printk(KERN_ALERT "read %dth offset=%ld len=%ld
    ",r_cnt,offset,len);
     58             /* 从块设备读取数据 */
     59             memcpy(req->buffer, blkdev_data+offset,len);
     60         }else{
     61             printk(KERN_ALERT "write %dth offset=%ld len=%ld
    ",w_cnt,offset,len);
     62             /* 把缓冲区的数据写入块设备 */
     63             memcpy(blkdev_data + offset, req->buffer,len);
     64         }
     65         __blk_end_request_all(req, 1);
     66     }
     67 }
     68 
     69 static int lhy_blkdev_init(void){
     70     
     71     printk("<0>lhy_blkdev_init!
    ");
     72     /* 注册分区 */
     73     /*1. 分配一个gendisk结构体*/
     74     blkdev_disk = alloc_disk(16);        //此设备号个数,分区个数+1
     75     /*2.设置 */
     76     /*2.1 分配/设置队列: 提供读写能力 */
     77     blkdev_queue = blk_init_queue(do_block_request, &blkdev_lock);
     78     blkdev_disk->queue = blkdev_queue;
     79     /*2.2 其他设置  */
     80     blkdev_major = register_blkdev(0,"blkdev");
     81     blkdev_disk->major = blkdev_major;
     82     blkdev_disk->first_minor = 0;
     83     sprintf(blkdev_disk->disk_name, "blkdev");
     84     blkdev_disk->fops = &blkdev_fops;
     85     set_capacity(blkdev_disk, BLKDEV_SIZE >> 9);
     86     /*3. 硬件相关,分配内存*/
     87     lock_buf = vmalloc(BLKDEV_SIZE);
     88     /*4.注册 */
     89     add_disk(blkdev_disk);
     90 
     91     return 0;
     92 }
     93 
     94 static void lhy_blkdev_exit(void){
     95     printk("<0>lhy_blkdev_init!
    ");
     96     unregister_blkdev(blkdev_major,"blkdev");
     97        /* 释放删除分区 add_disk() */
     98     del_gendisk(blkdev_disk);
     99     /* add_disk() */
    100     printk("<0>putdisk
    ");
    101     put_disk(blkdev_disk);
    102     /* blk_init_queue() */
    103     blk_cleanup_queue(blkdev_queue);
    104     printk("<0>kfree
    ");
    105     if(blkdev_data)
    106         vfree(blkdev_data);
    107 }
    108 
    109 module_init(lhy_blkdev_init);
    110 module_exit(lhy_blkdev_exit);
    111 MODULE_LICENSE("GPL");
    blkdev.c


     
     

  • 相关阅读:
    day30---内置函数
    day30---绑定方法与非绑定方法
    元类以及属性查找
    python 内置方法/魔法方法
    python 面向对象高级-反射机制
    centos7下jenkins升级
    屏蔽百度右侧热搜
    centos7部署汉化版gitlab
    CentOS 7 安装 Jenkins
    centos7安装与配置ansible
  • 原文地址:https://www.cnblogs.com/lihaiyan/p/4328361.html
Copyright © 2011-2022 走看看