Linux下C/C++实现进程内存使用分析工具(memstat)

在做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文件系统还提供了内核空间和用户空间之间的通信介质。

Linux下C/C++实现进程内存使用分析工具(memstat)_第1张图片

如果列出目录,您会发现每个进程的PID都有一个专用目录。

Linux下C/C++实现进程内存使用分析工具(memstat)_第2张图片

现在让我们检查指定PID的特定进程,您可以从ps命令获得任何正在运行的进程的PID.

ps -aux

Linux下C/C++实现进程内存使用分析工具(memstat)_第3张图片Linux下C/C++实现进程内存使用分析工具(memstat)_第4张图片
在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: 文件可以查看某个进程的代码段、栈区、堆区、动态库、内核区对应的虚拟地址

Linux下C/C++实现进程内存使用分析工具(memstat)_第5张图片
smaps:文件是基于 /proc/PID/maps 的扩展,他展示了一个进程的内存消耗,比同一目录下的maps文件更为详细。maps文件只能显示简单的分区,smap文件可以显示每个分区的更详细的内存占用数据。

Linux下C/C++实现进程内存使用分析工具(memstat)_第6张图片
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)

运行结果

Linux下C/C++实现进程内存使用分析工具(memstat)_第7张图片memstat - 显示整个系统内存使用情况。memstat通过遍历/proc下所有进程,然后解析内存使用情况。

给定一个进程ID,memstat可以显示进程pid的内存使用情况

总结

/proc文件系统是一种内核和内核模块用来向进程发送信息的机制。这个伪文件系统可以和内核内部的数据结构进行交互,获取实时的进程信息。注意,/proc文件系统是存储与内存而不是硬盘,/proc虚拟文件系统实质是以文件系统的形式访问内核数据的接口。内存管理是一个巨大的话题,后续再分享。

Welcome to follow the WeChat official account 【程序猿编码

参考:proc(5) - Linux manual page

你可能感兴趣的:(C/C++,linux,c语言,c++,内存,进程)