Linux writeback机制

Linux 采用内存页来缓存磁盘文件内容,从而提高系统整体IO访问性能,这就是我们熟知的pagecache机制,对于进程的一次写文件操作,内核只是简单的把修改写到内存,并把页面标记为脏页,然后直接返回,具体的回写操作,由内核周期性的启动线程来完成,这个我们称为writeback机制。

1 脏页的产生

修改文件内容和属性都会产生脏页,当进程调用write进行写操作时,最终会通过set_page_dirty函数标记脏页,并唤醒内核后台回写线程。

如果进程只写了page中的一部分,是否有必要回写整个页了?当然是没必要的,我们只需要标记page中的某个block为脏页就可以了,所以脏页的标记有两种模式

自上往下标记

当一个page都是脏页时,会同步标记所有的buffer_head为dirty状态。

set_page_dirty-> /*标记整个page为dirty*

__set_page_dirty_buffers-> /*把page对应的buffer_head标记为dirty*/

__set_page_dirty-> /*标记page为dirty*/

__mark_inode_dirty /*标记inode为dirty*/

自下往上标记

当一个page的部分buffer_head为dirty状态时,没必要回写所有的buffer_head

mark_buffer_dirty-> /*标记单个buffer为dirty*/

__set_page_dirty-> /*标记page为dirty*/

__mark_inode_dirty /*标记inode为dirty*/

2 writeback回写时机

既然数据是异步落盘,那么内核必须要在适当的时机,把内存中的数据写回磁盘,内核总体有五种情况会触发回写:

1)当系统显示执行sync操作,或者进程调用fsync系统调用时,强制脏数据落盘;

2)内核周期性(for_kupdate 5秒)的启动回写线程(wb_workfn,dirty_writeback_centisecs = 500),回刷驻留时间超过dirty_expire_centisecs(3000)30秒的脏页。

3) 内核后台(for_background)检查脏页比例达到系统可用内存的vm.dirty_background_ratio(10%)时,就会回写时间超过dirty_expire_centisecs的脏页;此时业务进程写脏页仍然不受影响;

4)当进程write数据时,检查脏页比例达到系统可用内存的dirty_ratio(20%)时,阻塞当前写进程,然后进行脏页平衡(balance_dirty_pages_ratelimited),唤醒后台回写进程,回写时间超过dirty_expire_centisecs的脏页

5)内存紧张时,业务进程申请内存触发direct reclaim,会直接唤醒kworker线程(wakeup_flusher_threads)

6) 最后一种回写触发时保证dirtytime类型的inode能够被回写,一般要12小时触发一次kworker线程

3.数据结构与初始化

每块磁盘对应着一个BDI设备,用struct backing_dev_info表示,系统上所有BDI设备通过struct list_head bdi_list链表串在一起,一个BDI设备对应一个struct bdi_writeback结构,存放该设备需要处理的脏页及脏页回写函数,而一次具体的回写操作由wb_writeback_work表示。

3.1数据结构关系

1c9b86b10abc7ea8384ff96662d62f06.png

3.2 注册和初始化

bdi在add_disk()函数中进行注册,以链表的形式组织到全局变量bdi_list中;bdi初始化过程中会创建名为writeback的bdi_wq,提供回写的kworker;request queue分配过程中会初始化bdi;

staticint __init default_bdi_init(void)

{

bdi_wq = alloc_workqueue("writeback", WQ_MEM_RECLAIM | WQ_FREEZABLE |

WQ_UNBOUND | WQ_SYSFS, 0);

}

每个request_queue队列对应一个BDI设备

blk_mq_init_queue->blk_alloc_queue_node

struct request_queue *blk_alloc_queue_node(gfp_t gfp_mask, int node_id)

{

q->backing_dev_info = bdi_alloc_node(gfp_mask, node_id);

err = bdi_init(&q->backing_dev_info);

}

回写线程初始化

static int wb_init(struct bdi_writeback *wb, struct backing_dev_info *bdi,

int blkcg_id, gfp_t gfp)

{

wb->bdi = bdi;

/* 脏inode链表*/

INIT_LIST_HEAD(&wb->b_dirty);

/*超过dirty_expire_centisecs时间的inode链表,回写进程直接从b_io取脏页回写 */

INIT_LIST_HEAD(&wb->b_io);

/* 更多需要被回写的inode链表*/

INIT_LIST_HEAD(&wb->b_more_io);

/* time被修改的inode链表*/

INIT_LIST_HEAD(&wb->b_dirty_time);

/* 待回写work链表*/

INIT_LIST_HEAD(&wb->work_list);

/* 回写work线程*/

INIT_DELAYED_WORK(&wb->dwork, wb_workfn);

wb->dirty_sleep = jiffies;

}

4.回写线程wb_workfn分析

函数调用层级关系

a41f4a8da8c6b63a196491db581da635.png

五种链表,三种模式

五种链表

bdi_writeback结构体中有5个关键链表,实现了脏页的周期性回写

work_list: BDI设备需要高优先级回写的任务

b_dirty:mark_inode_dirty标记的脏inode直接链接到b_dirty

b_bio:回写线程需要回刷脏页时,会从b_ditry链表截取满足回写模式的超时要求的inode到b_bio链表

b_more_io:当inode的状态与work回写模式不匹配时,会先将当前回写的inode链表挂入b_more_io

b_dirty_time: 需要修改meta信息的inode链表

464601eebd1a855fba3d05d324de24b2.png

回写模式

从函数调用上看,可以把理解有三种回写模式

for_kupdate:周期性的回刷(5秒),会把dirty 时间超过30s的inode进行回写(b_dirty->b_bio)

for_background: 当系统的dirty page超过dirtyable memory的dirty_background_ratio(10%)时,开始backgound writeback,当系统的dirty page低于dirtyable memory的10%时,停止回写。这里dirtyable memory(最大可变成脏页的内存数)=total_free_page + inactive_file + active_file - totalreserve_pages

writeback_work回刷:如果非回刷线程(比如sync)需要回写数据,可以构造bdi_writeback_work,回刷线程会优先回刷,且不会检查inode的dirty时间。

kupdate保证dirty page能够及时回写,避免数据丢失风险,而background模式则保证系统总的dirty page保持在一定阈值以下。

代码分析

1.wb_workfn 线程

void wb_workfn(struct work_struct *work)
{
    struct bdi_writeback *wb = container_of(to_delayed_work(work),
                        struct bdi_writeback, dwork);

    set_worker_desc("flush-%s", dev_name(wb->bdi->dev));
    current->flags |= PF_SWAPWRITE;

    if (likely(!current_is_workqueue_rescuer() ||
           !test_bit(WB_registered, &wb->state))) {
        do { 
            /* 调用wb_do_writeback回写*/
            pages_written = wb_do_writeback(wb);
            trace_writeback_pages_written(pages_written);
        } while (!list_empty(&wb->work_list));
    }    
    /*最后检测一次work_list,work_list都是高优先级的回刷任务,需要立即执行 */
    if (!list_empty(&wb->work_list))
        wb_wakeup(wb);
    /* 如果wb上有脏页,且使能了周期性的回刷,则启动Delay work进行下一个周期的回刷*/
    else if (wb_has_dirty_io(wb) && dirty_writeback_interval)
        wb_wakeup_delayed(wb);

    current->flags &= ~PF_SWAPWRITE;
}

2.wb_do_writeback

static long wb_do_writeback(struct bdi_writeback *wb)
{
    struct wb_writeback_work *work;
    long wrote = 0;

    set_bit(WB_writeback_running, &wb->state);
    /* 优先处理work_list的回刷任务*/
    while ((work = get_next_work_item(wb)) != NULL) {
        trace_writeback_exec(wb, work);
        wrote += wb_writeback(wb, work);
        finish_writeback_work(wb, work);
    }    

    /*   
     * Check for a flush-everything request
     */
    /*一般是内存回收触发的回刷 */
    wrote += wb_check_start_all(wb);

    /* 检测周期性的回刷*/
    wrote += wb_check_old_data_flush(wb);
    /*检测background回刷 */
    wrote += wb_check_background_flush(wb);
    clear_bit(WB_writeback_running, &wb->state);

    return wrote;
}

3.wb_writeback

static long wb_writeback(struct bdi_writeback *wb,
             struct wb_writeback_work *work)
{
    unsigned long wb_start = jiffies;
    long nr_pages = work->nr_pages;
    oldest_jif = jiffies;
    work->older_than_this = &oldest_jif;
    spin_lock(&wb->list_lock);
    for (;;) {
        /*work定义的page已经回刷完成 */
        if (work->nr_pages <= 0)
            break;
        /*如果work_list上存在高优先级的回刷任务,则停止background和kupdate回刷 */
        if ((work->for_background || work->for_kupdate) &&
            !list_empty(&wb->work_list))
            break;
        /*如果系统dirty page低于dirty_background_ratio定义的值,则结束background回写 */
        if (work->for_background && !wb_over_bg_thresh(wb))
            break;
        /*如果是kupdate回写,则设置inode dirty超时时间为30s */
        if (work->for_kupdate) {
            oldest_jif = jiffies -
                msecs_to_jiffies(dirty_expire_interval * 10);
        } else if (work->for_background)/*background回刷不会检查inode dirty时间,只会检查系统的整体dirty page水位 */
            oldest_jif = jiffies;

        trace_writeback_start(wb, work);
        /*这里根据设置的oldest_jif时间,把inode从b_dirty链表移到b_bio链表,并把属于同一个
        super_block的inode放到一起。b_bio上的inode会立即被回刷
        */
        if (list_empty(&wb->b_io))
            queue_io(wb, work);
        /*先回刷特定super_block的inode*/
        if (work->sb)
            progress = writeback_sb_inodes(work->sb, wb, work);
        else /* 回刷普通的inode*/
            progress = __writeback_inodes_wb(wb, work);
        trace_writeback_written(wb, work);
      

        if (progress)
            continue;

        if (list_empty(&wb->b_more_io))
            break;
        trace_writeback_wait(wb, work);
        inode = wb_inode(wb->b_more_io.prev);
        spin_lock(&inode->i_lock);
        spin_unlock(&wb->list_lock);
        /* This function drops i_lock... */
        调度检测
        inode_sleep_on_writeback(inode);
        spin_lock(&wb->list_lock);
    }
    spin_unlock(&wb->list_lock);

    return nr_pages - work->nr_pages;
}

5 writeback参数调节

这两个参数决定系统dirty page超过多少时开启background回写,同时只能一个生效

dirty_background_bytes

dirty_background_ratio(default for 10%)

这两个参数决定系统dirty page超过多少时开启脏页平衡(),同时只能一个生效

dirty_bytes

dirty_ratio(default for 20%)

dirty_writeback_centisecs(default for 500):回刷线程扫描周期,默认为5秒

dirty_expire_centisecs:脏页回写时间,默认dirty 时间超过30秒就会被回写

dirtytime_expire_seconds:meta信息被修改的inode回写时间,默认为12H一次。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值