Linux 内存优化

内存使用情况分析

DRAM 大小

硬件上 DDR 确定之后, DRAM 大小就已经确定。

uboot 会根据 DRAM 驱动提供的接口获取 DRAM 的大小,然后修改 dts 中的 memory 节点,Linux 启动时解析 dts 获取 DRAM 的大小。 uboot 启动 log 中会打印 dram 的大小。比如 R329 方案 uboot 启动时会有如下 log:

1
[01.300]DRAM: 128 MiB

执行:

1
2
3
root@TinaLinux:/# hexdump -C /sys/firmware/devicetree/base/memory@40000000/reg
00000000 00 00 00 00 40 00 00 00 00 00 00 00 08 00 00 00 |....@...........|
00000010

也可以获取 dram 的起始地址与大小。如下面 R329 例子所示,其中 0x40000000 为起始地址, 0x08000000 为 dram 的 size.

系统内存使用情况

free 命令

进入 Linux 用户空间,执行 free 命令可获得当前系统的内存使用情况。比如 R329 方案,某次执行 free 命令的结果如下:

1
2
3
4
5
root@TinaLinux:/# free
total used free shared buffers cached
Mem: 110592 79960 30632 36 9172 22860
-/+ buffers/cache: 47928 62664
Swap: 0 0 0

free 命令输出说明如下:

第一行 Mem,当前系统内存的使用情况

  • total: Linux 内核可支配的内存
  • used:系统已使用的内存
  • free:系统尚未使用的内存
  • shared:共享内存以及 tmpfs、 devtmpfs 所占用的内存。共享内存指使用 shmget、shm_open、 mmap 等接口创建的共享内存
  • buffers: Buffers 表示块设备 block device 所占用的缓存页,包括直接读写块设备、以及文件系统元数据 metadata 等
  • cached: Cache 里包括所有与文件对应的内存页。如果一个文件不再与进程关联,该文件不会立即回收,此时仍然包含在 Cached 中;此外, Cached 中还包含 tmpfs 中的文件以及 shmem 等

第二行

-/+ buffers/cache,减号表示第一行 used 内存减去 buffers 与 cached 内存,即 Mem_used - Mem_buffers - Mem_cached; 加号表示第一行 free 内存加上 buffers 与 cached 内存,即 Mem_free + Mem_buffers + Mem_cached。从应用程序的角度看, buffers 和 cached 是潜在可用的内存。

第三行 Swap,交换分区的使用情况

Tina 产品上使用 flash 作为存储器,读写次数是有限的,而 swap 分区特点是会被频繁地读写,导致 flash 寿命变短,因此 tina 上没有创建 swap 分区。

  • total: 交换分区总大小
  • used: 已使用的交换分区大小
  • free: 空闲的交换分区大小

/proc/meminfo 节点

实际上 free 命令信息来源是从/proc/meminfo 节点。比如 R329 方案,某次执行 cat /proc/meminfo 命令的结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
root@TinaLinux:/# cat /proc/meminfo
MemTotal: 110592 kB
MemFree: 30380 kB
MemAvailable: 62044 kB
Buffers: 9276 kB
Cached: 22872 kB
SwapCached: 0 kB
Active: 17580 kB
Inactive: 16336 kB
Active(anon): 1780 kB
Inactive(anon): 24 kB
Active(file): 15800 kB
Inactive(file): 16312 kB
Unevictable: 0 kB
Mlocked: 0 kB
SwapTotal: 0 kB
SwapFree: 0 kB
Dirty: 0 kB
Writeback: 0 kB
AnonPages: 1800 kB
Mapped: 3324 kB
Shmem: 36 kB
Slab: 13536 kB
SReclaimable: 4224 kB
SUnreclaim: 9312 kB
KernelStack: 1232 kB
PageTables: 200 kB
NFS_Unstable: 0 kB
Bounce: 0 kB
WritebackTmp: 0 kB
CommitLimit: 55296 kB
Committed_AS: 29116 kB
VmallocTotal: 263061440 kB
VmallocUsed: 0 kB
VmallocChunk: 0 kB
CmaTotal: 24576 kB
CmaFree: 2684 kB

相关说明如下:

  • MemTotal、 MemFree、 Buffers、 Cached、 Shmem,即 free 命令第一行。
  • MemAvailable:使用 MemFree, Active(file), Inactive(file), SReclaimable 和/proc/zoneinfo 中的 low watermark 根据特定算法计算得出,是一个估值,并不精确。可用来评估应用程序层面可用的内存。
  • Active/Inactive: Active 表示最近使用的内存,回收的优先级低; Inactive 表示最近较少使用的内存,回收的优先级高。可细分为 Active/Inactive 匿名页与文件页。所谓文件页,就是与文件对应的内存页,如进程的代码、映射的文件都属于文件页,当内存不足时,这部分的内存可以写回到存储器中;与之对应的就属于匿名页,即没有与具体文件对应的页,如进程的堆栈等,内存不足时,如果存在 swap 分区,可以将匿名页写入到交换分区,如果没有 swap 分区,则只能常驻内存中。
  • AnonPages:匿名页。
  • Mapped:设备或文件映射的大小。比如共享内存、动态库、 mmap 的文件等都统计在该内存中。
  • slab/SReclaimable/SUnreclaim:内核 slab 使用的内存,包含可回收与不可回收部分。
  • KernelStack:内核栈大小。
  • PageTables:页表的大小(用于将虚拟地址翻译为物理地址),内存分配越多,此块内存就会增大。
  • CommitLimit/Committed_AS: overcommit 的阈值/已经申请的内存大小(不是已分配)。 Overcommit 是 Linux 一种内存申请处理方式,为了跑更多更大的程序,大部分申请内存的请求都回复 “yes”,总申请的内存大于总物理内存。
  • VmallocTotal/VmallocUsed/VmallocChunk: vmalloc 区 域 大 小/vmalloc 区 域 使 用大小/vmalloc 区域中最大可用的连续区块大小。这部分在新版本内核上移除了。
  • CmaTotal/CmaFree:总 CMA 内存/空闲 CMA 内存。

保留内存

R329 DRAM 大小为 128M,而 free 命令中显示系统可支配总内存只有 110592KB=108M,为什么? 这里就涉及到一个概念:保留内存(Reserved Memory)。保留内存是指把系统中的一部分内存保留起来用作特殊用途,这部分内存通常不会被释放,也不会被转移到交换分区。

进入控制台,执行 cat /sys/kernel/debug/memblock/reserved 可以查看 reserved memory 使用情况:当前 R329 reserved memory 使用情况如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
root@TinaLinux:/# cat /sys/kernel/debug/memblock/reserved
0: 0x0000000040080000..0x00000000408a3fff, size:8336K
1: 0x00000000408a5000..0x00000000408a5fff, size:4K
2: 0x0000000041700000..0x000000004171767f, size:93K
3: 0x0000000041800000..0x00000000420fffff, size:9216K
4: 0x0000000045000000..0x00000000467fffff, size:24576K
5: 0x0000000046d86000..0x0000000046f85fff, size:2048K
6: 0x0000000047f29e00..0x0000000047f59e5f, size:192K
7: 0x0000000047f59e80..0x0000000047f59edf, size:0K
8: 0x0000000047f59f00..0x0000000047f8400f, size:168K
9: 0x0000000047f84080..0x0000000047f84087, size:0K
10: 0x0000000047f84100..0x0000000047f84107, size:0K
11: 0x0000000047f85180..0x0000000047fff2e6, size:488K
......

在内核 cmdline 加上 memblock=debug bootmem_debug=1 参数,在内核启动时,会打印上述 reserved memory 详细信息。由于内容太多,这里不展示了。

经分析对比,当前 R329 reserved memory 主要包含如下几个部分:

  • 0: 0x0000000040080000..0x00000000408a3fff, size:8336K
    内核代码段、数据段。详细包括 text, init, data, bss 四段,其中 init 在内核启动完成后会被释放。

  • 1: 0x00000000408a5000..0x00000000408a5fff, size:4K

  • 2: 0x0000000041700000..0x000000004171767f, size:93K
    DTB 占用内存

  • 3: 0x0000000041800000..0x00000000420fffff, size:9216K
    TEE 内存(共 8M,包括 SHM 2M, ATF 1M, OS 1M, TA 4M)。DSP 内存(共 1M)。

  • 4: 0x0000000045000000..0x00000000467fffff, size:24576K
    CMA 内存,共 24M,在 cmdline 中通过 cma=24M 配置而来。在初始化的过程中, CMA 内存会全部导入伙伴系统(具体是通过 cma_init_reserved_areas 函数来实现),所以内核是可以支配 CMA 内存的

  • 5: 0x0000000046d86000..0x0000000046f85fff, size:2048K
    所有 struct page 结构体的总大小。 struct page 结构体用来描述物理上的页帧。当前 R329 上配置一个页的大小为 4K,因此总共有 128M / 4K = 32768 个页,而 sizeof(struct page) 的值为 64B,因此这一块共 32768 * 64 = 2048 KB。注: aarch64 内核 sizeof(struct page) 的值为 64B, arm32 内核 sizeof(struct page) 的值为 32B。

  • 6: 0x0000000047f29e00..0x0000000047f59e5f, size:192K
    主要是 vfs cache,包括 Dentry 和 Inode 的 hash table,存放最近访问的 Dentry 和 Inode 节点,加速对虚拟文件系统的访问。

  • 8: 0x0000000047f59f00..0x0000000047f8400f, size:168K
    主要是 per-cpu 变量占用的内存。

  • 11: 0x0000000047f85180..0x0000000047fff2e6, size:488K
    主要是解析 dtb 生成 struct device_node 结构体所用的内存。

buffers & cached

free 命令第二行为什么要-/+ buffers/cache? buffers 与 cached 内存都属于空闲内存么?

Linux 为加速 IO 访问速度,会使用空闲内存来缓存文件以及元数据等内容,这就是 buffers 和 cached 内存。当内存不足时,这些内存会被回收,供内核与应用使用。所以 buffer 与 cache 实际上是已经使用了的内存,由于可以回收,属于潜在的空闲内存。但是并非所有的 buffer 和 cache 都可以回收,比如:

  • 如果有某个进程访问块设备或者普通文件,就需要 buffers 和 cached 空间,这部分就不能释放。
  • shared、 tmpfs 也包含在 cached 空间中。

系统使用的内存

free 命令的结果展示系统已经使用了 47928KB 的内存,都用到哪里去了呢?

进程使用的内存

新增一个进程使用了哪些内存? 首先,访问文件系统加载进程的可执行文件、库等,导致 buffer/cache 增大;其次,进程本身在用户空间运行时需要有自己的地址空间信息(用 mm_struct 结构体来表示,包含代码段、数据段、用户栈等地址空间描述);再次,内核会为进程创建进程描述符(task_struct)、内存描述符(mm_struct)等结构体,用于管理进程;此外,进程还有对应的内核栈,当进程陷入内核时需要内核栈来支持内核函数调用等等。 我们常说的进程使用的内存,指的是在用户空间所使用的内存。 关于用户进程的内存使用,涉及几个通用概念:

  • VSS: Virtual Set Size 使用的虚拟内存(包含共享库占用的内存)
  • RSS: Resident Set Size 实际使用物理内存(包含共享库占用的内存)
  • PSS: Proportional Set Size 实际使用的物理内存(比例分配共享库占用的内存)
  • USS: Unique Set Size 进程独自占用的物理内存(不包含共享库占用的内存)

一般来说: VSS >= RSS >= PSS >= USS

在/proc//smaps 节点中包含了进程的每一个内存映射的统计值,包含了 PSS、 RSS 等信息。 所以对/proc//smaps 节点中所有的 PSS 进行累加,即可统计出所有进程在用户空间所使用的内存,具体命令如下:

1
grep ^Pss /proc/[0-9]*/smaps | awk '{total+=$2}; END {print total}'

查看 rss 信息:

1
cat /proc/`pidof ***app`/status | grep RSS

此外还有 ramparser 统计工具:

1
2
3
4
root@Tina:/proc/684#  ramparser -a | grep Total
Mem Total = 256 Mb (Free 7.39 Mb, Available 7.75 Mb)
Mem Used Total = 235.96 Mb
Mem Used Total = Kernel used total(21.21Mb) + pss total(100.72Mb) +

总使用内存

单一个进程,涉及到了很多种类的内存使用,完全统计起来不太现实。为统计系统总使用内存,可将其划分为用户空间使用内存与内核使用内存。 用户空间使用的内存 = Buffers + Cached + AnonPages。 内核使用的内存 = Slab + PageTable + KernelStack + CmaUsed + Vmalloc + X。

其中,

  • CmaUsed = CmaTotal - CmaFree。
  • Vmalloc 表示/proc/vmallocinfo 中的 vmalloc 分配的内存,包含了内核模块使用的内存。计算方法为 grep vmalloc /proc/vmallocinfo | awk '{total+=$2}; END {print total}'。
  • X 表示直接通过 alloc_pages/get_free_page 分配的内存,这部分内存未纳入统计,属于内存黑洞。

内存优化

主要包括:

  • 保留内存优化
  • 内核使用内存优化
  • 用户空间使用内存优化

保留内存优化

内核静态内存优化

内核静态内存包括内核代码段数据段。优化方法主要有如下几种: 关闭不需要的模块,关闭模块下不需要的功能

  • 在内核根目录,执行。/scripts/ksize vmlinux 各个模块的代码段数据段的统计信息:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    Linux Kernel (vmlinux)                                      total |       text       data        bss
    ----------------------------------------------------------------------------------------------------
    vmlinux 6471329 | 4315221 2013884 142224
    ----------------------------------------------------------------------------------------------------
    drivers 2899612 | 2032098 819710 47804
    net 753530 | 721792 23658 8080
    fs 664960 | 642997 3151 18812
    kernel 543470 | 467211 34187 42072
    mm 314765 | 288517 6932 19316
    crypto 254117 | 196774 57271 72
    sound 193477 | 188029 3852 1596
    lib 156575 | 154010 236 2329
    block 151072 | 147441 2399 1232
    ipc 30522 | 29794 724 4
    init 25199 | 10990 14145 64
    security 4780 | 4756 8 16
    ----------------------------------------------------------------------------------------------------
    sum 5992079 | 4884409 966273 141397
    delta 479250 | -569188 1047611 827

    drivers total | text data bss
    ----------------------------------------------------------------------------------------------------
    drivers/built-in.o 2899612 | 2032098 819710 47804
    ----------------------------------------------------------------------------------------------------
    drivers/video 1012684 | 244524 739328 28832
    drivers/usb 247516 | 235586 8306 3624
    drivers/mtd 180132 | 171969 5455 2708
    drivers/media 171917 | 167120 3313 1484
    drivers/base 168782 | 164886 3264 632
    drivers/mmc 150962 | 149050 1716 196
    drivers/tty 92952 | 89978 1070 1904
    drivers/clk 71921 | 55673 15584 664
    drivers/hid 69921 | 66181 3728 12
    drivers/regulator 60558 | 58806 1612 140
    drivers/char 59195 | 54555 3640 1000
    drivers/input 57220 | 54728 2348 144
    drivers/pinctrl 56336 | 49295 6957 84
    drivers/spi 46753 | 45264 1485 4
    drivers/cpufreq 46675 | 41339 1088 4248
    drivers/i2c 46201 | 45552 597 52
    drivers/of 36229 | 35370 371 488
    drivers/thermal 30927 | 28822 2029 76
    drivers/gpio 30829 | 30392 404 33
    drivers/staging 27577 | 27104 449 24
    drivers/power 24587 | 22547 1708 332
    drivers/iommu 23637 | 23029 544 64
    drivers/rtc 21690 | 21150 384 156
    drivers/mfd 21408 | 14180 7220 8
    drivers/iio 18761 | 18545 160 56
    drivers/dma 18404 | 17998 310 96
    drivers/pwm 17398 | 16766 456 176
    drivers/irqchip 15919 | 13483 2340 96
    drivers/dma-buf 14119 | 14064 23 32
    drivers/soc 12843 | 11375 1332 136
    drivers/watchdog 10668 | 10174 449 45
    drivers/clocksource 9076 | 8380 592 104
    drivers/bus 6536 | 5762 738 36
    drivers/hwmon 5226 | 5054 144 28
    drivers/misc 4730 | 4358 360 12
    drivers/reset 3838 | 3718 120 0
    drivers/firmware 3587 | 3527 4 56
    drivers/net 1479 | 1431 48 0
    drivers/mpp 350 | 334 8 8
    ----------------------------------------------------------------------------------------------------
    sum 2899543 | 2032069 819684 47790
    delta 69 | 29 26 14

    net total | text data bss
    ----------------------------------------------------------------------------------------------------
    net/built-in.o 753530 | 721792 23658 8080
    ----------------------------------------------------------------------------------------------------
    net/ipv4 343777 | 327407 12726 3644
    net/core 231660 | 222656 7276 1728
    net/xfrm 50407 | 48727 1292 388
    net/unix 27015 | 24614 344 2057
    net/packet 26639 | 26370 269 0
    net/netlink 25701 | 25138 423 140
    net/key 20676 | 20372 300 4
    net/*.o 15735 | 15307 376 52
    net/sched 8093 | 7528 565 0
    net/ethernet 2443 | 2403 40 0
    net/ipv6 1263 | 1227 28 8
    ----------------------------------------------------------------------------------------------------
    sum 753409 | 721749 23639 8021
    delta 121 | 43 19 59

    fs total | text data bss
    ----------------------------------------------------------------------------------------------------
    fs/built-in.o 664960 | 642997 3151 18812
    ----------------------------------------------------------------------------------------------------
    fs/*.o 351463 | 339657 1550 10256
    fs/proc 83517 | 78968 361 4188
    fs/jffs2 79570 | 79278 136 156
    fs/fat 51402 | 51230 144 28
    fs/kernfs 21130 | 16955 75 4100
    fs/configfs 19069 | 18820 237 12
    fs/squashfs 19013 | 18965 44 4
    fs/debugfs 15752 | 15688 52 12
    fs/nls 11112 | 10996 116 0
    fs/sysfs 7133 | 7086 39 8
    fs/devpts 3299 | 2938 353 8
    fs/ramfs 1840 | 1796 40 4
    ----------------------------------------------------------------------------------------------------
    sum 664300 | 642377 3147 18776
    delta 660 | 620 4 36

    kernel total | text data bss
    ----------------------------------------------------------------------------------------------------
    kernel/built-in.o 543470 | 467211 34187 42072
    ----------------------------------------------------------------------------------------------------
    kernel/*.o 228463 | 209842 11969 6652
    kernel/time 94112 | 83229 6691 4192
    kernel/printk 54979 | 18467 8424 28088
    kernel/sched 47388 | 43798 3446 144
    kernel/irq 43205 | 40313 812 2080
    kernel/rcu 31236 | 29275 1932 29
    kernel/power 18951 | 17575 828 548
    kernel/locking 16625 | 16616 5 4
    kernel/bpf 8404 | 8048 64 292
    ----------------------------------------------------------------------------------------------------
    sum 543363 | 467163 34171 42029
    delta 107 | 48 16 43

    sound total | text data bss
    ----------------------------------------------------------------------------------------------------
    sound/built-in.o 193477 | 188029 3852 1596
    ----------------------------------------------------------------------------------------------------
    sound/soc 101278 | 98458 2688 132
    sound/core 91802 | 89198 1148 1456
    sound/*.o 586 | 558 20 8
    ----------------------------------------------------------------------------------------------------
    sum 193666 | 188214 3856 1596
    delta -189 | -185 -4 0

    lib total | text data bss
    ----------------------------------------------------------------------------------------------------
    lib/built-in.o 156575 | 154010 236 2329
    ----------------------------------------------------------------------------------------------------
    lib/*.o 192773 | 192059 644 70
    lib/zlib_deflate 16704 | 14364 60 2280
    lib/zlib_inflate 11187 | 11187 0 0
    lib/xz 8199 | 8163 36 0
    lib/lzo 2551 | 2551 0 0
    ----------------------------------------------------------------------------------------------------
    sum 231414 | 228324 740 2350
    delta -74839 | -74314 -504 -21

    block total | text data bss
    ----------------------------------------------------------------------------------------------------
    block/built-in.o 151072 | 147441 2399 1232
    ----------------------------------------------------------------------------------------------------
    block/*.o 143829 | 140218 2383 1228
    block/partitions 7227 | 7207 16 4
    ----------------------------------------------------------------------------------------------------
    sum 151056 | 147425 2399 1232
    delta 16 | 16 0 0

  • 关闭内核调试功能
  • 开启 CONFIG_CC_OPTIMIZE_FOR_SIZE 宏,使能-Os 编译参数
  • 排查内核占用空间大的符号

执行 nm --size -r vmlinux,可以列出所有符号占用的内存

  • 开启 CONFIG_THUMB2_AVOID_R_ARM_THM_JUMP11 和 CONFIG_THUMB2_KERNEL。注:会带来性能损失,具体损失根据实际平台典型应用场景进行测试

DTB 内存优化

Tina 内核中提供的 DTS 一般来说比较全面,对于特定的方案,往往用不了那么多,可以针对性的删除一些节点。

TEE 内存优化

Tina 中一些平台,会默认配置一些 TEE 保留内存。对于特定方案来说,很有可能用不了那么多内存。 TEE 默认保留配置与 bl31.bin、 optee-${CHIP}.bin 是配套的,因此修改时需要注意步更新。 比如 R329 默认配置了 8M 内存(SHM 2M,ATF 1M,OS 1M,TA 4M),但是对于非安全方案来说,只需要保留 ATF 就够了;对于不使用 TA 的安全方案,只需要保留 ATF 与 OS 的内存就可以了。

内核使用内存优化

内核使用的内存包括 Slab、 PageTable、 KernelStack、 CmaUsed、Vmalloc 等

Slab 优化

目前 Tina 上大部分方案默认选用的是 SLUB 分配器

  • 关闭 slab 调试宏 COFNIG_SLUB_DEBUG 与 CONFIG_SLABINFO
  • 尝试使用针对微小的嵌入式系统的 SLOB

内核模块优化

  • 不要开机全部加载,实时加载,实时卸载
  • 将内核模块编译到内核镜像中

用户空间使用内存优化

  • 使用更小的 C 库
  • 使用 size 优化的编译选项,比如-Os, -mthumb 等
  • 将 tmpfs 下大文件保持到 flash 上
  • 对于 64 位 CPU,可以使用 32 位的 rootfs
  • 使用更小的库或应用程序。比如使用 mbedtls,而不是 openssl
  • 减少守护进程数量,实时运行/关闭特定程序
  • 将只被一次依赖的动态库转化为动态库;使用 dlopen 来控制动态库的生存周期
  • 优化程序源码

参考文献

《全志SDK文档》