发新话题
打印

Linux2.6驱动程序开发讨论(之一)-I/O层数据结构

Linux2.6驱动程序开发讨论(之一)-I/O层数据结构

  
Linux2.6中I/O的操作与2.4有较大的区别,在此提出2.6I/O层操作的主要数据结构,抛砖引玉:
bio-表示活动的I/O操作;
buffer_head-表示块到页的映射;
请求结构-表示具体的I/O请求       

在搞清楚这几个结构的基础上,我们再进行后面的话题。
透析真谛,似拨云穿雾;共享智慧,如春风沐浴
http://www.kerneltravel.net

TOP

buffer_head\bio在这里:http://bbs.lupaworld.com/htm_data/255/0701/26263.html
struct request members explanation
Classification flags:

      D       driver member
      B       block layer member
      I       I/O scheduler member
Member                            Flag       Comment
------                            ----       -------

struct list_head queuelist       BI       Organization on various internal queues

void *elevator_private              I       I/O scheduler private data

unsigned char cmd[16]              D       Driver can use this for setting up a cdb before execution, see
                                  blk_queue_prep_rq

unsigned long flags              DBI       Contains info about data direction, request type, etc.

int rq_status                     D       Request status bits

kdev_t rq_dev                     DBI       Target device

int errors                     DB       Error counts

sector_t sector                     DBI       Target location

unsigned long hard_nr_sectors       B       Used to keep sector sane

unsigned long nr_sectors       DBI       Total number of sectors in request

unsigned long hard_nr_sectors       B       Used to keep nr_sectors sane

unsigned short nr_phys_segments       DB       Number of physical scatter gather
                                  segments in a request

unsigned short nr_hw_segments       DB       Number of hardware scatter gather
                                  segments in a request

unsigned int current_nr_sectors       DB       Number of sectors in first segment
                                  of request

unsigned int hard_cur_sectors       B       Used to keep current_nr_sectors sane

int tag                            DB       TCQ tag, if assigned

void *special                     D       Free to be used by driver

char *buffer                     D       Map of first segment, also see
                                  section on bouncing SECTION

struct completion *waiting       D       Can be used by driver to get signalled
                                  on request completion

struct bio *bio                     DBI       First bio in request

struct bio *biotail              DBI       Last bio in request

request_queue_t *q              DB       Request queue this request belongs to

struct request_list *rl              B       Request list this request came from

TOP


块,扇区及其大小

块设备中最小的可寻址单元是扇区。扇区大小一般是2的整数倍,而最常见的大小是512个字节。扇区的大小是设备的物理属性,扇区是所有块设备的基本单元—块设备无法对比它还小的单元进行寻址和操作,不过许多块设备能够一次就传输多个扇区。虽然大多数块设备的扇区大小都是512字节,不过其他大小的扇区也很常见(比如,很多CD-ROM盘的扇区都是2K大小)。

虽然各种软件的用途不同,但是它们都会用到自己的最小逻辑可寻址单元—块。块是文件系统的一种抽象—只能基于块来访问文件系统。虽然物理磁盘寻址是按照扇区级进行的,但是内核执行的所有磁盘操作都是按照块进行的。由于扇区是设备的最小可寻址单元,所以块不能比扇区还小,只能数倍于扇区大小。另外内核(对有扇区的硬件设备)还要求块大小是2的整数倍,而且不能超过一个页的长度 。所以,对块大小的最终要求是,必须是扇区大小的2的整数倍,并且要小于页面大小。所以通常块大小是512字节,1K或4K。

扇区和块还有一些不同的叫法,为了不引起混淆,我们在这里简要介绍一下它们的其他名称。扇区—设备的最小寻址单元,有时会被称作“硬扇区”或“设备块”;同样地,块—文件系统的最小寻址单元,有时会被称作“文件块”或“I/O块”。在这一章里,会一直使用“扇区”和“块”这两个术语,但你还是应该记住它们的这些别名。

和硬盘相关的其他常见术语还有—簇,柱面,磁头等。这些术语都和具体的块设备相关,一般情况下,用户空间的软件用不到这些概念。扇区对内核的重要性在于所有设备的I/O操作都必须基于扇区来进行;反过来,块是内核使用的较高层概念,它是比扇区高一层的抽象。
透析真谛,似拨云穿雾;共享智慧,如春风沐浴
http://www.kerneltravel.net

TOP


缓冲区和缓冲区头


当一个块被调入内存时(也就是说,在读入后或等待写出时),它要存储在一个缓冲区中。每个缓冲区与一个块对应,它相当于是磁盘块在内存中的表示。前面提到过,块包含一个或多个扇区,但大小不能超过一个页面,所以一个页可以容纳一个或多个内存中的块。由于内核在处理数据时需要一些相关的控制信息(比如块属于那一个块设备,块对应于哪个缓冲区等),所以每一个缓冲区都有一个对应的描述符。该描述符用buffer_head结构体表示,被称作缓冲区头,在文件<linux/buffer_head.h>中定义,它包含了内核操作缓冲区所需要的全部信息。
下面给出缓冲区头结构体和其中各个域的说明:
struct buffer_head {
      unsigned long        b_state;       /*缓冲区状态标志*/
      atomic_t        b_count;       /*缓冲区使用计数*/
      struct buffer_head        *b_this_page;       /*页面中的缓冲区*/
      struct page        *b_page;       /*存储缓冲区的页面*/
      sector_t         b_blocknr;       /*逻辑块号*/
      u32         b_size;       /*块大小(以字节为单位)*/
      char        *b_data;       /*页面中的缓冲区*/
      struct block_device        *b_bdev;       /*块设备*/
      bh_end_io_t        *b_end_io;       /*I/O完成方法*/
       void        *b_private;       /*完成方法数据*/
      struct list_head         b_assoc_buffers;        /*相关的映射链表*/
};
b_state域表示缓冲区的状态,可以是下表1中一种标志或多种标志的组合。合法的标志存放在bh_state_bits枚举中,该枚举在<linux/buffer_head.h>中定义。
表1   bh_state 标志
状态标志              意   义
BH_Uptodate        该缓冲区包含可用数据
BH_Dirty            该缓冲区是脏的(缓存中的内容比磁盘中的块内容新,
所以缓冲区内容必须被写回磁盘)
BH_Lock            该缓冲区正在被I/O操作使用,被锁定以防被并发访问
BH_Req            该缓冲区有I/O请求操作
BH_Mapped            该缓冲区是映射磁盘块的可用缓冲区
BH_New           缓冲区是通过get_block()刚刚映射的,尚且不能访问
          
BH_Async_Read        该缓冲区正通过end_buffer_async_read()被异步I/O读操作使用
BH_Async_write  该缓冲区正通过end_buffer_async_write()被异步I/O写操作使用
BH_Delay             该缓冲区尚未和磁盘块关联
BH_Boundary         该缓冲区处于连续块区的边界——下一个块不再连续
bh_state_bits列表还包含了一个特殊标志——BH_PrivateStart,该标志不是可用状态标志,使用它是为了指明可被其他代码使用的起始位。块I/O层不会使用BH_PrivateStart或更高的位。那么某个驱动程序希望通过b_state域存储信息时就可以安全地使用这些位。驱动程序可以在这些位中定义自己的状态标志,只要保证自定义的状态标志不与块I/O层的专用位发生冲突就可以了。
b_count域表示缓冲区的使用记数,可通过两个定义在文件<linux/buffer_head.h>中的内联函数对此域进行增减。
static inline void get_bh(struct buffer_head *bh)
{
      atomic_inc(&bh->b_count);
}

static inline void put_bh(struct buffer_head *bh)
{
      atomic_dec(&bh->b_count);
}
在操作缓冲区头之前,应该先使用get_bh()函数增加缓冲区头的引用计数,确保该缓冲区头不会再被分配出去;当完成对缓冲区头的操作之后,还必须使用put_bh()函数减少引用计数。

与缓冲区对应的磁盘物理块由b_blocknr域索引,该值是b_bdev域指明的块设备中的逻辑块号。
与缓冲区对应的内存物理页由b_page域表示,另外,b_data域直接指向相应的块(它位于b_page域所指明的页面中的某个位置上),块的大小由b_size域表示,所以块在内存中的起始位置在b_data处,结束位置在(b_data + b_size)处。

缓冲区头的目的在于描述磁盘块和物理内存缓冲区(在特定页面上的字节序列)之间的映射关系。这个结构体在内核中只扮演一个描述符的角色,说明从缓冲区到块的映射关系。

在2.6内核以前,缓冲区头的作用比现在还要重要。因为缓冲区头作为内核中的I/O操作单元,不仅仅描述了从磁盘块到物理内存的映射,而且还是所有块I/O操作的容器。可是,将缓冲区头作为I/O操作单元带来了两个弊端。首先,缓冲区头是一个很大且不易控制的数据结构体(现在是缩减过的了),而且缓冲区头对数据的操作既不方便也不清晰。对内核来说,它更倾向于操作页面结构,因为页面操作起来更为简便,同时效率也高。使用一个巨大的缓冲区头表示每一个独立的缓冲区(可能比页面小)效率低下,所以在2.6版本中,许多I/O操作都是通过内核直接对页面或地址空间进行操作来完成,不再使用缓冲区头了。这其中所做的一些工作会在“页高速缓存和页回写”中进行讨论,具体情况请参考address_space结构和pdflush等守护进程(daemon)部分。

缓冲区头带来的第二个弊端是:它仅能描述单个缓冲区,当作为所有I/O的容器使用时,缓冲区头会迫使内核打断对大块数据的I/O操作(比如写操作),使其成为对多个buffer_head结构体进行操作。这样做必然会造成不必要的负但和空间浪费。所以2.5开发版内核的主要目标就是为块I/O操作引入一种新型、灵活并且轻量级的容器,也就是要介绍的bio结构体。

透析真谛,似拨云穿雾;共享智慧,如春风沐浴
http://www.kerneltravel.net

TOP



为了解决处理器与存储设备之间速度不匹配的问题,引入了缓存技术,块缓存由两部分组成,第一部分称为块缓存头部(buffer_head),第二部分是块缓存数据区,分别存储,用b_page指明数据区所在的页,b_size表明数据区的大小。
linux-2.6.13的块缓存初始化function
void __init buffer_init(void)
{
      int nrpages;

      bh_cachep = kmem_cache_create("buffer_head",
                    sizeof(struct buffer_head), 0,
                    SLAB_RECLAIM_ACCOUNT|SLAB_PANIC, init_buffer_head, NULL);

      /*
       * Limit the bh(指的是buffer_head数据结构类型) occupancy to 10% of ZONE_NORMAL
       * 对其规模进行限定
       */
      nrpages = (nr_free_buffer_pages() * 10) / 100;
      max_buffer_heads = nrpages * (PAGE_SIZE / sizeof(struct buffer_head));
      hotcpu_notifier(buffer_cpu_notify, 0);//通知cpu确认function
}
Linux 采用 Node、Zone 和page三级结构来描述物理内存。
块缓存的大小不是固定的,同样设备同样文件系统类型的大小相同。


附件

Fid_255/255_4983.gif (11 KB)

2007-1-24 19:00

Fid_255/255_4983.gif

TOP



linux-2.4.18的块缓存初始化function
/* ===================== Init ======================= */

/*
* allocate the hash table and init the free list
* Use gfp() for the hash table to decrease TLB misses, use
* SLAB cache for buffer heads.
*/
void __init buffer_init(unsigned long mempages)
{
      int order, i;
      unsigned int nr_hash;

      /* The buffer cache hash table is less important these days,
       * trim it a bit.
       */
      mempages >>= 14;

      mempages *= sizeof(struct buffer_head *);

      for (order = 0; (1 << order) < mempages; order++)
             ;

      /* try to allocate something until we get it or we're asking
         for something that is really too small */

      do {
             unsigned long tmp;

             nr_hash = (PAGE_SIZE << order) / sizeof(struct buffer_head *);
             bh_hash_mask = (nr_hash - 1);

             tmp = nr_hash;
             bh_hash_shift = 0;
             while((tmp >>= 1UL) != 0UL)
                    bh_hash_shift++;

             hash_table = (struct buffer_head **)
                 __get_free_pages(GFP_ATOMIC, order);
      } while (hash_table == NULL && --order > 0);
      printk("Buffer-cache hash table entries: %d (order: %d, %ld bytes)\n",
             nr_hash, order, (PAGE_SIZE << order));

      if (!hash_table)
             panic("Failed to allocate buffer hash table\n");

      /* Setup hash chains. */
      for(i = 0; i < nr_hash; i++)
             hash_table = NULL;

      /* Setup lru lists. */
      for(i = 0; i < NR_LIST; i++)
             lru_list = NULL;

}

TOP

发新话题