Chinaunix首页 | 论坛 | 博客
  • 博客访问: 169566
  • 博文数量: 67
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 22
  • 用 户 组: 普通用户
  • 注册时间: 2015-06-17 17:44
个人简介

一入程序深似海,从此妹子成路人

文章分类

全部博文(67)

文章存档

2016年(13)

2015年(54)

我的朋友

分类: LINUX

2015-06-18 08:43:20

原文地址:linux之块设备驱动 作者:_ChinaUnix

    Linux操作系统有两类主要的设备文件:
1.字符设备:以字节为单位进行顺序I/O操作的设备,无需缓冲区且被直接读写。
2.块设备:只能以块单位接收输入返回,对于I/O请求有对应的缓冲区,可以随机访问,块设备的访问位置必须能够在介质的不同区间前后移动。在块设备中,最小的可寻址单元是扇区,扇区的大小一般是2的整数倍,常见的大小为512个字节。
   上图是一个块设备操作的分层实现图
1.当一个进程被Read时,内核会通过VFS层去读取要读的文件块有没有被cache了,这个cache由一个buffer_head结构读取。如果要读取的文件块还没有被cache,则就要从文件系统中去读取,通过一个address_space结构来引用,如果调用文件系统读取函数去读取一个扇区的数据。当它从磁盘读出数据时,将数据页连入到cache中,当下一次在读取时,就不需要从磁盘去读取了。Read完后将请求初始化成一个bio结构,并提交给通用块层。
2.它通过submit_bio()去完成,通用层在调用相应的设备IO调度器,这个调度器的调度算法,将这个bio合并到已经存在的request中,或者创建一个新的request,并将创建的插入到请求队列中,最后就剩下块设备驱动层来完成后面的所有工作。
内核中块得I/O操作的是由bio结构表示的

点击(此处)折叠或打开

  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.     /* Number of segments after physical and DMA remapping
  11.      * hardware coalescing is performed.
  12.      */
  13.     unsigned short        bi_hw_segments;

  14.     unsigned int        bi_size;    /* residual I/O count */

  15.     /*
  16.      * To keep track of the max hw size, we account for the
  17.      * sizes of the first and last virtually mergeable segments
  18.      * in this bio
  19.      */
  20.     unsigned int        bi_hw_front_size;
  21.     unsigned int        bi_hw_back_size;

  22.     unsigned int        bi_max_vecs;    /* max bvl_vecs we can hold */

  23.     struct bio_vec        *bi_io_vec;    /* the actual vec list */

  24.     bio_end_io_t        *bi_end_io;
  25.     atomic_t        bi_cnt;        /* pin count */

  26.     void            *bi_private;

  27.     bio_destructor_t    *bi_destructor;    /* destructor */
  28. };
此结构体的目的主要是正在执行的I/O操作,其中的bi_io_vecs、bi_vcnt、bi_idx三者都可以相互找到。bio_vec描述一个特定的片段,片段所在的物理页,块在物理页中的偏移页,整个bio_io_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.     sector_t b_blocknr;        /* start block number */
  6.     size_t b_size;            /* size of mapping */
  7.     char *b_data;            /* pointer to data within the page */

  8.     struct block_device *b_bdev;
  9.     bh_end_io_t *b_end_io;        /* I/O completion */
  10.      void *b_private;        /* reserved for b_end_io */
  11.     struct list_head b_assoc_buffers; /* associated with another mapping */
  12.     struct address_space *b_assoc_map;    /* mapping this buffer is
  13.                          associated with */
  14.     atomic_t b_count;        /* users using this buffer_head */
  15. };
下面来看看块设备的核心ll_rw_block函数

点击(此处)折叠或打开

  1. void ll_rw_block(int rw, int nr, struct buffer_head *bhs[])
  2. {
  3.     int i;

  4.     for (i = 0; i < nr; i ) {
  5.         struct buffer_head *bh = bhs[i];

  6.         if (!trylock_buffer(bh))
  7.             continue;
  8.         if (rw == WRITE) {
  9.             if (test_clear_buffer_dirty(bh)) {
  10.                 bh->b_end_io = end_buffer_write_sync;
  11.                 get_bh(bh);
  12.                 submit_bh(WRITE, bh);
  13.                 continue;
  14.             }
  15.         } else {
  16.             if (!buffer_uptodate(bh)) {
  17.                 bh->b_end_io = end_buffer_read_sync;
  18.                 get_bh(bh);
  19.                 submit_bh(rw, bh);
  20.                 continue;
  21.             }
  22.         }
  23.         unlock_buffer(bh);
  24.     }
  25. }
请求块设备驱动将多个物理块读出或者写到块设备,块设备的读写都是在块缓冲区中进行。

点击(此处)折叠或打开

  1. int submit_bh(int rw, struct buffer_head * bh)
  2. {
  3.     struct bio *bio;
  4.     int ret = 0;

  5.     BUG_ON(!buffer_locked(bh));
  6.     BUG_ON(!buffer_mapped(bh));
  7.     BUG_ON(!bh->b_end_io);
  8.     BUG_ON(buffer_delay(bh));
  9.     BUG_ON(buffer_unwritten(bh));

  10.     /*
  11.      * Only clear out a write error when rewriting
  12.      */
  13.     if (test_set_buffer_req(bh) && (rw & WRITE))
  14.         clear_buffer_write_io_error(bh);

  15.     /*
  16.      * from here on down, it's all bio -- do the initial mapping,
  17.      * submit_bio -> generic_make_request may further map this bio around
  18.      */
  19.     bio = bio_alloc(GFP_NOIO, 1);

  20.     bio->bi_sector = bh->b_blocknr * (bh->b_size >> 9);
  21.     bio->bi_bdev = bh->b_bdev;
  22.     bio->bi_io_vec[0].bv_page = bh->b_page;
  23.     bio->bi_io_vec[0].bv_len = bh->b_size;
  24.     bio->bi_io_vec[0].bv_offset = bh_offset(bh);

  25.     bio->bi_vcnt = 1;
  26.     bio->bi_idx = 0;
  27.     bio->bi_size = bh->b_size;

  28.     bio->bi_end_io = end_bio_bh_io_sync;
  29.     bio->bi_private = bh;

  30.     bio_get(bio);
  31.     submit_bio(rw, bio);

  32.     if (bio_flagged(bio, BIO_EOPNOTSUPP))
  33.         ret = -EOPNOTSUPP;

  34.     bio_put(bio);
  35.     return ret;
这个函数主要是调用submit_bio,最终调用generic_make_request去完成将bio传递给驱动去处理。

点击(此处)折叠或打开

  1. void generic_make_request(struct bio *bio)
  2. {
  3.     struct bio_list bio_list_on_stack;

  4.     if (!generic_make_request_checks(bio))
  5.         return;


  6.     if (current->bio_list) {
  7.         bio_list_add(current->bio_list, bio);
  8.         return;
  9.     }

  10.     BUG_ON(bio->bi_next);
  11.     bio_list_init(&bio_list_on_stack);
  12.     current->bio_list = &bio_list_on_stack;
  13.     do {
  14.         struct request_queue *q = bdev_get_queue(bio->bi_bdev);

  15.         q->make_request_fn(q, bio);

  16.         bio = bio_list_pop(current->bio_list);
  17.     } while (bio);
  18.     current->bio_list = NULL; /* deactivate */
  19. }
这个函数主要是取出块设备相应的队列中的每个设备,在调用块设备驱动的make_request,如果没有指定make_request就调用内核默认的__make_request,这个函数主要作用就是调用I/O调度算法将bio合并,或插入到队列中合适的位置中去。
整个流程为:
那么request_fn指向那个函数呢?在内核中搜搜request_fn,发现

点击(此处)折叠或打开

  1. request_queue_t *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)
  2. {
  3.     return blk_init_queue_node(rfn, lock, -1);
  4. }
  5. EXPORT_SYMBOL(blk_init_queue);

  6. request_queue_t *
  7. blk_init_queue_node(request_fn_proc *rfn, spinlock_t *lock, int node_id)
  8. {
  9.     request_queue_t *q = blk_alloc_queue_node(GFP_KERNEL, node_id);

  10.     if (!q)
  11.         return NULL;

  12.     q->node = node_id;
  13.     if (blk_init_free_list(q)) {
  14.         kmem_cache_free(requestq_cachep, q);
  15.         return NULL;
  16.     }


  17.     if (!lock) {
  18.         spin_lock_init(&q->__queue_lock);
  19.         lock = &q->__queue_lock;
  20.     }

  21.     q->request_fn        = rfn;
  22.     q->prep_rq_fn        = NULL;
  23.     q->unplug_fn        = generic_unplug_device;
  24.     q->queue_flags        = (1 << QUEUE_FLAG_CLUSTER);
  25.     q->queue_lock        = lock;

  26.     blk_queue_segment_boundary(q, 0xffffffff);

  27.     blk_queue_make_request(q, __make_request);
  28.     blk_queue_max_segment_size(q, MAX_SEGMENT_SIZE);

  29.     blk_queue_max_hw_segments(q, MAX_HW_SEGMENTS);
  30.     blk_queue_max_phys_segments(q, MAX_PHYS_SEGMENTS);

  31.     q->sg_reserved_size = INT_MAX;

  32.     /*
  33.      * all done
  34.      */
  35.     if (!elevator_init(q, NULL)) {
  36.         blk_queue_congestion_threshold(q);
  37.         return q;
  38.     }

  39.     blk_put_queue(q);
  40.     return NULL;
  41. }
原来request_queue的make_request_fn指向__make_request()函数,这个函数复杂I/O调度,并对bio做些合并等。下面来看看__make_request()做了些什么?
由上面可以分析得出,其中有一个很重要的结构体

点击(此处)折叠或打开

  1. struct request {
  2.     struct list_head queuelist;//连接这个请求到请求队列.
  3. //追踪请求硬件完成的扇区的成员. 第一个尚未被传送的扇区被存储到 hard_sector, 已经传送的扇区总数在 ha//rd_nr_sectors, 并且在当前 bio 中剩余的扇区数是 hard_cur_sectors. 这些成员打算只用在块子系统; 驱动//不应当使用它们.
  4.     struct request_queue *q;
  5.     sector_t hard_sector;
  6.     unsigned long hard_nr_sectors;
  7.     unsigned int hard_cur_sectors;
  8.     struct bio *bio;//bio 是给这个请求的 bio 结构的链表. 你不应当直接存取这个成员; 使用 rq_for_each_bio(后面描述) 代替.
  9.     unsigned short nr_phys_segments;//被这个请求在物理内存中占用的独特段的数目, 在邻近页已被合并后
  10.     char *buffer;//随着深入理解,可见到这个成员仅仅是在当前 bio 上调用 bio_data 的结果.
  11. };
request_queue只是一个请求队列,通过可以找到requeue,然后通过bio结构体对应的page读取物理内存中的信息。
下面看看内核使用的块设备的例子

点击(此处)折叠或打开

  1. static int jsfd_init(void)
  2. {
  3.     static DEFINE_SPINLOCK(lock);
  4.     struct jsflash *jsf;
  5.     struct jsfd_part *jdp;
  6.     int err;
  7.     int i;

  8.     if (jsf0.base == 0)
  9.         return -ENXIO;

  10.     err = -ENOMEM;
  11. //1. 分配gendisk: alloc_disk
  12.     for (i = 0; i < JSF_MAX; i++) {
  13.         struct gendisk *disk = alloc_disk(1);
  14.         if (!disk)
  15.             goto out;
  16.         jsfd_disk[i] = disk;
  17.     }
  18. //2. 设置,分配/设置队列: request_queue_t  // 它提供读写能力
  19.     if (register_blkdev(JSFD_MAJOR, "jsfd")) {
  20.         err = -EIO;
  21.         goto out;
  22.     }

  23.     jsf_queue = blk_init_queue(jsfd_do_request, &lock);
  24.     if (!jsf_queue) {
  25.         err = -ENOMEM;
  26.         unregister_blkdev(JSFD_MAJOR, "jsfd");
  27.         goto out;
  28.     }
  29. //3设置gendisk其他信息             // 它提供属性: 比如容量
  30.     for (i = 0; i < JSF_MAX; i++) {
  31.         struct gendisk *disk = jsfd_disk[i];
  32.         if ((i & JSF_PART_MASK) >= JSF_NPART) continue;
  33.         jsf = &jsf0;    /* actually, &jsfv[i >> JSF_PART_BITS] */
  34.         jdp = &jsf->dv[i&JSF_PART_MASK];

  35.         disk->major = JSFD_MAJOR;
  36.         disk->first_minor = i;
  37.         sprintf(disk->disk_name, "jsfd%d", i);
  38.         disk->fops = &jsfd_fops;
  39.         set_capacity(disk, jdp->dsize >> 9);
  40.         disk->private_data = jdp;
  41.         disk->queue = jsf_queue;
  42. //4 注册: add_disk
  43.         add_disk(disk);
  44.         set_disk_ro(disk, 1);
  45.     }
  46.     return 0;
  47. out:
  48.     while (i--)
  49.         put_disk(jsfd_disk[i]);
  50.     return err;
  51. }





阅读(894) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~