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

    如果列出目录,您会发现每个进程的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);
    	}
    
    ...
    }
    ...
    
    • 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

    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

  • 相关阅读:
    基于web在线餐饮网站的设计与实现——仿Coco线上订奶茶饮料6个页面(HTML+CSS+JavaScript)
    如何查看SSL证书是OV还是DV?
    【Java快速复习】一.数据类型与结构设计
    Redis数据类型-Set-原理
    登陆认证,权限控制——持续更新的一篇博客
    从信源熵到互信息
    You can’t specify target table ‘xxx’ for update in FROM clause
    S25FL256S介绍及FPGA实现思路
    基类、接口、抽象类的区别
    前端周刊第十四期
  • 原文地址:https://blog.csdn.net/chen1415886044/article/details/127776836