首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

ok6410学习笔记(19.块设备驱动程序设计)

ok6410学习笔记(19.块设备驱动程序设计)

本节还存在问题:问题一:为什么开辟的内存是0扇区,下文中有具体说明。
问题二:块设备能不能像字符设备一样进行,read,write,下文中有详细说明。
问题三:对于整体的块设备的体系结构开不是很透彻,应该再看看<linux内核注释> 和 <linux内核设计与实现>两本书的块设备部分

本节知识点:预备知识:1.块设备与字符设备的区别:a.块设备和字符设备的读取单元不同,块设备是以一块为基本单元进行读写的,一般是512字节,字符设备是以字节为单位进行读写的。
                                                b.块设备可以随机访问(随机对各个块进行访问)    字符设备必须按照顺序访问(按照地址进行访问)
2.块设备在linux中的体系架构:
   
        当访问块设备文件的时候,linux先调用VFS虚拟文件系统,先在硬盘缓存中去寻找有没有要访问的数据,如果有就直接返回,没有就继续调用下面的文件系统设备文件。然后在Mapping layer层来寻找文件的inode,通过inode让操作系统找到数据在磁盘上的逻辑地址,即sector。Generic Block Layer层是通用块设备层,这个层把块设备分成若干个扇区,并处理上层的读写请求,变成若干个bio结构。IO scheduler layer层针对,各种机械存储设备(硬盘),进行IO请求的调度,如电梯调度算法。Block Device Driver 块设备驱动,负责对硬件进行控制。
        总结一下:VFS:负责统一文件接口
                           Mapping Layer:负责通过inode找到sector
                           Generic Block Layer:负责分扇区和处理上层读写请求
                           IO scheduler layer:负责IO请求进行调度
重点函数:1、描述块设备的结构
        struct  gendisk

{


int major; //  主设备号

                int first_minor; //第一个设备的次设备号

int minor; //有多少设备  一般是在申请块设备函数alloc_disk函数中填写的

     
char disk_name[DISK_NAME_LEN];//驱动名  /dev路径下的名

                struct block_device_operations *fops;// 块设备处理函数
  
struct request_queue *queue;//IO请求队列


}

2、set_capacity(struct gendisk *gd, MY_BLKDEV_BYTES>>9)  这个函数的第一个参数是 块设备结构  第二个参数是开辟的内存除上512 应该是有多少个块  这个函数的功能应该是告诉内核这个块设备有多少个块
3、alloc_disk(minor)  对于块设备的申请要使用这个函数,不能自己直接给上面的那个结构体赋值,这个函数的参数是 此类块设备有多少个设备
4、add_disk(struct gendisk *gd) 把块设备添加到linux内核中去  如果在内核配置的时候安装了udev 这个函数可以在/dev目录下直接产生块设备文件
5、del_gendisk(struct gendisk *gd) 把块设备从内核中卸载掉
6、IO请求的结构
      struct  request
      {

struct list_head queuelist;//链表结构


sector_t sector;//要操作的首扇区

   
unsigned long nr_sectors;//要操作的扇区数目


struct bio* bio;//请求的bio结构体的链表


struct bio* biotail;//请求的bio结构体的链表尾

       }
     request IO请求和bio结构的关系就是  request是操作系统根据IO调度机制把一个一个的bio(一次块设备请求)组成成一个链表 就变成了一次IO请求了  好多个request IO请求就变成了request_queue  IO请求队列
7、struct request_queue *blk_init_queue(request_fn_proc *rfn,spinlock_t *lock)  该函数初始化IO请求队列(是用在想要使用linuxIO调度器的情况下的,如访问硬盘等) 第一个参数是处理IO请求队列的函数,第二个参数是自旋锁   返回值是IO请求队列
8、blk_cleanup_queue(request_queue *p) 清楚IO请求队列
9、struct request *elv_next_request(request_queue *q) 通过当前请求队列 去寻找下一个IO请求 (我觉得应该是在请求队列中 用一个链表保存了所有的IO请求和当前IO请求)
10、void blkdev_dequeue_request(request_queue *q) 从请求队列中 删除当前IO请求
11、end_request(req,1)  次函数非常重要 我出了问题 就是在这个函数上面  此函数功能是结束当前IO请求 如果成功第二个参数写1,如果不成功写0。1和0影响很大,千万别写错了,第一个参数是当前IO请求。
                                                           上面的函数是使用在 使用linuxIO调度器的情况的,如访问硬盘设备
                                                           下面的函数是使用在  无linuxIO调度器的情况   如U盘  内存
12、一次块设备IO请求结构
       struct bio

{


sector_t bi_sector;//要访问的第一个扇区

  
unsigned int bi_size;//以字节为单位所需传输的数据大小


struct bio_vec *bi_io_vec;//实际的vec列表


}


struct bio_vec


{


struct page *bv_page;//页指针  用来寻找IO请求缓冲区的页指针


unsigned int bv_len;//传输的数据长度,与bi_size有区别  后面详细说


unsigned int bv_offset;//偏移量 和页指针配合一起寻找IO请求缓冲区


}

13、struct request_queue *blk_alloc_queue(GFP_KERNEL)申请一个IO请求队列

14、blk_queue_make_request(request_queue *q, make_request_fn *mfn) 绑定请求函数和制造请求函数(详细结构在后面驱动结构里面详细说) 第一个参数是请求队列  第二个参数是处理请求函数
15、bio_endio(bio,0,-EIO) 结束当前bio(失败的情况)       bio_endio(bio,bio->bi_size,0)结束当前bio(成功的情况)

bio_endio()原型如下:
void bio_endio(struct bio* bio, unsigned int byetes, int error);

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

驱动结构:本节最重要的知识点就是了解块设备的驱动结构。本节的块设备分两类,一类是有IO调度器的,一类是没有IO调度器的。
一、有linux IO调度器的情况
1.使用blk_init_queue函数,初始化一个IO请求队列,并把请求队列处理函数绑定给这个请求队列。
2.使用alloc_disk函数,申请一个块设备结构
3.填充gendisk块设备结构(主设备号  次设备号  设备名  IO请求队列   块设备函数操作集,块设备的函数操作集没有read和write的 只有open,release,ioctl等)
4.使用set_capacity函数,把块设备有多少块,赋值给gendisk
6.使用add_disk函数,将这个块设备注册到linux内核,此时产生了设备文件。
7.填写IO请求处理函数:


a.因为是处理一个请求队列,所以应该使用elv_next_request遍历所有的IO请求,知道没有请求了,才退出函数



b.当通过elv_next_request得到一个请求request的时候,然后通过linux内核回馈的sector头扇区和current_nr_sectors操作扇区个数判断操作的扇区是否,超过了开辟的内存大小。这里默认内存开辟的首地址是0扇区,原因我也不知道。可能是跟后面的格式化有关系,这是我还没弄懂的知识点一



c.通过rq_data_dir()函数,判断IO请求的数据处理方向,即是读还是写。

        d.根据读写,决定是从IO请求的缓冲区(req->buffer)中度数据,还是往缓冲区中写数据。如果是对硬盘进行操作,则是通过寄存器的控制,把硬盘中的数据读出来写入buffer和把buffer中的数据写入硬盘。本节的代码是用内存模拟硬件设备,所以是在内存与缓冲区之间进行copy。
总结:首先声明,对于块设备能否进行应用程序的read和write  我不知道,这是我没弄懂的知识二。本节的测试是ramdisk,把块设备中开辟的内存,格式化成ext3文件系统,并挂载到mnt中的某个目录,制作成硬盘,进行文件的拷贝。
1.写入文件的过程,用户态-------->内核态------------>硬件设备,(1.)内核根据你要拷贝的文件大小先去寻找哪些硬件空间没有被使用,文件被内核分成好多个块即分成好多的bio结构,这些bio都有自己的sector,这些sector跟刚刚的硬件空间有着某种转换关系(Mapping Layer层),(2.)然后内核再把你要拷贝的文件一块一块的写入每一个IO请求的缓冲区中(Generic Block Layer层),(3.)对于这个使用了IO调度器的驱动,blk_init_queue调用了blk_queue_make_request(q,_make_request),这里面首先调用了_make_request函数,这个函数是把所以产生的bio结构,通过IO调度器(调度算法)制作成一个一个的IO请求,即request结构,再把request结构制作成request_queueIO请求队列,然后调用了q->make_request_fn=mfn,mfn这个函数就是blk_init_queue中我们自己写的那个函数,用来处理IO请求队列的。(IO schedule层),在这个层次中,产生了我们常常见到的request中的sector和current_nr_sectors,他们的来源是bio的sector,(4.)在驱动中,你再把这个缓存区中的数据写入硬件设备中去(Block Driver层),但是我觉得硬件设备不应该仅仅是通过纯寄存器去控制,还应该是同过内核分配的sector和current_nr_sectors去找到硬件空间,因为只有这样才能确定那里是被使用的,那里是空闲的。这里面sector和current_nr_sectors与内核直接的关系应该是在,linux内核移植的时候完成的。这个过成就是上面那个体系结构图的具体解释。

补充:bio、request和request_queue三者关系:
         首先是IO请求队列,这个里面有好多个IO请求request,当确定了文件大小,找到空闲硬件空间的时候,内核就产生了好多IO请求,每个IO请求都有一个sector,一个current_nr_sectors。一个IO请求一次可以操作好多个块,每一个块的每次IO请求就是一个bio,IO调度器就是优化一个request中的多个bio的。而一个request中的多个bio又有自己的sector,这就保证了不管IO调度器怎么优化,对于一次request的处理,都不会把文件拷贝到错的地址。(这里面就体现出了块设备的访问随机性),在这里我们考虑的最小单位是IO请求,bio的排序是调度器处理的,bio的顺序不会影响IO请求的处理的原因上面已经阐述过了


2.读出文件的过程,硬件设备------------>内核态---------->用户态,当读一个文件的时候,确定了文件的inode属性,找到了文件的物理地址属性,操作系统可以根据这个物理地址找到文件对应的一组sector和current_nr_sectors并形成一个IO请求队列,因为产生和查找都是根据物理地址来的所以这组sector和current_nr_sectors应该是当时write的时候的那组是完全对应的。所以就可以毫无错误的找到你要的问题,在写入各个IO请求的缓冲区中,用户态就得到了你想要的文件了。这里也体现了块设备的访问随机性。

这里面有一个问题很纠结:比如我用read和write在应用程序中,往块设备中写入和读出数据,因为是顺序读取的,在读取数据的时候没有inode,所以sector和current_nr_sectors是随机的,不一定是写入时候的那个sector和current_nr_sectors,所以我在IO请求的缓存区中没有读出对应的数据,但是貌似我的应用程序还真的接到了对应的数据。原因我也不是很清楚。


注意:在整个过程中,程序中对错误的处理,都是有一个前提的,对于本节代码来说前提就是内存开辟的头地址,就是0扇区(可能是因为格式化成ext3文件系统的缘故,把这个内存空间当成了一个硬盘了,自然内存首地址就成了0扇区)。对于真实的硬盘驱动程序,应该是在内核移植的时候都移植好的,不管是硬盘还是nandflash,他们的起始地址应该就是0扇区,也就是说在安全性检测的时候,只需用   (头扇区+扇区数)*512再与总共硬盘大小进行比较就好了。


二、无linux IO调度器的情况
1.使用blk_alloc_queue(GFP_KERNEL)  申请一个IO请求队列
2.blk_queue_make_request(request_queue *q, make_request_fn *mfn)  这个函数是把申请的IO请求队列和你自己定义的处理函数建立联系
3.使用alloc_disk函数,申请一个块设备结构
4.填充gendisk块设备结构(主设备号  次设备号  设备名  IO请求队列   块设备函数操作集,块设备的函数操作集没有read和write的 只有open,release,ioctl等)
5.使用set_capacity函数,把块设备有多少块,赋值给gendisk
6.使用add_disk函数,将这个块设备注册到linux内核,此时产生了设备文件。
7.填写IO请求队列处理函数:

a.使用bio的sector和size来进行安全检测,判断是否超出我们开辟的内存大小

b.使用bio_for_each_segment函数遍历bio,因为每一个bio应该是由很多个bio_vec组成的,每一个bio_vec中都有一个缓冲区。这里可以看出bi_size和bio_vec->len的区别了,bi_size应该是整个bio进行操作的总字节数,bio_vec->len应该是每一次bio_vec缓冲区数据传输的字节数。

c.使用bio_rw函数来判断bio的数据传输方向

d.根据页指针和偏移量找到bio_vec的缓冲区,再根据读写方向进行数据传输。此处也有像上一方法一样的对硬件进行的操作,此时的硬件应该也是与bio的sector有关的
总结:对于这个没有使用IO调度器的情况,步骤和上面的写入过程是一样的,只有第三步是不一样,当到达第三步的时候直接使用了blk_queue_make_request(q,make_request_fn *mfn)函数,中的mfn函数即我们自己建立的处理好多bio结构的函数,没有调用系统中__make_queue函数了,因为这个函数中有一步IO调度器,优化bio顺序,我们直接使用这个函数来处理各个bio结构,很自然这个方法也没用IO请求request。与上面的方法不同的是有IO调度器的最小单元是request,没有IO调度器的最小单元是bio。

下面的图展示出了,两者中的第三步的函数调用关系:


本节代码:

1.如果对本节代码进行检测:

a.insmod block.ko 块设备


b.ls /dev/my_blkdev  查看有没有产生设备文件


c.mkfs.ext3 /dev/my_blkdev  把块设备格式化成ext3文件系统


d.mkdir -p /mnt/blk   在mnt目录下创建一个文件夹 用来映射这个块设备


e.mount /dev/my_blkdev  /mnt/blk   把格式化好的块设备映射到新建的目录下


f.cp考入一些文件   再考出这些文件  看见你的块设备能不能读写


g.umount  /dev/my_blkdev   卸载 看看 blk目录下还有没有文件了

2.不使用IO调度器的 block.c:
[cpp] view plaincopy


  • #include <linux/module.h>
  • #include <linux/moduleparam.h>
  • #include <linux/init.h>
  • #include <linux/sched.h>
  • #include <linux/kernel.h> /* printk() */
  • #include <linux/slab.h>       /* kmalloc() */
  • #include <linux/fs.h>     /* everything... */
  • #include <linux/errno.h>  /* error codes */
  • #include <linux/timer.h>
  • #include <linux/types.h>  /* size_t */
  • #include <linux/fcntl.h>  /* O_ACCMODE */
  • #include <linux/hdreg.h>  /* HDIO_GETGEO */
  • #include <linux/kdev_t.h>
  • #include <linux/vmalloc.h>
  • #include <linux/genhd.h>
  • #include <linux/blkdev.h>
  • #include <linux/buffer_head.h>    /* invalidate_bdev */
  • #include <linux/bio.h>
  • #include <linux/major.h>

  • static
    struct gendisk *my_blkdev_disk;  
  • static
    struct request_queue *my_blkdev_queue;  
  • #define MY_BLKDEV_BYTES (16*1024*1024) //块设备大小为16M
  • unsigned char DATA[MY_BLKDEV_BYTES]; //这是当作块设备的内存

  • static
    int do_queue(struct request_queue *q,struct bio *bio) //IO请求队列处理函数
  • {  
  •             struct bio_vec *bvec;  
  •             int i;  
  •       void *dsk_mem;  
  •       /*bio->bi_sector头扇区     bio->bi_size 传输字节大小*/
  •       if ((bio->bi_sector << 9) + bio->bi_size > MY_BLKDEV_BYTES)   
  •       {  
  •           printk(KERN_EMERG": bad request: block=%llu, count=%u\n",(unsigned long
    long)bio->bi_sector, bio->bi_size);  
  •           bio_endio(bio,0,-EIO);  
  •           return 0;  
  •       }  
  •       dsk_mem = DATA + (bio->bi_sector << 9); //把bio内存中的头地址保存下来
  •       bio_for_each_segment(bvec, bio, i) //遍历bio  应该是一个bio 会分成好多个bvec步骤进行 数据传输
  •         {   
  •                     void *iovec_mem;  
  •                   switch(bio_rw(bio))  
  •                   {  
  •                         case READ:  
  •                         case READA:  
  •                                     iovec_mem=kmap(bvec->bv_page) + bvec->bv_offset;//用bio的页地址和偏移量 计算出缓冲区的虚拟地址
  •                                     memcpy(iovec_mem, dsk_mem, bvec->bv_len);//把头扇区开始的内容copy到  bio的缓冲区中
  •                                     printk(KERN_EMERG "READing is %s\n",(char *)iovec_mem);  
  •                                     kunmap(bvec->bv_page);//释放映射
  •                             break;  
  •                         case  WRITE:  
  •                                     iovec_mem=kmap(bvec->bv_page) + bvec->bv_offset;  
  •                                     memcpy(dsk_mem, iovec_mem, bvec->bv_len);  //把来自用户空间的内容  从bio缓冲区copy到头扇区
  •                                     printk(KERN_EMERG "WRITing is %s\n",(char *)iovec_mem);  
  •                                     kunmap(bvec->bv_page);  
  •                             break;  
  •                         default:  
  •                             printk(KERN_EMERG": unknown value of bio_rw: %lu\n",bio_rw(bio));  
  •                         bio_endio(bio,0,-EIO);//结束当前bio请求
  •                         return 0;  
  •                             break;  
  •                   }  
  •                   dsk_mem += bvec->bv_len; //在每次完成bio中 的一个bvec的时候  都要把模仿设备的内存指针进行移动
  •       }  
  •       bio_endio(bio,bio->bi_size,0);  
  •       return 0;  
  • }  
  • struct block_device_operations my_blkdev_fop = {  
  •         .owner                = THIS_MODULE,  
  • };  

  • static
    int __init blkdev_init(void)  
  • {  
  •             printk(KERN_EMERG "module init is finished!!!\n");  
  •             int ret;  
  •             /*初始化IO请求队列  并绑定IO队列处理函数*/
  •             my_blkdev_queue = blk_alloc_queue(GFP_KERNEL); //申请一个IO请求队列
  •             if (!my_blkdev_queue)   
  •             {  
  •           ret = -ENOMEM;  
  •           goto err_alloc_queue;  
  •       }  
  •       blk_queue_make_request(my_blkdev_queue, do_queue); //把申请的IO请求队列和我们自己的请求队列处理函数绑定起来   跳过以前操作系统带IO调度的那个函数

  •             /*跟系统申请 描述块设备的结构体  赋值给定义的全局变量指针*/
  •             my_blkdev_disk=alloc_disk(1); //动态分配gendisk结构体  参数为该设备使用的次设备号数量
  •         if (!my_blkdev_disk)   
  •         {  
  •           ret = -ENOMEM;  
  •           goto err_alloc_disk;  
  •       }  
  •             /*填充块设备结构体*/
  •             my_blkdev_disk->major=255;//主设备号为255
  •             my_blkdev_disk->first_minor=0;//只有一个设备在alloc_disk中体现的,这个是第一个设备的次设备号
  •             strcpy(my_blkdev_disk->disk_name,"my_blkdev");//填写设备名
  •             my_blkdev_disk->fops=&my_blkdev_fop; //填写设备操作函数集合
  •             my_blkdev_disk->queue=my_blkdev_queue; //填写该块设备的IO请求队列

  •             set_capacity(my_blkdev_disk, MY_BLKDEV_BYTES>>9); //一般一个扇区的大小是512个字节  这个函数应该是得到这个块设备上有几个扇区
  •             /*注册块设备 进入内核*/
  •             add_disk(my_blkdev_disk);//注册块设备驱动
  •             return 0;  

  • err_alloc_disk:  
  •         blk_cleanup_queue(my_blkdev_queue);//如果IO请求队列申请失败 则应该清除队列
  • err_alloc_queue:  
  •         return ret;  


  • }  

  • static
    void  __exit blkdev_exit(void)  
  • {  

  •         blk_cleanup_queue(my_blkdev_queue);  
  •         del_gendisk(my_blkdev_disk);  
  • }  

  • module_init(blkdev_init);  
  • module_exit(blkdev_exit);  


3.使用IO调度器的block.c:

[cpp] view plaincopy


  • #include <linux/module.h>
  • #include <linux/moduleparam.h>
  • #include <linux/init.h>
  • #include <linux/sched.h>
  • #include <linux/kernel.h> /* printk() */
  • #include <linux/slab.h>       /* kmalloc() */
  • #include <linux/fs.h>     /* everything... */
  • #include <linux/errno.h>  /* error codes */
  • #include <linux/timer.h>
  • #include <linux/types.h>  /* size_t */
  • #include <linux/fcntl.h>  /* O_ACCMODE */
  • #include <linux/hdreg.h>  /* HDIO_GETGEO */
  • #include <linux/kdev_t.h>
  • #include <linux/vmalloc.h>
  • #include <linux/genhd.h>
  • #include <linux/blkdev.h>
  • #include <linux/buffer_head.h>    /* invalidate_bdev */
  • #include <linux/bio.h>
  • #include <linux/major.h>

  • static
    struct gendisk *my_blkdev_disk;  
  • static
    struct request_queue *my_blkdev_queue;  
  • #define MY_BLKDEV_BYTES (16*1024*1024) //块设备大小为16M
  • unsigned char DATA[MY_BLKDEV_BYTES]; //这是当作块设备的内存

  • static
    void do_queue(struct request_queue *q) //IO请求队列处理函数
  • {  
  •             struct request *req;//定义一个请求
  •             /*要处理完这个请求队列上的所有请求*/
  •             while((req=elv_next_request(q))!=NULL)  //当第一次调用elv_next_request函数的时候  q中的保存的应该是链表头  然后每次调用elv_next_request函数都指向下一个  也就是说第一次调用elv_next_request函数得到的request应该是第一次io请求
  •             {  
  •                     /*我想看看每次请求中的sector都是不是0*/
  •                     printk(KERN_EMERG "sector is %llu , nr_sectors is %u \n",(unsigned long
    long)req->sector,req->current_nr_sectors);  
  •                     if(((req->sector+req->current_nr_sectors)<<9)>MY_BLKDEV_BYTES)  //我觉得这里应该不加上req->sector  问问老师
  •                     {  
  •                                 printk(KERN_EMERG "error:sector is %llu , nr_sectors is %u \n",(unsigned long
    long)req->sector,req->current_nr_sectors);  
  •                                 end_request(req,0);//结束本次请求   第二个参数是返回请求是否执行成功 成功为1 失败为0
  •                                 continue; //如果所操作的扇区大小 大于 我们最大分配内存的大小(此处做ramdisk 用内存当扇区) 则结束这次请求  进行下面的请求
  •                     }  
  •                     /*下面是针对请求的方向进行处理*/
  •                     switch(rq_data_dir(req))//该函数是判断请求的方向的 即数据流向
  •                     {  
  •                             case READ: //这里还不能使用copy_from_user或者copy_to_user因为req->buffer是内核空间的内存  是需要操作系统传递到用户空间的不是这里传递的  但是最好还是检查一下这个内存的有效性
  •                                 /*这里的读 应该是用户空间读  内核把req->buffer中的数据传递给用户空间*/
  •                                 memcpy(req->buffer,DATA+(req->sector<<9),req->current_nr_sectors<<9);  
  •                             //  memcpy(req->buffer,DATA+(0<<9),3<<9);
  •                             //  memcpy(req->buffer,"hello0",5);
  •                                 printk(KERN_EMERG"READing!!!   %s\n",req->buffer);  
  •                                 end_request(req,1);//结束本次请求   第二个参数是返回请求是否执行成功 成功为1 失败为0
  •                                 break;  
  •                             case WRITE:  
  •                                 /*这里的写  应该是用户空间写  用户空间通过内核把数据写入req->buffer中 应把这个buffer中的值 写入硬件中*/
  •                                 memcpy(DATA+(req->sector<<9),req->buffer,req->current_nr_sectors<<9);  
  •                             //  memcpy(DATA+(0<<9),req->buffer,3<<9);
  •                                 printk(KERN_EMERG"WRITEing!!!  %s\n",req->buffer);  
  •                                 end_request(req,1);//结束本次请求   第二个参数是返回请求是否执行成功 成功为1 失败为0
  •                                 break;  
  •                             default:  
  •                                 break;  
  •                     }  


  •             }  

  • }  
  • struct block_device_operations my_blkdev_fop = {  
  •         .owner                = THIS_MODULE,  
  • };  

  • static
    int __init blkdev_init(void)  
  • {  
  •             printk(KERN_EMERG "module init is finished!!!\n");  
  •             int ret;  
  •             /*初始化IO请求队列*/
  •             my_blkdev_queue=blk_init_queue(do_queue,NULL);//初始化块设备的IO请求队列  第一个参数是IO请求队列的处理函数名  第二个参数是一个自旋锁
  •             if (!my_blkdev_queue)   
  •             {  
  •           ret = -ENOMEM;  
  •           goto err_alloc_queue;  
  •       }  

  •             /*跟系统申请 描述块设备的结构体  赋值给定义的全局变量指针*/
  •             my_blkdev_disk=alloc_disk(1); //动态分配gendisk结构体  参数为该设备使用的次设备号数量
  •         if (!my_blkdev_disk)   
  •         {  
  •           ret = -ENOMEM;  
  •           goto err_alloc_disk;  
  •       }  
  •             /*填充块设备结构体*/
  •             my_blkdev_disk->major=255;//主设备号为255
  •             my_blkdev_disk->first_minor=0;//只有一个设备在alloc_disk中体现的,这个是第一个设备的次设备号
  •             strcpy(my_blkdev_disk->disk_name,"my_blkdev");//填写设备名
  •             my_blkdev_disk->fops=&my_blkdev_fop; //填写设备操作函数集合
  •             my_blkdev_disk->queue=my_blkdev_queue; //填写该块设备的IO请求队列

  •             set_capacity(my_blkdev_disk, MY_BLKDEV_BYTES>>9); //一般一个扇区的大小是512个字节  这个函数应该是得到这个块设备上有几个扇区
  •             /*注册块设备 进入内核*/
  •             add_disk(my_blkdev_disk);//注册块设备驱动
  •             return 0;  

  • err_alloc_disk:  
  •         blk_cleanup_queue(my_blkdev_queue);//如果IO请求队列申请失败 则应该清除队列
  • err_alloc_queue:  
  •         return ret;  


  • }  

  • static
    void  __exit blkdev_exit(void)  
  • {  

  •         blk_cleanup_queue(my_blkdev_queue);  
  •         del_gendisk(my_blkdev_disk);  
  • }  

  • module_init(blkdev_init);  
  • module_exit(blkdev_exit);  


http://blog.csdn.net/mbh_1991/article/details/9397969
返回列表