Linux 性能优化实战(倪朋飞)---内存
内存映射
只有实际使用的虚拟内存才分配物理内存,分配后的物理内存通过内存映射管理。
为了完成内存映射,内核为每个进程维护一张页表,记录虚拟地址与物理地址的映射关系。
页表存储在 CPU 的内存管理单元 MMU 中。
TLB(Translation Lookaside Buffer)是 MMU 中页表的高速缓存。
MMU 以页(大小通常为 4KB)为单位管理内存。
为解决页表项过多的问题,e.g. 仅 32 位系统就需 100 多万个页表项(4GB/4KB),Linux 提供了两种机制:多级页表和大页。
虚拟内存空间分布
用户空间内存,从低到高分别为:
- 只读段,包括代码和常量等。
- 数据段,包括全局变量等。
- 堆,包括动态分配的内存,从低地址向上增长。
- 文件映射段,包括动态库、共享内存等,从高地址开始向下增长。
- 栈,包括局部变量和函数调用的上下文等。栈大小是固定的,一般是 8MB。
堆和文件映射段的内存是动态分配的。
内存分配与回收
C 标准库采用 malloc() 分配内存,对应到系统调用,有两种实现方式,brk 和 mmap()。
brk() 分配小内存(小于 128K),通过移动堆顶的位置实现,释放后不会立刻归还内存,而是被缓存起来,供重复使用。
mmap() 分配大内存(大于 128K),在文件映射段找一块空闲内存分配出去,释放后直接归还系统,所以每次都会发生缺页异常。
两种调用发生后,不会真正分配内存,而是在内存被首次访问时,通过缺页异常进入内核,由内核分配。
Linux 使用伙伴系统管理内存分配,以页为单位,如果遇到比页小的对象(如不到 1K),对于用户空间,malloc 通过 brk() 分配;对于内核空间,通过 slab 分配器,可将 slab 看作构建在伙伴系统上的一个缓存。
当内存紧张时,系统会通过一系列机制回收内存:
- 回收缓存,如 LRU 算法;
- 回收不常访问的内存,把不常使用的内存通过交换分区直接写到磁盘;
- 杀死进程,通过 OOM(Out of Memory)直接杀死占用大量内存的进程。
查看内存
查看系统内存:
# 注意不同版本的 free 输出可能会有所不同
$ free
total used free shared buff/cache available
Mem: 4039436 570832 2195068 11972 1273536 3108340
Swap: 1533948 0 1533948
- total:总内存大小;
- used:已使用内存的大小,包含了共享内存;
- free:未使用内存的大小;
- shared:共享内存的大小;
- buff/cache:缓存和缓存区的大小;
- available:新进程可用内存的大小,包含未使用内存和可回收的缓存。
查看进程内存:
# 按下 M 切换到内存排序
$ top
top - 21:34:30 up 1:03, 1 user, load average: 0.05, 0.21, 0.16
Tasks: 206 total, 2 running, 204 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.3 us, 0.7 sy, 0.0 ni, 99.0 id, 0.0 wa, 0.0 hi, 0.1 si, 0.0 st
KiB Mem : 25.0/4039436 [|||||||||||||| ]
KiB Swap: 0.0/1533948 [ ]
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1695 root 20 0 476544 76896 34408 R 2.7 1.9 0:04.31 Xorg
4572 yjp 20 0 1244828 82788 51872 S 2.3 2.0 0:04.78 compiz
4855 yjp 20 0 663612 35588 28484 S 1.3 0.9 0:00.66 gnome-term+
1714 root 10 -10 393912 50248 7464 S 0.7 1.2 0:06.27 ovs-vswitc+
2139 root 20 0 795780 67780 39256 S 0.3 1.7 0:10.40 dockerd
- VIRT:进程虚拟内存大小,包括进程申请过的内存,即便没有分配;
- RES:常驻内存大小,即实际使用的物理内存大小,不包括 Swap 和共享内存;
- SHR:共享内存大小,如与其它进程共同使用的共享内存、加载的动态链接库和程序代码段等;
- %MEM:进程使用的物理内存占系统内存的百分比。
Buffer 和 Cache
Buffer 是对磁盘数据的缓存,Cache 是文件数据的缓存,它们会用在读请求,也会用在写请求中。
验证:
磁盘和文件写
# 清理文件页、目录项、Inodes 等各种缓存
$ echo 3 > /proc/sys/vm/drop_caches
终端1,读取随机设备,生成一个 500MB 大小的文件:
$ dd if=/dev/urandom of=/tmp/file bs=1M count=500
终端2,查看:
$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
0 0 0 7499460 1344 230484 0 0 0 0 29 145 0 0 100 0 0
1 0 0 7338088 1752 390512 0 0 488 0 39 558 0 47 53 0 0
1 0 0 7158872 1752 568800 0 0 0 4 30 376 1 50 49 0 0
1 0 0 6980308 1752 747860 0 0 0 0 24 360 0 50 50 0 0
0 0 0 6977448 1752 752072 0 0 0 0 29 138 0 0 100 0 0
0 0 0 6977440 1760 752080 0 0 0 152 42 212 0 1 99 1 0
...
0 1 0 6977216 1768 752104 0 0 4 122880 33 234 0 1 51 49 0
0 1 0 6977440 1768 752108 0 0 0 10240 38 196 0 0 50 50 0
cache 增长,buff 几乎不变。
终端1:
# 首先清理缓存
$ echo 3 > /proc/sys/vm/drop_caches
# 然后运行 dd 命令向磁盘分区 /dev/sdb1 写入 2G 数据
$ dd if=/dev/urandom of=/dev/sdb1 bs=1M count=2048
终端2:
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 0 7584780 153592 97436 0 0 684 0 31 423 1 48 50 2 0
1 0 0 7418580 315384 101668 0 0 0 0 32 144 0 50 50 0 0
1 0 0 7253664 475844 106208 0 0 0 0 20 137 0 50 50 0 0
1 0 0 7093352 631800 110520 0 0 0 0 23 223 0 50 50 0 0
1 1 0 6930056 790520 114980 0 0 0 12804 23 168 0 50 42 9 0
1 0 0 6757204 949240 119396 0 0 0 183804 24 191 0 53 26 21 0
1 1 0 6591516 1107960 123840 0 0 0 77316 22 232 0 52 16 33 0
buff 增长,cache 几乎不变。
磁盘和文件读
结果同磁盘和文件写。
参考
倪朋飞. Linux 性能优化实战.
更多推荐
所有评论(0)