PageDirty、PageWriteback、PageReclaim、PageReferenced、PageUptodate等page的各个状态源码讲解

news/2024/11/23 0:27:31/

在看内核文件系统read/write、pagecache、内存回收相关代码时,多多少少应该看过if(PageDirty(page))、if(PageWriteback(page))、if(PageReclaim(page))、if (PageReferenced(page))、if (PageUptodate(page))、trylock_page这样内核代码,依次判断page是否有” Dirty”、”writeback”、” Reclaim”、” Referenced”、” Uptodate”、”lock”状态。曾经迷茫过,page的这些状态是怎么设置的?又是怎么清理掉的?这些page的状态又会产生什么影响?本文主要谈论这些。

首先各个page的状态定义在include/linux/page-flags.h 文件的pageflags枚举变量里:

  1. enum pageflags {
  2.     PG_locked,      /* Page is locked. Don't touch. */
  3.     PG_error,
  4.     PG_referenced,
  5.     PG_uptodate,
  6.     PG_dirty,
  7.     PG_lru,
  8.     PG_active,
  9.     PG_slab,
  10.     PG_owner_priv_1,    /* Owner use. If pagecache, fs may use*/
  11.     PG_arch_1,
  12.     PG_reserved,
  13.     PG_private,     /* If pagecache, has fs-private data */
  14.     PG_private_2,       /* If pagecache, has fs aux data */
  15.     PG_writeback,       /* Page is under writeback */
  16.     .........
  17.     PG_reclaim,     /* To be reclaimed asap */
  18.    
  19. }

在该文件中定了很多设置page状态的宏定义

比如清理page” PageUptodate”状态的宏定义 ClearPageUptodate如下:

  1. CLEARPAGEFLAG(Uptodate, uptodate)
  2. #define CLEARPAGEFLAG(uname, lname)                 \
  3. static inline void ClearPage##uname(struct page *page)          \
  4.             { clear_bit(PG_##lname, &page->flags); }

再比如清理page状态的writeback状态的test_clear_page_writeback宏定义

  1. TESTSCFLAG(Writeback, writeback)
  2. #define TESTSCFLAG(uname, lname)                    \
  3.     TESTSETFLAG(uname, lname) TESTCLEARFLAG(uname, lname)
  4.    
  5. #define TESTCLEARFLAG(uname, lname)                 \
  6. static inline int TestClearPage##uname(struct page *page)       \
  7.         { return test_and_clear_bit(PG_##lname, &page->flags); }

设置和清理page的” Dirty”、”writeback”、” Reclaim”、” Referenced”、” Uptodate”、”lock”状态的宏定义及函数整理如下:

page状态

设置page状态

清理page状态

PG_dirty

TestSetPageDirty(page)或set_page_dirty(page)

TestClearPageDirty(page)

PG_reclaim

SetPageReclaim(page)

TestClearPageReclaim(page)

PG_writeback

set_page_writeback(page)

test_set_page_writeback(page)

PG_referenced

SetPageReferenced(page)

mark_page_accessed(page)

PG_locked

__set_page_locked(page) 或trylock_page(page)或lock_page

__clear_page_locked(page)或unlock_page(page)

PG_uptodate

SetPageUptodate(page)

ClearPageUptodate(page

如果你要是找不到这些宏定义,去include/linux/page-flags.h文件查找应该就可以找到。下边就说说设置和清理page的” Dirty”、”writeback”、” Reclaim”、” Referenced”、” Uptodate”、”lock”状态的内核过程,基于ext4文件系统,内核源码3.10.96,详细源码注释见https://github.com/dongzhiyan-stack/kernel-code-comment。

1 page的” Dirty”状态 设置和清理过程

以sync  wirte 过程为例,标记page脏页 和 脏页数加1,源码流程如下:

vfs_write->do_sync_write->ext4_file_write->generic_file_aio_write->generic_file_buffered_write->generic_perform_write->ext4_write_end->block_write_end->__block_commit_write->mark_buffer_dirty->TestSetPageDirty(page) __set_page_dirty->account_page_dirtied->__inc_zone_page_state(page, NR_FILE_DIRTY)

__set_page_dirty源码如下:

  1. static void __set_page_dirty(struct page *page,
  2.         struct address_space *mapping, int warn)
  3. {
  4.     unsigned long flags;
  5.     spin_lock_irqsave(&mapping->tree_lock, flags);
  6.     if (page->mapping) {    /* Race with truncate? */
  7.         WARN_ON_ONCE(warn && !PageUptodate(page));
  8.         //增加脏页NR_FILE_DIRTYBDI_DIRTIED
  9.         account_page_dirtied(page, mapping);
  10.         //增加radix treePAGECACHE_TAG_DIRTY脏页统计
  11.         radix_tree_tag_set(&mapping->page_tree,
  12.                 page_index(page), PAGECACHE_TAG_DIRTY);
  13.     }
  14.     spin_unlock_irqrestore(&mapping->tree_lock, flags);
  15.     //标记page所属文件的inode
  16.     __mark_inode_dirty(mapping->host, I_DIRTY_PAGES);
  17. }
  18. void account_page_dirtied(struct page *page, struct address_space *mapping)
  19. {
  20.     trace_writeback_dirty_page(page, mapping);
  21.     if (mapping_cap_account_dirty(mapping)) {
  22.         //增加脏页NR_FILE_DIRTY
  23.         __inc_zone_page_state(page, NR_FILE_DIRTY);
  24.         __inc_zone_page_state(page, NR_DIRTIED);
  25.         //BDI_RECLAIMABLE1
  26.         __inc_bdi_stat(mapping->backing_dev_info, BDI_RECLAIMABLE);
  27.         //BDI_DIRTIED1
  28.         __inc_bdi_stat(mapping->backing_dev_info, BDI_DIRTIED);
  29.         task_io_account_write(PAGE_CACHE_SIZE);
  30.         current->nr_dirtied++;
  31.         this_cpu_inc(bdp_ratelimits);
  32.     }
  33. }

启动文件数据传输执行,清理page脏页标记,脏页数减1,源码流程如下:

vfs_write->do_sync_write->ext4_file_write->generic_file_aio_write->generic_write_sync->vfs_fsync_range->ext4_sync_file->filemap_write_and_wait_range->__filemap_fdatawrite_range->do_writepages->generic_writepages->write_cache_pages->clear_page_dirty_for_io(page)->TestClearPageDirty(page)dec_zone_page_state(page, NR_FILE_DIRTY)

2 page的” writeback”状态 设置和清理过程

启动文件数据传输执行,设置page的” writeback”状态,源码流程如下:

vfs_write->do_sync_write->ext4_file_write->generic_file_aio_write->generic_write_sync->vfs_fsync_range->ext4_sync_file->filemap_write_and_wait_range->__filemap_fdatawrite_range->do_writepages->generic_writepages->write_cache_pages->__writepage->ext4_writepage->ext4_bio_write_page->set_page_writeback(page)

接着是执行启动page脏页数据落盘,源码流程如下:

vfs_write->do_sync_write->ext4_file_write->generic_file_aio_write->generic_write_sync->vfs_fsync_range->ext4_sync_file->filemap_write_and_wait_range->__filemap_fdatawrite_range->do_writepages->generic_writepages->write_cache_pages->__writepage->ext4_writepage->ext4_bio_write_page->ext4_io_submit->submit_bio

高版本内核流程是

vfs_write->do_sync_write->ext4_file_write->generic_file_aio_write->generic_write_sync->vfs_fsync_range->ext4_sync_file->filemap_write_and_wait_range->__filemap_fdatawrite_range->do_writepages->ext4_writepages->ext4_io_submit->submit_bio

之后进程在page的writeback等待队列休眠,等page数据传输完成被唤醒,源码流程如下:

vfs_write->do_sync_write->ext4_file_write->generic_file_aio_write->generic_write_sync->vfs_fsync_range->ext4_sync_file->filemap_write_and_wait_range->filemap_fdatawait_range->wait_on_page_writeback

等page脏页数据落盘完成,产生硬中断和软中断,软中断里然后清理page的writeback标记,唤醒在该page等待队列休眠的进程,源码流程如下:

blk_done_softirq->scsi_softirq_done->scsi_finish_command->scsi_io_completion->scsi_end_request->blk_update_request->bio_endio->ext4_end_bio->ext4_finish_bio->end_page_writeback->test_clear_page_writeback(page)

end_page_writeback函数源码如下:

  1. void end_page_writeback(struct page *page)
  2. {
  3.     //如果该page被设置了"Reclaim"标记位,
  4.     if (TestClearPageReclaim(page))
  5.         rotate_reclaimable_page(page);
  6.    
  7.     //清除掉page writeback标记
  8.     if (!test_clear_page_writeback(page))
  9.         BUG();
  10.     smp_mb__after_clear_bit();
  11.     //唤醒在该pagePG_writeback等待队列休眠的进程
  12.     wake_up_page(page, PG_writeback);
  13. }

这里整理一下page的 Dirty状态、writeback状态变迁过程

  1. 进程write操作把最新的文件数据写入page文件页,page成脏页,则page被标记Dirty,并且脏页数加
  2. 要把page脏页刷入磁盘文件系统了,则清理page脏页标记,并且脏页数减1
  3. 要把page脏页刷入磁盘文件系统了,page被标记writeback
  4. 进程执行submit_bio把page发送把脏页刷入磁盘的命令
  5. 进程在page的writeback等待队列休眠
  6. page脏页刷入磁盘完成,产生硬中断和软中断,软中断里然后清理page的writeback标记,唤醒在该page等待队列休眠的进程

3 page的” Reclaim”状态 设置和清理过程

内存回收,设置page的Reclaim状态,然后把page页数据刷回磁盘,源码流程如下:

shrink_page_list->pageout->SetPageReclaim(page)ext4_writepage()

等page脏页数据落盘完成,产生硬中断和软中断,软中断里清理page的Reclaim状态,然后把page添加到inactive lru list尾部,下次就先回收这个page。源码流程如下:

blk_done_softirq->scsi_softirq_done->scsi_finish_command->scsi_io_completion->scsi_end_request->blk_update_request->bio_endio->ext4_end_bio->ext4_finish_bio->end_page_writeback->TestClearPageReclaim(page)rotate_reclaimable_page(page)

看下rotate_reclaimable_page函数源码

  1. /*内存回收完成后,被标记"reclaimable"page的数据刷入了磁盘,执行rotate_reclaimable_page->end_page_writeback把该page移动到inactive lru链表尾,下轮内存回收就会释放该page到伙伴系统*/
  2. void rotate_reclaimable_page(struct page *page)
  3. {
  4.     //page没有上PG_lockedpage不是脏页,page要有acive标记,page没有设置不可回收标记,page要在lru链表
  5.     if (!PageLocked(page) && !PageDirty(page) && !PageActive(page) &&
  6.         !PageUnevictable(page) && PageLRU(page)) {
  7.         struct pagevec *pvec;
  8.         unsigned long flags;
  9.         //page->count ++
  10.         page_cache_get(page);
  11.         local_irq_save(flags);
  12.         //取出本地cpu lru缓存pagevec
  13.         pvec = &__get_cpu_var(lru_rotate_pvecs);
  14.        
  15.         //先尝试把page添加到本地cpu lru缓存pagevec,如果添加后lru缓存pagevec满了,则把lru缓存pagevec中的所有page移动到inactive lru链表
  16.         if (!pagevec_add(pvec, page))
  17.             pagevec_move_tail(pvec);
  18.         local_irq_restore(flags);
  19.     }

}

4 page的” Referenced”状态 设置和清理过程

当读写page文件时,其实就是访问page,最后都执行mark_page_accessed(),整理的几个流程如下:

  • vfs_write->do_sync_write->ext4_file_write->generic_file_aio_write->generic_file_buffered_write->generic_perform_write->mark_page_accessed
  • do_generic_file_read->mark_page_accessed
  • ext4_readdir->ext4_bread->ext4_getblk->sb_getblk->__getblk->__find_get_block->touch_buffer->mark_page_accessed

还有写文件过程vfs_write->do_sync_write->ext4_file_write->generic_file_aio_write->generic_file_buffered_write->generic_perform_write,看下源码:

  1. static ssize_t generic_perform_write(struct file *file,
  2.                 struct iov_iter *i, loff_t pos)
  3. {
  4.     ext4_write_begin->lock_page(page);
  5.     //write系统调用传入的最新文件数据从用户空间buf复制到page文件页
  6.     copied = iov_iter_copy_from_user_atomic(page, i, offset, bytes);
  7.     mark_page_accessed(page);//这里标记page最近被访问
  8.     ext4_write_end->unlock_page(page);
  9.     balance_dirty_pages_ratelimited(mapping);//脏页平衡
  10. } 

mark_page_accessed如下:

  1. void mark_page_accessed(struct page *page)
  2. {
  3.     //pageinactive的、page"Referenced"标记、page可回收、page lru链表
  4.     if (!PageActive(page) && !PageUnevictable(page) &&
  5.             PageReferenced(page) && PageLRU(page)) {
  6.         //pageinactive lru链表移动到active lru链表
  7.         activate_page(page);
  8.         //清理page"Referenced"标记
  9.         ClearPageReferenced(page);
  10.     } else if (!PageReferenced(page)) {//page之前没有"Referenced"标记
  11.         SetPageReferenced(page);//设置page"Referenced"标记
  12.     }
  13. }

显然,随着page随着被访问的次数增加,page的referenced状态就会发生改变,并且page也会在inactive/active lru链表之间迁移,主要有如下3步:

  • 1 page在inactive lru链表且page无Referenced标记,则设置page的Referenced标记。
  • 2 page在inactive lru链表且page有Referenced标记,则把page移动到active lru链表,并清理掉Referenced标记
  • 3 page在active lru链表且无referenced标记,则把仅仅标记该page的Referenced标记

   Referenced标记表示该page被访问了,上边这3步表示了page的3个状态的顺序变迁。一个page在inactive lru链表并且长时间未被访问,第一次有进程访问该page,则只是把page标记Referenced。第2次进程再访问该page,则把该page移动到active lru链表,但清理掉Referenced标记。第3次再有进程访问该page,则标记该page Referenced。如下是转移过程:pageinactive lru(unreferenced)----->pageinactive lru(referenced) ----->pageactive lru(unreferenced) ----->pageactive lru(referenced)

5 page的” Uptodate”状态 设置和清理

     当我们第一次读取文件,需要从磁盘读取各个文件页数据到page文件页,先执行do_generic_file_read->……->ext4_readpages->mpage_readpages->mpage_bio_submit-> submit_bio ,之后就等待磁盘文件数据读取到page文件页指向的内存。文件数据传输完成执行 blk_update_request->bio_endio->mpage_end_io->SetPageUptodate就会设置page的“PageUptodate”状态。那什么时候清理page的“PageUptodate”呢?正常情况并不会清理!

        清理page的Uptodate状态执行ClearPageUptodate,但是查看write写文件过程的内核代码时,并没有发现执行ClearPageUptodate呀。我最初是这样想的:进程1在写文件page1页面时,很快把该page的数据刷入磁盘,此时应该要清理掉page的PageUptodate状态吧?然后,进程2读取文件时,要重新从磁盘读取该文件的page1文件页对应的数据,因为page1的PageUptodate状态被清理掉了,需从磁盘重新读取该page的文件数据。等读完数据到page1的页面指向的内存,会执行SetPageUptodate设置page的PageUptodate状态。最后,进程2判断出page1已经是PageUptodate状态,顺利读到page1页面的最新数据并返回。这个理解错误的!

        因为进程1写文件page1页面时,是先把用户空间的数据保存到page1对应的内存,然后才会把该page的数据刷入磁盘。等进程2读取page1页面的数据时,page1页面指向的内存已经是最新的文件数据,没有必要再从磁盘读取呀!

        所以我认为,page的PageUptodate状态只有在第一次从磁盘读取文件数据到文件页面page时才有效。或者说,只有在读取的那一片磁盘文件数据没有映射的文件页page时才有效:按照读取的文件地址范围,建立磁盘文件与对应文件页page的映射,然后等待本次读取的文件数据到该page指向的内存,page被标记PageUptodate状态,把该page的数据读取到read系统调用传入的buf,就顺利读读到了文件数据。

        接着,page的PageUptodate状态应该一直存在,除非page被释放吧。那怎么做到读写该page数据同步呢?要向文件的该page文件页写数据,复制最新的文件数据,lock_page就行了吧,复制完unlock_page。然后read系统调用读取文件时就可以直接读取该page最新的数据了。lock_page的目的是为了防止在修改page文件页数据时,禁止其他进程此时读写该page文件页的数据吧。我的理解对不对呢?下一节讲解

6 page的” lock”状态 设置和清理

对page得 lock操作是执行lock_page(page) 或trylock_page(page) 或 __set_page_locked(page) 宏或函数,对page的unlock操作是执行__clear_page_locked(page) 和 unlock_page(page)宏或函数。读取文件触发文件页page预读时,page文件页内存还不是最新的文件数据,需对page加PG_locked锁。源码流程如下:

do_generic_file_read->page_cache_sync_readahead/page_cache_async_readahead->ondemand_readahead->__do_page_cache_readahead->read_pages->ext4_readpages->mpage_readpages->mpage_readpages->add_to_page_cache_lru->add_to_page_cache->__set_page_locked

之后进程 trylock_page(page) 获取page PG_locked锁,获取失败则pagePG_locked等待队列休眠。等把文件最新数据读取到该page文件页内存,产生硬中断中断、软中断,软中断回调函数执行blk_update_request->bio_endio->mpage_end_io,里边执行SetPageUptodate(page)设置page的"PageUptodate"状态,还执行unlock_page(page)清理page的PG_locked锁,然后唤醒在page的PG_locked等待队列休眠的进程。pagePG_locked等待队列休眠被唤醒后,if (PageUptodate(page)) 成立,则先 unlock_page(page) 释放page PG_locked锁,然后把page文件页数据读取read系统调用传入的buf。这个过程在讲解文件预读那篇文章详细解释过,可以看下。

再列举一个过程,在文件write过程,需要先把用户空间传入的最新文件数据写入page文件页过程也会lock_page。看些函数流程,vfs_write->do_sync_write->ext4_file_write->generic_file_aio_write->generic_file_buffered_write->generic_perform_write,源码如下:

  1. static ssize_t generic_perform_write(struct file *file,
  2.                 struct iov_iter *i, loff_t pos)
  3. {
  4.     ext4_write_begin->lock_page(page);
  5.     //write系统调用传入的最新文件数据从用户空间buf复制到page文件页
  6.     copied = iov_iter_copy_from_user_atomic(page, i, offset, bytes);
  7.     mark_page_accessed(page);//这里标记page最近被访问
  8.     ext4_write_end->unlock_page(page);
  9.     balance_dirty_pages_ratelimited(mapping);//脏页平衡
  10. } 

这应该可以说明,在把write系统调用传入的最新文件数据从用户空间buf复制到page文件页过程,是lock_page的。此时其他进程若想访问page文件页的数据,lock_page将会失败,只能等前边完成向page文件页复制最新的文件。本小节涉及的其他函数源码整理如下:

  1. static inline void __set_page_locked(struct page *page)
  2. {
  3.     __set_bit(PG_locked, &page->flags);
  4. }
  5. static inline void __clear_page_locked(struct page *page)
  6. {
  7.     __clear_bit(PG_locked, &page->flags);
  8. }
  9. //清除pagePG_locked标记位,并唤醒在page PG_locked等待队列的休眠的进程
  10. void unlock_page(struct page *page)
  11. {
  12.     VM_BUG_ON(!PageLocked(page));
  13.     clear_bit_unlock(PG_locked, &page->flags);//清除page PG_locked标记
  14.     smp_mb__after_clear_bit();
  15.     wake_up_page(page, PG_locked);//唤醒在page PG_locked等待队列的休眠的进程
  16. }
  17. static inline void lock_page(struct page *page)
  18. {
  19.     might_sleep();
  20.     if (!trylock_page(page))
  21.         __lock_page(page);
  22. }
  23. //尝试对page加锁,如果page之前已经其他进程被加锁则加锁失败返回0,否则当前进程对page加锁成功并返回1
  24. static inline int trylock_page(struct page *page)
  25. {
  26.     return (likely(!test_and_set_bit_lock(PG_locked, &page->flags)));
  27. }

http://www.ppmy.cn/news/708483.html

相关文章

java利用PageHelper.startPage(page, pageSize)分页

代码如下: RequestMapping(value "/selectLockLog", produces "text/html;charsetUTF-8")ResponseBodypublic String selectLockLog(String deviceuid,RequestParam(requiredtrue,defaultValue"1")Integer page, RequestParam(requ…

附加:PageHelper分页插件的:Page和PageInfo的区别;

说明: (1)为什么写本篇博客?: ● 在【Spring Boot电商项目29:商品分类模块八:后台的【分类列表(平铺)】接口;】中,实现分页功能时,使用…

page分页

page分页 PageBean:int totalCount (总记录数)int totalPage (总页码)List<T> list (每页数据)int currentPage (当前页码)int rows (每页显示的记录数)分页使用步骤 1.创建分页对象PageBean. 2.在前端利用bootstrap创建表格和分页工具。&#xff08;如果需要条件…

电脑键盘中英文按键有哪些?有什么作用?

对于电脑键盘&#xff0c;相信大家并不陌生&#xff0c;而在电脑键盘上面有着很多按键&#xff0c;除了数字按键和字母按键之外&#xff0c;还有很多看不懂的英文按键。那么这些英文按键是什么意思呢&#xff1f;如果并不了解&#xff0c;那么小编就为大家介绍所有英文按键的意…

IntelliJ IDEA快捷键大全 + 动图演示

&#x1f447;&#x1f447;关注后回复 “进群” &#xff0c;拉你进程序员交流群&#x1f447;&#x1f447; 来源&#xff1a;blog.csdn.net/weixin_67276852?typeblog 大家好 本文参考了 IntelliJ IDEA 的官网&#xff0c;列举了IntelliJ IDEA&#xff08;Windows 版&#…

I.MX6ULL_Linux_驱动篇(39) 阻塞和非阻塞IO

阻塞和非阻塞 IO 是 Linux 驱动开发里面很常见的两种设备访问模式&#xff0c;在编写驱动的时候一定要考虑到阻塞和非阻塞。本章我们就来学习一下阻塞和非阻塞 IO&#xff0c;以及如何在驱动程序中处理阻塞与非阻塞&#xff0c;如何在驱动程序使用等待队列和 poll 机制。 阻塞…

开发环境总结

docker安装minio环境搭建 #下载镜像 docker pull minio/minio #创建目录 mkdir -p /data/minio/data #运行docker容器 docker run -p 9000:9000 -p 9001:9001 --name minio \ -d --restartalways \ -v /data/minio/data:/data \ -e MINIO_ROOT_USER$user \ -e MINIO_ROOT_PASS…

鼠标经过变色,ONMOUSEOVER变色

1.鼠标经过变色&#xff0c;ONMOUSEOVER变色 <table width"100%" style"border-top:0"> <tr style"background-color:#c1e5ff;" οnmοuseοver"this.runtimeStyle.backgroundColor #ddd;" οnmοuseοut"t…