Linuxのページキャッシュのwrite back処理概要

# はじめに

ページキャッシュのwrite backを行うまでの流れに関して記憶があいまいであったため、ソースコードを確認した。せっかくなので、メモをする。
参照したLinuxカーネルのバージョンは5.10.20である。

# ページキャッシュにwriteするまでの流れ
## write()システムコールからたどる

Buffered I/Oがどのようになっているのか、writeから読んでいく。


```c:fs/read_write.c
SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf, 
        size_t, count)
{
    return ksys_write(fd, buf, count);
}
```

```c:fs/read_write.c
ssize_t vfs_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
{
// 略
     if (file->f_op->write)
         ret = file->f_op->write(file, buf, count, pos);
     else if (file->f_op->write_iter)
         ret = new_sync_write(file, buf, count, pos);
     else
         ret = -EINVAL;
```

この文書では、ファイルシステムはEXT4を題材にしてコードを読むこととしたい。file_operations構造体は以下のようになっており、writeハンドラはセットされない。

```c:fs/ext4/file.c
const struct file_operations ext4_file_operations = {
    .llseek     = ext4_llseek,
    .read_iter  = ext4_file_read_iter,
    .write_iter = ext4_file_write_iter,
```

よって、今回の場合、new_sync_write()が呼ばれることになる。new_sync_write()は、本質的にはext4_file_write_iter()を呼び出すための関数である。

```c:fs/ext4/file.c
static ssize_t
ext4_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
{
    struct inode *inode = file_inode(iocb->ki_filp);

    if (unlikely(ext4_forced_shutdown(EXT4_SB(inode->i_sb))))
        return -EIO;

#ifdef CONFIG_FS_DAX
    if (IS_DAX(inode))
        return ext4_dax_write_iter(iocb, from);
#endif
    if (iocb->ki_flags & IOCB_DIRECT)
        return ext4_dio_write_iter(iocb, from);
    else
        return ext4_buffered_write_iter(iocb, from);
}
```

今回は、ページキャッシュのwrite backの概要を知りたいので、ext4_buffered_write_iter()を読む。

```c:fs/ext4/file.c
static ssize_t ext4_buffered_write_iter(struct kiocb *iocb,
                    struct iov_iter *from)
{
// 略
    ret = generic_perform_write(iocb->ki_filp, from, iocb->ki_pos);
// 略
```
generic_perform_write()は、以下のようになっており、a_ops->write_begin()とa_ops->write_end()に挟まれて、iov_iter_copy_from_user_atomic()が呼び出されている。

```c:mm/filemap.c
ssize_t generic_perform_write(struct file *file,
                struct iov_iter *i, loff_t pos)
{
    struct address_space *mapping = file->f_mapping;
    const struct address_space_operations *a_ops = mapping->a_ops;
// 略
    do {
// 略
        status = a_ops->write_begin(file, mapping, pos, bytes, flags,
                        &page, &fsdata);
        if (unlikely(status < 0))
            break;

        if (mapping_writably_mapped(mapping))
            flush_dcache_page(page);

        copied = iov_iter_copy_from_user_atomic(page, i, offset, bytes);
        flush_dcache_page(page);

        status = a_ops->write_end(file, mapping, pos, bytes, copied,
                        page, fsdata);
// 略
```
上の処理の概要は以下のとおりである。

- a_ops->write_begin()ではページキャッシュを取得する。
- iov_iter_copy_from_user_atomic()では、ユーザ空間から渡された書き込みデータを該当ページキャッシュにコピーする。この時点では、RAMからRAMへのコピーである。
- a->ops->write_end()では、主に該当ページキャッシュの状態がダーティーに遷移したときの処理を行う。実は、この中でwrite backの処理をWorkqueue経由で依頼する。



## write_beginおよびwrite_end
write_begin, write_endについては、一番シンプルそうな関数を選んで読むことにする。

```c:fs/ext4/inode.c
static const struct address_space_operations ext4_aops = {
    .readpage       = ext4_readpage,
    .readahead      = ext4_readahead,
    .writepage      = ext4_writepage,
    .writepages     = ext4_writepages,
    .write_begin        = ext4_write_begin,
    .write_end      = ext4_write_end,
    .set_page_dirty     = ext4_set_page_dirty,
```

### write_begin

write_beginから読む。先に書いたとおり、ページキャッシュの取得をしている。ページキャッシュがない場合、ストレージ(バッキングストア)から読み出してFillするか、0-fillされたページを用意する。いずれにせよ、ページキャッシュとして何らかのメモリページを渡す。

今回は「ページキャッシュのwrite backの概要」が知りたいので、ページキャッシュを取得する処理の詳細は割愛する。

```c
static int ext4_write_begin(struct file *file, struct address_space *mapping,
                loff_t pos, unsigned len, unsigned flags,
                struct page **pagep, void **fsdata)
{
// 略
retry_grab:
    page = grab_cache_page_write_begin(mapping, index, flags);
    if (!page)
        return -ENOMEM;
// 略
#ifdef CONFIG_FS_ENCRYPTION
// 略
#else
    if (ext4_should_dioread_nolock(inode))
        ret = __block_write_begin(page, pos, len,
                      ext4_get_block_unwritten);
    else
        ret = __block_write_begin(page, pos, len, ext4_get_block);
#endif
    if (!ret && ext4_should_journal_data(inode)) {
        ret = ext4_walk_page_buffers(handle, page_buffers(page),
                         from, to, NULL,
                         do_journal_get_write_access);
    }
    
```

### write_end
先に書いたとおり、write_end()では、ダーティな状態への遷移を主に取り扱っている。

```c:fs/ext4/inode.c
static int ext4_write_end(struct file *file,
              struct address_space *mapping,
              loff_t pos, unsigned len, unsigned copied,
              struct page *page, void *fsdata)
{
// 略
    if (inline_data) {
// 略
    } else
        copied = block_write_end(file, mapping, pos,
                     len, copied, page, fsdata);
```
block_write_end()から、__block_commit_write() --> mark_buffer_dirty() --> \_\_mark_inode_dirty()のルートをたどる。

```c:fs/buffer.c
int block_write_end(struct file *file, struct address_space *mapping,
            loff_t pos, unsigned len, unsigned copied,
            struct page *page, void *fsdata)
{          
    // 略
    /* This could be a short (even 0-length) commit */
    __block_commit_write(inode, page, start, start+copied);
	// 略
```

\_\_mark_inode_dirty()は以下のとおりであり、wb_wakeup_delayed()を呼ぶ。

```c:fs/fs-writeback.c
void __mark_inode_dirty(struct inode *inode, int flags)
{
// 略
    if ((inode->i_state & flags) != flags) {
// 略
        if (!was_dirty) {
// 略
            if (wakeup_bdi &&
                (wb->bdi->capabilities & BDI_CAP_WRITEBACK))
                wb_wakeup_delayed(wb);
            return;
        }
    }
// 略
```
また、wb_wakeup_delayed()は以下のようになっており、queue_delayed_work()を通して、dirty_writeback_internal * 10 ミリ秒後に起動するようにWorkqueueに依頼する。

```c:mm/backing-dev.c
void wb_wakeup_delayed(struct bdi_writeback *wb)
{
    unsigned long timeout;

    timeout = msecs_to_jiffies(dirty_writeback_interval * 10);
    spin_lock_bh(&wb->work_lock);
    if (test_bit(WB_registered, &wb->state))
        queue_delayed_work(bdi_wq, &wb->dwork, timeout);
    spin_unlock_bh(&wb->work_lock);
}
```
デフォルトでは、dirty_writeback_internal は500である。よって、500 * 10ミリ秒 = 5秒がデフォルトの遅延時間となる。

```c:mm/page-writeback.c
unsigned int dirty_writeback_interval = 5 * 100; /* centiseconds */
```

# write back処理
先のqueue_delayed_work()で遅延処理されるのは、以下のとおりwb_workfnである。

```c:mm/backing-dev.c
static int wb_init(struct bdi_writeback *wb, struct backing_dev_info *bdi,
           gfp_t gfp)
{
// 略
    INIT_DELAYED_WORK(&wb->dwork, wb_workfn);
// 略
```
wb_workfn()は以下の関数で、通常想定されるルートでは、wb_do_writeback()を呼び出してwrite backを行う。

割愛するが、最終的にはwb_do_writeback()からいくつかの関数呼び出しを経てdo_writepages()が呼ばれ、その中でページャ経由でのWriteが行われる。

```c:fs/fs-writeback.c
void wb_workfn(struct work_struct *work)
{   
    struct bdi_writeback *wb = container_of(to_delayed_work(work),
                        struct bdi_writeback, dwork);
// 略
    current->flags |= PF_SWAPWRITE;

    if (likely(!current_is_workqueue_rescuer() ||
           !test_bit(WB_registered, &wb->state))) {
        /*
         * The normal path.  Keep writing back @wb until its
         * work_list is empty.  Note that this path is also taken
         * if @wb is shutting down even when we're running off the
         * rescuer as work_list needs to be drained.
         */
        do {
            pages_written = wb_do_writeback(wb);
            trace_writeback_pages_written(pages_written);
        } while (!list_empty(&wb->work_list));
    } else {
// 略
    }

    if (!list_empty(&wb->work_list))
        wb_wakeup(wb);
    else if (wb_has_dirty_io(wb) && dirty_writeback_interval)
        wb_wakeup_delayed(wb);

    current->flags &= ~PF_SWAPWRITE;
}
```