zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    第六章学习小结
    malloc iOS
    iOS事件传递机制
    对 runloop 的理解
    深恶痛绝重写setter和getter
    数据库常见问题总结
    iOS多应用自动打包
    一段文字中包含多种语言时行间距问题
    一个成熟应用的排版方案
    Flask纪要
  • 原文地址:https://www.cnblogs.com/beijiqie1104/p/11971338.html
Copyright © 2011-2022 走看看