免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
1234下一页
最近访问板块 发新帖
查看: 11458 | 回复: 36
打印 上一主题 下一主题

[文件系统] 文件写入过程 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2012-10-09 18:26 |只看该作者 |正序浏览
本帖最后由 blake326 于 2012-10-09 18:29 编辑

3.6 kernel

SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,
                size_t, count)
{
        struct file *file;
        ssize_t ret = -EBADF;
        int fput_needed;

        file = fget_light(fd, &fput_needed);
        if (file) {
                loff_t pos = file_pos_read(file);
                ret = vfs_write(file, buf, count, &pos);
                file_pos_write(file, pos);
                fput_light(file, fput_needed);
        }

        return ret;
}
ssize_t vfs_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
{
        ssize_t ret;

        if (!(file->f_mode & FMODE_WRITE))
                return -EBADF;
        if (!file->f_op || (!file->f_op->write && !file->f_op->aio_write))
                return -EINVAL;
        if (unlikely(!access_ok(VERIFY_READ, buf, count)))
                return -EFAULT;

        ret = rw_verify_area(WRITE, file, pos, count);
        if (ret >= 0) {
                count = ret;
                if (file->f_op->write)
                        ret = file->f_op->write(file, buf, count, pos);
                else
                        ret = do_sync_write(file, buf, count, pos);
                if (ret > 0) {
                        fsnotify_modify(file);
                        add_wchar(current, ret);
                }
                inc_syscw(current);
        }

        return ret;
}
ssize_t do_sync_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos)
{
        struct iovec iov = { .iov_base = (void __user *)buf, .iov_len = len };
        struct kiocb kiocb;
        ssize_t ret;

        init_sync_kiocb(&kiocb, filp);
        kiocb.ki_pos = *ppos;
        kiocb.ki_left = len;
        kiocb.ki_nbytes = len;

        for (; {
                ret = filp->f_op->aio_write(&kiocb, &iov, 1, kiocb.ki_pos);
                if (ret != -EIOCBRETRY)
                        break;
                wait_on_retry_sync_kiocb(&kiocb);
        }

        if (-EIOCBQUEUED == ret)
                ret = wait_on_sync_kiocb(&kiocb);
        *ppos = kiocb.ki_pos;
        return ret;
}
ssize_t generic_file_aio_write(struct kiocb *iocb, const struct iovec *iov,
                unsigned long nr_segs, loff_t pos)
{
        struct file *file = iocb->ki_filp;
        struct inode *inode = file->f_mapping->host;
        ssize_t ret;

        BUG_ON(iocb->ki_pos != pos);

        sb_start_write(inode->i_sb);
        mutex_lock(&inode->i_mutex);
        ret = __generic_file_aio_write(iocb, iov, nr_segs, &iocb->ki_pos);
        mutex_unlock(&inode->i_mutex);

        if (ret > 0 || ret == -EIOCBQUEUED) {
                ssize_t err;

                err = generic_write_sync(file, pos, ret);
                if (err < 0 && ret > 0)
                        ret = err;
        }
        sb_end_write(inode->i_sb);
        return ret;
}

ssize_t __generic_file_aio_write(struct kiocb *iocb, const struct iovec *iov,
                                 unsigned long nr_segs, loff_t *ppos)
{
        struct file *file = iocb->ki_filp;
        struct address_space * mapping = file->f_mapping;
        size_t ocount;                /* original count */
        size_t count;                /* after file limit checks */
        struct inode         *inode = mapping->host;
        loff_t                pos;
        ssize_t                written;
        ssize_t                err;

        ocount = 0;
        err = generic_segment_checks(iov, &nr_segs, &ocount, VERIFY_READ);
        if (err)
                return err;

        count = ocount;
        pos = *ppos;

        /* We can write back this queue in page reclaim */
        current->backing_dev_info = mapping->backing_dev_info;
        written = 0;

        err = generic_write_checks(file, &pos, &count, S_ISBLK(inode->i_mode));
        if (err)
                goto out;

        if (count == 0)
                goto out;

        err = file_remove_suid(file);
        if (err)
                goto out;

        err = file_update_time(file);
        if (err)
                goto out;

        /* coalesce the iovecs and go direct-to-BIO for O_DIRECT */
        if (unlikely(file->f_flags & O_DIRECT)) {
                loff_t endbyte;
                ssize_t written_buffered;

                written = generic_file_direct_write(iocb, iov, &nr_segs, pos,
                                                        ppos, count, ocount);
                if (written < 0 || written == count)
                        goto out;
                /*
                 * direct-io write to a hole: fall through to buffered I/O
                 * for completing the rest of the request.
                 */
                pos += written;
                count -= written;
                written_buffered = generic_file_buffered_write(iocb, iov,
                                                nr_segs, pos, ppos, count,
                                                written);
                /*
                 * If generic_file_buffered_write() retuned a synchronous error
                 * then we want to return the number of bytes which were
                 * direct-written, or the error code if that was zero.  Note
                 * that this differs from normal direct-io semantics, which
                 * will return -EFOO even if some bytes were written.
                 */
                if (written_buffered < 0) {
                        err = written_buffered;
                        goto out;
                }

                /*
                 * We need to ensure that the page cache pages are written to
                 * disk and invalidated to preserve the expected O_DIRECT
                 * semantics.
                 */
                endbyte = pos + written_buffered - written - 1;
                err = filemap_write_and_wait_range(file->f_mapping, pos, endbyte);
                if (err == 0) {
                        written = written_buffered;
                        invalidate_mapping_pages(mapping,
                                                 pos >> PAGE_CACHE_SHIFT,
                                                 endbyte >> PAGE_CACHE_SHIFT);
                } else {
                        /*
                         * We don't know how much we wrote, so just return
                         * the number of bytes which were direct-written
                         */
                }
        } else {
                written = generic_file_buffered_write(iocb, iov, nr_segs,
                                pos, ppos, count, written);
        }
out:
        current->backing_dev_info = NULL;
        return written ? written : err;
}
ssize_t
generic_file_buffered_write(struct kiocb *iocb, const struct iovec *iov,
                unsigned long nr_segs, loff_t pos, loff_t *ppos,
                size_t count, ssize_t written)
{
        struct file *file = iocb->ki_filp;
        ssize_t status;
        struct iov_iter i;

        iov_iter_init(&i, iov, nr_segs, count, written);
        status = generic_perform_write(file, &i, pos);

        if (likely(status >= 0)) {
                written += status;
                *ppos = pos + status;
          }
       
        return written ? written : status;
}
****************************************************************************************
同sys_read一样,开始调用流程都很明了。
generic_perform_write的pos表示要写的文件内偏移,iov_iter->count保存要写的数据大小。根据这两个参数计算出write涉及到的所有page偏移,对每个page分别处理:
1. a_ops->write_begin,查找或者分配相应的page缓存,有必要为这个page的每个block分配buffer_head,有必要还要从等待从磁盘读取这些数据。基本上write_begin起了一个类似于读取磁盘的作用。
2. iov_iter_copy_from_user_atomic通过kmap_atomic临时内核映射page到一个内核地址,然后把用户buf数据拷贝这个地址中去,然后释放映射。pagefault disable/enable作用???
3. a_ops->write_end根据情况更新buffer_head,page的状态,并且调用mark_inode_dirty通知per-bdi内核线程进行回写。
****************************************************************************************
static 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;
        long status = 0;
        ssize_t written = 0;
        unsigned int flags = 0;

        /*
         * Copies from kernel address space cannot fail (NFSD is a big user).
         */
        if (segment_eq(get_fs(), KERNEL_DS))
                flags |= AOP_FLAG_UNINTERRUPTIBLE;

        do {
                struct page *page;
                unsigned long offset;        /* Offset into pagecache page */
                unsigned long bytes;        /* Bytes to write to page */
                size_t copied;                /* Bytes copied from user */
                void *fsdata;

                offset = (pos & (PAGE_CACHE_SIZE - 1));
                bytes = min_t(unsigned long, PAGE_CACHE_SIZE - offset,
                                                iov_iter_count(i));

again:
                /*
                 * Bring in the user page that we will copy from _first_.
                 * Otherwise there's a nasty deadlock on copying from the
                 * same page as we're writing to, without it being marked
                 * up-to-date.
                 *
                 * Not only is this an optimisation, but it is also required
                 * to check that the address is actually valid, when atomic
                 * usercopies are used, below.
                 */
                if (unlikely(iov_iter_fault_in_readable(i, bytes))) {
                        status = -EFAULT;
                        break;
                }

                status = a_ops->write_begin(file, mapping, pos, bytes, flags,
                                                &page, &fsdata);
                if (unlikely(status))
                        break;

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

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

                mark_page_accessed(page);
                status = a_ops->write_end(file, mapping, pos, bytes, copied,
                                                page, fsdata);
                if (unlikely(status < 0))
                        break;
                copied = status;

                cond_resched();

                iov_iter_advance(i, copied);
                if (unlikely(copied == 0)) {
                        /*
                         * If we were unable to copy any data at all, we must
                         * fall back to a single segment length write.
                         *
                         * If we didn't fallback here, we could livelock
                         * because not all segments in the iov can be copied at
                         * once without a pagefault.
                         */
                        bytes = min_t(unsigned long, PAGE_CACHE_SIZE - offset,
                                                iov_iter_single_seg_count(i));
                        goto again;
                }
                pos += copied;
                written += copied;

                balance_dirty_pages_ratelimited(mapping);
                if (fatal_signal_pending(current)) {
                        status = -EINTR;
                        break;
                }
        } while (iov_iter_count(i));

        return written ? written : status;
}

****************************************************************************************
ext2_write_begin首先调用grab_cache_page_write_begin,查找page,没有则分配一个,但是如果page正在被写回(PG_writeback状态,写请求正在被提交但是没完成),则必须等待上一个写过程完成,要不然数据就乱大了。
然后有必要的话__block_write_begin为page的每个block分配buffer_head,然后对每个buffer_head分别进行处理:
1. 检查该写请求是否涉及到该block,没有设计的话则继续处理下一个block。
2. 如果该buffer_head还没有映射到磁盘,BH_Mapped没有设置,新分配的buffer_head都是没有影射的,所以这里又要使用ext2_get_block去读取这个映射关系了,同sys_read中的区别是,这里只读取一个块的映射关系。
3. 检查page如果已经uptodate,设置buffer_head uptodate。如果用户先sys_read,再sys_write的话这里page一般是uptodate的。
4. 反之假设用户直接sys_write的话,这里的page就不是uptodate的,通过ll_rw_block提交buffer_head读请求将磁盘数据读取出来。
所以block都处理完毕了,一种直接sys_write的情况就是,通过ll_rw_block发出了4个buffer_head读取请求,那么现在必须通过wait_on_buffer等待这些读取全部完成才能继续。
****************************************************************************************
static int ext2_write_begin(struct file *file, struct address_space *mapping,
                loff_t pos, unsigned len, unsigned flags,
                struct page **pagep, void **fsdata)
{
        int ret;

        ret = block_write_begin(mapping, pos, len, flags, pagep,
                                ext2_get_block);
        if (ret < 0)
                ext2_write_failed(mapping, pos + len);
        return ret;
}
int block_write_begin(struct address_space *mapping, loff_t pos, unsigned len,
                unsigned flags, struct page **pagep, get_block_t *get_block)
{
        pgoff_t index = pos >> PAGE_CACHE_SHIFT;
        struct page *page;
        int status;

        page = grab_cache_page_write_begin(mapping, index, flags);
        if (!page)
                return -ENOMEM;

        status = __block_write_begin(page, pos, len, get_block);
        if (unlikely(status)) {
                unlock_page(page);
                page_cache_release(page);
                page = NULL;
        }

        *pagep = page;
        return status;
}
struct page *grab_cache_page_write_begin(struct address_space *mapping,
                                        pgoff_t index, unsigned flags)
{
        int status;
        gfp_t gfp_mask;
        struct page *page;
        gfp_t gfp_notmask = 0;

        gfp_mask = mapping_gfp_mask(mapping);
        if (mapping_cap_account_dirty(mapping))
                gfp_mask |= __GFP_WRITE;
        if (flags & AOP_FLAG_NOFS)
                gfp_notmask = __GFP_FS;
repeat:
        page = find_lock_page(mapping, index);
        if (page)
                goto found;

        page = __page_cache_alloc(gfp_mask & ~gfp_notmask);
        if (!page)
                return NULL;
        status = add_to_page_cache_lru(page, mapping, index,
                                                GFP_KERNEL & ~gfp_notmask);
        if (unlikely(status)) {
                page_cache_release(page);
                if (status == -EEXIST)
                        goto repeat;
                return NULL;
        }
found:
        wait_on_page_writeback(page);
        return page;
}
int __block_write_begin(struct page *page, loff_t pos, unsigned len,
                get_block_t *get_block)
{
        unsigned from = pos & (PAGE_CACHE_SIZE - 1);
        unsigned to = from + len;
        struct inode *inode = page->mapping->host;
        unsigned block_start, block_end;
        sector_t block;
        int err = 0;
        unsigned blocksize, bbits;
        struct buffer_head *bh, *head, *wait[2], **wait_bh=wait;

        BUG_ON(!PageLocked(page));
        BUG_ON(from > PAGE_CACHE_SIZE);
        BUG_ON(to > PAGE_CACHE_SIZE);
        BUG_ON(from > to);

        blocksize = 1 << inode->i_blkbits;
        if (!page_has_buffers(page))
                create_empty_buffers(page, blocksize, 0);
        head = page_buffers(page);

        bbits = inode->i_blkbits;
        block = (sector_t)page->index << (PAGE_CACHE_SHIFT - bbits);

        for(bh = head, block_start = 0; bh != head || !block_start;
            block++, block_start=block_end, bh = bh->b_this_page) {
                block_end = block_start + blocksize;
                if (block_end <= from || block_start >= to) {
                        if (PageUptodate(page)) {
                                if (!buffer_uptodate(bh))
                                        set_buffer_uptodate(bh);
                        }
                        continue;
                }
                if (buffer_new(bh))
                        clear_buffer_new(bh);
                if (!buffer_mapped(bh)) {
                        WARN_ON(bh->b_size != blocksize);
                        err = get_block(inode, block, bh, 1);
                        if (err)
                                break;
                        if (buffer_new(bh)) {
                                unmap_underlying_metadata(bh->b_bdev,
                                                        bh->b_blocknr);
                                if (PageUptodate(page)) {
                                        clear_buffer_new(bh);
                                        set_buffer_uptodate(bh);
                                        mark_buffer_dirty(bh);
                                        continue;
                                }
                                if (block_end > to || block_start < from)
                                        zero_user_segments(page,
                                                to, block_end,
                                                block_start, from);
                                continue;
                        }
                }
                if (PageUptodate(page)) {
                        if (!buffer_uptodate(bh))
                                set_buffer_uptodate(bh);
                        continue;
                }
                if (!buffer_uptodate(bh) && !buffer_delay(bh) &&
                    !buffer_unwritten(bh) &&
                     (block_start < from || block_end > to)) {
                        ll_rw_block(READ, 1, &bh);
                        *wait_bh++=bh;
                }
        }
        /*
         * If we issued read requests - let them complete.
         */
        while(wait_bh > wait) {
                wait_on_buffer(*--wait_bh);
                if (!buffer_uptodate(*wait_bh))
                        err = -EIO;
        }
        if (unlikely(err))
                page_zero_new_buffers(page, from, to);
        return err;
}




评分

参与人数 1可用积分 +6 收起 理由
瀚海书香 + 6 赞一个!

查看全部评分

论坛徽章:
16
2015亚冠之吉达阿赫利
日期:2015-08-17 11:21:462015年迎新春徽章
日期:2015-03-04 09:58:11酉鸡
日期:2014-12-07 09:06:19水瓶座
日期:2014-11-04 14:23:29天秤座
日期:2014-03-02 08:57:52双鱼座
日期:2014-02-22 13:07:56午马
日期:2014-02-14 11:08:18双鱼座
日期:2014-02-13 11:09:37卯兔
日期:2014-02-06 15:10:34子鼠
日期:2014-01-20 14:48:19戌狗
日期:2013-12-19 09:37:46射手座
日期:2013-12-19 09:33:47
37 [报告]
发表于 2012-12-05 11:18 |只看该作者
本帖最后由 embeddedlwp 于 2012-12-05 11:25 编辑

还有,你有没有发现一个问题,如果调用wb_check_background_flush发现超过了background flush threshold,那么会wirteback,但是这里是per-bdi的writeback,并没有writeback其他bdi,如果并不是当前这个bdi造成大量diry pages,并且当前bdi对应的pages都是频繁访问,那么错误的将本bdi进行writeback,不但降低了performance,还没解决问题。

看到community里曾经一个人的patch
writeback: add dirty_background_centisecs per bdi variable
貌似经过讨论author想设置一个类似bdi->dirty_background_bytes的东西,如果真的有这样一个东西,可以把bdi_thresh设置过来,这样在wb_check_background_flush里加个判断,如果不是本bdi造成的over threshold,那么就不要乱flush了。

论坛徽章:
16
2015亚冠之吉达阿赫利
日期:2015-08-17 11:21:462015年迎新春徽章
日期:2015-03-04 09:58:11酉鸡
日期:2014-12-07 09:06:19水瓶座
日期:2014-11-04 14:23:29天秤座
日期:2014-03-02 08:57:52双鱼座
日期:2014-02-22 13:07:56午马
日期:2014-02-14 11:08:18双鱼座
日期:2014-02-13 11:09:37卯兔
日期:2014-02-06 15:10:34子鼠
日期:2014-01-20 14:48:19戌狗
日期:2013-12-19 09:37:46射手座
日期:2013-12-19 09:33:47
36 [报告]
发表于 2012-12-04 21:05 |只看该作者
回复 34# blake326


wu fengguang slides中这个图,b_io链表上什么时候会有non-expired的呢?


   

论坛徽章:
16
2015亚冠之吉达阿赫利
日期:2015-08-17 11:21:462015年迎新春徽章
日期:2015-03-04 09:58:11酉鸡
日期:2014-12-07 09:06:19水瓶座
日期:2014-11-04 14:23:29天秤座
日期:2014-03-02 08:57:52双鱼座
日期:2014-02-22 13:07:56午马
日期:2014-02-14 11:08:18双鱼座
日期:2014-02-13 11:09:37卯兔
日期:2014-02-06 15:10:34子鼠
日期:2014-01-20 14:48:19戌狗
日期:2013-12-19 09:37:46射手座
日期:2013-12-19 09:33:47
35 [报告]
发表于 2012-12-04 13:10 |只看该作者
回复 34# blake326


貌似最近360在招人,有内核方面的,你网上搜搜看。


   

论坛徽章:
0
34 [报告]
发表于 2012-12-04 11:53 |只看该作者
忘了差不多了,要重看。即把的比了几天寄存器了,但疼垃圾的事。不想干外包了。fuck,有兄弟好工作推荐马?

论坛徽章:
16
2015亚冠之吉达阿赫利
日期:2015-08-17 11:21:462015年迎新春徽章
日期:2015-03-04 09:58:11酉鸡
日期:2014-12-07 09:06:19水瓶座
日期:2014-11-04 14:23:29天秤座
日期:2014-03-02 08:57:52双鱼座
日期:2014-02-22 13:07:56午马
日期:2014-02-14 11:08:18双鱼座
日期:2014-02-13 11:09:37卯兔
日期:2014-02-06 15:10:34子鼠
日期:2014-01-20 14:48:19戌狗
日期:2013-12-19 09:37:46射手座
日期:2013-12-19 09:33:47
33 [报告]
发表于 2012-12-04 10:00 |只看该作者
回复 32# blake326



可否感兴趣把你的
todo:
1. 忽略了cache相关。
2. page各种状态转换
也总结总结,然后我们讨论讨论,呵呵!


   

论坛徽章:
0
32 [报告]
发表于 2012-12-04 09:29 |只看该作者
本帖最后由 blake326 于 2012-12-04 09:30 编辑

回复 31# embeddedlwp


    假设block=1024B

    读文件的时候,比如读4个page,如果这四个page都是连续的话,那么产生一个bio。bi_io_vec数量是4个,每个biiovec表示一个page,大小4096B

   写文件的时候,写4个page,每个page一次处理,如果page不再内存,那么分配一个page,然后用为page分配4个buffer head,每个bufferhead 产生一个bio,大小1024B。所以写4个page共产生了16个bio,每个bio有一个bi_io_vec,大小是1024B。但是,有可能这16个bio会被合并到同一个request中。


write 用buffer head原因:
   为什么写文件直接用buffer head来读取,这里看到写page顶多一次读取一个page(而读取一次可能读取多个page),可能这也是写文件直接用bufferhead的一个原因吧。

论坛徽章:
16
2015亚冠之吉达阿赫利
日期:2015-08-17 11:21:462015年迎新春徽章
日期:2015-03-04 09:58:11酉鸡
日期:2014-12-07 09:06:19水瓶座
日期:2014-11-04 14:23:29天秤座
日期:2014-03-02 08:57:52双鱼座
日期:2014-02-22 13:07:56午马
日期:2014-02-14 11:08:18双鱼座
日期:2014-02-13 11:09:37卯兔
日期:2014-02-06 15:10:34子鼠
日期:2014-01-20 14:48:19戌狗
日期:2013-12-19 09:37:46射手座
日期:2013-12-19 09:33:47
31 [报告]
发表于 2012-12-03 17:47 |只看该作者
blake326 发表于 2012-12-03 17:34
回复 29# embeddedlwp


1.你的意思是不是说即使是read page,page在磁盘上连续,仍然会被搞成buffer page,只是用一个bio罢了?
2.仍然没搞明白为什么write page,并且page的各个block在磁盘上连续要多个bio.

论坛徽章:
0
30 [报告]
发表于 2012-12-03 17:34 |只看该作者
回复 29# embeddedlwp


    假设block=1024B

    读文件的时候,比如读4个page,如果这四个page都是连续的话,那么产生一个bio。bi_io_vec数量是4个,每个biiovec表示一个page,大小4096B

   写文件的时候,写一个page,如果page不再内存,那么分配一个page,然后用为page分配4个buffer head,每个bufferhead 产生一个bio,大小1024B

   为什么写文件直接用buffer head来读取,这里看到写page顶多一次读取一个page(而读取一次可能读取多个page),可能这也是写文件直接用bufferhead的一个原因吧。

论坛徽章:
16
2015亚冠之吉达阿赫利
日期:2015-08-17 11:21:462015年迎新春徽章
日期:2015-03-04 09:58:11酉鸡
日期:2014-12-07 09:06:19水瓶座
日期:2014-11-04 14:23:29天秤座
日期:2014-03-02 08:57:52双鱼座
日期:2014-02-22 13:07:56午马
日期:2014-02-14 11:08:18双鱼座
日期:2014-02-13 11:09:37卯兔
日期:2014-02-06 15:10:34子鼠
日期:2014-01-20 14:48:19戌狗
日期:2013-12-19 09:37:46射手座
日期:2013-12-19 09:33:47
29 [报告]
发表于 2012-12-03 17:01 |只看该作者
blake326 发表于 2012-12-03 16:38
好问题啊,kernel读文件确实按照这样的规则来处理的。
但是,写文件的话,如果page不再内存,kernel是 ...



是不是所有的page都是buffer page? 只是如果一个page的各个block如果在磁盘上不连续就发多个bio,如果连续发一个bio。 求指教!
  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP