page _refcount和_mapcount字段

linux page有两个非常重要的引用计数字段_refcount和_mapcount,都是atomic_t类型,其中,_refcount表示内核中应用该page的次数。当_refcount = 0时,表示该page为空闲或者将要被释放。当_refcount > 0,表示该page页面已经被分配且内核正在使用,暂时不会被释放。

_refcount

内核中常用的加减_refcount应用计数的API:get_page()和put_page()


static inline void get_page(struct page *page)
{
    page = compound_head(page);
    /*
     * Getting a normal page or the head of a compound page
     * requires to already have an elevated page->_refcount.
     */
    VM_BUG_ON_PAGE(page_ref_count(page) <= 0, page);
    page_ref_inc(page);
}

static inline void put_page(struct page *page)
{
    page = compound_head(page);

    /*
     * For private device pages we need to catch refcount transition from
     * 2 to 1, when refcount reach one it means the private device page is
     * free and we need to inform the device driver through callback. See
     * include/linux/memremap.h and HMM for details.
     */
    if (IS_HMM_ENABLED && unlikely(is_device_private_page(page) ||
        unlikely(is_device_public_page(page)))) {
        put_zone_device_private_or_public_page(page);
        return;
    }

    if (put_page_testzero(page))
        __put_page(page);
}

增加_refcount场景
1)alloc_pages 分配成功_refcount = 1

2)设置page->private的时候

3)加入到address_space

注意page处于LRU的时候_refcount = 1

上面三个路径可以通过write写数据场景结合源码分析,我们知道write系统调用可以使用page cache加速写性能,我们就以该场景看下page->_refcount引用计数的变化情况。write写数据场景的page cache创建是mm/filemap.c : grab_cache_page_write_begin

/*
 * Find or create a page at the given pagecache position. Return the locked
 * page. This function is specifically for buffered writes.
 */
struct page *grab_cache_page_write_begin(struct address_space *mapping,
					pgoff_t index, unsigned flags)
{
	struct page *page;
	int fgp_flags = FGP_LOCK|FGP_WRITE|FGP_CREAT;
    ...

no_page:
	if (!page && (fgp_flags & FGP_CREAT)) {
        ..
		page = __page_cache_alloc(gfp_mask);
		if (!page)
			return NULL;

		if (WARN_ON_ONCE(!(fgp_flags & (FGP_LOCK | FGP_FOR_MMAP))))
			fgp_flags |= FGP_LOCK;

		/* Init accessed so avoid atomic mark_page_accessed later */
		if (fgp_flags & FGP_ACCESSED)
			__SetPageReferenced(page);

		err = add_to_page_cache_lru(page, mapping, index, gfp_mask);
		if (unlikely(err)) {
			put_page(page);
			page = NULL;
			if (err == -EEXIST)
				goto repeat;
		}

		/*
		 * add_to_page_cache_lru locks the page, and for mmap we expect
		 * an unlocked page.
		 */
		if (page && (fgp_flags & FGP_FOR_MMAP))
			unlock_page(page);
	}

	return page;
}
  1. __page_cache_alloc通过alloc_page创建页面,page刚刚创建_refcount = 1
  2. add_to_page_cache_lru将page分别加入lru链表和address_space,这两个步骤中都会增加_refcount,该函数返回后_refcount = 3

add_to_page_cache_lru


int add_to_page_cache_lru(struct page *page, struct address_space *mapping,
				pgoff_t offset, gfp_t gfp_mask)
{
	void *shadow = NULL;
	int ret;

	__SetPageLocked(page);
	ret = __add_to_page_cache_locked(page, mapping, offset,
					 gfp_mask, &shadow);
	if (unlikely(ret))
		__ClearPageLocked(page);
	else {
		...
		if (!(gfp_mask & __GFP_WRITE) && shadow)
			workingset_refault(page, shadow);
		lru_cache_add(page);
	}
	return ret;
}


static int __add_to_page_cache_locked(struct page *page,
				      struct address_space *mapping,
				      pgoff_t offset, gfp_t gfp_mask,
				      void **shadowp)
{
    ...
	get_page(page);
    ...
error:
	page->mapping = NULL;
	/* Leave page->index set: truncation relies upon it */
	put_page(page);
	return error;
}

void lru_cache_add(struct page *page)
{
    ...
	get_page(page);
    ...
}

_add_to_page_cache_locked和lru_cache_add都会增加_refcount引用计数。

4)page被映射到其他用户进程pte时,_refcount引用技数会加1。

例如子进程创建时共享父进程的地址空间,设置父进程的pte页表项内容到子进程中并增加该页的_refcount计数:

do_fork->copy_process->copy_mm->dup_mmap->copy_page_range->...->copy_pte_range->copy_one_pte函数。

5) 对于PG_swapable的页面,_add_to_swap_cache会增加_refcount引用计数

6)内核对页面进程操作的一些关键路径上也会增加_refcount。比如内核的follow_page和get_user_pages

_mapcount

_mapcount表示这个页面被进程映射的个数,即已经映射了多少个用户pte页表。

  • _mapcount = -1,表示没有pte映射到该页面中
  • _mapcount = 0,表示只有父进程映射了页面,匿名页面刚分配时,_mapcount = 0
  • _mapcount > 0,表示除了父进程外还有其他进程映射了这个页面,同样以子进程创建共享父进程地址空间为例,设置父进程的pte页表项到子进程中并增加该页面的_mapcount。

get_page增加_refcount,page_dump_rmap增加_mapcount

问题:read/write系统调用产生的page cache _mapcount是多少

其实是-1。因为这些page cache是单纯内核alloc_pages产生的,本质上是没有映射用户空间页表的。

weak_ptr是C++11引入的一种智能指针,它可以用来解决shared_ptr的循环引用问题。weak_ptr指向一个shared_ptr管理的对象,但它不会增加该对象的引用计数,也不控制该对象的生命周期。如果shared_ptr被销毁了,那么weak_ptr就会变成空指针。 下面是一个简单的weak_ptr实现: ```c++ template<typename T> class shared_ptr; template<typename T> class weak_ptr { public: weak_ptr() : m_ptr(nullptr), m_refCount(nullptr) {} weak_ptr(const shared_ptr<T>& sp) : m_ptr(sp.m_ptr), m_refCount(sp.m_refCount) { if (m_refCount) { m_refCount->weakCount++; } } weak_ptr(const weak_ptr<T>& wp) : m_ptr(wp.m_ptr), m_refCount(wp.m_refCount) { if (m_refCount) { m_refCount->weakCount++; } } ~weak_ptr() { reset(); } weak_ptr<T>& operator=(const shared_ptr<T>& sp) { reset(); m_ptr = sp.m_ptr; m_refCount = sp.m_refCount; if (m_refCount) { m_refCount->weakCount++; } return *this; } weak_ptr<T>& operator=(const weak_ptr<T>& wp) { reset(); m_ptr = wp.m_ptr; m_refCount = wp.m_refCount; if (m_refCount) { m_refCount->weakCount++; } return *this; } void reset() { if (m_refCount) { m_refCount->weakCount--; if (m_refCount->weakCount == 0 && m_refCount->refCount == 0) { delete m_refCount; delete m_ptr; } } m_ptr = nullptr; m_refCount = nullptr; } shared_ptr<T> lock() const { if (expired()) { return shared_ptr<T>(); } else { return shared_ptr<T>(*this); } } bool expired() const { return use_count() == 0; } long use_count() const { if (m_refCount) { return m_refCount->refCount; } else { return 0; } } private: T* m_ptr; shared_ref_count* m_refCount; }; ``` 在实现中,我们需要维护一个shared_ref_count类,用来计数一个对象的引用次数和weak_ptr的数量。 ```c++ class shared_ref_count { public: shared_ref_count() : refCount(1), weakCount(0) {} long refCount; long weakCount; }; ``` 在weak_ptr的构造函数中,我们需要将weakCount增加。在析构函数中,我们需要调用reset()方法,将weakCount减少,并在refCount和weakCount都为0时释放资源。 在reset()方法中,我们首先将weakCount减少,然后判断refCount和weakCount是否都为0,如果是,则释放资源。 在lock()方法中,我们首先判断是否过期(即use_count()是否为0),如果是,则返回一个空的shared_ptr,否则返回一个新的shared_ptr。 在expired()方法中,我们判断use_count()是否为0。 在use_count()方法中,我们返回refCount的值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值