块设备驱动注册和注销、加载与卸载、块设备驱动的I/O请求

     块设备驱动注册和注销

块设备驱动的第一个任务就是将他们自己注册到内核中,其函数原型如下:

    int register_blkdev(unsigned int major, const char* name);

major参数是块设备要使用的主设备号,name为设备名,它会在/proc/devices中被现实.如果major0,内核会自动分配一个新的主设备号,并由该函数返回.如果返回值为负值,则说明设备号分派失败.

register_blkdev对应的注销函数是unregister_blkdev(),原型如下:

    int unreister_blkdev(unsigned int major, const char* name);

这里unreister_blkdevregister_blkdev的参数必须匹配,否则这个函数会返回-EINVAL.

Linux2.6,register_blkdev的调用是可选的.register_blkdev这个调用在Linux2.6中只完成了两件事情:①如果需要,分派一个主设备号;②在/proc/devices中创建一个入口.

块设备驱动的模块加载与卸载

1)块设备驱动的模块加载完成的工作如下:

Ø 分配,初始化请求队列,绑定请求队列和请求函数

Ø 分配,初始化gendisk,gendiskmajor,fops,queue等成员赋值,最后添加gendisk.

Ø 注册块设备驱动.

代码1:使用blk_alloc_queue函数完成块设备驱动的模块加载模板

    static int __init xxx_init(void){

        //分配gendisk

        xxx_disks = alloc_disk(1);

        if(!xxx_disks){

            goto out;

        }

        //块设备驱动注册

        if(register_blkdev(xxx_MAJOR, "xxx"){

            err = -EIO;

            goto out;

        }

        //"请求队列"分配

        xxx_queue = blk_alloc_queue(GFP_KERNEL);

        if(!xxx_queue){

            goto out_queue;

        }

        blk_queue_make_request(xxx_queue, &xxx_make_request);//绑定"制造请求"函数

        blk_queue_hardsect_size(xxx_queue,xxx_blocksize);//告知内核硬件扇区尺寸

        //gendisk初始化

        xxx_disks->major = xxx_MAJOR;

        xxx_disks->first_minor = 0;

        xxx_disks->fops = &xxx_fop;

        xxx_disks->queue = xxx_queue;

        sprintf(xxx_disks->disk_name, "xxx%d", i);

        set_capacity(xxx_disks, xxx_size);//设置gendisk容量为xxx_size个扇区大小

        add_disk(xxx_disks);

        return 0;

        out_queue:unregister_blkdev(xxx_MAJOR, "xxx");

        out:put_disk(xxx_disks);

        blk_cleanup_queue(xxx_queue);

        return -ENOMEM;

    }

代码2:使用blk_init_queue函数完成块设备驱动的模块加载模板

    static int __init xxx_init(void){

        //块设备驱动注册

        if(register_blkdev(xxx_MAJOR, "xxx"){

            err = -EIO;

            goto out;

        }

        //请求队列初始化

        xxx_queue = blk_init_queue(xxx_request, xxx_lock);

        if(!xxx_queue){

            goto out_queue;

        }

        blk_queue_hardsect_size(xxx_queue, xxx_blocksize);//告知内核硬件扇区大小

        //gendisk初始化

        xxx_disks->major = xxx_MAJOR;

        xxx_disks->first_minor = 0;

        xxx_disks->fops = &xxx_fop;

        xxx_disks->queue = xxx_queue;

        sprintf(xxx_disks->disk_name, "xxx%d", i);

        set_capacity(xxx_disks, xxx_size*2);//设置gendisk容量为xxx_size个扇区大小

        add_disk(xxx_disks);

        return 0;

        out_queue:unregister_blkdev(xxx_MAJOR, "xxx");

        out:put_disk(xxx_disks);

        blk_cleanup_queue(xxx_queue);

        return -ENOMEM;

    }

2)块设备驱动的模块卸载完成的工作如下:

Ø 清除请求队列.

Ø 删除gendiskgendisk的引用

Ø 删除对块设备的引用,注销块设备驱动.

代码3:块设备驱动模块卸载函数模板

    static void __exit xxx_exit(void){

        if(bdev){

            invalidate_bdev(xxx_bdev, 1);

            blkdev_put(xxx_bdev);

        }

        del_gendisk(xxx_disks);//删除gendisk

        put_disk(xxx_disks);

        blk_cleanup_queue(xxx_queue[i]);//清除请求队列

        unregister_blkdev(xxx_MAJOR, "xxx");

    }

2.块设备驱动的打开与释放

块设备驱动的open()release()函数不是必须的,一个简单的块设备驱动可以不提供open()release()函数.

块设备驱动的open()函数和字符设备驱动的open()和类似,都以相关inodefile结构体指针作为参数,当一个结点引用一个块设备时,inode->i_bdev->bd_disk包含一个指向关联gendisk的结构体的指针.因此类似字符设备,可将gendiskprivate_data赋给fileprivate_data,private_data同样最好是指向描述该设备的设备结构体xxx_dev的指针.如下面的代码:

    static int xxx_open(struct inode* inode, struct file* file){

        struct xxx_dev* dev = inode->i_bdev->db_disk->private_data;

        file->private_data = dev;

        ...

        return 0;

    }

3.块设备驱动的ioctl

块设备可以包含一个ioctl()函数,以提供对该设备的IO控制,实际上搞成的块设备层代码处理了绝大多数ioctl(),因此具体的块设备驱动中,通常不在需要实现很多ioctl()命令.下面的代码中只实现一个命令HDIO_GETGEO,用于获得磁盘的几何信息(geometry,CHS,Cylinder, Head, Sector/Track).

    static int xxx_ioctl(struct inode* inode, struct file* file,\

                             unsigned int cmd, unsigned long arg){

        long size;

        struct hd_geometry geo;

        struct xxx_dev* dev = file->private_data;

        switch(cmd){

            case HDIO_GETGEO:

                size = dev->size * (hardsect_size / KERNEL_SECTOR_SIZE);

                geo.cylinders = (size & ~0x3f) >> 6;

                geo.heads = 4;

                geo.sectors = 16;

                if(copy_to_user((void __user*)arg, &geo, sizeof(geo)){

                    return -EFAULT;

                }

                return 0;

        }

        return -ENOTTY;//未知命令

    }


块设备驱动的I/O请求

Ø 使用请求队列

块设备驱动请求函数的原型为:

    void request(request_queue_t* q);

这个函数不能由驱动自己调用,只有当内核认为是时候让驱动处理对设备的读写等操作时,它才会调用这个函数.请求函数可以在没有完成请求队列中的所有请求的情况下返回,甚至它一个请求不完成都可以返回.但对大部分设备而言,一般会在请求函数中处理完所有请求后才返回.

    static void xxx_request(request_queue_t* q){

        struct request* req;

        //elv_next_request()用于获取队列中第一个未完成的请求

        //end_request()会将请求从请求队列中剥离

        while((req = elv_next_request(q)) != NULL){

            struct xxx_dev* dev = req->rq_disk->private_data;

            if(!blk_fs_request(req)){//如果不是文件系统请求,直接清除,调用end_request().

                printk(KERN_NOTICE "Skip non-fs request\n");

                end_request(req, 0);//通知请求处理失败.第二个参数0代表请求失败.

                continue;

            }

            xxx_transfer(dev, req->sector, req->current_nr_sectors, req->buffer,\

                rq_data_dir(req));//处理这个请求.

            end_request(req, 1);//通知成功完成这个请求.1,表示请求成功.

        }

    }

    static void xxx_transfer(struct xxx_dev* dev, unsigned long sector,

        unsigned long nsect, char* buffer, int write){

            unsigned long offset = sector * KERNEL_SECTOR_SIZE;

            unsigned long nbytes = nsect * KERNEL_SECTOR_SIZE;

            if((offset + nbytes) > dev->size){

                printk(KERN_NOTICE "Beyond-end write (%ld %ld)\n", offset, nbytes);

                return ;

            }

            if(write)

                write_dev(offset, buffer, nbytes);//向设备写nbytes个字节的数据.

            else

                read_dev(offset, buffer, nbytes);//从设备读取nbytes个字节的数据.

    }

下面是end_that_request_first()的源码和分析

    //end_request()源码清单

    void end_request(struct request* req, int uptodate){

        //当设备完成一个IO请求的部分或全部扇区传输后,必须告知块设备层.end_that_request_first

        //原型为:int end_that_request_first(struct request* req, int success, int count);

        //此函数高数块设备层,已经完成count各扇区的传送.返回表示所有扇区传送完毕.

        if(!end_that_request_first(req, uptodate, req->hard_cur_sectors)){

            //add_disk_randomness()作用是使用块IO请求的定时来给系统的随机数池贡献熵,它不影

            //块设备,但仅当磁盘的操作时间是真正随机的时候,才调用它.

            add_disk_randomness(req->rq_disk);

            blkdev_dequeue_request(req);//清除此请求.

            end_that_request_last(req);//通知等待此请求的对象,此请求已经完成

        }

    }

下面是一个更复杂的请求函数,分别遍历了request,bio,以及bio中的segment

    //请求函数遍历请求,bio和段

    static void xxx_full_request(request_queue_t* q){

        struct request* req;

        int sectors_xferred;

        struct xxx_dev* dev = q->queuedata;

        //XXX 遍历每个请求

        while((req = elv_next_request(q)) != NULL){

            if(!blk_fs_request(req)){

                printk(KERN_NOTICE "Skip non-fs request\n");

                end_request(req, 0);

                continue;

            }

            sectors_xferred = xxx_xfer_reqeust(dev, req);

            if(!end_that_request_first(req, 1, sectors_xferred)){

                blkdev_dequeue_reqeust(req);

                end_that_request_last(req);

            }

        }

    }

    //XXX 请求处理

    static int xxx_xfer_request(struct xxx_dev* dev, struct reqeust* req){

        struct bio* bio;

        int nsect = 0;

        //遍历请求中的每个bio

        rq_for_each_bio(bio, req){

            xxx_xfer_bio(dev, bio);

            nsect += bio->bi_size / KERNEL_SECTOR_SIZE;

        }

        return nsect;

    }

    //XXX bio处理

    static int xxx_xfer_bio(struct xxx_dev* dev, struct bio* bio){

        int i;

        struct bio_vec* bvec;

        sector_t sector = bio->bi_sector;

        //遍历每一个segment

        bio_for_each_segment(bvec, bio, i){

            char* buffer = __bio_kmap_atomic(bio, i, KM_USER0);

            xxx_transfer(dev, sector, bio_cur_sectors(bio), buffer,\

                bio_data_dir(bio) == WRITE);

            sector += bio_cur_sectors(bio);

            __bio_kunmap_atomic(bio, KMUSER0);

        }

        return 0;

    }

Ø 不使用请求队

对于机械的磁盘设备而言,请求队列有助于提高系统性能.但对于如SD,RAM盘等可随机访问的块设备,请求队列无法获益.对于这些设备,块层支持"无队列"的操作模式,驱动为此必须提供一个"制造请求"函数(注意:这不是请求函数哦),"制造请求"函数的原型为:

typedef int (make_request_fn) (request_queue_t* q, struct bio* bio);

此函数的第一个参数,是一个"请求队列",但实际并不包含任何请求.所以主要参数是bio,它表示一个或多个要传送的缓冲区.此函数或直接进行传输,或将请求重定向给其他设备.在处理完成之后,应使用bio_endio()通知处理结束.bio_endio()原型如下:

    void bio_endio(struct bio* bio, unsigned int byetes, int error);

bytes是已经传送的字节数(注意:bytesbio->bi_size),这个函数同时更新了bio的当前缓冲区指针.当设备进一步处理bio,驱动应再次调用bio_endio(),如不能完成请求,将错误码赋给error参数,并在函数中得以处理.此函数无论处理IO成功与否都返回0,如果返回非零值,bio将再次被提交:

    static int xxx_make_request(request_queue_t* q, struct bio* bio){

        struct xxx_dev* dev = q->queuedata;

        int status = xxx_xfer_bio(dev, bio);//处理bio

        bio_endio(bio, bio->bi_size, status);//报告结束

        return 0;

    }

说明:这里要指出,如果是无队列的IO请求处理,其加载模块应使用<<代码1:使用blk_alloc_queue函数完成块设备驱动的模块加载模板>>,否则应使用<<代码2:使用blk_init_queue函数完成块设备驱动的模块加载模板>>.代码1与代码2见:Linux块设备驱动(3)--块设备驱动相关模块模板

原文转自:http://blog.chinaunix.net/space.php?uid=23399063&do=blog&view=me&frmd=-1


你可能感兴趣的:(块设备驱动注册和注销、加载与卸载、块设备驱动的I/O请求)