• 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

  • 相关阅读:
    docker 安装mysql
    Java web项目搭建系列之二 Jetty下运行项目
    Java web项目搭建系列之一 Eclipse中新建Maven项目
    Maven 添加其他Maven组件配置问题
    C# 中定义扩展方法
    Oracle 函数
    【Webservice】2 counts of IllegalAnnotationExceptions Two classes have the same XML type name
    Linux精简版系统安装网络配置问题解决
    Rsync 故障排查整理
    Failed to set session cookie. Maybe you are using HTTP instead of HTTPS to access phpMyAdmin.
  • 原文地址:https://www.cnblogs.com/beijiqie1104/p/11971338.html
Copyright © 2020-2023  润新知