Linux内存类型
一般来说,业务进程使用的内存主要有以下几种情况:
-
用户空间的匿名映射页(Anonymous pages in User Mode address spaces),比如调用malloc分配的内存,以及使用MAP_ANONYMOUS的mmap;当系统内存不够时,内核可以将这部分内存交换出去;
-
用户空间的文件映射页(Mapped pages in User Mode address spaces),包含map file和map tmpfs;前者比如指定文件的mmap,后者比如IPC共享内存;当系统内存不够时,内核可以回收这些页,但回收之前可能需要与文件同步数据;
-
文件缓存(page in page cache of disk file);发生在程序通过普通的read/write读写文件时,当系统内存不够时,内核可以回收这些页,但回收之前可能需要与文件同步数据;
-
buffer pages,属于page cache;比如读取块设备文件。
Linux Top支持的内存参数
使用top命令,按f
按键进入字段选择模式,可以选择更多top支持的指标,上,下方向键选择,空格键确定;并同时在’VIRT’参数上按’s’键,表示top显示的时候按VIRT
指标排序。
Fields Management for window 1:Def, whose current sort field is VIRT
Navigate with Up/Dn, Right selects for move then <Enter> or Left commits,
'd' or <Space> toggles display, 's' sets sort. Use 'q' or <Esc> to end!
* PID = Process Id * SWAP = Swapped Size (KiB) * RSlk = RES Locked (KiB)
* USER = Effective User Name * CODE = Code Size (KiB) * RSsh = RES Shared (KiB)
* PR = Priority * DATA = Data+Stack (KiB) CGNAME = Control Group name
* NI = Nice Value * nMaj = Major Page Faults
* VIRT = Virtual Image (KiB) * nMin = Minor Page Faults
* RES = Resident Size (KiB) nDRT = Dirty Pages Count
* SHR = Shared Memory (KiB) WCHAN = Sleeping in Function
* S = Process Status Flags = Task Flags <sched.h>
* %CPU = CPU Usage CGROUPS = Control Groups
* %MEM = Memory Usage (RES) SUPGIDS = Supp Groups IDs
* TIME+ = CPU Time, hundredths SUPGRPS = Supp Groups Names
* COMMAND = Command Name/Line TGID = Thread Group Id
PPID = Parent Process pid OOMa = OOMEM Adjustment
UID = Effective User Id OOMs = OOMEM Score current
RUID = Real User Id ENVIRON = Environment vars
RUSER = Real User Name vMj = Major Faults delta
SUID = Saved User Id vMn = Minor Faults delta
SUSER = Saved User Name USED = Res+Swap Size (KiB)
GID = Group Id nsIPC = IPC namespace Inode
GROUP = Group Name nsMNT = MNT namespace Inode
PGRP = Process Group Id nsNET = NET namespace Inode
TTY = Controlling Tty nsPID = PID namespace Inode
TPGID = Tty Process Grp Id nsUSER = USER namespace Inode
SID = Session Id nsUTS = UTS namespace Inode
nTH = Number of Threads LXC = LXC container name
P = Last Used Cpu (SMP) * RSan = RES Anonymous (KiB)
TIME = CPU Time * RSfd = RES File-based (KiB)
这里我们主要关注如下内存指标:VIRT
,
RES
, SHR
,SWAP
, CODE
, DATA
, nMaj
, nMin
, RSan
, RSfd
, RSlk
RSsh
,经过定制后的top看起来是如下这样的:
top - 14:36:34 up 11 days, 3:52, 5 users, load average: 0.88, 0.93, 0.99
Tasks: 622 total, 1 running, 380 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.1 us, 0.1 sy, 0.0 ni, 99.8 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 19667219+total, 16811641+free, 7277708 used, 21278080 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 18772587+avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND SWAP CODE DATA nMaj nMin RSan RSfd RSlk RSsh
2863765 root 20 0 17.139g 2.346g 22680 S 0.3 1.3 35:42.99 qemu-syst+ 0 10824 16.552g 0 14k 2.325g 22660 0 20
1185988 root 20 0 8690284 2.937g 22920 S 1.0 1.6 102:49.77 qemu-syst+ 0 10952 4790564 0 375k 2.916g 22900 0 20
3333090 falcon 20 0 4588624 38420 7240 S 0.0 0.0 24:58.97 falcon-ag+ 0 6444 584208 0 11k 31180 7240 0 0
3155 root 20 0 3932828 121680 10708 S 0.0 0.1 3:27.14 devpi-ser+ 0 4 533308 10 34k 110972 10708 0 0
2134 root 10 -10 3645212 417552 8416 S 1.0 0.2 188:09.43 ovs-vswit+ 0 100 409052 0 42k 409136 8416 3.476g 0
1435 root 20 0 2705640 66616 28572 S 0.0 0.0 13:44.90 dockerd 0 32668 608428 261 72k 38044 28572 0 0
1743 root 20 0 2422500 27820 8104 S 0.0 0.0 8:23.19 docker-co+ 0 7496 512056 70 22k 19716 8104 0 0
1503 root 20 0 1276176 31344 20592 S 0.0 0.0 4:08.83 libvirtd 0 472 289924 113 12k 10752 20592 0 0
4121036 root 20 0 623520 11864 4984 S 0.0 0.0 0:01.35 qemu-nbd 0 1480 271352 0 1762 6880 4984 0 0
826 root 20 0 484100 327264 316964 S 0.0 0.2 0:29.71 systemd-j+ 0 120 18464 1 48k 10300 4372 0 312592
2957 root 20 0 413516 5428 2512 S 0.0 0.0 0:00.37 docker-co+ 0 2248 78220 21 490 2916 2512 0 0
2113 root 20 0 314440 10844 8352 S 0.0 0.0 0:01.15 smbd 0 76 1596 23 21k 2492 8212 0 140
参数的具体解释:
VIRT
: 进程当前使用的所有虚拟内存总和,/proc/meminfo
中Committed_AS
参数是所有进程的VIRT总和;RES
: Resident Memory Size (KiB), 进程当前使用的物理内存,其中包括与其他进程共享的物理内存,RES = RSan + RSfd + RSsh;SHR
: Shared Memory Size (KiB), 进程与其他进程共享使用的物理内存;CODE
: 进程当前可执行二进制程序,依赖的库使用的物理内存;DATA
: 进程当前通过malloc, mmap等申请的虚拟地址总和,这些空间可能并没有被访问或者写入过,所以并没有被计入RES
。nMaj
: Major Page Fault Count, 一个程序可能占几Mb, 但并不是所有的指令都要同时运行, 有些是在初始化时运行, 有些是在特定条件下才会去运行. 因此linux并不会把所有的指令都从磁盘加载到page内存. 那么当cpu在执行指令时, 如果发现下一条要执行的指令不在实际的物理内存page中时, CPU 就会 raise a page fault, 通知MMU把下面要执行的指令从磁盘加载到物理内存page中。Major Fault通常意味着需要磁盘IO。nMin
: Minor Page Fault count, 实际需要的页面已经在物理内存page中了, 只是这个page没有被分配给当前进程, 这时CPU就会raise一个minor page fault, 让MMU把这个page分配给当前进程使用, 因此minor page fault并不需要去访问磁盘。RSan
: Resident Anonymous Memory Size (KiB), 当前通过malloc,mmap匿名映射占用的物理内存;RSfd
: Resident File-Backed Memory Size (KiB), 当前通过mmap文件映射占用的物理内存;RSsh
: Resident Shared Memory Size (KiB), 该进程通过匿名shm*/mmap系统调用获得的共享内存占用的物理内存;RSlk
: Resident Locked Memory Size (KiB), 该进程通过mlock/mlockall调用获得的不可以被换出的内存,OpenVSwitch –mlockall参数就支持通锁定内存,保证用来接收/发送缓存不会被交换出去。
DATA,VIRT,RES的区别
观察下面这个程序的top行为:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main(){
int *data, size, count, i;
printf( "fyi: your ints are %d bytes large\n", sizeof(int) );
printf( "Enter number of ints to malloc: " );
scanf( "%d", &size );
data = malloc( sizeof(int) * size );
if( !data ){
perror( "failed to malloc" );
exit( EXIT_FAILURE );
}
printf( "Enter number of ints to initialize: " );
scanf( "%d", &count );
for( i = 0; i < count; i++ ){
data[i] = 1337;
}
printf( "I'm going to hang out here until you hit <enter>" );
while( getchar() != '\n' );
while( getchar() != '\n' );
exit( EXIT_SUCCESS );
}
$ ./a.out
fyi: your ints are 4 bytes large
Enter number of ints to malloc: 1250000
Enter number of ints to initialize: 500000
通过top观察到的结果
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ SWAP CODE DATA COMMAND
<程序开始>
11129 xxxxxxx 16 0 3628 408 336 S 0 0.0 0:00.00 3220 4 124 a.out
<分配1250000 ints>
11129 xxxxxxx 16 0 8512 476 392 S 0 0.0 0:00.00 8036 4 5008 a.out
<初始化其中500000 ints>
11129 xxxxxxx 15 0 8512 2432 396 S 0 0.0 0:00.00 6080 4 5008 a.out
整理简化下上面的结果
时间点 | DATA | CODE | RES | VIRT |
---|---|---|---|---|
before allocation | 124 | 4 | 408 | 3628 |
after 5MB allocation | 5008 | 4 | 476 | 8512 |
after 2MB initialization | 5008 | 4 | 2432 | 8512 |
结论:
- 进程通过任何方式获得的虚拟内存空间都会计入VIRT。
- DATA是进程通过malloc等方式获得的数据内存空间,这是虚拟空间,可能还并没有使用。
- RES是进程的物理空间,被初始化(使用)过的DATA会计入RES。