1.Linux内核的内存管理模块都对哪些页面进行了统计?
统计项 | 描 述 |
---|
NR_FREE_PAGES | 空闲页面数量 |
NR_ZONE_LRU_BASE | 用于LRU_BASE的统计。LRU链表是从LRU_BASE开始标记的 |
NR_ZONE_INACTIVE_ANON | 不活跃匿名页面数量 |
NR ZONE ACTIVE_ANON | 活跃匿名页面数量 |
NR_ZONE_INACTIVE_FILE | 不活跃文件映射页面数量 |
NR_ZONE_ACTIVE_FILE | 活跃文件映射页面数量 |
NR_ZONE_UNEVICTABLE | 不可回收的页面数量 |
NR_ZONE_WRITE_PENDING | 脏页、正在回写以及不稳定的页面数量 |
NR_MLOCK | 使用mlock()锁住的页面数量 |
NR_PAGETABLE | 用于页表的页面数量 |
NR_KERNEL STACK KB | 用于内核栈的页面数量 |
NR_BOUNCE | 跳跃页面的数量 |
NR_ZSPAGES | 用于zsmalloc机制的页面数量 |
NR_FREE_CMA_PAGES | CMA中的空闲贝面数量 |
NR_VM_ZONE_STAT_ITEMS | ZONE中vm stat计数值的项数 |
2.请解释/proc/meminfo节点中每一项的含义。
统计项 | 描述(实现) |
---|
MemTotal | 系统当前可用物理内存总量,通过读取全局变量_toalrampages 来获得 |
MemFree | 系统当前剩余空闲物理内存,通过读取全局变量vm_zone_stat[]数组中的NR_FREE_PAGES来获得 |
MemAvailable | 系统中可使用页面的数量,由si_mem_available()函数来计算。公式为Available=memfree+pagecache+reclaimable-totalreserve pages. 这里包括了空闲页面(memfree)、文件映射页面(pagecache)、可回收的页面(reclaimable),最后减去系统保留的页面 |
Buffers | 用于块层的缓存,由nr_blockdev_pages()函数来计算 |
Cached | 用于页面高速缓存的页面。计算公式为Cached=NR_FILE_PAGES-swap_cache-Buffers |
SwapCached | 这里统计交换缓存的数量,交换缓存类似于内容缓存,只不过它对应的是交换分区,而内容缓存对应的是文件。这里表示匿名页面曾经被交换出去,现在又被交换回来,但是页面内容还在交换缓存中。 |
Active | 活跃的匿名页面(LRUACTIVE_ANON)和 活跃的文件映射页面(LRU_ACTIVE_FILE) |
Inactive | 不活跃的匿名页面(LRUINACTIVE_ANON) 和不活跃的文件映射页面(LRU_NACTIVE_FILE) |
Active(anon) | 活跃的匿名页面(LRU_ACTIVE_ANON) |
Inactive(anon) | 不活跃的匿名页面(LRU_INACTIVE_ANON |
Active(file) | 活跃的文件映射页面(LRU_ACTIVE_FILE) |
Inactive(file) | 不活跃的文件映射页面(LRUINACTIVE_FI LE) |
Unevictable | 不能回收的页面(LRUUNEVICTABLE) |
Mlocked | 不会被交换到交换分区的页面,由全局的vm zone_stat[]中的NR_MLOCK 来统计 |
SwapTotal | 交换分区的大小 |
SwapFree | 交换分区的空闲空间大小 |
Dirty | 脏页的数量,由全局的vm_node_stat[]中的NR_FILE_DIRTY来统计 |
Wnteboel | 正在回写的页面数量,由全局的vm_node_stat[]中的NR_WRITEBACK来统计。 |
AnonPages | 统计有反向映射(RMAP)的页面,通常这些页面都是匿名页面并且都映射到了用户空间,但是并不是所有匿名页面都配置了反向映射,如部分的 shmem 和tmpfs页面就没有设置反向映射。这个计数由全局的vm_node_stat[]中的NR_ANON_MAPPED来统计 |
Mapped | 统计所有映射到用户地址空间的内容缓存页面,由全局的vmnode_stat[]中的NR_FILE_MAPPED 来统计 |
Shmem | 共享内存(基于tmpfs 实现的shmem、devtmfs等)页面的数量,由全局的vm node stat中的 NR SHMEM 来统计 |
KReclaimable | 内核可回收的内存,包括可回收的slab页面(NR_SLAB_RECLAIMABLE)和其他的可回收的内核页面(NR_KERNEL_MISC_RECLAIMABLE) |
Slab | 所有slab 页面,包括可回收的 slab页面(NR_SLAB_RECLAIMABLE)和不可回收的slab 页面(NR_SLAB_UNRECLAIMABLE) |
SReclaimable | 可回收的slab 页面(NR_SLAB_RECLAIMABLE) |
SUnreclaim | 不可回收的slab 页面(NR_SLAB_UNRECLAIMABLE) |
KernelStack | 所有进程内核栈的总大小,由全局的 vm zone stat门中的NR_KERNEL_STACK_KB来统计 |
PageTables | 所有用于页表的页面数量,由全局的vmzone_stat[]中的NR PAGETABLE来统计 |
NFS_Unstable | 在NFS中,发送到服务器端但是还没有写入磁盘的页面(NR_UNSTABLE_NFS) |
WritebackTmp | 回写过程中使用的临时缓存(NR_WRITEBACK_TEMP) |
VmallocTotal | vmalloc 区域的总大小 |
VmallocUsed | 已经使用的vmalloc 区域总大小 |
Percpu | percpu机制使用的页面,由pcpu_nr_pages函数来统计 |
AnonHugePages | 统计透明巨页的数量 |
ShmemHugePages | 统计在shmem 或者 tmpfs中使用的透明巨页的数量 |
ShmemPmdMapped | 使用透明巨页并且映射到用户空间的 shmem 或者 tmpfs 的页面数量 |
CmaTotal | CMA机制使用的内存 |
CmaFree | CMA机制中空闲的内存 |
HugePages_Total | 普通巨页的数量,普通巨页的页面是预分配的 |
HugePages_Free | 空闲的普通巨页的数量 |
Hugepagesize | 普通巨页的大小,通常是2MB或者1GB |
Hugetlb | 普通巨页的总大小,单位是KB |
3.为什么/proc/meminfo节点中的MemTotal不等于QEMU虚拟机中分配的内存大小?
- 这是因为我们reserved部分内存,而reserved的内存分为动态申请和静态配置。
- 如内核静态使用的内存如内核代码段,是动态申请的。
- 如在dts配置配置reserved-memory节点则属于静态申请。
- 可以通过cat /sys/kernel/debug/memblock/reserved节点查看。
- 特别注意通过reserved-memory节点配置时如果带no-map属性则该内存不会再reserved节点体现,也不会被内核管理。
- 无论通过哪种方式申请reserved的内存都有可能释放,释放时会打印Freeing unused 。。。。
- 故有如下公式:物理内存=MemTotal + reserved内存 + no-map内存。
4.为什么slab 要区分 SReclaimable和SUnreclaim?
- 一个slab分配器由一个或者多个连续的物理页面组成。在为slab 分配器分配物理页面时根据slab描述符(cachep->flags)是否设置了SLAB_RECLAIM_ACCOUNT标志位来判断这些页面是属于SReclaimable还是属于SUnreclaim。
- 而在创建slab描述符时若发现设置了SLAB_RECLAIM_ACCOUNT,那么分配物理页面的行为就是可回收的,即设置__GFP_RECLAIMABLE,表示这些页面是可以被slab机制的收割机回收的。
- 在slab机制里,有一个定时器会定时扫描和检查哪些slab分配器可以被销毁,如果一个slab分配器中都是空闲的slab对象,那么这个slab分配器就可以被回收,并且slab分配器占用的页面会被释放,详见cache_reap()函数。
- 因此,统计SReclaimable和SUnreclaim页面的含义是在计算系统可用的总内存数量,即meminfo中的MemAvailable,详见si_mem_available()函数。
5.在/proc/meminfo节点中,为什么Active(anon)+Inactive(anon)不等于AnonPages?
- 我们知道Active(anon)表示LRU链表中的活跃匿名页面,Inactive(anon)表示LRU链表中的不活跃匿名页面,这两个值相加,表示系统的LRU链表中的总匿名页面数量。
- 而AnonPages表示和用户态进程地址空间建立映射关系。当一个匿名页面和进程地址空间建立映射关系时会调用page_add_new_anon_rmap()函数来新增一个RMAP。
- 但是shmem(基于tmpfs实现)使用的页面会被添加到系统的匿名页面的LRU链表中,因为它会被计入Active(anon)或Inactive(anon)之中。主要原因是shmem使用的页面基于RAM内存,它可以被写入交换分区里。在分配shmem页面时设置了
PG_SwapBacked
标志位,见shmem_alloc_and_acct_page()函数。 - 通过判断
PG_SwapBacked
标志位来确定将页面添加到匿名页面的LRU链表还是文件映射的LRU链表中,见page_is_file_cache()函数。若没有设置PG_SwapBacked
标志位,则页面时文件映射的页面,会被添加到文件映射的LRU链表中;否则,被添加到匿名页面的LRU链表中。 - 另外,shmem页面并没有计入AnonPages中,而是计入了MM_SHMEMAPAGES类型的计数值(即Shmem)中。shmem页面一方面被添加到了匿名页面的LRU链表里,另一方面被统计到文件映射页面的计数中,是个“另类”页面。
6.在/proc/meminfo节点中,为什么Active(file)+Inactive(file)不等于Mapped?
- Active(file)+Inactive(file)表示系统LRU链表中所有文件映射页面的总和。
- 而Mapped表示统计所有映射到用户地址空间的内容缓存页面,由NR_FILE_MAPPED来统计。当一个内容缓存映射到用户态的进程地址空间时,会调用page_add_file_rmap()函数来建立RMAP,并增加NR_FILE_MAPPED计数值。
- 有一个特殊情况需要考虑,就是shmem页面。它会被计入NR_FILE_MAPPED计数值中,但是它会设置PG_SwapBacked标志位,因此它会被计入匿名页面。当创建一个shmem页面是会把他计入NR_FILE_PAGES和NR_SHMEM计数值中。
7.在/proc/meminfo节点中,为什么Active(file)+Inactive(file)不等于Cached?
- Cached计数值的计算公式是Cache=NR_FILE_PAGES-swap_cache-Buffers。又因为shmem页面被计入NR_FILE_PAGES里,它在匿名页面LRU链表的计数值里。Active(file)+Inactive(file)表示系统的LRU链表中所有文件映射页面的总和。
- 因此LRU链表中所有文件映射页面总和不等于Cache计数值。
8./proc/PID/status(PID表示进程的ID)节点中有不少和具体进程内存相关的信息,请简述这些信息的含义。
- proc文件系统包含每个进程的相关信息,其中/proc/PID/status节点有不少和具体进程内存相关的信息。下面说明与内存相关的信息
cat /proc/*/status | grep -E 'Name|Pid|Vm*|Rss*|Hu*'
成员名 | 含义 |
---|
Name | 进程的名称 |
Pid | PID。 |
VmPeak | 进程使用的最大虚拟内存,通常情况下它等于进程的内存描述符mm 中的 total_vm. |
VmSize | 进程使用的虚拟内存,它等于mm->total_vm。 |
VmLck | 进程锁住的内存,它等于mm->locked_vm,这里指使用mlock()锁住的内存。 |
VmPin | 进程固定住的内存,它等于mm->pinned_vm,这里指使用 get_user_page()固定住的内存。 |
VmHWM | 进程使用的最大物理内存,它通常等于进程使用的匿名页面、文件映射页面以及共享内存页面的大小总和。 |
VmRSS | 进程使用的最大物理内存,它常常等于VmHWM,计算公式为 VmRSS= RssAnon+RssFile+RssShmem. |
RssAnon | 进程使用的匿名页面,通过get_mm_counter(mm,MM_ANONPAGES)获取。 |
RssFile | 进程使用的文件映射页面,通过get_mm_counter(mm, MM_FILEPAGES)获取。 |
RssShmem | 进程使用的共享内存页面,通过get_mm_counter(mm, MM_SHMEMPAGES)获取。 |
RssFile | 进程使用的文件映射页面,通过get_mm_counter(mm, MM_FILEPAGES)获取 RssShmem:进程使用的共享内存页面,通过getmm_counter(mm,MM SHMEMPAGES获取。 |
VmData | 进程私有数据段的大小,它等于 mm->data_vm。 |
VmStk | 进程用户栈的大小,它等于mm->stack_vm。 |
VmExe | 进程代码段的大小,通过内存描述符mm中的start_code和end_code两个成员获取。 |
VmLib | 进程共享库的大小,通过内存描述符mm中的exec_vm和VmExe计算。 |
VmPTE | 进程页表大小,通过内存描述符 mm 中的pgtables_bytes 成员获取。 |
VmSwap | 进程使用的交换分区的大小,通过get_mm_counter(mm,MM_SWAPENTS)获取。 |
HugetlbPages | 进程使用巨页的大小,通过内存描述符 mm 中的 hugetlb_usage成员获取。 |
9./proc/meminfo节点中SwapTotal减去SwapFree等于系统中已经使用的swap内存大小,我们称之为 S_swap。另外,我们写一个小程序来遍历系统中所有的进程,并把进程中 proc/PID/status节点的VmSwap值都累加起来,我们把它称为P_swap,为什么这两个值不相等?
- S_swap并不一定等于P_swap的原因是因为shmem的原因。
- shmem(共享内存)比较特殊,它是基于tmpfs来实现的,本质上它是基于RAM的一个文件系统,因此它具有文件的属性,如有文件节点、页面高速缓存等。另外,它的内存不能随便丢弃。当系统内存短缺时会把shmem暂时写入交换分区以便腾出内存,因此它有部分匿名页面的属性。
- 但shmem页面并没有被统计到进程MM_SWAPENTS计数中,/proc/PID/status节点中的VmSwap不包含被写入交换区的shmem页面。
10.请简述min_free_kbytes的含义和作用。
- Linux内核为了防止内存被恶意进程占用,在每个内存管理区设置了一部分预留内存,即最低警戒水位(watermark[WMARK_MIN])。进程分配内存的行为是有优先级的,对于普通优先级的分配行为,是不能访问预留内存的,只有对于高优先级的分配行为,才能访问,如高优先级的进程可以通过设置__GFP_HIGH、__GPF__ATOMIC甚至__GPF_MEMALLOC来访问预留内存。若系统预留内存小于1024kb,那么可能会导致系统出问题,具有高优先级分配行为的进程没法得到内存。
11.请简述lowmem_reserve_ratio的含义和作用。
- lowmem_reserve[]数组用于防止页面分配器过渡的从低端内存管理区中分配内存,可以由lowmem_reserve_ratio节点来修改,以页面分单位。
- 低端内存管理区的内存一般有特殊用途,如ZONE_DMA用于ISA总线的设备,通常有些应用程序分配内存之后会使用mlock()来锁住这部分内存,导致这些内存就不能被交换到交换分区,从而导致ZONE_DMA变少。
- 此外,为防止系统过早在低端内存管理区中触发OOM Killer机制,而系统的高端内存管理区却又大量空闲内存。
12.请简述zone_reclaim_mode的含义和作用。
- 当页面分配器在一个内存管理区里分配失败时,若zone_reclaim_mode为0,则表示可以从下一个内存管理区或者下一个内存节点中分配内存;否则,表示可以在这个内存管理区中进行一些内存回收,然后继续尝试在该内存管理区中分配内存。
- zone_reclaim_mode是一个按位或操作的数值,我们可以根据如下位来设置不同的组合。
13.请简述 watermark_scale_factor的含义和作用。
- 除了min_free_kbytes,watermark_scale_factor也会影响每个内存管理区的低水位和高水位。
- 内存管理区的低水位会影响kswapd内存线程唤醒的时机,内存管理区的高水位会影响kswapd内核线程进入睡眠的时机。通常,当页面分配器发现在低水位分配失败时,会唤醒kswapd内核线程。而当内存管理区水位高于高水位时,会让kswapd内核线程停止工作并进入睡眠状态。
- 在__setup_per_zone_wmarks()函数中,watermark_scale_factor的默认值为10,分母为10000,因此表示两个水位之间的距离是系统总内存的0.1%,如最低警戒水位与低水位的差距是总内存的0.1%,watermark_scale_factor最大可以设置为1000,即两个水位之间的差距最大为总内存的10%。
14.请简述影响脏页回写的参数有哪些?它们的含义和作用分别是什么?
dirty_background_bytes
:当脏页所占的内存数量(指所有可用内存,即空闲页面+可回收内存页面)超过dirty_background_bytes时,内核回写线程(writeback thread)开始回写脏页。dirty_background_ratio
:当脏页所占的百分比达到dirty_background_ratio时,内核回写线程开始回写脏页数据,直到脏页比例低于此值。注意,对于dirty_background_bytes和dirty_background_ratio,我们只能设置其中一个。当设置其中一个时,另外一个立即变成0。dirty_background_ratio的默认值为10。dirty_bytes
:当系统的脏页总数达到dirty_bytes值时,write系统调用会被阻塞,并开始回写脏页数据,直到脏页总数低于此值。注意,该值不能设置为小于两个页面大小的字节数,否则,设置不生效并且系统会默认加载之前的旧值。dirty_ratio
:当脏页所占的百分比(空闲内存页+可回收内存页)达到dirty_ratio时,write系统调用被阻塞并开始回写脏页数据,直到脏页比例低于此值。dirty_ratio的默认值为20。注意,对于dirty_ratio和dirty_bytes,我们只能设置其中一个。dirty_expire_centisecs
:脏数据的过期时间。当内核回写线程被唤醒后会被检查哪些数据的存在时间超过了这个时间,并将这些脏数据回写到磁盘,单位是百分之1秒,也就是10ms、该值默认是3000,即若脏数据的存在时间超过30s,那么内核回写线程唤醒之后优先回写这些脏数据。dirty_writeback_centisecs
:内核回写线程周期性唤醒的时间间隔,默认是5s。drop_caches
:用来回收干净的页面高速缓存和一些可以回收的slab对象,如文件系统中inode、dentries等。其默认值的含义如下。