在做Linux系统优化的时候,提到进程消耗的内存大小,我们或多或少听到VSS、RSS、PSS、USS等信息。自然的,Linux也提供了非常多的方法来监控宝贵的内存资源的使用情况。可以在Linux上敲命令 ps 、top、free查看得到。
我们在Linux上启动进程,会有一个栈空间(stack)和一个堆空间(heap), 栈空间用于函数调用和局部变量,堆空间是C语言的 malloc 来分配的全局指针。这些都是进程的私有数据,除了这些,还有映射进来的动态库,进程间的共享内存等共享空间。另外,从进程自身的角度看,虚拟内存是进程独立的,所有内存都是私有的,包括自身代码、共享库、堆栈等,它不用关心共享内存的事情。但实际上在物理内存的层面,很多东西是可以共享的,比如共享的代码库(.so)、自身代码甚至是自身运行时私有的堆栈内存。
什么是虚拟内存与物理内存
简单来讲,当我们的进程向系统申请内存时,比如通过malloc方法,得到的其实是虚拟内存。物理内存对于进程来说是透明的,进程直接操作的是虚拟内存。而数据和代码是存放在真实的物理内存的,之所以进程在虚拟内存中寻址可以获取数据,是因为虚拟内存与物理内存存在着映射关系。
说一说RSS、SWP、USS、SHR、WSS
- RSS
RSS表示了进程中真正被加载到物理内存中的页的大小。但是用它来表示进程占用的内存大小也不太合适,因为还有个共享代码库的概念(Shared Libraries)。
比如libxxx.so这个程序库,有多个进程会用到它,而系统在物理内存只会加载一遍这个代码库,然后这块物理内存会被映射到不同进程的虚拟内存空间中,对于单独的进程来说,就像是这个库只加载在自己的虚拟内存中一样,不需要关心它是否与其它进程共享。
而进程的RSS是包含这块共享库的内存空间的,因此如果简单把系统中所有进程的RSS相加的话,结果是比系统总的内存大的,因为共享库占的内存被计算了多遍。
- SWP
Linux中Swap(即:交换分区),类似于Windows的虚拟内存,就是当内存不足的时候,把一部分硬盘空间虚拟成内存使用,从而解决内存容量不足的情况。Android是基于Linux的操作系统,所以也可以使用Swap分区来提升系统运行效率。
- USS
是单个进程私有的内存大小,即该进程独占的内存部分。USS揭示了运行一个特定进程在的真实内存增量大小。如果进程终止,USS就是实际被返还给系统的内存大小。
- SHR
SHR是share(共享)的缩写,它表示的是进程占用的共享内存大小。
- WSS
进程保持工作所需的内存,是估算进程最近访问过的 Pages 数,包括物理内存、内核内存、脏页。
使用 /proc 下文件
proc文件系统是在系统启动时动态创建的虚拟文件系统,并在系统关闭时解散。它包含关于当前正在运行的进程的有用信息,它被视为内核的控制和信息中心。proc文件系统还提供了内核空间和用户空间之间的通信介质。
如果列出目录,您会发现每个进程的PID都有一个专用目录。
现在让我们检查指定PID的特定进程,您可以从ps命令获得任何正在运行的进程的PID.
ps -aux
在linux中,/proc包括每个正在运行的进程(包括内核进程)的目录,在名为/proc/PID的目录中,以下是存在的目录:
/proc/PID/cmdline 命令行参数。
/proc/PID/cpu 执行该命令的当前和最后一个cpu。
/proc/PID/cwd 链接到当前工作目录。
/proc/PID/environ 环境变量的值。
/proc/PID/exe 链接到此进程的可执行文件。
/proc/PID/fd 目录,其中包含所有文件描述符。
/proc/PID/maps 内存映射到可执行文件和库文件。
/proc/PID/mem 此进程持有的内存。
/proc/PID/root 链接到此进程的根目录。
/proc/PID/stat 进程状态。
/proc/PID/statm 进程内存状态信息。
/proc/PID/status 可读形式的过程状态。
/ proc / PID/ pagemap 来获取给定页面的物理地址。
/ proc / PID/comm 包含进程的命令名
/ proc / PID/smaps显示每个分区更详细的内存占用数据
maps: 文件可以查看某个进程的代码段、栈区、堆区、动态库、内核区对应的虚拟地址
smaps:文件是基于 /proc/PID/maps 的扩展,他展示了一个进程的内存消耗,比同一目录下的maps文件更为详细。maps文件只能显示简单的分区,smap文件可以显示每个分区的更详细的内存占用数据。
pagemap :此文件允许用户空间进程找出每个虚拟页面映射到的物理帧。
pagemap条目是二进制格式。专门用来 记录所链接进程的物理页号信息 。
kpagecount:这个文件包含64位计数 , 表示每一页被映射的次数,按照PFN值固定索引。 kpageflags:此文件包含为64位的标志集 ,表示该页的属性,按照PFN索引。
memstat-进程内存使用分析工具C/C++实现
在Linux下,一切都作为文件进行管理;甚至设备也可以作为文件访问。尽管可能认为“普通”文件是文本文件或二进制文件,但/proc目录包含一种奇怪的类型:虚拟文件。这些文件已列出,但实际上并不存在于磁盘上。
....
static void get_system_meminfo(void)
{
FILE *meminfo_file;
meminfo_file = fopen("/proc/meminfo", "r");
if (!meminfo_file)
die("fopen(/proc/meminfo failed (%s)", strerror(errno));
line[0] = '\0';
while (fgets(line, sizeof(line), meminfo_file)) {
if (!memcmp(line, "MemTotal:", 9))
meminfo.mem_total = read_proc_count(&line[9]);
else if (!memcmp(line, "MemFree:", 8))
meminfo.mem_free = read_proc_count(&line[8]);
else if (!memcmp(line, "MemAvailable:", 13))
meminfo.mem_avail = read_proc_count(&line[13]);
else if (!memcmp(line, "Shmem:", 6))
meminfo.shared = read_proc_count(&line[6]);
else if (!memcmp(line, "Buffers:", 8))
meminfo.buffers = read_proc_count(&line[8]);
else if (!memcmp(line, "Cached:", 7))
meminfo.cached = read_proc_count(&line[7]);
else if (!memcmp(line, "SwapCached:", 11))
meminfo.swap_cached = read_proc_count(&line[11]);
else if (!memcmp(line, "SwapTotal:", 10))
meminfo.swap_total = read_proc_count(&line[10]);
else if (!memcmp(line, "SwapFree:", 9))
meminfo.swap_free = read_proc_count(&line[9]);
else if (!memcmp(line, "PageTables:", 11))
meminfo.page_tables = read_proc_count(&line[11]);
else if (!memcmp(line, "KernelStack:", 12))
meminfo.k_stacks = read_proc_count(&line[12]);
else if (!memcmp(line, "Slab:", 5))
meminfo.k_slabs = read_proc_count(&line[5]);
else if (!memcmp(line, "KReclaimable:", 13))
meminfo.k_reclaimable = read_proc_count(&line[13]);
else if (!memcmp(line, "Hugepagesize:", 13))
meminfo.huge_page_size = read_proc_count(&line[13]);
line[0] = '\0';
}
fclose(meminfo_file);
}
...
int main(int argc, char *argv[])
{
first_pid = parse_command_line(argc, argv);
if (first_pid < argc)
parse_pids_from_cmdline(argc, argv, first_pid);
else
parse_pids_from_proc();
get_system_config();
get_system_meminfo();
if (args.general)
print_general_info();
if (args.maps) {
kpc_fd = open("/proc/kpagecount", O_RDONLY);
if (kpc_fd < 0)
die("failed to open /proc/kpagecount (%s)", strerror(errno));
kpf_fd = open("/proc/kpageflags", O_RDONLY);
if (kpf_fd < 0)
die("failed to open /proc/kpageflags (%s)", strerror(errno));
}
if (!args.verbose)
print_heading();
wss_grand_total = 0;
for (pid = args.pids; *pid; ++pid) {
sprintf(path, "/proc/%u/cmdline", *pid);
cmd_file = fopen(path, "r");
if (!cmd_file) {
fprintf(stderr, "Failed to access /proc/%u/\n", *pid);
continue;
}
if (!fgets(cmdline, sizeof(cmdline), cmd_file))
cmdline[0] = '\0';
fclose(cmd_file);
if (cmdline[0] == '\0') {
if (!args.all)
continue; /* kernel process */
sprintf(path, "/proc/%u/comm", *pid);
cmd_file = fopen(path, "r");
if (!cmd_file)
die("failed to open /proc/PID/comm (%s)", strerror(errno));
if (!fgets(&cmdline[1], sizeof(cmdline) - 2, cmd_file)) {
cmdline[0] = '\0';
} else {
cmdline[0] = '[';
char *p = strchr(cmdline, '\n');
if (p)
*p = ']';
}
fclose(cmd_file);
}
if (args.verbose)
print_verbose_heading(*pid, cmdline);
memset(&total, 0, sizeof(total));
if (args.maps)
maps_count_process(*pid, kpc_fd, kpf_fd, &total);
else
smaps_count_process(*pid, &total);
wss_grand_total += total.wss;
if (args.kibyte)
reduce_pstats_to_kib(&total);
if (args.verbose)
print_verbose_totals(&total);
else
print_totals(*pid, &total, cmdline);
}
...
}
...
If you need to add the complete source code of memstat, you can use WeChat (c17865354792)
运行结果
memstat - 显示整个系统内存使用情况。memstat通过遍历/proc下所有进程,然后解析内存使用情况。
给定一个进程ID,memstat可以显示进程pid的内存使用情况
总结
/proc文件系统是一种内核和内核模块用来向进程发送信息的机制。这个伪文件系统可以和内核内部的数据结构进行交互,获取实时的进程信息。注意,/proc文件系统是存储与内存而不是硬盘,/proc虚拟文件系统实质是以文件系统的形式访问内核数据的接口。内存管理是一个巨大的话题,后续再分享。
Welcome to follow the WeChat official account 【程序猿编码】
参考:proc(5) - Linux manual page