Chinaunix首页 | 论坛 | 博客
  • 博客访问: 306522
  • 博文数量: 72
  • 博客积分: 2580
  • 博客等级: 少校
  • 技术积分: 675
  • 用 户 组: 普通用户
  • 注册时间: 2009-11-07 17:36
文章分类

全部博文(72)

文章存档

2012年(7)

2011年(17)

2010年(46)

2009年(2)

分类: LINUX

2011-07-15 17:40:36

由于页高速缓存的缓存作用,写操作实际上会被延迟。当页高速缓存中的数据比后台存储的数据更新时,那么该数据就被称为脏数据。在内存中积累起来的页最终必须被写回磁盘。在以下两种情况发生时,脏页被写回磁盘:

1. 当空闲的内存低于一个特定的阈值时,内核必须将脏页写回磁盘,以便释放内存。

2. 当脏页在内存中驻留时间超过一个特定的阈值时,内核必须将超时的脏页写回磁盘,以确保脏页不会无限期地驻留在内存。

   在老内核中,这是由两个独立的内核线程分别完成。但是在2.6版本中,由一组内核线程统一执行这两种工作---pdflush后回写线程。这两个目标是如何实现的?

   首先,pdflush线程在系统中的空闲内存低于一个阈值时,将脏页刷新回磁盘。该后台回写例程的目的在于在可用物理内存过低时,释放脏页以重新获得内 存。特定的内存与之可以通过dirty_background_ratio sysctl系统调用设置(看kernel/Sysctl.c)。

  

  1. 在mm/Page_writeback.c中
  2. /* Start background writeback (via pdflush) at this percentage
  3.  */
  4. int dirty_background_ratio = 10;

   当空闲内存比阈值dirty_background_ratio还低时,内核便会调用函数wakeup_bdflush()唤醒一个pdflush线程,随后pdflush线程进一步调用函数background_writeout()开始将脏页写回磁盘。该函数的参数指定试图写回的页面数目,该函数会连续的写出数据,直到满足一下两个条件:

1. 已经有指定的最小数据的页被写出到磁盘

2. 空闲内存数已经回升,超过阈值dirty_background_ratio

  1. 在mm/Page-writeback.c中
  2. /*
  3.  * writeback at least _min_pages, and keep writing until the amount of dirty
  4.  * memory is less than the background threshold, or until we're all clean.
  5.  */
  6. static void background_writeout(unsigned long _min_pages)
  7. {
  8.     long min_pages = _min_pages;
  9.     struct writeback_control wbc = {
  10.         .bdi        = NULL,
  11.         .sync_mode  = WB_SYNC_NONE,
  12.         .older_than_this = NULL,
  13.         .nr_to_write    = 0,
  14.         .nonblocking    = 1,
  15.         .range_cyclic   = 1,
  16.     };
  17.     for ( ; ; ) {
  18.         long background_thresh;
  19.         long dirty_thresh;
  20.         get_dirty_limits(&background_thresh, &dirty_thresh, NULL);
  21.         if (global_page_state(NR_FILE_DIRTY) +
  22.             global_page_state(NR_UNSTABLE_NFS) < background_thresh
  23.                 && min_pages <= 0)
  24.             break;
  25.         wbc.encountered_congestion = 0;
  26.         wbc.nr_to_write = MAX_WRITEBACK_PAGES;
  27.         wbc.pages_skipped = 0;
  28.         writeback_inodes(&wbc);
  29.         min_pages -= MAX_WRITEBACK_PAGES - wbc.nr_to_write;
  30.         if (wbc.nr_to_write > 0 || wbc.pages_skipped > 0) {
  31.             /* Wrote less than expected */
  32.             congestion_wait(WRITE, HZ/10);
  33.             if (!wbc.encountered_congestion)
  34.                 break;
  35.         }
  36.     }
  37. }

   pdflush后台例程会被周期唤醒,将那些在内存中驻留时间过长的脏页写出,确保内存中不会有长期存在的脏页。如果系统发生崩溃,由于内存处于混乱中,所以那些在内存中还没来得及写回磁盘的脏页就会丢失,所以周期性同步页高速缓存和磁盘非常重要。

  在系统启动时,内核初始化一个定时器,让它周期地唤醒pdflush线程,随后使其运行函数wb_kupdate()。该函数将把所有驻留时间超过百分之 dirty_expire_centisecs秒的脏页写回。然后定时器将再次被初始化为百分之dirty_expire_centisecs秒后唤醒 pdflush线程。

  1. 在mm/Page-writeback.c中
  2. /*
  3.  * Periodic writeback of "old" data.
  4.  *
  5.  * Define "old": the first time one of an inode's pages is dirtied, we mark the
  6.  * dirtying-time in the inode's address_space.  So this periodic writeback code
  7.  * just walks the superblock inode list, writing back any inodes which are
  8.  * older than a specific point in time.
  9.  *
  10.  * Try to run once per dirty_writeback_interval.  But if a writeback event
  11.  * takes longer than a dirty_writeback_interval interval, then leave a
  12.  * one-second gap.
  13.  *
  14.  * older_than_this takes precedence over nr_to_write.  So we'll only write back
  15.  * all dirty pages if they are all attached to "old" mappings.
  16.  */
  17. static void wb_kupdate(unsigned long arg)
  18. {
  19.     unsigned long oldest_jif;
  20.     unsigned long start_jif;
  21.     unsigned long next_jif;
  22.     long nr_to_write;
  23.     struct writeback_control wbc = {
  24.         .bdi        = NULL,
  25.         .sync_mode  = WB_SYNC_NONE,
  26.         .older_than_this = &oldest_jif,
  27.         .nr_to_write    = 0,
  28.         .nonblocking    = 1,
  29.         .for_kupdate    = 1,
  30.         .range_cyclic   = 1,
  31.     };
  32.     sync_supers();
  33.     oldest_jif = jiffies - dirty_expire_interval;
  34.     start_jif = jiffies;
  35.     next_jif = start_jif + dirty_writeback_interval;
  36.     nr_to_write = global_page_state(NR_FILE_DIRTY) +
  37.             global_page_state(NR_UNSTABLE_NFS) +
  38.             (inodes_stat.nr_inodes - inodes_stat.nr_unused);
  39.     while (nr_to_write > 0) {
  40.         wbc.encountered_congestion = 0;
  41.         wbc.nr_to_write = MAX_WRITEBACK_PAGES;
  42.         writeback_inodes(&wbc);
  43.         if (wbc.nr_to_write > 0) {
  44.             if (wbc.encountered_congestion)
  45.                 congestion_wait(WRITE, HZ/10);
  46.             else
  47.                 break;  /* All the old data is written */
  48.         }
  49.         nr_to_write -= MAX_WRITEBACK_PAGES - wbc.nr_to_write;
  50.     }
  51.     if (time_before(next_jif, jiffies + HZ))
  52.         next_jif = jiffies + HZ;
  53.     if (dirty_writeback_interval)
  54.         mod_timer(&wb_timer, next_jif);
  55. }

   总而言之,pdflush线程周期地被唤醒并把超过特定期限的脏页写回磁盘。

   系统管理员可以在/proc/sys/vm中设置回写的相关参数,也可以通过sysctl系统调用设置它们。

  pdflush线程的实现代码在文件mm/pdflush.c中,回写机制的实现代码在文件mm/page-writeback.c和fs-writeback.c中。

 pdflush设置

变量 描述
dirty_expire_centisecs 该数值以百分之一秒为单位,它描述超时多久的数据将被周期性执行的pdflush线程写出
dirty_ratio 占全部内存百分比,当一个进程产生的脏页达到这个比例时,就开始被写出
dirty_writeback_centisecs 该数值以百分之一秒为单位,它描述pdflush线程的运行频率
laptop_mode 一个布尔值,用于控制膝上型电脑模式
dirty_background_ratio 占全部内存的百分比。当内存中空闲页到达这个比例时,pdflush线程开始回写脏页

 

  • 膝上型电脑模式

  膝上型电脑模式是一种特殊的页回写策略,该策略主要意图是将硬盘转动的机械行为最小化,允许硬盘尽可能长时间的停滞,以此延长电池供电时间。该模式可通过 /proc/sys/vm/laptop_mode文件进行配置。通常,该配置文件内容为0,即膝上型电脑模式关闭,写1则启用该模式。

  该模式的页写回行为与传统方式相比只有一处变化。除了当缓存中的页面太旧要执行回写脏页以外,pdflush还好找准磁盘运转的实际,把所有其他的物理磁盘IO、刷新脏换成等统统写回磁盘,以便保证不会专门为了写磁盘而去主动激活磁盘运行。

   所述Linux发布版会在电脑接上或拔下电池时,自动开启或禁止膝上型电脑模式及其需要的pdflush可调节开关。因此机器可在使用电池电源时自动进入膝上型电脑模式,而在插上交流电源时恢复到常规的页回写模式。

  • 避免拥塞的方法:使用多线程

  因为磁盘的吞吐量有限,如果只有惟一线程执行回写操作,那么这个线程很容易等待对一个磁盘上的操作。为了避免出现这样的情况,内核需要多个回写线程并发执行,这样单个设备队列的拥塞就不会称为系统的瓶颈了。

   2.6内核使用多个pdflush线程,每个线程可以相互独立地将脏页刷新回磁盘,而且不同的pdflush线程处理不同的设备队列。线程的数目可以根据 系统的运行时间进行调整。pdflush线程数量取决于页回写的数量和拥塞情况,动态调整。如果所有存在的pdflush线程都忙着写回数据,那么一个新 线程就会被创建,确保不会出现一个设备队列处于拥塞状态,而其他设备队列却在等待---不是接收--回写数据的情况。如果堵塞,pdflush线程的数量 便会自动减少,以便节约内存。

   为了避免每一pdflush线程都挂起在同一个堵塞的队列上,pdflush线程利用了拥塞避免策略,它们会积极的试图写回那些不属于拥塞队列的页面。这样一来,pdflush线程通过分派回写工作,阻止多个线程在同一个忙设备上纠缠。

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