linux内存中的page,pte,alloc_page的flag
内核中关于内存的这几个flag容易混淆,他们的功能相互关联,下面简要总结一下他们的区别,其中的重要flag的用途页表项的flag:使用页表项的有两个:CPU和MMU,MMU的功能有两个,将虚拟地址转换为物理地址,检查访问权限是否合法,它是arch完全相关的page的flag:page是物理地址空间管理的元数据,它是纯粹的软件概念,基本是通用的alloc_page的flag:根据物理内存的稀有程序,
内核中关于内存的这几个flag容易混淆:pte,page,alloc_page,他们的功能相互关联,下面简要分析一下他们的区别和其中的重要flag的场景
- 页表项的flag
使用页表项的有两个对象:CPU和MMU,其中主要是CPU设置页表,MMU使用页表.
MMU的功能有两个:
1.将虚拟地址转换为物理地址
2.检查访问权限是否合法,它是arch完全相关的 - page的flag
page是物理地址空间管理的元数据,它是纯粹的软件概念,基本是通用的 - alloc_page的flag mask
根据物理内存的稀有程序,页帧受到区别对待,分成了ZONE_DMA,ZONE_DMA32,ZONE_NORMAL,ZONE_HIGH;而根据得到的物理页用途不同,一种是在内核运行期间一直占用,另一个种给进程使用,在进程结束就会释放回buddy,这两个页的生命周期对内存碎片的影响程度不同,从这个角度根据可移动性也进行了划分;另一个是当前申请物理内存的紧急程度也不同,在内存紧张的情况下特别是当空闲页小于low水位时得到的对待也不同;
分配物理页不仅需要size,还需要上面这些指示标志.
alloc_page
1.指示首先要从哪个ZONE中申请页,它并没有直接使用ZONE_DMA区域的值而是使用flag中的低3位存储zone信息,其中什么也不设置即全是0代表ZONE_NORMAL,它是比较特殊的;
第4位表示迁移类型的__GFP_MOVABLE
,可以和前面3位zone信息的任何一个组合使用
prefer zone | ZONE_MOVABLE | ZONE_DMA32 | ZONE_HIGHMEM | ZONE_DMA |
---|---|---|---|---|
zone modifier | bit3 | bit2 | bit1 | bit0 |
数组GFP_ZONE_TABLE
中存储其了不同zone信息和迁移类型的组合关系,其中一些是互斥的,例如0x3既可以用DMA又可以用HIGHMEM,互相矛盾,标记为BAD:
* bit result
* =================
* 0x0 => NORMAL
* 0x1 => DMA or NORMAL
* 0x2 => HIGHMEM or NORMAL
* 0x3 => BAD (DMA+HIGHMEM)
* 0x4 => DMA32 or DMA or NORMAL
...
让开发者直接使用这些位信息是非常不友好的,所以使用宏的形式对这些位信息进行了包装.
标记 | 用途 |
---|---|
GFP_DMA | 从ZONE_DMA中分配 |
__GFP_HIGHMEM | 从ZONE_HIGHMEM中分配 |
GFP_DMA32 | 从ZONE_DMA32中分配 |
__GFP_MOVABLE | 分配的页面是可迁移的,而不是从ZONE_MOVABLE中申请 |
内核还提供了一种zone分配fallback机制,当首选的ZONE空闲页满足不了需求的时候,可以从fallback的zone中申请,通常为MOVABLE=>HIGHMEM=>NORMAL=>DMA32=>DMA
,假如从NORMAL申请失败则尝试从DMA32中申请,如果还失败则从DMA区域中申请.
和迁移类型协作的时候,首先尝试同一个ZONE中的其他迁移类型(fallback),如果失败则尝试同一个node的其他zone.
2.指示申请者的上下文,例如中断上下文中进行页申请时不能休眠
Flag | Description |
---|---|
__GFP_WAIT | 申请页的上下文不是紧急的,可以进行睡眠或者重新调度 |
__GFP_HIGH | 在内核线程或者是高优先级的进程中申请的,它可以使用emergecy pool的内存 在申请page时会检查当前空闲页数量是否小于watermark,它允许水位降低到普通上下文水位的一半 |
__GFP_IO | 申请页的上下文正在做底层的IO操作,如果内存紧张不允许发生底层IO来获取空闲页 |
__GFP_FS | 文件系统中申请页,如果内存紧张不要通过收缩文件系统数据来获取空闲页 |
__GFP_NOWARN | 申请页失败时会打印WARNING信息,抑制这种信息打印,调用者会处理这种场景 |
__GFP_REPEAT | 内存紧张时多次尝试回收页,最终没有回收到任何页时返回失败 |
__GFP_NOFAIL | 内存紧张时无限尝试回收页,直至回收到可用页 |
__GFP_NORETRY | 内存紧张时不需要多次进行页回收操作 |
其中__GFP_IO
和__GFP_FS
标记使用场景:在文件系统或者IO栈上操作中进行页申请操作,此时如果空闲页到达了min水位进行同步页面回收,在页面回收时进行脏页回写或者swap操作,这些操作同样需要进行申请页,这样形成一个死锁状态.
在文件系统上进行页申请需要标记__GFP_FS
,在IO栈操作时标记__GFP_IO
,而标记__GFP_IO
时一般也标记了__GFP_FS
,文件系统操作通常离不开IO操作.
提供了常用场景的flag组合给开发者使用,驱动开发者不需要关心内存管理的细节.
flag | 组成的flag | 使用场景 |
---|---|---|
GFP_NOWAIT | (GFP_ATOMIC & ~__GFP_HIGH) | 调用者不能休眠或者调度,但是不会使用emergency pool的内存,它可能分配失败,中断上下文使用 |
GFP_ATOMIC | (__GFP_HIGH) | 调用者不能休眠或者调度,允许使用emergency pool的内存,优先级最高的api,中断上下文中使用 |
GFP_NOIO | (__GFP_WAIT) | 调用者可以休眠,调度走,触发页面回收时不进行IO操作 |
GFP_NOFS | (__GFP_WAIT | __GFP_IO) | 调用者可以休眠,但是内存紧张的时候不能进行文件系统内容回收 |
GFP_KERNEL | (__GFP_WAIT | __GFP_IO | __GFP_FS) | 调用者可以休眠,但是内存紧张的时候不能进行任何的回写操作 普通的进程上下文使用,内核中使用最广泛的上下文场景 |
GFP_TEMPORARY | (__GFP_WAIT| __GFP_IO | __GFP_FS | __GFP_RECLAIMABLE) | 调用者可以休眠,但是内存紧张的时候不能进行任何的回写操作,优先从RECLAIM类型中分配 |
GFP_USER | (__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_HARDWALL) | 用户进程申请的,但是内核或者设备也可以直接访问;主要是设备将buffer映射到用户空间,但是驱动还能操作DMA进行操纵buffer |
GFP_HIGHUSER | (__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_HARDWALL | __GFP_HIGHMEM) | 和GFP_USER类似,但是设备可以访问高端内存 |
分配页面时,buddy会检查当前页分配后是否过低,有个水位watermark在启动时根据zone的大小计算high,low,min的数量.当空闲页小于low时,会唤醒kswapd进行回收操作并且返回页给申请者,kswapd发现空闲页数量大于high时才终止页回收,这样可以减少频繁的触发页回收操作;
如果继续频繁申请页,到达min时,此时就会开始同步直接页回收,一直回收到足够的页之后才会返回分配的页.
对于GFP的flag中__GFP_HIGH
会让系统对于min水位容忍度更强,平常min=28个页,低于这个阈值时就会触发同步页面回收,使用`__GFP_HIGH`时低于min=24个页才会触发同步页面回收.
reflink:https://lwn.net/Articles/594725/
https://lwn.net/Kernel/Index/#gfp_t
pte flag
pte是给MMU使用的,各个厂家的MMU都不一样,它是arch相关的.
在x86上cr3加载一级页表项PGD,而如何通过虚拟地址找到物理地址呢?通常我们看到内核中通过pgd_val, pte_val,pgd_offset等操纵页表项,这些内核的代码都是给CPU执行的,是属于软件的,而MMU就是将这些软件行为进行固化成电路,原理是相同的,不再赘述.
pte的flag定义在include/asm-x86_64/pgtable.h
#define _PAGE_BIT_PRESENT 0
#define _PAGE_BIT_RW 1
#define _PAGE_BIT_USER 2
#define _PAGE_BIT_ACCESSED 5 //当页被访问时设置,MMU自动设置该位,页回收时根据该标志位在inactive_list,active_list中移动位置
#define _PAGE_BIT_DIRTY 6 //当pte present时有物理页映射,它描述dirty,writeback过程使用。当pte no present时,复用该标志位来表示后备存储是否是file。
#define _PAGE_BIT_PSE 7 /* 4 MB (or 2MB) page */ 系统设置page为4k,当设置PSE设置的是4M/2M的大页
#define _PAGE_BIT_GLOBAL 8 /* Global TLB entry PPro+ */
#define _PAGE_BIT_NX 63 /* No execute: only valid after cpuid check */
通常页表就是给MMU用的,页表项记录虚实地址对应关系和页的权限,但是Linux通过MMU异常实现了一些复杂的功能,例如COW,按需分配物理页,swap等.
PRESENT | RW | USER | address | VMA权限 | 场景 |
---|---|---|---|---|---|
0 | READ | USER | 合法的VMA内 | MAY_READ|MAY_EXEC | 1.如果pte为空,映射一个ZERO PAGE 2.pte中_PAGE_BIT_DIRTY置位,则按需读文件 3.如果pte中地址不为空,它表示swap信息,完成swap in操作 |
1 | * | USER | 合法的VMA内 | NONE | 非法错误,见下面补充描述 |
0 | * | USER | 不在合法的VMA内 | VM_GROUPUP | 扩充栈 |
0 | WRITE | USER | 合法的VMA内 | MAY_READ | 按需分配物理页 |
1 | WRITE | USER | 合法的VMA内 | MAY_WRITE | COW按需分配页 |
0 | * | KERNEL | addr >VMALLOC_START && addr < VMALLOC_END | vmap_struct只有分配类型的标志:IO,memory | vmalloc区域页表同步 |
用户空间编程的时候为了防止错误访问,可能会mmap一段没有权限的区域来充当gap,多线程的线程私有空间都需要一些gap区域来防止越界访问,这部分属于设计上的防护措施,包括内核中的gap区域都利用了MMU对于权限访问的检查。
page flag
page的flag定义在include/linux/page-flags.h
,它是软件通用的。每个物理页都需要有一个page对象来管理,内存越大,page占用内存越多。例如8G内存,页帧大小4k,每个page占64bytes,总共就需要128M=8G/4k*64
的内存,在实际使用中需要严格管理page对象,所以它里面有很多的union的使用,在page处于不同管理下中代表不同功能。
#define PG_locked 0 //当把页内容写入磁盘或者从磁盘载入到页时,置位,在此期间不能对该页数据进行读/写;在IO完成后清除该标志位
#define PG_error 1 //当把页内容写入磁盘或者从磁盘载入到页时,如果发生IO错误,置位来向调用者传递错误信息;调用者进行异常处理后,清除标志
#define PG_referenced 2 //和PG_active一起完成lru算法,见下面
#define PG_uptodate 3 //标记当前内容是否有效,当读IO成功完成后,置位;写操作发生错误后,清除标志
#define PG_dirty 4 //当内存和磁盘中内容不一致的时候,需要写入磁盘永久保存。当发生写操作后(write/truncate等)标记,writeback定时回写脏页,回写完成后清除标志
#define PG_lru 5 //页在活动或非活动页链表中,参与lru算法 ,见下面
#define PG_active 6 //置位表示在active_list中,否则在Inactive_list中;和PG_referenced一起完成lru算法
#define PG_slab 7 //page被slab使用
#define PG_owner_priv_1 8 /* Owner use. If pagecache, fs may use*/
#define PG_arch_1 9 //在x86 上没有使用
#define PG_reserved 10 //设置标志使页面不被交换或者标记没有被使用。
#define PG_private 11 /* If pagecache, has fs-private data */
#define PG_writeback 12 //writeback正在将页写到磁盘上
#define PG_nosave 13 /* Used for system suspend/resume */
#define PG_compound 14 //复合页
#define PG_swapcache 15 //页属于swap的page cache
#define PG_mappedtodisk 16 /* Has blocks allocated on-disk */
#define PG_reclaim 17 //页面回收内存对页已经做了写入磁盘的标记
#define PG_nosave_free 18 /* Used for system suspend/resume */
#define PG_buddy 19 //page时空闲的,在buddy管理中
1.内核中连续的page组合在一起,buddy中管理页是以2^order
的形式管理的,从挂载在free_area的不同list来区分当前连续页的数量;当申请页时,也是以2^order的形式申请的,但是从buddy移除掉之后就不知道究竟有几个连续的页,通过PG_compound来标记是复合页,通过lru.next来实际管理之间的关系。
2.随着linux的长时间运行,空闲页面会越来越少,为了防止linux内核进入请求页面的僵局中,Linux内核采用页面回收算法从用户进程和内核page cache中回收内存,另外slab也是可以回收的,不过只是那些注册了收缩函数的的slab,例如dcache和icache,根据需要把要回收页框的内容交换到磁盘上的交换区。页面回收算法使用LRU的思想,但是给了它两次机会。它使用PG_referenced来标示页最近是否被引用过,使用PG_active来标示页当前的活跃程度。
有两个接口mark_page_accessed和page_referenced,使用mark_page_accessed处理刚刚引用过的页在LRU中的位置,根据当前页在inactive_list/active_list上和PG_referenced进行状态迁移;
inactive,unreferenced -> inactive,referenced
inactive,referenced -> active,unreferenced
active,unreferenced -> active,referenced
shrink_list中使用page_referenced进行清除当前PG_referenced标志,如果当前页在inactive_list并且当前PG_referenced没有设置,那这个页妥妥的要被回收了。
更多推荐
所有评论(0)