天天看點

Linux 預讀代碼分析

一. 預讀算法觸發條件

預讀觸發也就是什麼時候進行預讀算法判别?下面代碼2.6.32核心,分析代碼可見預讀算法觸發條件為兩種:

頁面缺失(頁緩存沒有找到)和讀取含預讀标志的頁面。

static void do_generic_file_read(struct file *filp, loff_t *ppos,

                read_descriptor_t *desc, read_actor_t actor)

{

   for (;;) {

                page = find_get_page(mapping, index);

                if (!page) {//頁緩存缺失(頁緩存沒有此頁面)

                         //進行同步預讀,意思使用者程序必須等待頁面讀入

                        page_cache_sync_readahead(mapping,

                                        ra, filp,

                                        index, last_index - index);

                        page = find_get_page(mapping, index);

                        if (unlikely(page == NULL))

                                goto no_cached_page;

                }

                if (PageReadahead(page)) { //頁緩存命中(頁面已在頁緩存中),此頁面含預讀标志,觸發異步預讀,所謂異步預讀就是使用者程序不必等待預讀i/o磁盤操作。

                        page_cache_async_readahead(mapping,

                                        ra, filp, page,

                                        index, last_index - index);

                }

               //頁緩存命中(頁面已在頁緩存中),此不頁面含預讀标志,不必觸發預讀操作,隻讀取此頁面内容即可

              。。。。。。。。。。。。。

     }

二. 預讀算法

預讀狀态的資料結構

struct file_ra_state {

        pgoff_t start;                 

        unsigned int size;             

        unsigned int async_size;       

        unsigned int ra_pages;         

        unsigned int mmap_miss;        

        loff_t prev_pos;               

};

start 和 size 構成一個預讀視窗,記錄了最近一次預取請求的位置和大小;

async_size 訓示了異步預取的位置提前量,即還剩餘多少未通路的預取頁面時啟動下一次預取 I/O;

prev_pos 記錄了最後的讀位置,可用于順序性測試。

預取算法核心内容預讀視窗設定,也就是多大合适?2.6.32核心預取視窗設定根據檔案讀操作方式來設定,檔案讀操作分為兩種順序讀和随機讀。随機讀無預取視窗而言。對于順序讀而言分為依次判斷是否初始化讀,順序讀,交織讀和其他方式讀來設定。

預讀視窗設定,可有三元組表示(start,size, async_size)

讀請求可有二進制組表示(offset,req_size)(注意區分讀請求和預讀請求)

預讀視窗根據讀請求而設定的

1).初始化讀:

就是從檔案頭開始讀取檔案,即檔案偏移量為0。預讀視窗設定:

initial_readahead:

    ra->start = offset; //預讀視窗開始

    ra->size = get_init_ra_size(req_size, max);//預讀視窗大小

    ra->async_size = ra->size > req_size ? ra->size - req_size : ra->size; //預讀提前量,還有多頁開始預讀

假設讀請求為(0,1),那麼預讀視窗為(0,4,3)

2).順序讀:

所謂順序讀基于上一次讀請求而言,上一次讀請求為(0,1),接下來話順序讀為(1,-)。

如果偏移量等于最大預讀視窗或者等于需要預讀線,設定預讀視窗為

上一次預讀視窗為(0,4,3),本次預讀視窗為(4,8,8)

    if ((offset == (ra->start + ra->size - ra->async_size) ||

         offset == (ra->start + ra->size))) {

        ra->start += ra->size;

        ra->size = get_next_ra_size(ra, max);

        ra->async_size = ra->size;

        goto readit;

    }

3).交織讀:

在交織的順序讀中,在同一fd, 并發流使互相預讀狀态無效。判斷條件:檔案頁緩存存在空洞

        if (hit_readahead_marker) {

                pgoff_t start;

                rcu_read_lock();

                start = radix_tree_next_hole(&mapping->page_tree, offset+1,max);

                rcu_read_unlock();

                if (!start || start - offset > max)

                        return 0;

                ra->start = start;

                ra->size = start - offset;     

                ra->size += req_size;

                ra->size = get_next_ra_size(ra, max);

                ra->async_size = ra->size;

                goto readit;

        }

其他方式:

4). 大檔案讀:也被當作順序讀,啟動預讀視窗設定

判斷條件:讀取檔案大小大于預取最大視窗

        if (req_size > max)

                goto initial_readahead;

5). 非對齊讀:

檔案讀請求(offset )的機關是位元組,當一個讀請求不是正好開始或結束于頁面邊界的時候,它就構成了一個非對齊讀。 非對齊讀識别條件上次結束與本次開始正好小于一個頁面。

判斷條件:offset - (ra->prev_pos >> PAGE_CACHE_SHIFT) <= 1UL

        if (offset - (ra->prev_pos >> PAGE_CACHE_SHIFT) <= 1UL)

                goto initial_readahead;

6). 丢失的順序讀:

查詢頁面緩存,分析長期運作的程式的曆史緩存情況,是否存在順序讀,存在話設定預讀視窗。

判斷條件:已檔案頁緩基樹,是否有順序讀情況。

        if (try_context_readahead(mapping, ra, offset, req_size, max))

                goto readit;

7). 不是上述情況,最後就是随機讀:

    return __do_page_cache_readahead(mapping, filp, offset, req_size, 0);

static unsigned long

ondemand_readahead(struct address_space *mapping,

           struct file_ra_state *ra, struct file *filp,

           bool hit_readahead_marker, pgoff_t offset,

           unsigned long req_size)

{

    unsigned long max = max_sane_readahead(ra->ra_pages);

    if (!offset)

        goto initial_readahead;

    if ((offset == (ra->start + ra->size - ra->async_size) ||

         offset == (ra->start + ra->size))) {

        ra->start += ra->size;

        ra->size = get_next_ra_size(ra, max);

        ra->async_size = ra->size;

        goto readit;

    }

    if (hit_readahead_marker) {

        pgoff_t start;

        rcu_read_lock();

        start = radix_tree_next_hole(&mapping->page_tree, offset+1,max);

        rcu_read_unlock();

        if (!start || start - offset > max)

            return 0;

        ra->start = start;

        ra->size = start - offset;    

        ra->size += req_size;

        ra->size = get_next_ra_size(ra, max);

        ra->async_size = ra->size;

        goto readit;

    }

    if (req_size > max)

        goto initial_readahead;

    if (offset - (ra->prev_pos >> PAGE_CACHE_SHIFT) <= 1UL)

        goto initial_readahead;

    if (try_context_readahead(mapping, ra, offset, req_size, max))

        goto readit;

    return __do_page_cache_readahead(mapping, filp, offset, req_size, 0);

initial_readahead:

    ra->start = offset;

    ra->size = get_init_ra_size(req_size, max);

    ra->async_size = ra->size > req_size ? ra->size - req_size : ra->size;

readit:

    if (offset == ra->start && ra->size == ra->async_size) {

        ra->async_size = get_next_ra_size(ra, max);

        ra->size += ra->async_size;

    }

    return ra_submit(ra, mapping, filp);

}

繼續閱讀