11 Linux 块设备驱动程序

Posted beijiqie1104

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了11 Linux 块设备驱动程序相关的知识,希望对你有一定的参考价值。

参考:https://www.cnblogs.com/big-devil/p/8590007.html

Linux 块设备驱动程序

概念补充

扇区是硬件数据传输的基本单元,块则是虚拟文件系统传输数据的基本单位。Linux内核中,块的大小必须是2的次幂,但不能超过一个页(4K)的大小。

同一物理页面中的硬盘存储介质上连续的多个块组成一个段。段的大小与块的个数有关,段包含多个块,块包含多个扇区。段在内核中由bio_vec结构体描述,多个段的信息存放于结构体bio中的biz_io_vec数组中,段数组在后续的块设备处理流程中被合并成物理段。bio_vec段结构体定义如下:

  1. struct bio_vec {  
  2.     struct page *bv_page;//数据段所在的页,即bh->b_page  
  3.     unsigned int    bv_len;//数据段的长度 ,即bh->b_size 
  4.     unsigned int    bv_offset;//数据段页内偏移 ,即bh->b_data 
  5. };  

总结:

扇区由磁盘的物理特性决定,快缓冲区大小由内核决定,段由块缓冲区个数决定,但不能超过页的大小。三者关系如下:

技术图片

Linux内核中,块设备将数据存储在固定大小的块中,每个块具有固定的地址。内核中块设备与其他模块之间的关系如下:

技术图片

块设备的操作流程简述如下:

(1)、用户程序向磁盘中写入数据时,会发出write()系统调用给内核;

(2)、内核调用虚拟文件系统相应的函数,将需要写入的文件描述符和文件内容指针传递给该函数;

(3)、内核需要确定写入磁盘的位置,通过映射层知道需要写入磁盘的哪一块。

(4)、根据磁盘文件系统的类型,调用对应文件格式的写入函数,将数据发送给通用块层;

(5)、到达通用块层后,对设备发出写请求。内核利用通用块层的启动I/O调度器,对数据进行排序,调度器会将物理上相邻的读写合并在一起,加快访问速度;

(6)、最后块设备驱动向磁盘发送指令和数据,将数据写入磁盘。

Linux块设备驱动程序框架图如下:

技术图片

架构分析

bio结构体

当一个进行被read时,首先会去cache中查看是否有相关文件,若没有,系统会利用块设备驱动去磁盘中读取数据。于是read()函数将被初始化成一个bio结构体,提交给通用块层。一个bio对应一个i/o请求。在include/linux/bio.h文件中定义。

  1. struct bio {  
  2.     sector_t        bi_sector;  //bio结构体所要传输的第一个扇区(512字节),磁盘的位置  
  3.     struct bio      *bi_next;   //请求链表  
  4.     struct block_device *bi_bdev;//相关的块设备  
  5.     unsigned long       bi_flags;   //状态和命令标志  
  6.     unsigned long       bi_rw;      //读写  
  7.     unsigned short      bi_vcnt;    //bio_vec偏移的个数  
  8.     unsigned short      bi_idx;     //bvl_vec的当前索引  
  9.     unsigned short      bi_phys_segments;//结合后片段数目  
  10.     unsigned short      bi_hw_segments;//重映射后的片段数目  
  11.     unsigned int        bi_size;    //I/O计数  
  12.     unsigned int        bi_hw_front_size;//第一个可合并的段大小  
  13.     unsigned int        bi_hw_back_size;//最后一个可合并的段大小  
  14.     
  15.     unsigned int        bi_max_vecs;    //bvl_vecs数目上线  
  16.     struct bio_vec      *bi_io_vec; //bio_vec链表:内存的位置  
  17.     
  18.     bio_end_io_t        *bi_end_io;//bio_vec完成方法  
  19.     atomic_t        bi_cnt;     //使用计数  
  20.     
  21.     void            *bi_private;//拥有者的私有方法  
  22.     
  23.     bio_destructor_t    *bi_destructor; //销毁方法  
  24. };  

bio结构的核心是biz_io_vec数组,表示整个完整的缓冲区,由bio_vec组成,在内核中的定义如下:

  1. struct bio_vec {  
  2.     struct page *bv_page;//数据段所在的页  
  3.     unsigned int    bv_len;//数据段的长度  
  4.     unsigned int    bv_offset;//数据段页内偏移  
  5. };  

当一个块被调用到内存中时,要存储在一个缓冲区中。每一个缓冲区对应一个块,并拥有一个对应的描述符。该描述符使用buffer_head结构体表示,其定义如下:

  1. struct buffer_head {  
  2.     unsigned long b_state;      /* buffer state bitmap (see above) */  
  3.     struct buffer_head *b_this_page;/* circular list of page‘s buffers */  
  4.     struct page *b_page;        /* the page this bh is mapped to */  
  5.     
  6.     sector_t b_blocknr;     /* start block number */  
  7.     size_t b_size;          /* size of mapping */  
  8.     char *b_data;           /* pointer to data within the page */  
  9.     
  10.     struct block_device *b_bdev;  
  11.     bh_end_io_t *b_end_io;      /* I/O completion */  
  12.     void *b_private;        /* reserved for b_end_io */  
  13.     struct list_head b_assoc_buffers; /* associated with another mapping */  
  14.     struct address_space *b_assoc_map;  /* mapping this buffer is 
  15.                            associated with */  
  16.     atomic_t b_count;       /* users using this buffer_head */  
  17. };  

bio与bufferhead之间的关系

核心函数ll_rw_block

  1. void ll_rw_block(int rw, int nr, struct buffer_head *bhs[])  
  2. {  
  3.     int i;  
  4.     
  5.     for (i = 0; i < nr; i++) {  
  6.         struct buffer_head *bh = bhs[i];  
  7.     
  8.         if (rw == SWRITE)  
  9.             lock_buffer(bh);  
  10.         else if (test_set_buffer_locked(bh))  
  11.             continue;  
  12.     
  13.         if (rw == WRITE || rw == SWRITE) {  
  14.             if (test_clear_buffer_dirty(bh)) {  
  15.                 bh->b_end_io = end_buffer_write_sync;  
  16.                 get_bh(bh);  
  17.                 submit_bh(WRITE, bh);  
  18.                 continue;  
  19.             }  
  20.         } else {  
  21.             if (!buffer_uptodate(bh)) {  
  22.                 bh->b_end_io = end_buffer_read_sync;  
  23.                 get_bh(bh);  
  24.                 submit_bh(rw, bh);  
  25.                 continue;  
  26.             }  
  27.         }  
  28.         unlock_buffer(bh);  
  29.     }  
  30. }  

核心函数submit_bh

  1. int submit_bh(int rw, struct buffer_head * bh)  
  2. {  
  3.     struct bio *bio;  
  4.     int ret = 0;  
  5.     
  6.     BUG_ON(!buffer_locked(bh));  
  7.     BUG_ON(!buffer_mapped(bh));  
  8.     BUG_ON(!bh->b_end_io);  
  9.     
  10.     if (buffer_ordered(bh) && (rw == WRITE))  
  11.         rw = WRITE_BARRIER;  
  12.     
  13.     /* 
  14.      * Only clear out a write error when rewriting, should this 
  15.      * include WRITE_SYNC as well? 
  16.      */  
  17.     if (test_set_buffer_req(bh) && (rw == WRITE || rw == WRITE_BARRIER))  
  18.         clear_buffer_write_io_error(bh);  
  19.     
  20.     /* 
  21.      * from here on down, it‘s all bio -- do the initial mapping, 
  22.      * submit_bio -> generic_make_request may further map this bio around 
  23.      */  
  24.     bio = bio_alloc(GFP_NOIO, 1);  
  25.     
  26.     bio->bi_sector = bh->b_blocknr * (bh->b_size >> 9);  
  27.     bio->bi_bdev = bh->b_bdev;  
  28.     bio->bi_io_vec[0].bv_page = bh->b_page;  
  29.     bio->bi_io_vec[0].bv_len = bh->b_size;  
  30.     bio->bi_io_vec[0].bv_offset = bh_offset(bh);  
  31.     
  32.     bio->bi_vcnt = 1;  
  33.     bio->bi_idx = 0;  
  34.     bio->bi_size = bh->b_size;  
  35.     
  36.     bio->bi_end_io = end_bio_bh_io_sync;  
  37.     bio->bi_private = bh;  
  38.     
  39.     bio_get(bio);  
  40.     submit_bio(rw, bio);  
  41.     
  42.     if (bio_flagged(bio, BIO_EOPNOTSUPP))  
  43.         ret = -EOPNOTSUPP;  
  44.     
  45.     bio_put(bio);  
  46.     return ret;  
  47. }  

核心函数submit_bio

  1. void submit_bio(int rw, struct bio *bio)  
  2. {  
  3.     int count = bio_sectors(bio);  
  4.     
  5.     BIO_BUG_ON(!bio->bi_size);  
  6.     BIO_BUG_ON(!bio->bi_io_vec);  
  7.     bio->bi_rw |= rw;  
  8.     if (rw & WRITE) {  
  9.         count_vm_events(PGPGOUT, count);  
  10.     } else {  
  11.         task_io_account_read(bio->bi_size);  
  12.         count_vm_events(PGPGIN, count);  
  13.     }  
  14.     
  15.     if (unlikely(block_dump)) {  
  16.         char b[BDEVNAME_SIZE];  
  17.         printk(KERN_DEBUG "%s(%d): %s block %Lu on %s ",  
  18.             current->comm, current->pid,  
  19.             (rw & WRITE) ? "WRITE" : "READ",  
  20.             (unsigned long long)bio->bi_sector,  
  21.             bdevname(bio->bi_bdev,b));  
  22.     }  
  23.     
  24.     generic_make_request(bio);  
  25. }  

核心函数generic_make_request

  1. void generic_make_request(struct bio *bio)  
  2. {  
  3.     if (current->bio_tail) {  
  4.         /* make_request is active */  
  5.         *(current->bio_tail) = bio;  
  6.         bio->bi_next = NULL;  
  7.         current->bio_tail = &bio->bi_next;  
  8.         return;  
  9.     }  
  10.     
  11.     BUG_ON(bio->bi_next);  
  12.     do {  
  13.         current->bio_list = bio->bi_next;  
  14.         if (bio->bi_next == NULL)  
  15.             current->bio_tail = ¤t->bio_list;  
  16.         else  
  17.             bio->bi_next = NULL;  
  18.         __generic_make_request(bio);  
  19.         bio = current->bio_list;  
  20.     } while (bio);  
  21.     current->bio_tail = NULL; /* deactivate */  
  22. }  

该函数中主要实现取出每个bio,并执行__generic_make_request函数。

通用层在调用相应的IO调度器时,会将bio合并到已经存在的request中,或创建一个新的request,并将创建的插入到请求队列中,最后由块设备驱动层来完成。Linux内核中,对块设备的IO请求,会向块设备驱动发出一个请求,由结构体request 表示,其定义如下:

  1. struct request {  
  2.     struct list_head queuelist;//挂在请求队列链表的节点  
  3.     struct list_head donelist;//挂在已完成请求链表的节点  
  4.     
  5.     request_queue_t *q; //指向请求队列的指针  
  6.     
  7.     unsigned int cmd_flags;//命令标识  
  8.     enum rq_cmd_type_bits cmd_type;//命令标志  
  9.     
  10.     /* Maintain bio traversal state for part by part I/O submission. 
  11.      * hard_* are block layer internals, no driver should touch them! 
  12.      */  
  13.     
  14.     sector_t sector;        //下一个需要提交的扇区的偏移位置  
  15.     sector_t hard_sector;       //要完成的下一个扇区的偏移位置  
  16.     unsigned long nr_sectors;   //需要提交的扇区数目  
  17.     unsigned long hard_nr_sectors;  //剩余需要完成的扇区数目  
  18.     unsigned int current_nr_sectors;//当前segment中剩余需要提交的扇区数目  
  19.     unsigned int hard_cur_sectors;//当前segment中剩余需要完成的扇区数目  
  20.     
  21.     struct bio *bio;    //请求中第一个未完成的bio  
  22.     struct bio *biotail;  
  23.     
  24.     ...  
  25. };  

请求队列结构体request_queue定义如下:

  1. struct request_queue  
  2. {  
  3.     /* 
  4.      * Together with queue_head for cacheline sharing 
  5.      */  
  6.     struct list_head    queue_head;  
  7.     struct request      *last_merge;  
  8.     elevator_t      *elevator;  
  9.     struct request_list rq;  
  10.     
  11.     request_fn_proc     *request_fn;  
  12.     make_request_fn     *make_request_fn;  
  13.     prep_rq_fn      *prep_rq_fn;  
  14.     unplug_fn       *unplug_fn;  
  15.     merge_bvec_fn       *merge_bvec_fn;  
  16.     issue_flush_fn      *issue_flush_fn;  
  17.     prepare_flush_fn    *prepare_flush_fn;  
  18.     softirq_done_fn     *softirq_done_fn;  
  19.         ......  
  20.     
  21. };  

blk_init_queue函数

request_queue_t *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)

第一个参数rfn,指向"请求处理函数",请求处理函数"用于处理和硬盘之间的数据传输。

第二个参数lock,队列访问权限的自旋锁spinlock_t,通过DEFINE_SPINLOCK定义。

__generic_make_request函数中最终会调用make_request_fn函数,这里会分成两种情况。

第一种是执行请求队列中自定义的make_request_fn函数,这里使用blk_queue_make_request函数实现对请求队列和自定义请求处理函数之间的绑定。

技术图片

第二种使用系统默认的使用系统默认的__make_request。该函数中会启动I/O调度器,对bio进行处理,将其合并到请求队列中的一个请求结构中。最后调用request_fn_proc将数据写入或读出块设备。

对bio结构体的处理可以使用I/O调度器,也可以不使用I/O调度器。blk_init_queue函数使用I/O调度器,通过该函数完成对队列的初始化。blk_alloc_queue函数不使用I/O调度器,通过该函数申请请求队列。

块设备驱动程序的框架:

  1. 块设备驱动加载
    1. 使用alloc_disk函数申请一个gendisk结构体;
    2. 根据是否需要I/O调度,将情况分为两种情况,一种是使用请求队列进行数据传输,一种是不使用请求队列进行数据传输。
    3. 初始化gendisk结构体;
    4. 使用register_blkdev函数注册一个块设备,可选;
    5. 使用add_disk激活磁盘设备。
  2. 块设备驱动卸载
    1. 使用del_gendisk函数删除一个gendisk结构体;
    2. 使用blk_cleanup_queue函数删除请求队列;
    3. 使用blk_unregister_region函数卸载块设备,可选。

代码如下:

  1. #include <linux/module.h>  
  2. #include <linux/errno.h>  
  3. #include <linux/interrupt.h>  
  4. #include <linux/mm.h>  
  5. #include <linux/fs.h>  
  6. #include <linux/kernel.h>  
  7. #include <linux/timer.h>  
  8. #include <linux/genhd.h>  
  9. #include <linux/hdreg.h>  
  10. #include <linux/ioport.h>  
  11. #include <linux/init.h>  
  12. #include <linux/wait.h>  
  13. #include <linux/blkdev.h>  
  14. #include <linux/blkpg.h>  
  15. #include <linux/delay.h>  
  16. #include <linux/io.h>  
  17.     
  18. #include <asm/system.h>  
  19. #include <asm/uaccess.h>  
  20. #include <asm/dma.h>  
  21.     
  22. #define RAMBLOCK_SIZE   1024*1024  
  23.     
  24. static DEFINE_SPINLOCK(ramblock_lock);  
  25. static struct gendisk *ramblock_gendisk;  
  26. static struct request_queue *ramblock_queue;  
  27. static int major;  
  28. static struct block_device_operations ramblock_fops = {  
  29.     .owner  = THIS_MODULE,  
  30. };  
  31.     
  32.     
  33. static void do_ramblock_request (request_queue_t * q)  
  34. {  
  35.     struct request *req;  
  36.     static int cnt = 0;  
  37.     //printk("do_ramblock_request %d", ++cnt);  
  38.         
  39.     while ((req = elv_next_request(q)) != NULL) {  
  40.         end_request(req, 1);      
  41.     }  
  42. }__generic_make_request  
  43.     
  44. static int ramblock_init(void)  
  45. {  
  46.     /*1、分配gendisk结构体*/  
  47.     ramblock_gendisk = alloc_disk(16);  /*次设备号个数:分区个数+1*/  
  48.         
  49.     /*2 设置*/  
  50.     /*2.1、分配/设置队列,提供读写能力*/  
  51.     ramblock_queue = blk_init_queue(do_ramblock_request, &ramblock_lock);  
  52.     ramblock_gendisk->queue = ramblock_queue;  
  53.         
  54.     /*2.2 设置gendisk其他信息,比如:容量*/  
  55.     major                   = register_blkdev(0, "ramblock");  
  56.     ramblock_gendisk->major = major;  
  57.     ramblock_gendisk->first_minor = 0;  
  58.     sprintf(ramblock_gendisk->disk_name, "ramblock");  
  59.     ramblock_gendisk->fops  = &ramblock_fops;  
  60.     set_capacity(ramblock_gendisk, RAMBLOCK_SIZE/512);  
  61.         
  62.     /*3、注册*/  
  63.     add_disk(ramblock_gendisk);  
  64.     
  65.     return 0;  
  66. }  
  67.     
  68. static void ramblock_exit(void)  
  69. {  
  70.     blk_unregister_region(major, 256);  
  71.     del_gendisk(ramblock_gendisk);  
  72.     put_disk(ramblock_gendisk);  
  73.     blk_cleanup_queue(ramblock_queue);  
  74. }  
  75.     
  76.     
  77. module_init(ramblock_init);  
  78. module_exit(ramblock_exit);  
  79. MODULE_LICENSE("GPL");  

驱动程序测试:

1、insmod ramblock

2、格式化 mkdosfs /dev/ramblock

3、挂接 mount /dev/ramblock /tmp/

4、读写文件 cd /tmp/ 在里面vi文件

5、cat /dev/ramblock > ramblock.bin 将ramblock中的内容拷贝到ramblock.bin文件中,做一个磁盘映像

6、在PC桌面系统上查看

mount -o loop ramblock.bin /mnt

技术图片

对磁盘进行分区

磁盘分区

驱动中需要设置好磁头、柱面、扇区等参数

1、insmod ramblock

2、ls /dev/ramblock*

3、fdisk /dev/ramblock

 

以上是关于11 Linux 块设备驱动程序的主要内容,如果未能解决你的问题,请参考以下文章

linux 块设备驱动——块设备应用层的操作

Linux下驱动开发_块设备驱动开发(内存模拟存储)

linux驱动开发之块设备学习笔记

Linux 块设备驱动实验

linux中啥是块设备和字符设备?

linux内核的块设备驱动框架详解