BDI機制原本主要是用於檢測磁盤的繁忙程度等作用,從2.6.19內核開始,將此部分功能整合到了mm/backing_dev.c中,一直到2.6.31內核爲止,其功能也只是在不段的完善,但是髒數據的下刷依然是依靠pdflush。自2.6.32內核開始,徹底取消了pdflush,而是將此部分功能添加到BDI機制中,並且是爲每個設備創建了一個名爲“flush-設備主次設備號”的線程,用於髒數據的下刷。
2 結構backing_dev_info
瞭解BDI機制,首先得清楚結構backing_dev_info,其定義在描述設備的結構gendisk->request_queue內。
2.1 Kernel-2.6.18
在2.6.18內核中其定義如下:
struct backing_dev_info {
unsigned long ra_pages; /* maxreadahead in PAGE_CACHE_SIZE units */
unsigned long state; /* Always use atomic bitops on this */
unsigned int capabilities; /*Device capabilities */
congested_fn *congested_fn; /*Function pointer if device is md/dm */
void *congested_data; /* Pointer to aux data for congested func */
void (*unplug_io_fn)(structbacking_dev_info *, struct page *);
void *unplug_io_data;
};
其對應的state狀態有:
enum bdi_state {
BDI_pdflush, /* A pdflush thread is working thisdevice */
BDI_write_congested, /* Thewrite queue is getting full */
BDI_read_congested, /* Theread queue is getting full */
BDI_unused, /* Available bits start here */
};
其初始化在:
a) 函數blk_queue_make_request:初始化ra_pages、state、capabilities
b) 函數blk_alloc_queue_node:初始化unplug_io_fn、unplug_io_data
2.2 Kernel-2.6.32
在2.6.32內核中其定義如下:
struct backing_dev_info {
struct list_head bdi_list;
struct rcu_head rcu_head;
unsigned long ra_pages; /* maxreadahead in PAGE_CACHE_SIZE units */
unsigned long state; /* Always use atomic bitops on this */
unsigned int capabilities; /*Device capabilities */
congested_fn *congested_fn; /* Functionpointer if device is md/dm */
void *congested_data; /* Pointer to aux data for congested func */
void (*unplug_io_fn)(structbacking_dev_info *, struct page *);
void *unplug_io_data;
char *name; /* device_name */
struct percpu_counterbdi_stat[NR_BDI_STAT_ITEMS];
struct prop_local_percpucompletions;
int dirty_exceeded;
unsigned int min_ratio;
unsigned int max_ratio, max_prop_frac;
struct bdi_writeback wb; /* default writeback info for this bdi */
spinlock_t wb_lock; /* protects update side of wb_list */
struct list_head wb_list; /* theflusher threads hanging off this bdi */
struct list_head work_list;
struct device *dev;
#ifdef CONFIG_DEBUG_FS
struct dentry *debug_dir;
struct dentry *debug_stats;
#endif
};
其對應的state狀態有:
enum bdi_state {
BDI_pending, /* On its way to being activated */
BDI_wb_alloc, /* Default embedded wb allocated */
BDI_async_congested, /* Theasync (write) queue is getting full */
BDI_sync_congested, /* Thesync queue is getting full */
BDI_registered, /* bdi_register() was done */
BDI_unused, /* Available bits start here */
};
用於sys接口計數統計的數值:
enum bdi_stat_item {
BDI_RECLAIMABLE,
BDI_WRITEBACK,
NR_BDI_STAT_ITEMS
};
其初始化在:
a) 函數blk_alloc_queue_node:初始化ra_pages、state、capabilities、unplug_io_fn、unplug_io_data、name
b) 函數blk_alloc_queue_node-->bdi_init:初始化其他的值
2.3 總結
Kernel2.6.32想比kernel-2.6.18來說,在此結構中增加了一些與下刷有關的內容,也就是說在kernel2.6.32中,下刷相關的已經整合到BDI機制中。
用於描述設備bdi狀態的信息,也略微做了調整:
1. 用於讀寫隊列擁塞判斷的標記只是名稱改變了;
2. 由於下刷線程的調整,使得標記BDI_pdflush的功能已經變了,從名稱改爲BDI_pending也可以看出:
a) 在2.6.18中pdflush線程爲全局的,在下刷哪個設備,可以給哪個設備設置上BDI_pdflush標記用來表示該設備正處於下刷中;
b) 在2.6.32中,flush線程因爲整合到bdi機制中,每個設備有一個backing_dev_info結構,必然會導致每個設備一個flush線程,那麼當這個設備處於下刷時,可以設置BDI_pending標記
3. 在2.6.18基礎上,擴充了新的標記BDI_wb_alloc和BDI_registered,前者表示該設備上已經申請出一個下刷任務,後者表示該設備已經創建了flush線程。
由於backing_dev_info結構中描述每個設備的一些bdi狀態信息,BDI中提供了一個sys接口,在/sys/kernel/debug/bdi目錄下,每個設備以設備號區分,可以查看每個設備與bdi有關的狀態等。
瞭解了2.6.32中下刷線程的改變,下面就以IO路徑做爲入口點開始調研。
3 寫流程
對比以上2個流程可以看到:
1. 相同點:整體流程都一樣,部分不一樣的地方,也只是函數封裝的不同,實現上都一樣;
2. 不同點:在喚醒下刷髒數據的線程時,2.6.18調用pdflush線程,2.6.32調用的是bdi的workqueue。
從以上的不同點可以看出,在2.6.32內核中,下刷的工作已經交給了BDI機制。那麼下面繼續看兩種內核中下刷線程是如何初始化以及下刷時機上有什麼區別。
4 下刷線程初始化
4.1 Kernel-2.6.18
下刷線程初始化函數在mm/pdflush.c文件中。
在此文件中定義瞭如下兩個宏:
1. MIN_PDFLUSH_THREADS 2
2. MAX_PDFLUSH_THREADS 8
初始化時,創建了MIN_PDFLUSH_THREADS個名爲pdflush的線程,然後將線程加入鏈表pdflush_list中,線程執行函數爲pdflush。
這裏定義了結構pdflush_work來描述每一個pdflush線程:
struct pdflush_work {
structtask_struct *who; /* The thread */
void(*fn)(unsigned long); /* A callbackfunction */
unsignedlong arg0; /* Anargument to the callback */
structlist_head list; /* Onpdflush_list, when idle */
unsignedlong when_i_went_to_sleep;
};
在執行函數pdflush中,會有以下部分檢測:
if (jiffies - last_empty_jifs > 1 *HZ) {
/* unlocked list_empty() test is OK here*/
if(list_empty(&pdflush_list)) {
/* unlocked test is OK here */
if(nr_pdflush_threads < MAX_PDFLUSH_THREADS)
start_one_pdflush_thread();
}
}
上面代碼說明,如果在上次pdflush線程被喚醒時,導致了鏈表pdflush_list爲空,本次線程被喚醒舉例上次鏈表爲空的時間大於1s,且鏈表依然爲空,那麼可以主動再創建一個pdflush線程。從判斷上來看,最多可以創建8個pdflush線程。
每次下刷任務完成後,有如下判斷:
if(list_empty(&pdflush_list))
continue;
if (nr_pdflush_threads <=MIN_PDFLUSH_THREADS)
continue;
pdf = list_entry(pdflush_list.prev,struct pdflush_work, list);
if (jiffies -pdf->when_i_went_to_sleep > 1 * HZ) {
/* Limit exit rate */
pdf->when_i_went_to_sleep = jiffies;
break; /* exeunt */
}
如果線程數大於2,並且當前線程距離上次被喚醒處理下刷任務的時間已經大於1s,那麼可以認定當前下刷線程是比較空閒的,可以銷燬。也就是說,當有寫IO時,系統中最多時可以喚醒8個pdflush,當空閒時,系統只保留2個pdflush。
4.2 Kernel-2.6.32
4.2.1 系統啓動時
在kernel啓動時,會加載BDI模塊,代碼定義在文件mm/backing-dev.c中:
1. 創建名爲sync_supers的線程,此線程由定時器來喚醒,此外無其他喚醒模式。每5s鐘被喚醒執行一次函數sync_supers,用來下刷系統super_blocks鏈表中所有的元數據塊信息。
2. 定義了一個默認的結構backing_dev_info,同時會創建一個線程bdi-default,此線程執行函數爲bdi_forker_task。
a) 此函數爲一個死循環,中途無任何退出條件。
b) 當有新的設備被創建時:
i. 會爲每個設備定義一個結構backing_dev_info,然後將此結構掛到bdi_list鏈表尾;
ii. bdi-default線程會從bdi_list鏈表中獲取每個設備的結構bdi信息,將之從bdi_list鏈表中刪除,再加入bdi_pending_list;
iii. 然後從bdi_pending_list鏈表中獲取每個設備的bdi信息,同時將此設備的bdi信息從bdi_pending_list鏈表中刪除;
iv. 爲每個設備創建一個下刷線程“flush-主設備號:次設備號”,描述線程信息的結構爲bdi_writeback,其執行函數爲bdi_start_fn;
v. 當flush線程執行起來後,會再次將設備的bdi信息加入鏈表bdi_list尾,這樣每個設備的flush線程就可以從bdi_list上找到並喚醒了。
c) 當bdi_pending_list鏈表爲空時會睡眠5s,然後再自我喚醒,繼續運行。
下面爲結構bdi_writeback的定義:
struct bdi_writeback {
struct list_head list; /* hangs off the bdi */
struct backing_dev_info *bdi; /* our parent bdi */
unsigned int nr;
unsigned long last_old_flush; /* last old data flush */
struct task_struct *task; /* writeback task */
struct list_head b_dirty; /* dirty inodes */
struct list_head b_io; /* parked for writeback */
structlist_head b_more_io; /* parked for more writeback */
};
4.2.2 設備創建時
當有新設備創建時,每個設備都有自己的gendisk--> request_queue --> backing_dev_info結構,在調用函數block/genhd.c:add_disk函數時,會調用bdi_register_dev函數,爲每個設備申請結構device並初始化此結構信息,最終會調用函數bdi_register,將此設備bdi信息加入鏈表bdi_list中。之後就如同上面一樣,bdi-default線程會爲該設備創建線程flush。
每個設備flush線程的執行函數爲bdi_start_fn,其核心流程在函數bdi_writeback_task內,流程如下:
上面流程中線程空閒時間的判斷,是線程每5s被喚醒時,都沒有髒數據被下刷,則認爲線程已經空閒,當空閒>300HZ,則會退出此函數,即線程被註銷了。從這裏也可以看出每個設備的flush線程並不常存在,只有需要時才創建出來。
5 下刷時機
5.1 Kernel-2.6.18
在2.6.18內核中,有同步下刷和異步下刷兩種情況。
5.1.1 異步下刷
在kernel-2.6.18中,觸發異步下刷流程的函數有pdflush_operation和wakeup_pdflush。
函數mm/pdflush.c:pdflush_operation被調用地方:
1. fs/super.c:emergency_remount --> pdflush_operation(do_emergency_remount, 0)
函數do_emergency_remount真正要做的並不是下刷數據,而是要以只讀的形式remount文件系統,可通過給sysrq發信號調用。
2. fs/buffer.c:emergency_sync --> pdflush_operation(do_sync, 0)
函數do_sync會依次調用以下wakeup_pdflush(0)sync_inodes(0) sync_supers() sync_filesystems(0)等命令,sync模式下刷整個系統的數據,可通過給sysrq發信號調用。
3. mm/page-writeback.c:
a) balance_dirty_pages -->pdflush_operation(background_writeout, 0)
函數background_writeout,正常的下刷函數,上面函數第二個參數表示下刷任務,一般爲0表示此次下刷到水位值,此部分流程同cbd中下刷部分。
b) wakeup_pdflush(nr_pages) -->pdflush_operation(background_writeout, nr_pages)
下刷指定個page的數據塊。
c) wb_timer_fn --> pdflush_operation(wb_kupdate, 0)
定時觸發的下刷方式,每次被觸發後,都會下刷到水位值爲止。默認5s鐘調用一次。
d) laptop_timer_fn -->pdflush_operation(laptop_flush, 0)
函數laptop_flush也是會被定時觸發,此函數會調用do_sync(1)函數,此函數只有在laptop_mode值爲1時會被主動觸發一次,一般在系統停止運行時觸發。
函數mm/page-writeback.c:wakeup_pdflush被調用地方:
1. fs/buffer.c:
a) wakeup_pdflush(0):上面的do_sync函數調用,用於下刷當前所有的髒數據
b) wakeup_pdflush(1024):在函數free_more_memory中調用,當使用函數alloc_page_buffers申請page時,可能會觸發此次下刷,希望先下刷一部分髒數據,以能夠申請更多的page
2. mm/vmscan.c: wakeup_pdflush(laptop_mode ? 0 :total_scanned)
回收流程中觸發的下刷,當前髒數據過多,需要先下刷一部分髒數據才能完成回收任務。
5.1.2 同步下刷
爲了瞭解同步下刷在哪些地方調用,需要注意結構writeback_control的使用地方,
此結構在真正要下刷數據時(包括同步下刷和異步下刷),都會先初始化,用來指導當次以何種方式下刷,下刷多少數據等。
結構定義如下:
struct writeback_control {
struct backing_dev_info*bdi; /* If !NULL, only write back this
queue */
enum writeback_sync_modessync_mode;
unsigned long *older_than_this;/* If !NULL, only write back inodes
older than this */
long nr_to_write; /* Write this many pages, anddecrement
thisfor each page written */
long pages_skipped; /* Pages which were not written */
/*
* Fora_ops->writepages(): is start or end are non-zero then this is
* a hint that thefilesystem need only write out the pages inside that
* byterange. The byte at `end' is included in the writeoutrequest.
*/
loff_t range_start;
loff_t range_end;
unsigned nonblocking:1; /* Don't get stuck on request queues*/
unsigned encountered_congestion:1;/* An output: a queue is full */
unsigned for_kupdate:1; /* A kupdate writeback */
unsigned for_reclaim:1; /* Invoked from the page allocator */
unsigned for_writepages:1; /* This is a writepages() call */
unsigned range_cyclic:1; /* range_start is cyclic */
};
初始化此結構時,有時候只需要使用部分數值。
下面再看看哪些函數初始化了結構writeback_control(只列出幾個核心的函數):
1. 函數mm/vmscan.c::shrink_page_list:回收時,發現某個page爲髒,會試圖以sync模式先將此page下刷下去
2. 函數mm/filemap.c::__filemap_fdatawrite_range:當是direct模式寫數據時,會調用到此函數
3. 函數mm/page-writeback.c::write_one_page:此函數只用於寫一個page,一般也是需要同步的寫數據時調用
4. 函數mm/page-writeback.c::balance_dirty_pages:函數在寫IO路徑中被調用,主要是元數據的寫
5. 函數fs/fs-writeback.c::write_inode_now:此函數多用於元數據的下刷
6. 函數fs/fs-writeback.c::sync_inode_sb:此函數也是用於元數據的下刷
5.2 Kernel-2.6.32
在Kernel-2.6.32中,同樣有同步下刷和異步下刷兩種情況。
5.2.1 異步下刷
使用異步下刷的地方,都定義了結構wb_writeback_work,但是反過來條件並不成立,下面具體來講解。
此結構定義在fs/fs-writeback.c文件中,用來描述一次下刷任務的信息,如該次下刷任務以何種模式下刷,下刷多少數據等,多用在喚醒下刷線程之前定義。
結構信息如下:
struct wb_writeback_work {
long nr_pages;
struct super_block *sb;
enum writeback_sync_modessync_mode;
int for_kupdate:1;
int range_cyclic:1;
int for_background:1;
struct list_head list; /* pending work list */
struct completion *done; /* setif the caller waits */
};
觸發下刷的操作時,可以將此work掛到一個工作隊列中,由下刷線程來完成任務;也可以用此結構的信息,指導結構writeback_control中的值,然後直接調用下刷函數,完成工作任務。
下面可以看看哪些函數初始化了結構wb_writeback_work:
1. 函數__bdi_start_writeback,調用此函數的地方有以下幾處:
a) 函數bdi_start_writeback:此函數沒有被使用;
b) 函數bdi_start_background_writeback:此函數只在之前講的寫流程時的那個位置使用。
c) 函數wakeup_flusher_threads:此函數調用地方只有以下幾處:
i. fs/buffer.c:wakeup_flusher_threads(1024)
同2.6.18一樣,在函數free_more_memory中被調用,在申請page時,有時候想要申請更多的pages,但是當前系統剩餘的page達不到要申請的數值,這個時候就會嘗試調用回收函數,回收部分髒page。
ii. fs/sync.c:wakeup_flusher_threads(0)
函數SYSCALL_DEFINE0(sync),即sys_sync函數中被調用,當執行sync命令時,會被觸發。
iii. mm/page-writeback.c:wakeup_flusher_threads(0)
定時器laptop_timer_fn中調用,此定時器也以任務的形式加入到系統event工作隊列中,當此任務被喚醒時,其會下刷當前檢測到的所有髒數據。在函數blk_finish_request中被調用,當且僅當laptop_mode=1時纔會被觸發。在系統運行中laptop_mode=0,當執行reboot等操作時,此值會變成1。
iv. mm/vmscan.c:wakeup_flusher_threads(laptop_mode ? 0 : total_scanned)
回收線程中調用,觸發時機和2.6.18一樣。
2. 函數wb_check_old_data_flush:
此函數通過函數bdi_start_fn --> bdi_writeback_task --> wb_do_writeback調用,而bdi_start_fn爲線程flush(每個設備有一個)的執行函數 。
3. 函數writeback_inodes_sb_nr:
此函數經過一系列的封裝,在sync_filesystem(執行sync時會調用,不等待元數據下刷完成)和btrfs中函數shrink_delalloc(回收元數據函數)調用,此函數僅限於下刷元數據信息。
4. 函數sync_inodes_sb:
此函數在sync_filesystem(執行sync時會調用,需等待元數據下刷完成)中調用。
說明:上面的函數writeback_inodes_sb_nr和函數sync_inodes_sb雖然初始化了結構wb_writeback_work,但是也只是用來指導當前如何下刷元數據信息而已,其實並不能歸納到異步下刷章節,只是爲了便於講解,才放在這裏。
5.2.2 同步下刷
一般在真正進行下刷數據之前都會定義結構writeback_control,所以此函數在異步下刷和同步下刷真正下刷數據時都會定義,而同步下刷用此結構的地方非常多,所以此章通過查詢哪些函數定義了此結構,來確定哪裏進行了同步下刷操作。
此結構定義在include/linux/writeback.h中,用來指導當前下刷如何進行,多在線程被喚醒後或者需要進行同步下刷時定義。
結構信息如下:
struct writeback_control {
enum writeback_sync_modessync_mode;
unsigned long *older_than_this;/* If !NULL, only write back inodes
older than this */
unsigned long wb_start; /* Time writeback_inodes_wb was
called. This is needed to avoid
extra jobs and livelock */
long nr_to_write; /* Write this many pages, anddecrement
this for each pagewritten */
long pages_skipped; /* Pages which were not written */
/*
* Fora_ops->writepages(): is start or end are non-zero then this is
* a hint that thefilesystem need only write out the pages inside that
* byterange. The byte at `end' is included in the writeoutrequest.
*/
loff_t range_start;
loff_t range_end;
unsigned nonblocking:1; /* Don't get stuck on request queues*/
unsignedencountered_congestion:1; /* An output: a queue is full */
unsigned for_kupdate:1; /* A kupdate writeback */
unsigned for_background:1; /* A background writeback */
unsigned for_reclaim:1; /* Invoked from the page allocator */
unsigned range_cyclic:1; /* range_start is cyclic */
unsigned more_io:1; /* more io to be dispatched */
/* reserved for RedHat */
unsigned long rh_reserved[5];
};
當需要向下層寫數據或者下刷數據時,都可以預先定義此結構,將此結構傳給真正寫數據的函數,下刷函數會根據此結構初始化的值,完成當次寫數據任務。
初始化此結構時,有時候只需要使用部分數值。
下面再看看哪些函數初始化了結構writeback_control(只列出幾個核心的函數):
1. 函數mm/vmscan.c::shrink_page_list:回收時,發現某個page爲髒,會試圖以sync模式先將此page下刷下去
2. 函數mm/filemap.c::__filemap_fdatawrite_range:當是direct模式寫數據時,會調用到此函數
3. 函數mm/page-writeback.c::write_one_page:此函數只用於寫一個page,一般也是需要同步的寫數據時調用
4. 函數mm/page-writeback.c::balance_dirty_pages:函數在寫IO路徑中被調用,主要是元數據的寫
5. 函數fs/fs-writeback.c::write_inode_now:此函數多用於元數據的下刷
6. 函數fs/fs-writeback.c::sync_inode_metadata:此函數也是用於元數據的下刷
7. 函數fs/fs-writeback.c::wb_writeback:此函數被flush線程調用,前面講過
5.3 下刷時機總結
綜合前兩節:
1. 對於元數據,一般都需要直接寫下去,所以這個時候可以直接初始化結構writeback_control,調用writepage等函數將元數據寫下去;
2. 對於一般的寫IO流程、回收線程中的髒數據下刷流程,可以使用pdflush/flush線程完成下刷任務;
3. 相比2.6.18內核,在2.6.32中少了一個定時器的觸發flush線程的機制,不過此部分是以另外一種實現方式來實現了同樣的功能:
a) 在bdi初始化時,創建了一個線程sync_supers,此線程只是每隔5s被定時器喚醒,將當前的元數據下刷到底層磁盤
b) 在flush線程中,函數bdi_writeback_task和wb_check_old_data_flush中均有對下刷時間的判斷:
i. 函數wb_check_old_data_flush中,如果此次下刷距離上次下刷不足5s,本次直接退出
ii. 函數bdi_writeback_task中,每隔5s,會自動進行一次下刷操作;另外,還會檢測如果長達60s的時間,都沒有髒數據被下刷,此函數會退出(意味着flush線程將被註銷)。
6 下刷方式
6.1 Kernel-2.6.18
1. 對於元數據下刷、某個page的下刷,都是可以通過page或者inode找到要下刷的設備或者文件等;
2. 由水位觸發的下刷、回收時觸發的下刷,是通過調用函數wakeup_pdflush,此函數並沒有指定下刷哪個設備,而是先從全局鏈表super_blocks中獲取一個structsuper_block *sb,再在此結構中定義的鏈表sb->s_dirty和sb->s_io中查找下刷的數據,當任務完成,下刷任務終止。
說明:上面的實現邏輯可以看出當下刷某個設備的髒數據時,該設備上的髒數據沒有下刷完畢,是不會下刷下一個設備的髒數據的,但是系統中又設計了2-8個pdflush,避免下刷任務在一個設備上停留很長時間;從設計上看,一次最多可以同時下刷8個設備的髒數據。
6.2 Kernel-2.6.32
1. 對於元數據下刷、某個page的下刷,都是可以通過page或者inode找到要下刷的設備或者文件等,最終都可以找到其backing_dev_info結構。
2. 由水位觸發的下刷、回收時觸發的下刷,是通過調用函數wakeup_flusher_threads,此函數並沒有指定下刷哪個設備,而且通過查找mm/backing-dev.c中定義的全局鏈表bdi_list,找到鏈表上的每一個結構bdi,然後才逐個的喚醒每個設備上的flush線程進行下刷。
在前言部分提出瞭如下問題,在bwraid中當底層設備上還有asd、bwcache、cbd設備時,系統如何處理這些設備的下刷?
前面也已經提到,當新創建一個設備時,就會將設備對應的bdi信息掛到全局鏈表bdi_list尾,而在調用下刷函數wakeup_flusher_threads時,則是從bdi_list鏈表的頭逐個喚醒設備的flush線程,所以同一時刻,多個設備可能同時進行髒數據下刷的操作。
7 下刷流程
在這裏所說的流程主要是說writepages函數是如何實現的。單獨列出此函數是因爲在kernel-2.6.18和kernel-2.6.32有一點小區別,且這個函數類似cbd裏的destage_blocks函數,主要就是做下刷工作的。
7.1 Kernel-2.6.18
在文件fs/block_dev.c中writepages函數被註冊爲generic_writepages函數,然後調用函數mpage_writepages完成一次下刷任務。
函數mpage_writepages輸入參數有:
1. struct address_space *mapping:和cbd中的cache_space結構類似;
2. struct writeback_control *wbc:描述一次下刷任務、下刷模式的結構,同cbd中的writeback_ctrl結構;
3. get_block_t get_block:專門爲文件系統(如:ext2,hfs,jfs,fat等)提供的一個函數指針,用於在下刷流程中執行特定的操作。本文以塊設備爲例,此參數傳入爲NULL,如果爲NULL,在下刷某一個page時,就會調用a_ops->writepage(塊設備中定義爲函數blkdev_writepage)。
函數mpage_writepages流程如下:
說明:在while循環中,上來先設置scanned = 1,使得在while循環結束後,一定會退出此次下刷任務。
7.2 Kernel-2.6.32
在文件fs/block_dev.c中writepages函數被註冊爲generic_writepages函數,然後調用函數write_cache_pages完成一次下刷任務。
函數write_cache_pages支持的傳入參數有:
1. struct address_space *mapping:和cbd中的chache_space結構類似;
2. struct writeback_control *wbc:描述一次下刷任務、下刷模式的結構,同cbd中的writeback_ctrl結構;
3. writepage_t writepage:可以傳入用來下刷一個page的函數,在這裏傳入的函數爲__writepage;
4. void *data:可以定義一個自己的指針,做爲函數__writepage其中一個參數,在函數__writepage中最終會調用mapping->a_ops->writepage(page, wbc) (塊設備中定義爲函數blkdev_writepage)。
函數write_cache_pages流程如下:
說明:
1. 取消了寫阻塞的判斷
2. 當sync模式下刷時,可能會刷到radix tree尾部之後再次從頭部開始查找髒塊繼續刷一遍
7.3 下刷流程總結
兩個內核下刷流程的對比,在kernel-2.6.32中,髒數據每次下刷不會判斷寫阻塞,而且其可能還會從raidix tree中查找兩遍髒數據。一次下刷任務,比kernel-2.6.18下刷更多的數據。
8 總結
通過以上的調研可以知道,在2.6.32內核BDI機制中,在其功能基礎上增加了髒數據下刷的部分,其管理着系統中設備的元數據的下刷、髒數據的下刷等。
相比2.6.18的全局pdflush,在2.6.32中每個設備都有一個flush線程,當有多個設備都有寫IO時,2.6.32內核中下刷的效率就比2.6.18高,髒數據能夠被更快的被下刷下去。
當底層磁盤處理io的速度非常高時,加大髒數據的下刷,可以使更多的寫IO能夠被及時的處理。但是在某些應用中,這麼高效率的下刷髒數據,使得下刷髒數據不能進行更長的合併,下刷的髒數據就不夠長。每個設備都在下刷數據時,也會造成磁盤更大的抖動。