专题:Linux内存管理专题

关键词:struct page、_count、_mapcount、PG_locked/PG_referenced/PG_active/PG_dirty等。

Linux的内存管理是以页展开的,struct page非常重要,同时其维护成本也非常高。

这里主要介绍struct page中_count/_mapcount和flags参数。

flags是页面标志位集合,是内存管理非常重要的部分。

_count表示内核中引用该页面的次数;_mapcount表示页面被进程映射的个数,对反向映射非常重要。

1. struct page数据结构

struct page大量使用联合体union来优化其结构大小,因为每个物理页面都需要一个struct page数据结构,因此管理成本很高。

/*
* Each physical page in the system has a struct page associated with
* it to keep track of whatever it is we are using the page for at the
* moment. Note that we have no way to track which tasks are using
* a page, though if it is a pagecache page, rmap structures can tell us
* who is mapping it.--------------------------------------------------------------我们无法知道那个进程在使用一个页面,但是可以通过RMAP相关结构体知道谁映射到了此页面。
*
* The objects in struct page are organized in double word blocks in
* order to allows us to use atomic double word operations on portions
* of struct page. That is currently only used by slub but the arrangement
* allows the use of atomic double word operations on the flags/mapping
* and lru list pointers also.
*/
struct page {
/* First double word block */
unsigned long flags; /* Atomic flags, some possibly
* updated asynchronously */
union {
struct address_space *mapping; /* If low bit clear, points to----------表示页面所指向的地址空间,低两位用于判断是匿名映射还是KSM页面。位1表示匿名页面,位2表示KSM页面。
* inode address_space, or NULL.
* If page mapped as anonymous
* memory, low bit is set, and
* it points to anon_vma object:
* see PAGE_MAPPING_ANON below.
*/
void *s_mem; /* slab first object */---------------------------用于slab分配器,slab中第一个对象的开始地址,和mapping共同占用一个字的存储空间。
}; /* Second double word */
struct {
union {
pgoff_t index; /* Our offset within mapping. */
void *freelist; /* sl[aou]b first free object */
bool pfmemalloc; /* If set by the page allocator,
* ALLOC_NO_WATERMARKS was set
* and the low watermark was not
* met implying that the system
* is under some pressure. The
* caller should try ensure
* this page is only used to
* free other pages.
*/
}; union {
#if defined(CONFIG_HAVE_CMPXCHG_DOUBLE) && \
defined(CONFIG_HAVE_ALIGNED_STRUCT_PAGE)
/* Used for cmpxchg_double in slub */
unsigned long counters;
#else
/*
* Keep _count separate from slub cmpxchg_double data.
* As the rest of the double word is protected by
* slab_lock but _count is not.
*/
unsigned counters;
#endif struct { union {
/*
* Count of ptes mapped in
* mms, to show when page is
* mapped & limit reverse map
* searches.
*
* Used also for tail pages
* refcounting instead of
* _count. Tail pages cannot
* be mapped and keeping the
* tail page _count zero at
* all times guarantees
* get_page_unless_zero() will
* never succeed on tail
* pages.
*/
atomic_t _mapcount; struct { /* SLUB */
unsigned inuse:;
unsigned objects:;
unsigned frozen:;
};
int units; /* SLOB */
};
atomic_t _count; /* Usage count, see below. */
};
unsigned int active; /* SLAB */
};
};
...
}

flags是页面的重要标志位,下面是详细解释:

enum pageflags {
PG_locked, /* Page is locked. Don't touch. */---表示页面已经上锁了。如果该比特位置位,说明页面已经被锁定;内存管理其他模块不能访问这个页面,以防发生竞争。
PG_error,----------------------------------------------页面操作过程中发生错误会设置该位。
PG_referenced,-----------------------------------------控制页面活跃程度,在kswapd页面回收中使用。
PG_uptodate,-------------------------------------------表示页面的数据已经从块设备成功读取。
PG_dirty,----------------------------------------------表示页面内容发生改变,页面为脏,页面内容被改写后还没有和外部存储器进行同步操作。
PG_lru,------------------------------------------------表示页面加入了LRU链表,内核使用LRU链表管理活跃和不活跃页面。
PG_active,---------------------------------------------控制页面活跃成都,在kswapd页面回收中使用。
PG_slab,-----------------------------------------------用于slab分配器
PG_owner_priv_1, /* Owner use. If pagecache, fs may use*/--页面的所有者使用,如果是page cache页面,文件系统可能使用。
PG_arch_1,---------------------------------------------与体系结构相关的页面状态位。
PG_reserved,-------------------------------------------表示该页不可被换出。
PG_private, /* If pagecache, has fs-private data */--表示该页是有效的,。如果页面是page cache,那么包含一些文件系统相关的数据信息。
PG_private_2, /* If pagecache, has fs aux data */----如果是page cache,可能包含fs aux data。
PG_writeback, /* Page is under writeback */----表示页面的内容正在向块设备进行回写。
#ifdef CONFIG_PAGEFLAGS_EXTENDED
PG_head, /* A head page */
PG_tail, /* A tail page */
#else
PG_compound, /* A compound page */-------------一个混合页面
#endif
PG_swapcache, /* Swap page: swp_entry_t in private */---表示页面处于交换缓存。
PG_mappedtodisk, /* Has blocks allocated on-disk */
PG_reclaim, /* To be reclaimed asap */----------表示该页马上要被回收。
PG_swapbacked, /* Page is backed by RAM/swap */---------页面具有swap缓存功能,通常匿名页面才可以写回swap分区。
PG_unevictable, /* Page is "unevictable" */----表示页面不可回收。
#ifdef CONFIG_MMU
PG_mlocked, /* Page is vma mlocked */-----------表示页面对应的VMA处于locked状态。
#endif
#ifdef CONFIG_ARCH_USES_PG_UNCACHED
PG_uncached, /* Page has been mapped as uncached */
#endif
#ifdef CONFIG_MEMORY_FAILURE
PG_hwpoison, /* hardware poisoned page. Don't touch */
#endif
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
PG_compound_lock,
#endif
__NR_PAGEFLAGS, /* Filesystems */
PG_checked = PG_owner_priv_1, /* Two page bits are conscripted by FS-Cache to maintain local caching
* state. These bits are set on pages belonging to the netfs's inodes
* when those inodes are being locally cached.
*/
PG_fscache = PG_private_2, /* page backed by cache */ /* XEN */
/* Pinned in Xen as a read-only pagetable page. */
PG_pinned = PG_owner_priv_1,
/* Pinned as part of domain save (see xen_mm_pin_all()). */
PG_savepinned = PG_dirty,
/* Has a grant mapping of another (foreign) domain's page. */
PG_foreign = PG_owner_priv_1, /* SLOB */
PG_slob_free = PG_private,
}

内核定义了一些宏,用于检查页面是否设置了某个特定标志位,或者设置、清空某个标志位。

这些宏的定义在page-flags.h中:

#define PAGEFLAG(uname, lname) TESTPAGEFLAG(uname, lname)        \
SETPAGEFLAG(uname, lname) CLEARPAGEFLAG(uname, lname) #define TESTPAGEFLAG(uname, lname) \
static inline int Page##uname(const struct page *page) \
{ return test_bit(PG_##lname, &page->flags); } #define SETPAGEFLAG(uname, lname) \
static inline void SetPage##uname(struct page *page) \
{ set_bit(PG_##lname, &page->flags); } #define CLEARPAGEFLAG(uname, lname) \
static inline void ClearPage##uname(struct page *page) \
{ clear_bit(PG_##lname, &page->flags); }

以PG_lru为例:

PageLRU:检查页面是否设置了PG_lru表志位。

SetPageLRU:设置页中的PG_lru标志位。

ClearPageLRU:清除液中的PG_lry标志位。

flags处理存放上述标志位之外,还存放了page对应的zone信息。通过set_page_zone讲zone信息设置到page->flags中。

2. _count和_mapcount的区别

2.1 _count解释

_count表示内核中引用该页面的次数。

_count == 0:表示该页面位空闲或即将要被释放。

_count > 0:表示该页面已经被分配切内核正在使用,暂不会被释放。

内核中操作_count的引用技术API有get_page()和put_page()。

static inline void get_page(struct page *page)
{
if (unlikely(PageTail(page)))
if (likely(__get_page_tail(page)))
return;
/*
* Getting a normal page or the head of a compound page
* requires to already have an elevated page->_count.
*/
VM_BUG_ON_PAGE(atomic_read(&page->_count) <= , page);-------判断页面_count值不能小于等于0,因为伙伴系统分配好的页面初始值位1。
atomic_inc(&page->_count);-----------------------------------原子增加引用计数。
}

static inline int put_page_testzero(struct page *page)
  {
      VM_BUG_ON_PAGE(atomic_read(&page->_count) == 0, page);-----_count不能为0,如果为0,说明这页面已经被释放了。
      return atomic_dec_and_test(&page->_count);
  }

void put_page(struct page *page)
{
if (unlikely(PageCompound(page)))
put_compound_page(page);
else if (put_page_testzero(page))------------------------如果减1之后等于0,就会释放页面。
__put_single_page(page);-----------------------------释放页面
}

内核还有一对常用的变种宏:

#define page_cache_get(page)        get_page(page)
#define page_cache_release(page) put_page(page)

_count常用于内核中跟踪page页面的使用情况,常见的用法有:

(1)分配页面时_count引用计数会变成1。

分配页面函数alloc_pages()在成功分配页面后,_count引用计数应该为0,由set_page_refcounter()设置。

/*
* Turn a non-refcounted page (->_count == 0) into refcounted with
* a count of one.
*/
static inline void set_page_refcounted(struct page *page)
{
VM_BUG_ON_PAGE(PageTail(page), page);
VM_BUG_ON_PAGE(atomic_read(&page->_count), page);
set_page_count(page, );
}

(2)加入LRU链表时,page会被kswapd内核线程使用,因此_count引用计数会加1。

以malloc()为用户程序分配内存为例,发生缺页中断后do_anonymous_page()函数成功分配出来一个页面,在设置硬件PTE之前,调用lru_cache_add()函数把这个匿名页面添加到LRU链表中,在这个过程中,使用page_cache_get()宏来增加_count引用计数。

static void __lru_cache_add(struct page *page)
{
struct pagevec *pvec = &get_cpu_var(lru_add_pvec); page_cache_get(page);---------------------增加计数
if (!pagevec_space(pvec))
__pagevec_lru_add(pvec);
pagevec_add(pvec, page);
put_cpu_var(lru_add_pvec);
}

(3)被映射到其他用户进程pte时,_count引用计数会加1。

子进程在被创建时共享父进程地址空间,设置父进程的pte页表项内容到子进程中并增加该页面的_count计数。

(4)页面的private中私有数据。

对于PG_swapable页面,__add_to_swap_cache函数会增加_count引用计数。

对于PG_private页面,主要在block模块的buffer_head中引用。

(5)内核对页面进行操作等关键路径上也会使_count引用计数加1。

2.2 _mapcount解释

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

每个用户进程地址空间都有一份独立的页表,有可能出现多个用户进程地址空间同时映射到一个物理页面的情况,RMAP反向映射系统就是利用这个特性来实现的。

_mapcount引用计数主要用于RMAP反响映射系统中。

_mapcount == -1:表示没有pte映射到页面中。

_mapcount == 0:表示只有父进程映射了页面。

匿名页面刚分配时,_mapcount引用计数初始化为0.

void page_add_new_anon_rmap(struct page *page,
struct vm_area_struct *vma, unsigned long address)
{
VM_BUG_ON_VMA(address < vma->vm_start || address >= vma->vm_end, vma);
SetPageSwapBacked(page);
atomic_set(&page->_mapcount, ); /* increment count (starts at -1) */---------------------设为0
if (PageTransHuge(page))
__inc_zone_page_state(page, NR_ANON_TRANSPARENT_HUGEPAGES);
__mod_zone_page_state(page_zone(page), NR_ANON_PAGES,
hpage_nr_pages(page));
__page_set_anon_rmap(page, vma, address, );
}

_mapcount > 0:表示除了父进程外还有其他进程映射了这个页面。

设置父进程pte页表项内容到子进程中并增加该页面的_mapcount计数。

static inline unsigned long
copy_one_pte(struct mm_struct *dst_mm, struct mm_struct *src_mm,
pte_t *dst_pte, pte_t *src_pte, struct vm_area_struct *vma,
unsigned long addr, int *rss)
{
...
page = vm_normal_page(vma, addr, pte);
if (page) {
get_page(page);--------------------------增加_count计数
page_dup_rmap(page);---------------------增加_mapcount计数
if (PageAnon(page))
rss[MM_ANONPAGES]++;
else
rss[MM_FILEPAGES]++;
}
...
}

3. 页面所PG_locked

PG_locked用于设置页面锁,有两个函数用于申请页面锁:lock_page()和trylock_page()。

lock_page()用于申请页面锁,如果页面锁被其他进程占用,那么睡眠等待。

trylock_page()也同样检查PG_locked位,但是不等待。如果页面的PG_locked置位,则返回false,表明有其他进程已经锁住了页面;返回true表示获取锁成功。

int __sched
__wait_on_bit_lock(wait_queue_head_t *wq, struct wait_bit_queue *q,wait_on_bit_lock()------使用原子位操作,试着去置位,若已经置位,则任务被挂起,直到调用wake_up_bit()唤醒,等待的线程。可以被wake_up_bit唤醒。
wait_bit_action_f *action, unsigned mode)
{
do {
int ret; prepare_to_wait_exclusive(wq, &q->wait, mode);
if (!test_bit(q->key.bit_nr, q->key.flags))
continue;
ret = action(&q->key);
if (!ret)
continue;
abort_exclusive_wait(wq, &q->wait, mode, &q->key);
return ret;
} while (test_and_set_bit(q->key.bit_nr, q->key.flags));
finish_wait(wq, &q->wait);
return ;
} void __lock_page(struct page *page)
{
DEFINE_WAIT_BIT(wait, &page->flags, PG_locked);-----------------------------定义在哪位上等待。 __wait_on_bit_lock(page_waitqueue(page), &wait, bit_wait_io,
TASK_UNINTERRUPTIBLE);
} /*
* lock_page may only be called if we have the page's inode pinned.
*/
static inline void lock_page(struct page *page)
{
might_sleep();
if (!trylock_page(page))---------------------------------------------------如果原page->flags已经被置PG_locked,则调用__lock_page进行等待使用者释放。
__lock_page(page);
} #define test_and_set_bit_lock(nr, addr) test_and_set_bit(nr, addr) static inline int trylock_page(struct page *page)
{
return (likely(!test_and_set_bit_lock(PG_locked, &page->flags)));-----------尝试为page->flags设置PG_locked标志位,并且返回原来标志位的值。所以并不会等待。
}

Linux内存管理 (11)page引用计数的更多相关文章

  1. swift内存管理中的引用计数

    在swift中,每一个对象都有生命周期,当生命周期结束会调用deinit()函数进行释放内存空间. 观察这一段代码: class Person{ var name: String var pet: P ...

  2. 【原创】(六)Linux内存管理 - zoned page frame allocator - 1

    背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本: ...

  3. cocos2dx中的内存管理机制及引用计数

    1.内存管理的两大策略: 谁申请,谁释放原则(类似于,谁污染了内存,最后由谁来清理内存)--------->适用于过程性函数 引用计数原则(创建时,引用数为1,每引用一次,计数加1,调用结束时, ...

  4. OC基础15:内存管理和自动引用计数

    "OC基础"这个分类的文章是我在自学Stephen G.Kochan的<Objective-C程序设计第6版>过程中的笔记. 1.什么是ARC? (1).ARC全名为A ...

  5. 【原创】(十四)Linux内存管理之page fault处理

    背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本: ...

  6. 【原创】(十)Linux内存管理 - zoned page frame allocator - 5

    背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本: ...

  7. 【原创】(七)Linux内存管理 - zoned page frame allocator - 2

    背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本: ...

  8. 【原创】(八)Linux内存管理 - zoned page frame allocator - 3

    背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本: ...

  9. 【原创】(九)Linux内存管理 - zoned page frame allocator - 4

    背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本: ...

随机推荐

  1. 解读经典《C#高级编程》最全泛型协变逆变解读 页127-131.章4

    前言 本篇继续讲解泛型.上一篇讲解了泛型类的定义细节.本篇继续讲解泛型接口. 泛型接口 使用泛型可定义接口,即在接口中定义的方法可以带泛型参数.然后由继承接口的类实现泛型方法.用法和继承泛型类基本没有 ...

  2. 在Windows 下如何使用 AspNetCore Api 和 consul

    一.概念:什么是consul: Consul 是有多个组件组成的一个整体,作用和Eureka,Zookeeper相当,都是用来做服务的发现与治理. Consul的特性: 1. 服务的发现:consul ...

  3. [ASP.NET] ASP.NET Identity 中 ClaimsIdentity 解析

    各位好 : ) 最近筆者在嘗試改用ASP.NET Identity做為新系統的認證方式,發現到網路上給的資訊,不是很完整,所以做為一個工程屍,為了避免大家遇到一樣的問題.特地將一些有趣的地方記錄下來 ...

  4. 【转载】C#工具类:人民币金额转换为中文大写

    在涉及到金融业务这一块的系统设计中,时常能够遇到的情况是需要将阿拉伯数字的人民币金额自动转换为中文的大写金额.现在互联网上也有很多网站提供此类服务,只要你输入人民币的阿拉伯数字金额,输入后自动给你转换 ...

  5. python学习笔记(十 四)、web.py

    使用web.py 通过python进行网页的编写,下面我们来简单了解一哈web.py 的使用 1 url处理 使用特定的url结构来解析我们发送的请求.如下面所示: urls = ( '/login' ...

  6. CentOS7 yum方式安装MySQL5.7

    转载至博客:https://www.cnblogs.com/bigbrotherer/p/7241845.html 在CentOS中默认安装有MariaDB,这个是MySQL的分支,但为了需要,还是要 ...

  7. C#设计模式之二十一访问者模式(Visitor Pattern)【行为型】

    一.引言 今天我们开始讲“行为型”设计模式的第九个模式,该模式是[访问者模式],英文名称是:Visitor Pattern.如果按老规矩,先从名称上来看看这个模式,我根本不能获得任何对理解该模式有用的 ...

  8. 【代码笔记】Web-CSS-CSS 链接(link)

    一,效果图. 二,代码. <!DOCTYPE html> <html> <head> <meta charset="utf-8"> ...

  9. JMeter 内置日期(时间)函数总结

    JMeter 内置日期(时间)函数总结   by:授客 QQ:1033553122 1. 测试环境 apache-jmeter-3.3 下载地址: http://jmeter.apache.org/c ...

  10. C#判断远程计算机的指定端口是否打开的代码

    如下的内容段是关于C#判断远程计算机的指定端口是否打开的内容,应该能对小伙伴有一些用. using System.Net;if(!string.IsNullOrEmpty(txtPort.Text)) ...