Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1281950
  • 博文数量: 168
  • 博客积分: 3483
  • 博客等级: 中校
  • 技术积分: 1696
  • 用 户 组: 普通用户
  • 注册时间: 2006-02-06 13:17
文章分类

全部博文(168)

文章存档

2015年(6)

2014年(9)

2013年(47)

2012年(11)

2011年(13)

2010年(18)

2009年(11)

2008年(42)

2007年(11)

分类: LINUX

2008-01-16 22:35:17

* 文中的ULK指<<深入理解LINUX内核>>,LKD指<>
* 不加特殊说明的话:2.4指2.4.21,2.6指2.6.9
* 文中"-->"表示函数调用关系,"->"表示指针.
* 以下内容不保证正确.

一. 调查对象:页高速缓存(page cache)。
    顾名思义,页高速缓存缓存的是页面。缓存中的页来自对正规文件,块设备文件和内存映射文件的读写,
如此一来,页高速缓存就包含了文件的最近被访问过的全部页面。在执行I/O操作前,比如read( )操作,内核
会检查数据是否已经在高速缓存中了,如果所需数据确实在高速缓存中,那么就可以马上从缓存中得到所需的
页。
    在页和页的操作方法之间建立联系的主要数据结构为 address_space 对象,其保存在inode结构的i_data
中(inode中的i_mapping 和 page中的mapping也指向它)。
获取要读取的文件对应的address_space对象:filp->f_mapping(2.6中才有)
或 filp->f_dentry->d_inode->i_mapping

2.4.21 address_space 结构体定义:
struct address_space {
    struct list_head    clean_pages;    /* list of clean pages */
    struct list_head    dirty_pages;    /* list of dirty pages */
    struct list_head    locked_pages;   /* list of locked pages */
    unsigned long    nrpages;           /* number of total pages */
    struct address_space_operations *a_ops;    /* methods */
    struct inode        *host;          /* owner: inode, block_device */
    struct vm_area_struct    *i_mmap;   /* list of private mappings */
    struct vm_area_struct    *i_mmap_shared;   /* list of shared mappings */
    spinlock_t        i_shared_lock;    /* and spinlock protecting it */
    int            gfp_mask;            /* error bits/gfp mask */
};

2.6.5之后 address_space 结构体定义:
struct address_space {
    struct inode        *host;          /* owner: inode, block_device */
    struct radix_tree_root    page_tree;     /* radix tree of all pages */
    spinlock_t        tree_lock;        /* and spinlock protecting it */
    unsigned int        i_mmap_writable;     /* count VM_SHARED mappings */
    struct prio_tree_root    i_mmap;    /* tree of private and shared mappings */
    struct list_head    i_mmap_nonlinear;    /*list VM_NONLINEAR mappings */
    spinlock_t        i_mmap_lock;      /* protect tree, count, list */
    atomic_t        truncate_count;     /* Cover race condition with truncate */
    unsigned long        nrpages;       /* number of total pages */
    pgoff_t            writeback_index; /* writeback starts here */
    struct address_space_operations *a_ops;  /* methods */
    unsigned long        flags;         /* error bits/gfp mask */
    struct backing_dev_info *backing_dev_info;     /* device readahead, etc */
    spinlock_t        private_lock;     /* for use by the address_space */
    struct list_head    private_list;   /* ditto */
    struct address_space    *assoc_mapping;  /* ditto */
};

在2.6中的主要变化是:增加了一个page_tree成员,(2.6.5之后)去掉clean_pages,dirty_pages,locked_pages
三个成员。这些变化的主要原因是:在页高速缓存中页的管理和检索方法发生了改变,而改变目的当然是为了提
高效率。(还有一些与文件映射有关的成员,没研究.)

find_get_page( )
2.4版本内核中为页高速缓存维护一个全局页散列表:page_hash_table,并定义一个宏page_hash来对其进行检索
全局页散列表主要存在四个问题:
    * 由于使用单个的全局锁保护散列表,所以即使在中等规模机器上,锁争用情况也很严重,使性能受损。
    * 由于散列表需要包含所有页高速缓存中的页,可是搜索需要的只是和当前文件相关的那些页,所以散
      列表包含的页面相比搜索需要的页面要多的多。
    * 如果搜索失败,执行速度比希望的要慢的多,因为检索必须遍历指定哈希键值对应的整个链表。
    * 散列表比其他方法会消耗更多的内存。
2.6版本内核中引入基于基树的页高速缓存来解决这些问题。
    基树也是一种树型数据结构,只要给定了文件偏移量,就可以在基树中迅速检索到希望的数据。相关的
    检索函数radix_tree_lookup( )。

还有,在2.6.5之后clean_pages,dirty_pages,locked_pages三个成员被舍弃,也就是说clean,dirty和locked状
态的页不在单独维护一个双向链表来管理,取而代之的是:在基树的节点结构radix_tree_node中加入一个名为
tags的二维数组来存放子节点的状态位图方便检索,看mpage_writepages-->pagevec_lookup_tag( )的用法。
那2.4里那三个破链表到底是干什么用的呢?这些链表允许内核快速查找文件中处于指定状态的页。只要页改变它
的状态内核就把页描述符从一个链表移到另一个链表。(可能是开发者发现这样并不能提高多少效率,反而增加系
统负担。)

(详情请看中2.6.5-bk2的补丁)
另,与上面两处修改相对应的页描述符page中的相关字段也同样被舍弃。它们是list, next_hash, pprev_hash。

radix_tree_node结构(lib/radix-tree.c)
 47 struct radix_tree_node {
 48         unsigned int    count;
 49         void            *slots[RADIX_TREE_MAP_SIZE];
 50         unsigned long   tags[RADIX_TREE_TAGS][RADIX_TREE_TAG_LONGS];
 51 };

基树的结构参见<>的15.1.2. The Radix Tree一节的Figure 15-1. Two examples of a radix tree
====================================================================================================

二. 调查对象:缓冲区高速缓存(buffer cache),缓冲区首部
    每个缓冲区(buffer)缓存的是物理磁盘上的块,主要由buffer_head结构来描述。缓冲区并不单独
分配内存单元,而是存放在名为缓冲区页(buffer pages)的专门页中。因此缓冲区高速缓存所存放的RAM部分
总是页高速缓存所存放的RAM部分的子集。

2.4版本内核中的缓冲区高速缓存管理的相关概念:
缓冲区首部数据结构
    * 未使用的缓冲区首部链表unused_list(起内存高速缓存的作用,而slab分配器的bh_cachep就是
      辅助内存高速缓存)
    * 已使用的缓冲区首部链表BUF_CLEAN, BUF_DIRTY, BUF_LOCKED. lru_list(每个链表中第一个元素地址)
    * 未使用的缓冲区首部的散列表hash_table,由get_hash_table( )从散列表中检索。


{ 2.6中上面这些概念已经全部丢掉了,检索缓冲区主要通过__getblk-->__getblk_slow调用
__find_get_block-->__find_get_block_slow[调用find_get_page,page_has_buffers,page_buffers三个函数]
和 grow_buffers() 查找创建)
2.4中是通过getblk调用get_hash_table,grow_buffers两个函数查找创建. }

上面提到的动作由函数getblk()封装。(2.6中为__getblk)


在2.4中缓冲区头不仅描述了从磁盘块到物理内存的映射,而且还是所有块I/O操作的容器。但是一些开发者认为
将buffer_head作为I/O操作单元带来两个弊端:
    * buffer_head太大不易控制,而且它对数据的操作既不方便也不清晰。?
    * 它只能描述单个缓冲区,当作为所有I/O的容器时缓冲区头会迫使内核打断对大块数据的I/O操作,使
      其成为对多个buffer_head结构体进行操作,这样就造成不必要的负担和空间浪费。

由于buffer_head作为I/O容器带来的弊端,在2.6内核中对buffer_head结构做了简化,让它描述从磁盘块到物理
内存的映射信息。而新定义了一个名为bio的结构体作为块I/O操作的容器。(LKD p177)


相对于2.4这个变动很大,与块设备相关的驱动程序的接口都随之发生了变化.一时半会我也看不太明白,下面是一
些零散的东西和问题:

2.4 page结构里的buffers指向本页中第一个缓冲区的buffer_head.
2.6 page结构里由private成员在必要时指向第一个缓冲区的buffer_head.参考page_has_buffers()和
page_buffers()

什么时候进行页I/O操作,什么时候进行块I/O操作? 还是页操作在块操作上面一层,  ?
这两种I/O操作都依赖同一函数(ll_rw_block/submit_bio)来访问块设备,但是内核对它们使用不同的算法和缓冲
技术. (参见ULK 2nd 475)

2.4中bread主要调用getblk(),ll_rw_block()完成任务. 2.6中__bread()主要调用
__getblk(),__bread_slow()-->submit_bh()完成任务(不使用ll_rw_block了,可能是因为开发者觉得ll_rw_block
在这儿有点大才小用,所以给改了.)

因为bio结构的引入,两个版本中submit_bh的提交方式也发生了改变, 在2.6的submit_bh中,先把 buffer_head 转
换为bio结构,再调用submit_bio函数提交.

----------------------------------------------------------------------------------------------------
昨天遗留的两个问题
writeback_control   ULK 3rd  15.3 有提到
segment             ULK 3rd  14.1

xfs_write里的segment其实对应的就是write系统调用的第三个参数count.
 20 struct iovec
 21 {
 22         void __user *iov_base;  /* BSD uses caddr_t (1003.1g requires void *) */
 23         __kernel_size_t iov_len; /* Must be size_t (1003.1g) */
 24 };  (include/linux/uio.h)


man writev
       ssize_t writev(int fd, const struct iovec *vector, int count);

DESCRIPTION
       The  readv()  function reads count blocks from the file associated with
       the file descriptor fd into the multiple buffers described by vector.

       The writev() function writes at most count blocks described  by  vector
       to the file associated with the file descriptor fd.

       The pointer vector points to a struct iovec defined in as

          struct iovec {
              void *iov_base;   /* Starting address */
              size_t iov_len;   /* Number of bytes */
          };

       Buffers are processed in the order specified.
                                                                           2006.1.13

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

chinaunix网友2008-04-17 11:22:36

如果有洞,并且这个洞不是从头部开始,但一直延伸到尾部,则仍然可以没有buffer head。 否则若装得满满的block,并且这些block用get_block()算出来在磁盘上的位置有一个不相邻,就非得全带buffer head不可。

chinaunix网友2008-04-06 17:26:17

一直没搞清楚,好像如果page中的block在磁盘上相邻,那么这个page就不是buffer page,否则只要有一个不相邻,那么这个page中的每个block buffer都要有一个buffer head,不知道这样理解对不?

cgxu2008-01-19 16:40:05

问你点核心的, 块设备文件的pagecache的页在什么时机,什么条件下释放