块设备驱动程序

块设备驱动程序就是支持以块的方式进行读写的设备。块设备和字符设备最大的区别在于读写数据的基本单元不同。块设备读写数据的基本单元为块,例如磁盘通常为一个sector,而字符设备的基本单元为字节。从实现角度来看,字符设备的实现比较简单,内核例程和用户态API一一对应,这种映射关系由字符设备的file_operations维护。块设备接口则相对复杂,读写API没有直接到块设备层,而是直接到文件系统层,然后再由文件系统层发起读写请求。

结构体

struct block_device

block_device结构代表了内核中的一个块设备。它可以表示整个磁盘或一个特定的分区。当这个结构代表一个分区时,它的bd_contains成员指向包含这个分区的设备,bd_part成员指向设备的分区结构。当这个结构代表一个块设备时,bd_disk成员指向设备的gendisk结构。

    struct block_device {  
        dev_t           bd_dev;  
        struct inode *  bd_inode;   /*分区结点*/  
        int         bd_openers;  
        struct semaphore    bd_sem; /*打开/关闭锁*/  
        struct semaphore    bd_mount_sem;   /* 加载互斥锁*/  
        struct list_head    bd_inodes;  
        void *      bd_holder;  
        int         bd_holders;  
        struct block_device *   bd_contains;  
        unsigned        bd_block_size;//分区块大小  
        struct hd_struct *  bd_part;  
        unsigned        bd_part_count;//打开次数  
        int         bd_invalidated;  
        struct gendisk *    bd_disk;  
        struct list_head    bd_list;  
        struct backing_dev_info *bd_inode_backing_dev_info;  
        unsigned long   bd_private;  
    };  

struct gendisk

gendisk表示一个独立的磁盘设备或分区

    struct gendisk {  
        int major;          //主设备号  
        int first_minor;     
        int minors;         //最大的次设备号数量,如果设备不能分区,该值为1                                   
        char disk_name[32]; //主设备名  
        struct hd_struct **part;    //分区信息,有minors个  
        struct block_device_operations *fops;//设备操作  
        struct request_queue *queue;    //设备管理I/O请求  
        void *private_data;  
        sector_t capacity;  
        int flags;  
        char devfs_name[64];  
        int number;  
        struct device *driverfs_dev;  
        struct kobject kobj;  
        struct timer_rand_state *random;  
        int policy;  
        atomic_t sync_io;     
        unsigned long stamp, stamp_idle;  
        int in_flight;  
    #ifdef  CONFIG_SMP  
        struct disk_stats *dkstats;  
    #else  
        struct disk_stats dkstats;  
    #endif  
    };  

struct block_device_operation

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

block_device_operations并不能完全提供文件操作全部的API,实际上只提供了open、release等函数,其他的文件操作依赖于def_blk_fops:

    const struct file_operations def_blk_fops = {  
        .open   = blkdev_open,  
        .release    = blkdev_close,  
        .llseek = block_llseek,  
        .read       = do_sync_read,  
        .write  = do_sync_write,  
        .aio_read   = generic_file_aio_read,  
        .aio_write= generic_file_aio_write_nolock,  
        .mmap   = generic_file_mmap,  
        .fsync  = block_fsync,  
        .unlocked_ioctl = block_ioctl,  
    #ifdef CONFIG_COMPAT  
        .compat_ioctl   = compat_blkdev_ioctl,  
    #endif  
        .splice_read        = generic_file_splice_read,  
        .splice_write   = generic_file_splice_write,  
    };  

struct request_queue

系统对块设备进行读写操作时,通过块设备通用的读写操作函数将一个请求保存在该设备的操作请求队列(request queue)中,然后调用这个块设备的底层处理函数,对请求队列中的操作请求进行逐一执行。request_queue结构描述了块设备的请求队列,该结构定义如下:

    struct request_queue  
    {  
        struct list_head    queue_head;  
        struct request      *last_merge;  
        elevator_t      elevator;  
        /*请求队列列表*/  
        struct request_list     rq;  
        request_fn_proc     *request_fn;  
        merge_request_fn    *back_merge_fn;  
        merge_request_fn    *front_merge_fn;  
        merge_requests_fn   *merge_requests_fn;  
        make_request_fn     *make_request_fn;  
        prep_rq_fn          *prep_rq_fn;  
        unplug_fn           *unplug_fn;  
        merge_bvec_fn       *merge_bvec_fn;  
        activity_fn         *activity_fn;  
        /*自动卸载状态*/  
        struct timer_list   unplug_timer;  
        int         unplug_thresh;    
        unsigned long       unplug_delay;   /*自动卸载延时*/  
        struct work_struct  unplug_work;  
        struct backing_dev_info backing_dev_info;  
        void                *queuedata;  
        void                *activity_data;  
        unsigned long       bounce_pfn;  
        int             bounce_gfp;  
        unsigned long       queue_flags;//各种队列标志  
        /*保护队列结构,避免重入*/  
        spinlock_t          *queue_lock;  
        /* 请求的核心结构*/  
        struct kobject kobj;  
        /*请求的配置*/  
        unsigned long       nr_requests;    /* 请求的最大数*/  
        unsigned int        nr_congestion_on;  
        unsigned int        nr_congestion_off;  
        unsigned short      max_sectors;  
        unsigned short      max_phys_segments;  
        unsigned short      max_hw_segments;  
        unsigned short      hardsect_size;  
        unsigned int        max_segment_size;  
        unsigned long       seg_boundary_mask;  
        unsigned int        dma_alignment;  
        struct blk_queue_tag    *queue_tags;  
        atomic_t        refcnt;  
        unsigned int        in_flight;  
        /*sg 参数配置*/  
        unsigned int        sg_timeout;  
        unsigned int        sg_reserved_size;  
    };  

struct request


struct bio


struct bio_vec


几个结构体之间的关系

块设备驱动程序_第1张图片


块设备驱动框架

块设备驱动的I/O请求处理有两种方式:使用请求队列和不使用请求队列。
使用请求队列有助于提高系统性能,但对于一些完全可随机访问的块设备,使用请求队列不能获得更大的益处,这时候通用块层提供了一种无队列的操作模式,使用这种模式,驱动必须提供制造请求函数。


使用请求队列

/*请求处理函数,请求队列的处理流程如下:
 *首先:从请求队列中拿出一条请求
 *其次:判断这一条请求的方向,是向设备写还是读,然后将数据装入缓冲区
 *最后:通知请求完成*/
static void ramdisk_do_request(struct request_queue_t *queue)
{
    struct request *req;

    /*使用循环一条请求一条请求的来处理,elv_next_request函数是遍历队列中的每一条请求*/
    while(req = elv_next_request(queue) != NULL)
    {
        /*判断要传输数据的总长度大小是否超过范围*/
        if ((req->sector + req->current_nr_sectors) << 9 > RAMDISK_SIZE)
        {
            /*如果超过范围就直接报告请求失败*/
            end_request(req, 0);
            continue;
        }

        /*判断请求处理的方向*/
        switch (rq_data_dir(req))
        {
            case READ:
                memcpy(req->buffer, disk_data + (req->sector << 9), req->current_nr_sectors << 9);
                end_request(req, 1);/*报告请求处理成功*/
                break;

            case WRITE:
                memcpy(disk_data + (req->sector << 9), req->buffer, req->current_nr_sectors << 9);
                end_request(req, 1);/*报告请求处理成功*/
                break;

            default:
                break;
        }
    }
}

static int __int ramdisk_init(void)
{
    /*块设备驱动注册*/
    register_blkdev(RAMDISK_MAJOR, RAMDISK_NAME);

    /*使用请求队列的方式*/
    ramdisk_queue = blk_init_queue(ramdisk_do_request, NULL);

    /*分配gendisk*/

    /*初始化gendisk*/

    /*添加gendisk到系统中*/

}

不使用请求队列

/*绑定请求制造函数。注意:第一个参数仍然是请求队列,但在这里实际不包含任何请求。
所以这里要处理的重点对象的bio中的每个bio_vec,他表示一个或多个要传送的缓冲区。*/
static int ramdisk_make_request(struct request_queue_t *queue, struct bio *bio)
{
    int i;
    struct bio_vec *bvec;
    void *disk_mem;
    void *bvec_mem;

    /*在遍历段之前先判断要传输数据的总长度大小是否超过范围*/
    if((bio->bi_sector << 9) + bio->bi_size > RAMDISK_SIZE)
    {
        /*如果超出范围就通知这个bio处理失败*/
        bio_endio(bio, 0, -EIO);

        return 0;
    }

    /*获得这个bio请求在块设备内存中的起始位置*/
    disk_mem = disk_data + (bio->bi_sector << 9);

    /*开始遍历这个bio中的每个bio_vec*/
    bio_for_each_segment(bvec, bio, i)
    {
        /*因bio_vec中的内存地址是使用page *描述的,故在高端内存中需要用kmap进行映射后才能访问,
        再加上在bio_vec中的偏移位置,才是在高端物理内存中的实际位置*/
        bvec_mem = kmap(bvec->bv_page) + bvec->bv_offset;

        /*判断bio请求处理的方向*/
        switch(bio_data_dir(bio))
        {
            case READ:
            case READA:
                memcpy(bvec_mem, disk_mem, bvec-> bv_len);
                break;

            case WRITE :
                memcpy(disk_mem, bvec_mem, bvec-> bv_len);
                break;

            default :
                kunmap(bvec->bv_page);
        }

        /*处理完每一个bio_vec都应把kmap映射的地址取消掉*/
        kunmap(bvec->bv_page);

        /*累加当前bio_vec中的内存长度,以确定下一个bio_vec在块设备内存中的位置*/
        disk_mem += bvec->bv_len;
    }

    /*bio中所有的bio_vec处理完后报告处理结束*/
    bio_endio(bio, bio->bi_size, 0);

    return 0;
}

static int __int ramdisk_init(void)
{
    /*块设备驱动注册*/
    ramdisk_major = register_blkdev(RAMDISK_MAJOR, RAMDISK_NAME);

    /*使用制造请求的方式,先分配ramdisk_queue*/
    ramdisk_queue = blk_alloc_queue(GFP_KERNEL);

    /*再绑定请求制造函数*/
    blk_queue_make_request(ramdisk_queue, &ramdisk_make_request);

    /*分配gendisk*/

    /*初始化gendisk*/

    /*添加gendisk到系统中*/
}

程序

见:内存模拟块设备驱动程序
分区:

~ # fdisk /dev/sbulla 
sbull_media_change
sbull_getgeo
Device contains neither a valid DOS partition table, nor Sun, SGI, OSF or GPT disklabel
Building a new DOS disklabel. Changes will remain in memory only,
until you decide to write them. After that the previous content
won't be recoverable.


Command (m for help): n
Command action
   e   extended
   p   primary partition (1-4)
p
Partition number (1-4): 1
First cylinder (1-16, default 1): Using default value 1
Last cylinder or +size or +sizeM or +sizeK (1-16, default 16): Using default value 16

Command (m for help): w
The partition table has been altered.
Calling ioctl() to re-read partition table
sbull_revalidate
 sbulla: sbulla1

格式化:mkfs.vfat /dev/sbulla1
挂载:mount /dev/sbulla1 /mnt


参考文章

  1. 块设备驱动实例
  2. 嵌入式Linux之我行——RamDisk块设备驱动实例开发讲解

你可能感兴趣的:(linux块设备与mmc子系统)