精华内容
下载资源
问答
  • total——总物理内存 used——已使用内存,一般情况这个值会比较大,因为这个值包括了cache+应用程序使用的内存 free——完全未被使用的内存 shared——应用程序共享内存 buffers——缓存,主要用于目录方面,inode...

    # sync

    # echo 1 > /proc/sys/vm/drop_caches

    echo 2 > /proc/sys/vm/drop_caches

    echo 3 > /proc/sys/vm/drop_caches

    cache释放:

    To free pagecache:

    echo 1 > /proc/sys/vm/drop_caches

    To free dentries and inodes:

    echo 2 > /proc/sys/vm/drop_caches

    To free pagecache, dentries and inodes:

    echo 3 > /proc/sys/vm/drop_caches

    说明,释放前最好sync一下,防止丢数据。

    因为LINUX的内核机制,一般情况下不需要特意去释放已经使用的cache。这些cache起来的内容可以增加文件以及的读写速度。

    先说下free命令怎么看内存

    48304ba5e6f9fe08f3fa1abda7d326ab.png

    [[email protected] proc]# free

    total   used   free     shared buffers cached

    Mem: 515588 295452 220136 0      2060   64040

    -/+ buffers/cache: 229352 286236

    Swap: 682720 112 682608

    48304ba5e6f9fe08f3fa1abda7d326ab.png

    其中第一行用全局角度描述系统使用的内存状况:

    total——总物理内存

    used——已使用内存,一般情况这个值会比较大,因为这个值包括了cache+应用程序使用的内存

    free——完全未被使用的内存

    shared——应用程序共享内存

    buffers——缓存,主要用于目录方面,inode值等(ls大目录可看到这个值增加)

    cached——缓存,用于已打开的文件

    note:

    total=used+free

    used=buffers+cached (maybe add shared also)

    第二行描述应用程序的内存使用:

    前个值表示-buffers/cache——应用程序使用的内存大小,used减去缓存值

    后个值表示+buffers/cache——所有可供应用程序使用的内存大小,free加上缓存值

    note:

    -buffers/cache=used-buffers-cached

    +buffers/cache=free+buffers+cached

    第三行表示swap的使用:

    used——已使用

    free——未使用

    手动执行sync命令(描述:sync 命令运行 sync 子例程。如果必须停止系统,则运行 sync 命令以确保文件系统的完整性。sync 命令将所有未写的系统缓冲区写到磁盘中,包含已修改的 i-node、已延迟的块 I/O 和读写映射文件)

    [[email protected] test]# echo 3 > /proc/sys/vm/drop_caches

    [[email protected] test]# cat /proc/sys/vm/drop_caches

    3

    !将/proc/sys/vm/drop_caches值设为3

    有关/proc/sys/vm/drop_caches的用法在下面进行了说明

    /proc/sys/vm/drop_caches (since Linux 2.6.16)

    Writing to this file causes the kernel to drop clean caches,

    dentries and inodes from memory, causing that memory to become

    free.

    To free pagecache, use echo 1 > /proc/sys/vm/drop_caches; to

    free dentries and inodes, use echo 2 > /proc/sys/vm/drop_caches;

    to free pagecache, dentries and inodes, use echo 3 >

    /proc/sys/vm/drop_caches.

    Because this is a non-destructive operation and dirty objects

    展开全文
  • Linux 内存管理

    2021-05-14 00:20:37
    虚拟内存1.1. 为什么要使用虚拟内存技术1.2. 理论前提1.3. 虚拟内存实现1.4. 页机制1.5.... slabLinux 内存管理虚拟内存众所周知,Linux采用虚拟内存管理技术,每个进程都有独立的进程地址空间。这是Li...

    虚拟内存

    1.1. 为什么要使用虚拟内存技术

    1.2. 理论前提

    1.3. 虚拟内存实现

    1.4. 页机制

    1.5. MMU

    1.6. 物理内存映射

    进程的内存分布

    2.1. 内核态

    2.2. 用户态

    进程内存管理

    3.1. 物理内存管理

    3.2. node、zone、page

    3.3. 伙伴算法

    3.4. slab

    Linux 内存管理

    虚拟内存

    众所周知,Linux采用虚拟内存管理技术,每个进程都有独立的进程地址空间。这是Linux内存管理的基础,所以我们先讲解一下虚拟内存技术。虚拟内存技术是基于交换技术(swap)的,只不过交换的是页或者段。

    为什么要使用虚拟内存技术

    或者说使用虚拟技术的好处:

    扩大内存(主要催生虚拟内存原因)

    安全性提高(不直接访问物理内存)

    易于开发(每个进程拥有独立的用户空间)

    举一个栗子来说明:

    如果我们直接使用物理内存,CPU需要某一个值的时候,直接去物理内存中取就可以了,简单直接。

    但是我们需要知道物理地址的值,每次程序开始执行,也就是执行程序从磁盘被load到物理内存中之后,我们必须告诉CPU,程序是从哪一个地址开始执行的(即PC寄存器的值);

    还有一个致命的缺点是:程序使用的内存会被物理内存所限制,比如我们的机器上只有512M内存,那我们的程序就不能使用需占1G内存的程序了,这点或许是催生虚拟内存产生的最主要原因。

    理论前提

    我们知道想要装下超过物理内存大小的程序,那么我们可以选择增加内存条(硬扩充),同时也可以通过软件进行扩充(软扩充),其中交换技术(包括虚拟内存技术)就属于软扩充。

    虚拟内存技术就是只装入程序的一部分,就开始运行整个程序。那么这样内存压力就会变小,能够运行大内存需求的软件。

    那么这么做可以吗?当然可以,当时提出一个理论来支撑:程序局部性原理。

    在一段时间内,整个程序的执行仅限于程序中的某一部分。相应地,执行所访问的存储空间也局限于某个内存区域。局部性原理又表现为:时间局部性和空间局部性。

    时间局部性是指如果程序中的某条指令一旦执行,则不久之后该指令可能再次被执行;如果某数据被访问,则不久之后该数据可能再次被访问。空间局部性是指一旦程序访问了某个存储单元,则不久之后。其附近的存储单元也将被访问。

    虚拟内存实现

    实现原理:

    当进程要求运行的时,不是将他的全部信息装入内存,而是将其一部分先装入内存,另一部分暂时留在外存,进程在运行过程中,要使用信息不在内存时,发生中断,由操作系统将他们调如内存,以保证进程的正常运行。

    但是呢,从进程角度来说,会认为它拥有连续可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。

    fb345b94501f

    而这一部分外部磁盘存储(辅存)就叫做交换分区。交换分区的主要功能是当全部的 RAM 被占用并且需要更多内存时,用磁盘空间代替 RAM 内存。

    内核使用一个内存管理程序来检测最近没有使用的内存块(内存页)。内存管理程序将这些相对不经常使用的内存页交换到硬盘上专门指定用于“分页”或交换的特殊分区。那些换出到硬盘的内存页面被内核的内存管理代码跟踪,如果需要,可以被分页回 RAM。

    总结一下,虚拟内存实现可以总结为三步:

    先加载进程的一部分数据

    需要不在内存的数据,发生中断(缺页中断/缺段中断)

    将数据调入内存

    我们知道,根据存储管理可以分为分页式存储、段式存储、段页式存储3种。Linux 采用的是段页式存储,但是,大多数文章为什么都没有提及段式存储相关,为什么呢?

    因为在Linux内部的地址的映射过程为逻辑地址–>线性地址–>物理地址,逻辑地址经段机制转化成线性地址;线性地址又经过页机制转化为物理地址。简单的讲就是,在虚拟内存管理的时候,实质上的管理的是一段一段的(逻辑段),这一段一段的内存组和起来就是我们常规理解的线形地址,而在这一段一段的虚拟内存再使用页机制转化到物理内存上。

    fb345b94501f

    我们要知道,页是信息的物理单位,分页是为实现离散分配方式,以消减内存的外零头,提高内存的利用率。段则是信息的逻辑单位,它含有一组其意义相对完整的信息。分段的目的是为了能更好地满足用户的需要。这也是段和页的区别。

    Linux上的段机制和页机制下面会分开讲解。段机制是Linux在组织虚拟出来的虚拟内存,而页机制则是在实现虚拟内存。

    页机制

    虚拟内存空间中的地址叫做“虚拟地址”;而实际物理内存空间中的地址叫做“实际物理地址”或“物理地址”。

    尽管处理器的最小可寻址单位通常为字或字节,但是Linux中内存管理单元(MMU,把虚拟地址转换为物理地址的硬件设备)是以页为单位处理。我们把虚拟出来的内存分成等大的内存块,叫做页(虚拟内存空间的顺序划分),把物理内存分成同等大小的内存块,叫做页框(对物理内存按顺序等大小的划分)。

    我们知道,缺页中断过后操作系统会将对应的页调入内存,那么常规操作系统是怎么实现这个逻辑的呢?

    首先检测是否有空闲的页框,如果有,那么将从交换分区调入的页装配到空闲页框中(swap in)。如果没有空闲页框,那系统按预定的置换策略(页面置换算法)自动选择一个或一些在内存的页面,如果这个或者这些页面为dirty,那么就将其换到(swap out)交换分区,空出页框了,就可以安置所需的页(swap in)。

    fb345b94501f

    值得说明的是,交换分区的数据来源于从物理内存中淘汰出来的页面,同时当缺页中断的时候,就是将交换分区中的页面调入物理内存中。

    但是,在页面置换算法上,Linux 并没有采取这一种常规做法,Linux有一个守护进程kswapd,比较每个内存区域的高低水位来检测是否有足够的空闲页面来使用。每次运行时,仅有一个确定数量的页面被回收。这个阈值是受限的,以控制I/O压力。每次执行回收,先回收容易的,再处理难的。回收的页面会加入到空闲链表中。

    Linux采用的页面置换算法是一种改进地LRU算法--最近最少使用(LRU)页面的衰老算法,维护两组标记:活动/非活动和是否被引用。第一轮扫描清除引用位,如果第二轮运行确定被引用,就提升到一个不太可能回收的状态,否则将该页面移动到一个更可能被回收的状态。

    处于非活动列表的页面,自从上次检查未被引用过,因而是移除的最佳选择。被引用但不活跃的页面同样会被考虑回收,是因为一些页面是守护进程访问的,可能很长时间不再使用。

    状态转换如下:

    fb345b94501f

    页框置换算法中考虑的页面状态

    另外,内存管理还有一个守护进程pdflush,会定期醒来,写回脏页面;或者可用内存下降到一定水平后被内核唤醒。

    MMU

    经过前面讲解,对于一个进程(程序)来说,看到的就是一大块连续的线形地址了。但是想要探究更为具体的实现,那就要介绍MMU了。

    内存管理单元(Memory Management Unit)简称MMU,MMU位于处理器内核和连接高速缓存以及物理存储器的总线之间。当处理器内核取指令或者存取数据的时候,都会提供一个有效地址(effective address),或者称为逻辑地址、虚拟地址,MMU会将逻辑地址映射为物理地址。

    fb345b94501f

    这对于多进程系统非常重要。例如,在32位Linux里,进程A在地址0x08048000映射了可执行文件,进程B同样在地址0x08048000映射了可执行文件,如果A进程读地址0x08048000,读到的是A的可执行文件映射到RAM的内容,而进程B读取地址0x08048000时,则读到的是B的可执行文件映射到RAM的内容。意思就是说,两个进程虽然虚拟地址是一样的,但是他们的对应的真实数据页可能是不一样的。

    要将虚拟地址转换成物理地址,可以通过建立一张映射表完成。页帧(Page Frame)是指物理内存中的一页内存,MMU虚实地址映射就是寻找物理页帧的过程。

    MMU软件配置的核心是页表(Page Table),它描述MMU的映射规则,即虚拟内存哪(几)个页映射到物理内存哪(几)个页帧。页表由一条条代表映射规则的记录组成,每一条称为一个页表条目(Page Table Entry),整个页表保存在片外内存,MMU通过查找页表确定一个虚拟地址应该映射到什么物理地址,以及是否有权限映射。

    既然所有发往内存的地址信号都要经过MMU处理,那么MMU就可以以很小的代价承担更大的责任,比如内存保护。可以在PTE条目中预留出几个比特,用于设置访问权限的属性,如禁止访问、可读、可写和可执行等。设好后,CPU访问一个虚拟地址时,MMU找到页表中对应PTE,把指令的权限需求与该PTE中的限定条件做比对,若符合要求就把虚拟地址转换成物理地址,否则不允许访问,并产生异常。

    物理内存映射

    我们知道Linux MMU处理单位为页,一个32bits虚拟地址,每页大小4k,可以划分为2^20个内存页,如果物理页帧随意映射,页表的空间占用就是(2^20)*sizeof(PTE)*进程数(每个进程都要有自己的页表),PTE一般占4字节,即每进程4M,这对空间占用和MMU查询速度都很不利。

    实际应用中不需要每次都按最小粒度的页来映射,很多时候可以映射更大的内存块。因此最好采用变化的映射粒度,既灵活又可以减小页表空间。Linux 采用的就是三级页表。随着64位CPU,比如X86_64,出现四级页表页开始诞生,原理相同,这里看一下Linux的三级页表。

    fb345b94501f

    页全局目录 (Page Global Directory,即PGD) :全局字典,指向中间页目录。

    页中间目录( Page Middle Directory,即PMD) :中间字典,也可以理解为二级目录,指向PTE中的表项。

    页表 (Page Table,即PTE):页表(PTE),指向物理页面 。

    偏移量(Page Offset):即页内偏移。

    线性地址、页表和页表项线性地址不管系统采用多少级分页模型,线性地址本质上都是索引+偏移量的形式.

    进程的内存分布

    以32位的计算机为例,一共可以虚拟出4G(2^32)的虚拟内存,每个进程都有各自独立的进程地址空间,意思就是每一个进程都有4G的线性虚拟地址空间。4G进程地址空间被划分两部分,内核空间和用户空间。用户空间从0到3G,内核空间从3G到4G;但是内核空间是由内核负责映射,不会跟着进程变化;内核空间地址有自己对应的页表,用户进程各自有不同的页表。可以理解为每个普通进程都有自己的用户空间,但是内核空间被所有普通进程所共享(每个进程虚拟空间的3G~4G部分是相同的 )。

    fb345b94501f

    另外,用户态进程只能访问0-3G,但是内核态进程既可以访问0-3G,也可以访问3G-4G地址空间。

    那内核态到底是什么呢?运行在内核态的进程相比于用户态的进程拥有更高的权限,用户态的进程能够访问的资源受到了极大的控制,而运行在内核态的进程可以“为所欲为”,它能够控制计算机的硬件资源,例如协调CPU资源,分配内存资源,并且提供稳定的环境供应用程序运行。

    下面来看看进程在虚拟内存中的分布情况

    内核态

    fb345b94501f

    固定映射区(Fixing Mapping Region):该区域和4G的顶端只有4k的隔离带,其每个地址项都服务于特定的用途,如ACPI_BASE等。

    永久内存映射区(PKMap Region):该区域可访问高端内存。访问方法是使用alloc_page(_GFP_HIGHMEM)分配高端内存页或者使用kmap函数将分配到的高端内存映射到该区域。

    动态内存映射区(Vmalloc Region):该区域由内核函数vmalloc来分配,特点是:线性空间连续,但是对应的物理空间不一定连续。vmalloc分配的线性地址所对应的物理页可能处于低端内存,也可能处于高端内存。

    直接映射区(Direct Memory Region):线性空间中从3G开始最大896M的区间,为直接内存映射区,该区域的线性地址和物理地址存在线性转换关系:线性地址=3G+物理地址。

    用户态

    fb345b94501f

    栈:栈是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。

    mmap 内存映射区:用于文件映射(包括动态库)和匿名映射。常见的就是使用 mmap 分配的虚拟内存区域。

    堆:堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)。

    BSS段:BSS段包含了未初始化的全局变量,在内存中bss段全部置零。

    数据段:数据段用来存放已初始化的全局变量,换句话说就是存放程序静态分配的变量和全局变量。

    代码段:代码段是用来存放可执行文件的操作指令,也就是说是它是可执行程序在内存中的镜像。代码段需要防止在运行时被非法修改,所以是不可写的。

    用一张图总结一下:

    fb345b94501f

    进程内存管理

    进程内存管理的对象是进程线性地址空间上的内存镜像,这些内存镜像其实就是进程使用的虚拟内存区域(virtual memory areas,即VMA)。

    Linux内核通过一个被称为进程描述符的task_struct 结构体来管理进程,这个结构体包含了一个进程所需的所有信息。mm_struct中的pgd为页表,就是前面讲的一级页表。task_struct中有一个结构体被称为内存描述符的mm_struct,描述了一个进程的整个虚拟地址空间。每个进程正是因为都有自己的mm_struct,才使得每个进程都有自己独立的虚拟的地址空间

    fb345b94501f

    每一段已经分配的虚拟内存区域都会以一个vm_area_struct结构体表示,而组织这些结构一共有两种形式:其中mm_struct->mm_rb是所有vm_area_struct组成的红黑树,而mm_struct->mmap是所有vm_area_struct组成的链表,vma这个数据结构被双重管理主要是为了加速查找速度(空间换时间)。

    mm_struct中的mmap指针指向的vm_area_struct链表的每一个节点就代表进程的一个虚拟地址空间,即一个VMA。一个VMA最终可能对应ELF可执行程序的数据段、代码段、堆、栈、或者动态链接库的某个部分。

    在这条链表上进行这样表示,我们就可以将其理解为最开始所说的逻辑分段了。

    物理内存管理

    通过mm_struct结构体完成了对虚拟内存的管理,那么物理内存是如何管理和分配的呢?

    经过前面介绍,我们知道Linux以页为单位进行分配,随之而来的就会有两个问题:

    如何解决页外碎片问题

    如何解决页内碎片问题

    Linux 采用 buddy 系统(伙伴系统)来解决页外碎片,采用 slab 分配器来解决页内碎片。同时 Linux 采用

    了 Node,Zone 和 page三级结构来描述物理内存的。buddy 系统是建立在这三级结构之上的。

    slab 同时又在 buddy 系统之上管理着物理页之内的内存请求(小内存分配)。

    node、zone、page

    在介绍管理物理内存之前我们要先了解一下什么是 UMA 和 NUMA。

    在多核系统中,如果物理内存对所有CPU来说没有区别,每个CPU访问内存的方式也一样,则这种体系结构被称为Uniform Memory Access(UMA)。

    fb345b94501f

    如果物理内存是分布式的,由多个cell组成(比如每个核有自己的本地内存),那么CPU在访问靠近它的本地内存的时候就比较快,访问其他CPU的内存或者全局内存的时候就比较慢,这种体系结构被称为Non-Uniform Memory Access(NUMA)。

    fb345b94501f

    Linux适用于各种不同的体系结构, 而不同体系结构在内存管理方面的差别很大,因此linux内核需要用一种体系结构无关的方式来表示内存。因此linux内核把物理内存按照CPU节点划分为不同的node, 每个node作为某个cpu结点的本地内存, 而作为其他CPU节点的远程内存, 而UMA结构下, 则任务系统中只存在一个内存node, 这样对于UMA结构来说, 内核把内存当成只有一个内存node节点的伪NUMA。

    fb345b94501f

    内存节点的数据结构为pg_data_t, 也就是struct pglist_data,而Linux会将所有 pg_data_t 使用双向链表组织起来。

    内存管理区(zone),zone由struct zone_struct 数据结构来描述。zone的类型由zone_t表示,主要有ZONE_DMA, ZONE_NORMAL, ZONE_HIGHMEM 这三种类型。

    分区

    功能

    ZONE_DMA

    可以用于DMA操作的页

    ZONE_NORMAL

    正常的、规则映射的页

    ZONE_HIGHMEM

    高内存地址的页,并不永久性映射

    内存区域的确切边界和布局是和硬件体系结构相关的。例如,在x86硬件上,一些设备只能在最低的16MB地址空间进行DMA操作,因此ZONE_DMA就在0-16M范围内,在决定分区的时候,先分配 ZONE_DMA 和 ZONE_HIGHMEM,剩下的就属于 ZONE_NORMAL。ZONE_DMA大小是硬件决定的,和ZONE_NORMAL属于低端内存,ZONE_HIGHMEM属于高端内存。

    节点、区域和页框之间的关系如下图:

    fb345b94501f

    上图中的zone_mem_map是一个页框的数组,它记录了一个内存分区的所有页框的使用情况。

    用图总结下:

    fb345b94501f

    image

    伙伴算法

    内核子系统中有一个分区页框分配器,用于处理对连续页框组的内存分配请求。其中名为管理区分配器部分接受动态内存分配与释放请求。

    fb345b94501f

    image.png

    在每个内存管理区(Zone)内,页框由伙伴系统来分配。为了达到更好的系统性能,一小部分页框保留在高速缓存中用于快速满足对单个页框的分配请求。

    在 i386 体系结构中,整个物理内存被分为4k大小的页框,我们知道经过页机制我们可以不必要求有大块连续物理内存,我们可以通过页机制东拼西凑出一块内存,虽然物理内存不连续,但是通过页机制,在虚拟内存上就可以虚拟出一块连续的虚拟内存。但是,我们会更倾向于分配连续的物理内存(页框),因为分配连续内存时,页表不需要更改,因此能降低TLB的刷新率(频繁刷新会在很大程度上降低访问速度)。有兴趣的建议了解快表的机制。

    那么为了能够分配连续的物理内存,就需要解决页外碎片(外部碎片)的问题,在Linux中采用伙伴算法来解决。

    Buddy 算法将所有的空闲物理页分成 10 组,每组分别包含大小 1,2,4,8,16,32,64,

    128,256,512 个连续物理页。每一组用链表组织起来。

    fb345b94501f

    image.png

    分配:

    对于一个 2^{order} 个连续页框大小的内存申请,伙伴系统首先查看 zone->free_area[order] 中是否有空闲的块。如果找到,则直接分配给请求对象。

    如果没有, 查找 zone->free_area[order+1] 是否有空闲块,如果有: 则摘下一块,并且分成两等分,分配一份给请求对象,另一份插入到 zone->free_area[order] 中。

    如果没有, 则依次往更大连续物理内存分组寻找,直到满足需求。

    回收:

    回收算法根据提供的块大小,将块放到大小对应的链表中。如果放入过程中发现有空闲伙伴块, 则合并伙伴, 形成更大的块放到对应链表中。

    是否为伙伴块需要满足的条件:

    两个块大小相同;

    两个块地址连续;

    两个块必须是同一个大块中分离出来的;

    slab

    通过页机制能够非常方便的分配到物理内存,但是通过伙伴算法分配的物理内存也产生了页内碎片(内部碎片)。但是内核使用的很多都是小对象,可能就几十字节,但是分配一个页框也是4K,这种分配造成的浪费非常大。比如存放文件描述符、进程描述符、虚拟内存区域描述符等行为所需的内存都不足一页。

    Linux为了解决这种问题呢,在内核实现了slab分配器,主要针对内核中经常分配并释放的对象。核心思想就是存储池的运用。

    slab会把对象池化,相同的对象放到一个存储池里,每个对象池都是一个 kmem_cache 结构的引用(称为一个 cache)。所有的对象池使用链表组织起来,即cache_chain。

    fb345b94501f

    image

    而每一个对象池(kmem_cache)存在3种slab:

    slabs_full:完全分配的slab

    slabs_partial:部分分配的slab

    slabs_empty:空slab,或者没有对象被分配

    每一个slab就是一个或者多个连续的物理框(通常只有一个),他们是从伙伴系统中申请过来的物理内存,被划成很多小块,用于快速分配给对应的小对象。其中, slabs_empty 列表中的 slab 是进行回收(reaping)的主要备选对象。正是通过此过程,slab 所使用的内存被返回给操作系统供其他用户使用。

    每一个slab是不断移动的。当一个 slab 中的所有对象都被使用完时,就从 slabs_partial 列表中移动到 slabs_full 列表中。当一个 slab 完全被分配并且有对象被释放后,就从 slabs_full 列表中移动到 slabs_partial 列表中。当所有对象都被释放之后,就从 slabs_partial 列表移动到 slabs_empty 列表中。

    fb345b94501f

    image

    每当要申请这样一个对象时,slab分配器就从一个slab列表中分配一个这样大小的单元出去,而当要释放时,将其重新保存在该列表中,而不是直接返回给伙伴系统。slab 分配器还可以支持硬件缓存对齐和着色,这允许不同缓存中的对象占用相同的缓存行,从而提高缓存的利用率并获得更好的性能。

    fb345b94501f

    最后,用一张经典的图总结一下:

    fb345b94501f

    学习的相关文章:

    展开全文
  • 作为c的程序员,最常见的就是排查内存泄漏,不过我们一般的内存泄漏是针对特定的程序去排查,相对来说比较容易,但是如果是维护人员,不知道哪个程序有内存泄漏,甚至是应用程序的内存泄漏,还是内核的...

    作为c的程序员,最常见的就是排查内存泄漏,不过我们一般的内存泄漏是针对特定的程序去排查,相对来说比较容易,但是如果是维护人员,不知道哪个程序有内存泄漏,甚至是应用程序的内存泄漏,还是内核的内存泄漏都不明确,所以一定要有一定的查内存泄漏的章法.

    一  虚拟内存泄露

    一般来说,我们观察系统的内存占用喜欢用top命令,然后输入m,对系统中整体的内存占用情况做个排序,然后在重点观察,内存占用排在前几位的进程,再逐步的分析,

    [root@VM-0-2-centos ~]# top -p 5576
    top - 18:21:46 up 198 days, 20:07,  2 users,  load average: 0.10, 0.04, 0.05
    Tasks:   1 total,   0 running,   1 sleeping,   0 stopped,   0 zombie
    %Cpu(s):  0.7 us,  0.3 sy,  0.0 ni, 99.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
    KiB Mem :  1882008 total,    78532 free,   116516 used,  1686960 buff/cache
    KiB Swap:        0 total,        0 free,        0 used.  1606660 avail Mem 
    
     PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND                                                                                                                 
     5576 root      20   0  184064  11248   1124 S  0.0  0.6  10:34.98 nginx   
    

    虽然top 也可以观察到单独的进程的内存变化,不过一般不太好比较内存变化的规律, 推荐使用pidstat工具,此工具需要先安装,通过命令:

    yum install sysstat
    

    pidstat 基本说明如下:-u:默认的参数,显示各个进程的cpu使用统计 -r:显示各个进程的内存使用统计 -d:显示各个进程的IO使用情况 -p:指定进程号 -w:显示每个进程的上下文切换情况 -t:显示选择任务的线程的统计信息外的额外信息 -TTASKCHILD | ALL

    假如我们观察到如下的内存占用情况:pidstat -r -p pid 5

    [root@VM-0-2-centos ~]# pidstat -r -p 5981 5
    Linux 3.10.0-1127.19.1.el7.x86_64 (VM-0-2-centos)  07/24/2021  _x86_64_ (1 CPU)
    
    06:25:55 PM   UID       PID  minflt/s  majflt/s     VSZ    RSS   %MEM  Command
    06:26:00 PM     0      5981      0.20      0.00    4416    352   0.02  a.out
    06:26:05 PM     0      5981      0.00      0.00    4416    352   0.02  a.out
    06:26:10 PM     0      5981      0.20      0.00    4456    352   0.02  a.out
    06:26:15 PM     0      5981      0.00      0.00    4456    352   0.02  a.out
    06:26:20 PM     0      5981      0.00      0.00    4456    352   0.02  a.out
    06:26:25 PM     0      5981      0.20      0.00    4496    352   0.02  a.out
    06:26:30 PM     0      5981      0.00      0.00    4496    352   0.02  a.out
    06:26:35 PM     0      5981      0.20      0.00    4536    352   0.02  a.out
    06:26:40 PM     0      5981      0.00      0.00    4536    352   0.02  a.out
    06:26:45 PM     0      5981      0.20      0.00    4576    352   0.02  a.out
    06:26:50 PM     0      5981      0.00      0.00    4576    352   0.02  a.out
    06:26:55 PM     0      5981      0.20      0.00    4616    352   0.02  a.out
    
    

    我们注意下,VSZ即虚拟内存的占用每10s增加40,单位为k,即10s增加40k的虚拟内存。下面来具体分析下这个程序的内存泄露情况。

    二 分析泄露原因

    我们来分析这个进程的内存分布情况,来分析这泄露的内存有什么特点:

    [root@VM-0-2-centos ~]# pmap -x 5981
    5981:   ./a.out
    Address           Kbytes     RSS   Dirty Mode  Mapping
    0000000000400000       4       4       0 r-x-- a.out
    0000000000600000       4       4       4 r---- a.out
    0000000000601000       4       4       4 rw--- a.out
    00007faab436e000    2720     272     272 rw---   [ anon ]
    00007faab4616000    1804     260       0 r-x-- libc-2.17.so
    00007faab47d9000    2048       0       0 ----- libc-2.17.so
    00007faab49d9000      16      16      16 r---- libc-2.17.so
    00007faab49dd000       8       8       8 rw--- libc-2.17.so
    00007faab49df000      20      12      12 rw---   [ anon ]
    00007faab49e4000     136     108       0 r-x-- ld-2.17.so
    00007faab4a06000    2012     212     212 rw---   [ anon ]
    00007faab4c03000       8       8       8 rw---   [ anon ]
    00007faab4c05000       4       4       4 r---- ld-2.17.so
    00007faab4c06000       4       4       4 rw--- ld-2.17.so
    00007faab4c07000       4       4       4 rw---   [ anon ]
    00007ffe0f3f5000     132      16      16 rw---   [ stack ]
    00007ffe0f47c000       8       4       0 r-x--   [ anon ]
    ffffffffff600000       4       0       0 r-x--   [ anon ]
    ---------------- ------- ------- ------- 
    total kB            8940     940     564
    
    

    其中Address为开始的地址,Kbytes是虚拟内存的大小,RSS为真实内存的大小,Dirty为未同步到磁盘上的脏页,Mode为内存的权限,rw为可写可读,rx为可读和可执行。通过几次观察,我们发现:

    00007faab436e000    2720     272     272 rw---   [ anon ]
    

    为泄露部分,此为匿名内存区,也就是没有映射文件,为malloc或mmap分配的内存。同样是每次增加40K。

    此时还是只能大概知道内存泄露的位置,我们还先找到具体的代码位置,这个该怎么分析?代码的申请,无非是通过malloc和brk这些库函数进行内存调用,我们可以用strace跟踪下。

    [root@VM-0-2-centos ~]# strace -f -t -p 5981 -o trace.strace
    strace: Process 5981 attached
    strace: Process 8519 attached
    strace: Process 8533 attached
    strace: Process 8547 attached
    strace: Process 8557 attached
    strace: Process 8575 attached
    ^Cstrace: Process 5981 detached
    

    我们通过-t选项来显示时间,-f来跟踪子进程。直接用cat命令查看跟踪的文件内容,会发现内容相当多,只要是系统调用都打印了出来,可以通过每次增加40k这个有用的信息搜索下:

    [root@VM-0-2-centos ~]# grep 40960  trace.strace 
    5981  19:01:44 mmap(NULL, 40960, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7faab403a000
    5981  19:01:55 mmap(NULL, 40960, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7faab4030000
    5981  19:02:06 mmap(NULL, 40960, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7faab4026000
    5981  19:02:17 mmap(NULL, 40960, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7faab401c000
    5981  19:02:28 mmap(NULL, 40960, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7faab4012000
    

    至此我们找到了具体的泄露代码位置。

    看下这个测试代码:

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/mman.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    #define _SCHED_H 
    #define __USE_GNU 
    #include <bits/sched.h>
     
    #define STACK_SIZE 40960
     
    int func(void *arg)
    {
        printf("thread enter.\n");
        sleep(1);
        printf("thread exit.\n");
     
        return 0;
    }
    
    
    int main()
    {
        int thread_pid;
        int status;
        int w;
     
        while (1) {
            void *addr = mmap(NULL, STACK_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0);
            if (addr == NULL) {
                perror("mmap");
                goto error;
            }
            printf("creat new thread...\n");
            thread_pid = clone(&func, addr + STACK_SIZE, CLONE_SIGHAND|CLONE_FS|CLONE_VM|CLONE_FILES, NULL);
            printf("Done! Thread pid: %d\n", thread_pid);
            if (thread_pid != -1) {
                do {
                    w = waitpid(-1, NULL, __WCLONE | __WALL);
                    if (w == -1) {
                        perror("waitpid");
                        goto error;
                    }
                } while (!WIFEXITED(status) && !WIFSIGNALED(status));
            }
            sleep(10);
       }
    
     error:
        return 0;
    }
    
    

    这个测试程序利用mmap申请一块匿名私有的内存,clone为系统函数,pthread_create 和fork底层都是调用它,用来创建进程/线程,将func的地址指针存放在子进程堆栈的某个位置处,该位置就是该封装函数本身返回地址存放的位置,最后一个参数为func的执行参数。clone可以更灵活控制共享,比如可以控制是否共享内存空间,是否共享打开文件,是否共享相同的信号处理函数等。

    我们可以看到,mmap申请内存后,需要通过munmap来释放,这里面没有释放,所以导致了虚拟内存泄露,这里面申请的内存只实际使用了4个字节,即复制了func的指针,其他的内存均没有使用,其实仔细观察会发现还有部分的物理内存泄露,每次4个字节,可以通过pmap -x 查到。

    在 waitpid后面添加:munmap(addr,STACK_SIZE); 即可以实现内存的释放。

    三  valgrind 分析程序内存泄露

    这个是比较常见的方法,一般通过下面命令来查看内存泄露:

    valgrind --tool=memcheck --leak-check=full ./b
    
    [root@VM-0-2-centos test]# valgrind --tool=memcheck --leak-check=full ./b
    ==14374== Memcheck, a memory error detector
    ==14374== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
    ==14374== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
    ==14374== Command: ./b
    ==14374== 
    ==14374== Warning: set address range perms: large range [0x5205040, 0x24605040) (undefined)
    address:0x5205040
    ==14374== Warning: set address range perms: large range [0x5205040, 0x24605040) (defined)
    524288000
    ==14374== 
    ==14374== HEAP SUMMARY:
    ==14374==     in use at exit: 524,288,000 bytes in 1 blocks
    ==14374==   total heap usage: 1 allocs, 0 frees, 524,288,000 bytes allocated
    ==14374== 
    ==14374== 524,288,000 bytes in 1 blocks are possibly lost in loss record 1 of 1
    ==14374==    at 0x4C29F73: malloc (vg_replace_malloc.c:309)
    ==14374==    by 0x400675: main (test.c:17)
    ==14374== 
    ==14374== LEAK SUMMARY:
    ==14374==    definitely lost: 0 bytes in 0 blocks
    ==14374==    indirectly lost: 0 bytes in 0 blocks
    ==14374==      possibly lost: 524,288,000 bytes in 1 blocks
    ==14374==    still reachable: 0 bytes in 0 blocks
    ==14374==         suppressed: 0 bytes in 0 blocks
    
    

    明确说明在test.c的17行有可能是内存泄露:==14374== by 0x400675: main (test.c:17)其他用法看说明吧。

    四 其他内存泄露分析

    其实上面的内存泄露是我们知道了具体的泄露的进程,然后再做详细分析。那么如果不知道哪里内存泄露了,有什么办法,可以通过分析meminfo文件,来观察泄露的类型。

    [root@VM-0-2-centos test]# cat /proc/meminfo
    MemTotal:        1882008 kB
    MemFree:          752948 kB
    MemAvailable:    1610108 kB
    Buffers:          564900 kB
    Cached:           399584 kB
    SwapCached:            0 kB
    Active:           808140 kB
    Inactive:         220812 kB
    Active(anon):      64548 kB
    Inactive(anon):      488 kB
    Active(file):     743592 kB
    Inactive(file):   220324 kB
    Unevictable:           0 kB
    Mlocked:               0 kB
    SwapTotal:             0 kB
    SwapFree:              0 kB
    ....
    

    这里面说明的挺详细的了,如果遇到内存问题,观察这个肯定会发现猫腻的。

    五 诗词欣赏

    蝶恋花.春景
    苏轼
    
    花褪残红青杏小。燕子飞时,绿水人家绕。
    枝上柳绵吹又少。天涯何处无芳草。
    
    墙里秋千墙外道。墙外行人,墙里佳人笑。
    笑渐不闻声渐悄。多情却被无情恼。
    
    展开全文
  • 简介我们知道,Linux用cache/buffer缓存数据,且有个回刷任务在适当时候把数据回刷到存储介质中。什么是适当的时候?换句话说,什么时候触发回刷?是数据达到多少阈值还是定时触发,或者两者都有?不同场景对...

    简介

    我们知道,Linux用cache/buffer缓存数据,且有个回刷任务在适当时候把脏数据回刷到存储介质中。什么是适当的时候?换句话说,什么时候触发回刷?是脏数据达到多少阈值还是定时触发,或者两者都有?

    不同场景对触发回刷的时机的需求也不一样,对IO回刷触发时机的选择,是IO性能优化的一个重要方法。

    Linux内核在/proc/sys/vm中有透出数个配置文件,可以对触发回刷的时机进行调整。内核的回刷进程是怎么运作的呢?这数个配置文件有什么作用呢?

    配置概述

    在/proc/sys/vm中有以下文件与回刷脏数据密切相关:

    配置文件

    功能

    默认值

    dirty_background_ratio

    触发回刷的脏数据占可用内存的百分比

    0

    dirty_background_bytes

    触发回刷的脏数据量

    10

    dirty_bytes

    触发同步写的脏数据量

    0

    dirty_ratio

    触发同步写的脏数据占可用内存的百分比

    20

    dirty_expire_centisecs

    脏数据超时回刷时间(单位:1/100s)

    3000

    dirty_writeback_centisecs

    回刷进程定时唤醒时间(单位:1/100s)

    500

    对上述的配置文件,有几点要补充的:

    XXX_ratio 和 XXX_bytes 是同一个配置属性的不同计算方法,优先级 XXX_bytes > XXX_ratio

    可用内存并不是系统所有内存,而是free pages + reclaimable pages

    脏数据超时表示内存中数据标识脏一定时间后,下次回刷进程工作时就必须回刷

    回刷进程既会定时唤醒,也会在脏数据过多时被动唤醒。

    dirty_background_XXX与dirty_XXX的差别在于前者只是唤醒回刷进程,此时应用依然可以异步写数据到Cache,当脏数据比例继续增加,触发dirty_XXX的条件,不再支持应用异步写。

    关于同步与异步IO的说明,可以看另一篇博客《Linux IO模型》

    更完整的功能介绍,可以看内核文档Documentation/sysctl/vm.txt。

    配置示例

    单纯的配置说明毕竟太抽象。结合网上的分享,我们看看在不同场景下,该如何配置?

    场景1:尽可能不丢数据

    有些产品形态的数据非常重要,例如行车记录仪。在满足性能要求的情况下,要做到尽可能不丢失数据。

    /* 此配置不一定适合您的产品,请根据您的实际情况配置 */

    dirty_background_ratio = 5

    dirty_ratio = 10

    dirty_writeback_centisecs = 50

    dirty_expire_centisecs = 100

    这样的配置有以下特点:

    当脏数据达到可用内存的5%时唤醒回刷进程

    当脏数据达到可用内存的10%时,应用每一笔数据都必须同步等待

    每隔500ms唤醒一次回刷进程

    内存中脏数据存在时间超过1s则在下一次唤醒时回刷

    由于发生交通事故时,行车记录仪随时可能断电,事故前1~2s的数据尤为关键。因此在保证性能满足不丢帧的情况下,尽可能回刷数据。

    此配置通过减少Cache,更加频繁唤醒回刷进程的方式,尽可能让数据回刷。

    此时的性能理论上会比每笔数据都O_SYNC略高,比默认配置性能低,相当于用性能换数据安全。

    场景2:追求更高性能

    有些产品形态不太可能会掉电,例如服务器。此时不需要考虑数据安全问题,要做到尽可能高的IO性能。

    /* 此配置不一定适合您的产品,请根据您的实际情况配置 */

    dirty_background_ratio = 50

    dirty_ratio = 80

    dirty_writeback_centisecs = 2000

    dirty_expire_centisecs = 12000

    这样的配置有以下特点:

    当脏数据达到可用内存的50%时唤醒回刷进程

    当脏数据达到可用内存的80%时,应用每一笔数据都必须同步等待

    每隔20s唤醒一次回刷进程

    内存中脏数据存在时间超过120s则在下一次唤醒时回刷

    与场景1相比,场景2的配置通过 增大Cache,延迟回刷唤醒时间来尽可能缓存更多数据,进而实现提高性能

    场景3:突然的IO峰值拖慢整体性能

    什么是IO峰值?突然间大量的数据写入,导致瞬间IO压力飙升,导致瞬间IO性能狂跌,对行车记录仪而言,有可能触发视频丢帧。

    /* 此配置不一定适合您的产品,请根据您的实际情况配置 */

    dirty_background_ratio = 5

    dirty_ratio = 80

    dirty_writeback_centisecs = 500

    dirty_expire_centisecs = 3000

    这样的配置有以下特点:

    当脏数据达到可用内存的5%时唤醒回刷进程

    当脏数据达到可用内存的80%时,应用每一笔数据都必须同步等待

    每隔5s唤醒一次回刷进程

    内存中脏数据存在时间超过30s则在下一次唤醒时回刷

    这样的配置,通过 增大Cache总容量,更加频繁唤醒回刷的方式,解决IO峰值的问题,此时能保证脏数据比例保持在一个比较低的水平,当突然出现峰值,也有足够的Cache来缓存数据。

    内核代码实现

    知其然,亦要知其所以然。翻看内核代码,寻找配置的实现,细细品味不同配置的细微差别。

    基于内核代码版本:5.5.15

    sysctl文件

    在 kernel/sysctl.c中列出了所有的配置文件的信息。

    static struct ctl_table vm_table[] = {

    ...

    {

    .procname= "dirty_background_ratio",

    .data= &dirty_background_ratio,

    .maxlen= sizeof(dirty_background_ratio),

    .mode= 0644,

    .proc_handler= dirty_background_ratio_handler,

    .extra1= &zero,

    .extra2= &one_hundred,

    },

    {

    .procname= "dirty_ratio",

    .data= &vm_dirty_ratio,

    .maxlen= sizeof(vm_dirty_ratio),

    .mode= 0644,

    .proc_handler= dirty_ratio_handler,

    .extra1= &zero,

    .extra2= &one_hundred,

    },

    {

    .procname= "dirty_writeback_centisecs",

    .data= &dirty_writeback_interval,

    .maxlen= sizeof(dirty_writeback_interval),

    .mode= 0644,

    .proc_handler= dirty_writeback_centisecs_handler,

    },

    }

    为了避免文章篇幅过大,我只列出了关键的3个配置项且不深入代码如何实现。

    我们只需要知道,我们修改/proc/sys/vm配置项的信息,实际上修改了对应的某个全局变量的值。

    每个全局变量都有默认值,追溯这些全局变量的定义

    int dirty_background_ratio = 10;

    unsigned long dirty_background_bytes;

    int vm_dirty_ratio = 20;

    unsigned long vm_dirty_bytes;

    unsigned int dirty_writeback_interval = 5 * 100; /* centiseconds */

    unsigned int dirty_expire_interval = 30 * 100; /* centiseconds */

    总结如下:

    配置项名

    对应源码变量名

    默认值

    dirty_background_bytes

    dirty_background_bytes

    0

    dirty_background_ratio

    dirty_background_ratio

    10

    dirty_bytes

    vm_dirty_bytes

    0

    dirty_ratio

    vm_dirty_ratio

    20

    dirty_writeback_centisecs

    dirty_writeback_interval

    500

    dirty_expire_centisecs

    dirty_expire_interval

    3000

    回刷进程

    通过ps aux,我们总能看到writeback的内核进程

    $ ps aux | grep "writeback"

    root 40 0.0 0.0 0 0 ? I< 06:44 0:00 [writeback]

    这实际上是一个工作队列对应的进程,在default_bdi_init()中创建。

    /* bdi_wq serves all asynchronous writeback tasks */

    struct workqueue_struct *bdi_wq;

    static int __init default_bdi_init(void)

    {

    ...

    bdi_wq = alloc_workqueue("writeback", WQ_MEM_RECLAIM | WQ_FREEZABLE |

    WQ_UNBOUND | WQ_SYSFS, 0);

    ...

    }

    回刷进程的核心是函数wb_workfn(),通过函数wb_init()绑定。

    static int wb_init(struct bdi_writeback *wb, struct backing_dev_info *bdi

    int blkcg_id, gfp_t gfp)

    {

    ...

    INIT_DELAYED_WORK(&wb->dwork, wb_workfn);

    ...

    }

    唤醒回刷进程的操作是这样的

    static void wb_wakeup(struct bdi_writeback *wb)

    {

    spin_lock_bh(&wb->work_lock);

    if (test_bit(WB_registered, &wb->state))

    mod_delayed_work(bdi_wq, &wb->dwork, 0);

    spin_unlock_bh(&wb->work_lock);

    }

    表示唤醒的回刷任务在工作队列writeback中执行,这样,就把工作队列和回刷工作绑定了。

    我们暂时不探讨每次会回收了什么,关注点在于相关配置项怎么起作用。在wb_workfn()的最后,有这样的代码:

    void wb_workfn(struct work_struct *work)

    {

    ...

    /* 如果还有需要回收的内存,再次唤醒 */

    if (!list_empty(&wb->work_list))

    wb_wakeup(wb);

    /* 如果还有脏数据,延迟唤醒 */

    else if (wb_has_dirty_io(wb) && dirty_writeback_interval)

    wb_wakeup_delayed(wb);

    }

    static void wb_wakeup(struct bdi_writeback *wb)

    {

    spin_lock_bh(&wb->work_lock);

    if (test_bit(WB_registered, &wb->state))

    mod_delayed_work(bdi_wq, &wb->dwork, 0);

    spin_unlock_bh(&wb->work_lock);

    }

    void wb_wakeup_delayed(struct bdi_writeback *wb)

    {

    unsigned long timeout;

    /* 在这里使用dirty_writeback_interval,设置下次唤醒时间 */

    timeout = msecs_to_jiffies(dirty_writeback_interval * 10);

    spin_lock_bh(&wb->work_lock);

    if (test_bit(WB_registered, &wb->state))

    queue_delayed_work(bdi_wq, &wb->dwork, timeout);

    spin_unlock_bh(&wb->work_lock);

    }

    根据kernel/sysctl.c的内容,我们知道dirty_writeback_centisecs配置项对应的全局变量是dirty_writeback_interval

    可以看到,dirty_writeback_interval在wb_wakeup_delayed()中起作用,在wb_workfn()的最后根据dirty_writeback_interval设置下一次唤醒时间。

    我们还发现通过msecs_to_jiffies(XXX * 10)来换算单位,表示dirty_writeback_interval乘以10之后的计量单位才是毫秒msecs。怪不得说dirty_writeback_centisecs的单位是1/100秒。

    脏数据量

    脏数据量通过dirty_background_XXX和dirty_XXX表示,他们又是怎么工作的呢?

    根据kernel/sysctl.c的内容,我们知道dirty_background_XXX配置项对应的全局变量是dirty_background_XXX,dirty_XXX对于的全局变量是vm_dirty_XXX。

    我们把目光聚焦到函数domain_dirty_limits(),通过这个函数换算脏数据阈值。

    static void domain_dirty_limits(struct dirty_throttle_control *dtc)

    {

    ...

    unsigned long bytes = vm_dirty_bytes;

    unsigned long bg_bytes = dirty_background_bytes;

    /* convert ratios to per-PAGE_SIZE for higher precision */

    unsigned long ratio = (vm_dirty_ratio * PAGE_SIZE) / 100;

    unsigned long bg_ratio = (dirty_background_ratio * PAGE_SIZE) / 100;

    ...

    if (bytes)

    thresh = DIV_ROUND_UP(bytes, PAGE_SIZE);

    else

    thresh = (ratio * available_memory) / PAGE_SIZE;

    if (bg_bytes)

    bg_thresh = DIV_ROUND_UP(bg_bytes, PAGE_SIZE);

    else

    bg_thresh = (bg_ratio * available_memory) / PAGE_SIZE;

    if (bg_thresh >= thresh)

    bg_thresh = thresh / 2;

    dtc->thresh = thresh;

    dtc->bg_thresh = bg_thresh;

    }

    上面的代码体现了如下的特征

    dirty_background_bytes/dirty_bytes的优先级高于dirty_background_ratio/dirty_ratio

    dirty_background_bytes/ratio和dirty_bytes/ratio最终会统一换算成页做计量单位

    dirty_background_bytes/dirty_bytes做进一除法,表示如果值为4097Bytes,换算后是2页

    dirty_background_ratio/dirty_ratio相乘的基数是available_memory,表示可用内存

    如果dirty_background_XXX大于dirty_XXX,则取dirty_XXX的一半

    可用内存是怎么计算来的呢?

    static unsigned long global_dirtyable_memory(void)

    {

    unsigned long x;

    x = global_zone_page_state(NR_FREE_PAGES);

    /*

    * Pages reserved for the kernel should not be considered

    * dirtyable, to prevent a situation where reclaim has to

    * clean pages in order to balance the zones.

    */

    x += global_node_page_state(NR_INACTIVE_FILE);

    x += global_node_page_state(NR_ACTIVE_FILE);

    if (!vm_highmem_is_dirtyable)

    x -= highmem_dirtyable_memory(x);

    return x + 1; /* Ensure that we never return 0 */

    }

    所以,

    可用内存 = 空闲页 - 内核预留页 + 活动文件页 + 非活动文件页 ( - 高端内存)

    脏数据达到阈值后是怎么触发回刷的呢?我们再看balance_dirty_pages()函数

    static void balance_dirty_pages(struct bdi_writeback *wb,

    unsigned long pages_dirtied)

    {

    unsigned long nr_reclaimable; /* = file_dirty + unstable_nfs */

    ...

    /*

    * Unstable writes are a feature of certain networked

    * filesystems (i.e. NFS) in which data may have been

    * written to the server's write cache, but has not yet

    * been flushed to permanent storage.

    */

    nr_reclaimable = global_node_page_state(NR_FILE_DIRTY) +

    global_node_page_state(NR_UNSTABLE_NFS);

    ...

    if (nr_reclaimable > gdtc->bg_thresh)

    wb_start_background_writeback(wb);

    }

    void wb_start_background_writeback(struct bdi_writeback *wb)

    {

    wb_wakeup(wb);

    }

    总结下有以下特征:

    可回收内存 = 文件脏页 + 文件系统不稳定页(NFS)

    可回收内存达到dirty_background_XXX计算的阈值,只是唤醒脏数据回刷工作后直接返回,并不会等待回收完成,最终回收工作还是看writeback进程

    展开全文
  • LinuxLinux的共享内存

    万次阅读 多人点赞 2018-08-10 19:17:45
    实现进程间通信最简单也是最直接的方法就是共享内存——为参与通信的多个进程在内存中开辟一个共享区。由于进程可以直接对共享内存进行读写操作,因此这种通信方式效率特别高,但其弱点是,它没有互斥机制,需要信号...
  • 【随笔】Linux

    2021-05-19 06:26:20
    相较之内存,磁盘是一个低速设备,因此Linux中会通过一种叫“磁盘高速缓存”的软件机制来允许将磁盘上的一些数据保留在内存中,以加快访问速度。页高速缓存page cache就是Linux内核所使用的主要磁盘高速缓存。几乎...
  • Linux内存回收和交换

    千次阅读 2019-07-18 08:01:02
    前言Linux的swap相关部分代码从2.6早期版本到现在的4.6版本在细节之处已经有不少变化。本文讨论的swap基于Linux4.4内核代码。Linux内存管理是一套...
  • 下图是gonzo的例子:Linux进程在内核中是由task_struct进程描述符实现 的,task_struct的mm字段指向内存描述符mm_struct,他是进程的一个内存执行摘要。如上图所示,mm_struct存储了内存各个段 的开始和结束地址、...
  • 因此,需要深入学习 Linux 的虚拟内存管理方面的内容来解释这个现象。 Linux 的虚拟内存管理有几个关键概念:1. 每个进程有独立的虚拟地址空间,进程访问的虚拟地址并不是真正的物理地址2. 虚拟地址可通过每个进程上...
  • linux还存在虚拟内存

    2021-05-11 14:30:25
    Linux 的虚拟内存管理有几个关键概念:每个进程有独立的虚拟地址空间,进程访问的虚拟地址并不是真正的物理地址虚拟地址可通过每个进程上页表与物理地址进行映射,获得真正物理地址如果虚拟地址对应物理地址不在物理...
  • 概述当linux系统内存压力就大时,就会对系统的每个压力大的zone进程内存回收,内存回收主要是针对匿名页和文件页进行的。对于匿名页,内存回收过程中会筛选出一些不经常使用的匿名页,将它们写入到swap分区中,然后...
  • 内存是计算机中重要的部件之一,它是与CPU进行沟通的桥梁。计算机中所有程序的运行都是在内存中进行的,因此内存的性能对计算机的影响非常大。内存作用是用于暂时存放CPU中的运算数据,以及与硬盘等外部存储器交换的...
  • 一 简介:linux内存和mysql二 分类1 用户空间和内核空间用户空间内存,从低到高分别是五种不同的内存段1 只读段 包含代码和常量等2 数据段 包含全局变量等3 堆,包含动态分配的内存,从低地址开始增长4 文件映射段,包括...
  • linux对虚拟内存的限制发布时间:2007-10-18 00:40:57来源:红联作者:Mqqlecyro仅供学习参考。最近遇到了一个大内存(4G)情况下,Linux分配多少虚拟内存较为合适的问题。Linux的手册中,包括RHEL3、Fedora、Debain在内...
  • Linux内存映射-mmap

    2021-03-18 22:02:35
    linux/miscdevice.h> #include <linux/delay.h> #include <linux/kthread.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/init.h> #include <...
  • 为什么这么设计(Why’s THE Design)是一系列关于...我们都知道 Linux 会以页为单位管理内存,无论是将磁盘中的数据加载到内存中,还是将内存中的数据写回磁盘,操作系统都会以页面为单位进行操作,哪怕我们只向...
  • linux 内存

    2021-05-12 01:21:14
    Linux中,用户内存和内核内存是独立的,在各自的地址空间实现。地址空间是虚拟的,就是说地址是从物理内存中抽象出来的(通过一个简短描述的过程)。由于地址空间是虚拟的,所以可以存在很多。事实上,内核本身驻留...
  • 最近在学习内存管理的时候,发现对linux下的所谓内存如何管理如何分配都不熟悉,通过最近的查阅资料可总结如下,如有不妥之处欢迎大家批评与指正。总的的来说linux内存管理其实主要难理解的是以下几个部分:1、...
  • image正文0 内存模块image1 linux内存总体布局:内存分成用户态和内核态4G进程地址空间解析imageimage内核地址空间imageimage进程地址空间image2 地址转换和页表2.1 地址转换虚拟内存是指程序使用的逻辑地址。...
  • 点击上方“朱小厮的博客”,选择“设为星标”后台回复"书",获取后台回复“k8s”,可领取k8s资料前不久组内又有一次我比较期待的分享:”Linux 的虚拟内存”。是某天晚上...
  • 这又是我学习的《linux内核实战》一篇笔记吧,做了些补充,为了便于自己的记忆,理解和整理下。一直对Linux内存分配过程算是充满好奇,也看了些相关文章,不过有些和实际比较脱离,这两天学...
  • 名称:pmap - report memory map of a process(查看进程的内存映像信息)用法pmap [ -x | -d ] [ -q ] pids...pmap -V选项含义-x extended Show the extended format. 显示扩展格式-d device Show the device format....
  • Linux 默认给缓存预留了大量的空间(几乎所有),用于加速文件IO。当系统发生大量读写时,页面缓存常常会让物理内存耗尽,尽管没有swap到硬盘的危险,看着满满的物理内存,总是感觉不爽。下面这个内核参数可以保证预留...
  • Linux 内存与磁盘

    2021-02-15 16:40:34
    内存和磁盘都是计算机中的存储器,都具有存储功能,属于存储设备。在计算机中,磁盘和内存是相互配合共同作业的。 区别: 1、内存是一种高速,造价昂贵的存储设备;而磁盘速度较慢、造价低廉。 2、内存属于内部存储...
  • Linux实际内存使用情况计算

    千次阅读 2016-11-14 20:07:55
    Linux实际内存使用情况计算  原文地址:http://blog.csdn.net/spokeninchina/article/details/47320911  通常我们看到Linux用top命令查看系统运行状况,普遍看到Used Memory占到了将近总内存的90%,然后很多人...
  • 嵌入式linux一般会有裁剪,查看内存的工具和命令提供的信息可能没有那么全,该文记录下linux系统中查看内存的方法 下面的命令显示的内容可能会根据机器的内核和CPU的架构的不同而有一些出入,可能有些项名字不同...
  • Linux系统看内存的使用情况和分布情况着实一片混乱. 为此我们接着上篇<>http://blog.csdn.net/zengmuansha/article/details/51776899部分内容继续讲解FREE -M 这个命令显示的结果 着实不太友好要是变成下面...
  • 我们有进程对大文件进行后台写入....服务器有大量内存,允许它创建4GB的页.> dd if=/dev/zero of=todel bs=1048576 count=40964096+0 records in4096+0 records out4294967296 bytes (4.3 GB) copied,3.72657...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 21,474
精华内容 8,589
关键字:

linux脏内存

linux 订阅