zoukankan      html  css  js  c++  java
  • 【转】Linux Writeback机制分析

    1. bdi是什么?

        bdi,即是backing device info的缩写,顾名思义它描述备用存储设备相关描述信息,这在内核代码里用一个结构体backing_dev_info来表示。

        bdi,备用存储设备,简单点说就是能够用来存储数据的设备,而这些设备存储的数据能够保证在计算机电源关闭时也不丢失。这样说来,软盘存储设备、光驱存储设备、USB存储设备、硬盘存储设备都是所谓的备用存储设备(后面都用bdi来指示),而内存显然不是

    2. bdi工作模型  

     相对于内存来说,bdi设备(比如最常见的硬盘存储设备)的读写速度是非常慢的,因此为了提高系统整体性能,Linux系统对bdi设备的读写内容进行了缓冲,那些读写的数据会临时保存在内存里,以避免每次都直接操作bdi设备,但这就需要在一定的时机(比如每隔5秒、脏数据达到的一定的比率等)把它们同步到bdi设备,否则长久的呆在内存里容易丢失(比如机器突然宕机、重启),而进行间隔性同步工作的进程之前名叫pdflush,但后来在Kernel 2.6.2x/3x对此进行了优化改进,产生有多个内核进程,bdi-default、flush-x:y等。

       关于以前的pdflush不再多说,我们这里只讨论bdi-default和flush-x:y,这两个进程(事实上,flush-x:y为多个)的关系为父与子的关系,即bdi-default根据当前的状态Create或Destroy flush-x:y,x为块设备类型,y为此类设备的序号。如有两个TF卡,则分别为:flush-179:0、flush-179:1。

        一般而言,一个Linux系统会挂载很多bdi设备,在bdi设备注册(函数:bdi_register(…))时,这些bdi设备会以链表的形式组织在全局变量bdi_list下,除了一个比较特别的bdi设备以外,它就是default bdi设备(default_backing_dev_info),它除了被加进到bdi_list,还会新建一个bdi-default内核进程,即本文的主角。具体代码如下,我相信你一眼就能注意到kthread_run和list_add_tail_rcu这样的关键代码。

    [cpp] view plaincopy
     
    1. struct backing_dev_info default_backing_dev_info = {  
    2.     .name       = "default",  
    3.     .ra_pages   = VM_MAX_READAHEAD * 1024 / PAGE_CACHE_SIZE,  
    4.     .state      = 0,  
    5.     .capabilities   = BDI_CAP_MAP_COPY,  
    6. };  
    7. EXPORT_SYMBOL_GPL(default_backing_dev_info);  
    [cpp] view plaincopy
     
    1. static inline bool bdi_cap_flush_forker(struct backing_dev_info *bdi)  
    2. {  
    3.     return bdi == &default_backing_dev_info;  
    4. }  
    5.   
    6. int bdi_register(struct backing_dev_info *bdi, struct device *parent,  
    7.         const char *fmt, ...)  
    8. {  
    9.     va_list args;  
    10.     struct device *dev;  
    11.   
    12.     if (bdi->dev)    /* The driver needs to use separate queues per device */  
    13.         return 0;  
    14.   
    15.     va_start(args, fmt);  
    16.     dev = device_create_vargs(bdi_class, parent, MKDEV(0, 0), bdi, fmt, args);  
    17.     va_end(args);  
    18.     if (IS_ERR(dev))  
    19.         return PTR_ERR(dev);  
    20.   
    21.     bdi->dev = dev;  
    22.   
    23.     /* 
    24.      * Just start the forker thread for our default backing_dev_info, 
    25.      * and add other bdi's to the list. They will get a thread created 
    26.      * on-demand when they need it. 
    27.      */  
    28.     if (bdi_cap_flush_forker(bdi)) {  
    29.         struct bdi_writeback *wb = &bdi->wb;  
    30.   
    31.         wb->task = kthread_run(bdi_forker_thread, wb, "bdi-%s",  
    32.                         dev_name(dev));  
    33.         if (IS_ERR(wb->task))  
    34.             return PTR_ERR(wb->task);  
    35.     }  
    36.   
    37.     bdi_debug_register(bdi, dev_name(dev));  
    38.     set_bit(BDI_registered, &bdi->state);  
    39.   
    40.     spin_lock_bh(&bdi_lock);  
    41.     list_add_tail_rcu(&bdi->bdi_list, &bdi_list);  
    42.     spin_unlock_bh(&bdi_lock);  
    43.   
    44.     trace_writeback_bdi_register(bdi);  
    45.     return 0;  
    46. }  
    47. EXPORT_SYMBOL(bdi_register);  


    接着跟进函数bdi_forker_thread,它是bdi-default内核进程的主体:

    [cpp] view plaincopy
     
    1. static int bdi_forker_thread(void *ptr)   
    2. {   
    3. struct bdi_writeback *me = ptr;  
    4.   
    5. current->flags |= PF_SWAPWRITE;   
    6. set_freezable();  
    7.   
    8. /*  
    9. * Our parent may run at a different priority, just set us to normal  
    10. */   
    11. set_user_nice(current, 0);  
    12.   
    13. for (;;) {   
    14. struct task_struct *task = NULL;   
    15. struct backing_dev_info *bdi;   
    16. enum {   
    17. NO_ACTION, /* Nothing to do */   
    18. FORK_THREAD, /* Fork bdi thread */   
    19. KILL_THREAD, /* Kill inactive bdi thread */   
    20. } action = NO_ACTION;  
    21.   
    22. /*  
    23. * Temporary measure, we want to make sure we don't see  
    24. * dirty data on the default backing_dev_info  
    25. */   
    26. if (wb_has_dirty_io(me) || !list_empty(&me->bdi->work_list)) {   
    27. del_timer(&me->wakeup_timer);   
    28. wb_do_writeback(me, 0);   
    29. }  
    30.   
    31. spin_lock_bh(&bdi_lock);   
    32. /*  
    33. * In the following loop we are going to check whether we have  
    34. * some work to do without any synchronization with tasks  
    35. * waking us up to do work for them. Set the task state here  
    36. * so that we don't miss wakeups after verifying conditions.  
    37. */   
    38. set_current_state(TASK_INTERRUPTIBLE);   
    39. /* 遍历所有的bdi对象,检查这些bdi是否存在脏数据,如果有脏数据,那么需要为其fork线程,然后做writeback操作 */   
    40. list_for_each_entry(bdi, &bdi_list, bdi_list) {   
    41. bool have_dirty_io;  
    42.   
    43. if (!bdi_cap_writeback_dirty(bdi) ||   
    44. bdi_cap_flush_forker(bdi))   
    45. continue;  
    46.   
    47. WARN(!test_bit(BDI_registered, &bdi->state),   
    48. "bdi %p/%s is not registered! ", bdi, bdi->name);   
    49. /* 检查是否存在脏数据 */   
    50. have_dirty_io = !list_empty(&bdi->work_list) ||   
    51. wb_has_dirty_io(&bdi->wb);  
    52.   
    53. /*  
    54. * If the bdi has work to do, but the thread does not  
    55. * exist - create it.  
    56. */   
    57. if (!bdi->wb.task && have_dirty_io) {   
    58. /*  
    59. * Set the pending bit - if someone will try to  
    60. * unregister this bdi - it'll wait on this bit.  
    61. */   
    62. /* 如果有脏数据,并且不存在线程,那么接下来做线程的FORK操作 */   
    63. set_bit(BDI_pending, &bdi->state);   
    64. action = FORK_THREAD;   
    65. break;   
    66. }  
    67.   
    68. spin_lock(&bdi->wb_lock);  
    69.   
    70. /*  
    71. * If there is no work to do and the bdi thread was  
    72. * inactive long enough - kill it. The wb_lock is taken  
    73. * to make sure no-one adds more work to this bdi and  
    74. * wakes the bdi thread up.  
    75. */   
    76. /* 如果一个bdi长时间没有脏数据,那么执行线程的KILL操作,结束掉该bdi对应的writeback线程 */   
    77. if (bdi->wb.task && !have_dirty_io &&   
    78. time_after(jiffies, bdi->wb.last_active +   
    79. bdi_longest_inactive())) {   
    80. task = bdi->wb.task;   
    81. bdi->wb.task = NULL;   
    82. spin_unlock(&bdi->wb_lock);   
    83. set_bit(BDI_pending, &bdi->state);   
    84. action = KILL_THREAD;   
    85. break;   
    86. }   
    87. spin_unlock(&bdi->wb_lock);   
    88. }   
    89. spin_unlock_bh(&bdi_lock);  
    90.   
    91. /* Keep working if default bdi still has things to do */   
    92. if (!list_empty(&me->bdi->work_list))   
    93. __set_current_state(TASK_RUNNING);   
    94. /* 执行线程的FORK和KILL操作 */   
    95. switch (action) {   
    96. case FORK_THREAD:   
    97. /* FORK一个bdi_writeback_thread线程,该线程的名字为flush-major:minor */   
    98. __set_current_state(TASK_RUNNING);   
    99. task = kthread_create(bdi_writeback_thread, &bdi->wb,   
    100. "flush-%s", dev_name(bdi->dev));   
    101. if (IS_ERR(task)) {   
    102. /*  
    103. * If thread creation fails, force writeout of  
    104. * the bdi from the thread. Hopefully 1024 is  
    105. * large enough for efficient IO.  
    106. */   
    107. writeback_inodes_wb(&bdi->wb, 1024,   
    108. WB_REASON_FORKER_THREAD);   
    109. else {   
    110. /*  
    111. * The spinlock makes sure we do not lose  
    112. * wake-ups when racing with 'bdi_queue_work()'.  
    113. * And as soon as the bdi thread is visible, we  
    114. * can start it.  
    115. */   
    116. spin_lock_bh(&bdi->wb_lock);   
    117. bdi->wb.task = task;   
    118. spin_unlock_bh(&bdi->wb_lock);   
    119. wake_up_process(task);   
    120. }   
    121. bdi_clear_pending(bdi);   
    122. break;  
    123.   
    124. case KILL_THREAD:   
    125. /* KILL一个线程 */   
    126. __set_current_state(TASK_RUNNING);   
    127. kthread_stop(task);   
    128. bdi_clear_pending(bdi);   
    129. break;  
    130.   
    131. case NO_ACTION:   
    132. /* 如果没有可执行的动作,那么调度本线程睡眠一段时间 */   
    133. if (!wb_has_dirty_io(me) || !dirty_writeback_interval)   
    134. /*  
    135. * There are no dirty data. The only thing we  
    136. * should now care about is checking for  
    137. * inactive bdi threads and killing them. Thus,  
    138. * let's sleep for longer time, save energy and  
    139. * be friendly for battery-driven devices.  
    140. */   
    141. schedule_timeout(bdi_longest_inactive());   
    142. else   
    143. schedule_timeout(msecs_to_jiffies(dirty_writeback_interval * 10));   
    144. try_to_freeze();   
    145. break;   
    146. }   
    147. }  
    148.   
    149. return 0;   
    150. }  

    3. bdi相关数据结构

        在bdi数据结构中定义了一个writeback对象,该对象是对writeback内核线程的描述,并且封装了需要处理的inode队列。在bdi数据结构中有一条work_list,该work队列维护了writeback内核线程需要处理的任务。如果该队列上没有work可以处理,那么writeback内核线程将会睡眠等待。

        writeback

        writeback对象封装了内核线程task以及需要处理的inode队列。当page cache/buffer cache需要刷新radix tree上的inode时,可以将该inode挂载到writeback对象的b_dirty队列上,然后唤醒writeback线程。在处理过程中,inode会被移到b_io队列上进行处理。多条链表的方式可以降低多线程之间的资源共享。writeback数据结构具体定义如下:

    [cpp] view plaincopy
     
    1. struct bdi_writeback {   
    2. struct backing_dev_info *bdi; /* our parent bdi */   
    3. unsigned int nr;  
    4.   
    5. unsigned long last_old_flush; /* last old data flush */   
    6. unsigned long last_active; /* last time bdi thread was active */  
    7.   
    8. struct task_struct *task; /* writeback thread */   
    9. struct timer_list wakeup_timer; /* used for delayed bdi thread wakeup */   
    10. struct list_head b_dirty; /* dirty inodes */   
    11. struct list_head b_io; /* parked for writeback */   
    12. struct list_head b_more_io; /* parked for more writeback */   
    13. spinlock_t list_lock; /* protects the b_* lists */   
    14. };  


     

        writeback work

        wb_writeback_work数据结构是对writeback任务的封装,不同的任务可以采用不同的刷新策略。writeback线程的处理对象就是writeback_work。如果writeback_work队列为空,那么内核线程就可以睡眠了。

    Writeback_work的数据结构定义如下:


     

    [cpp] view plaincopy
     
    1. struct wb_writeback_work {   
    2.  long nr_pages;   
    3.  struct super_block *sb; /* superblock对象 */   
    4. unsigned long *older_than_this;   
    5.  enum writeback_sync_modes sync_mode;   
    6.  unsigned int tagged_writepages:1;   
    7.  unsigned int for_kupdate:1;   
    8.  unsigned int range_cyclic:1;   
    9.  unsigned int for_background:1;   
    10.  enum wb_reason reason; /* why was writeback initiated? */  
    11.   
    12.  struct list_head list; /* pending work list,链入bdi-> work_list队列 */   
    13. struct completion *done; /* set if the caller waits,work完成时通知调用者 */   
    14. };  

    4. writeback主要函数分析

       writeback机制的主要函数包括如下两个方面:

       1. 管理bdi对象并且fork相应的writeback内核线程处理cache数据的刷新工作。

       2. writeback内核线程处理函数,实现dirty page的刷新操作

    writeback线程管理

        Linux中有一个内核守护线程,该线程用来管理系统bdi队列,并且负责为block device创建writeback thread。当bdi中有dirty page并且还没有为bdi分配内核线程的时候,bdi_forker_thread程序会为其分配线程资源;当一个writeback线程长时间处于空闲状态时,bdi_forker_thread程序会释放该线程资源。

    转自:http://blog.csdn.net/myarrow/article/details/8918944

  • 相关阅读:
    2009年信息技术十大趋势
    转:ASP.NET MVC 第五个预览版和表单提交场景
    终于在博客园开通了
    与虚拟机Oracle连接出现ora12154问题的解决
    Frame框架页面加载中进度条的实现
    WordPress安装部署
    Xmarks不会关闭了,太好了!
    抠出淘宝的菜单树
    asp.net实现类似MaskTextBox功能
    win2003 64位系统下ODBC连接使用
  • 原文地址:https://www.cnblogs.com/MerlinJ/p/4058694.html
Copyright © 2011-2022 走看看