转载自:https://blog.youkuaiyun.com/guodongxiaren/article/details/44783767
上一篇博文中,我介绍了zmalloc.c文件中几个常用的函数,接下来给大家介绍一下该文件中的其他函数,其实本文中的很多函数要比上一篇文章中的函数要更有趣的,并且涉及到很多操作系统的知识。前面几个函数比较简单,一笔带过,后面几个是学习的重点。
开胃菜
zmalloc_enable_thread_safeness
- void zmalloc_enable_thread_safeness(void) {
- zmalloc_thread_safe = 1;
- }
zmalloc_used_memory
- size_t zmalloc_used_memory(void) {
- size_t um;
- if (zmalloc_thread_safe) {
- #if defined(__ATOMIC_RELAXED) || defined(HAVE_ATOMIC)
- um = update_zmalloc_stat_add(0);
- #else
- pthread_mutex_lock(&used_memory_mutex);
- um = used_memory;
- pthread_mutex_unlock(&used_memory_mutex);
- #endif
- }
- else {
- um = used_memory;
- }
- return um;
- }
zmalloc_set_oom_handler
- void zmalloc_set_oom_handler(void (*oom_handler)(size_t)) {
- zmalloc_oom_handler = oom_handler;
- }
不过zmalloc_oom_handler在声明的时候初始化了默认值——zmalloc_default_oom()。同样在上一篇博文中也有过介绍。
zmalloc_size
- #ifndef HAVE_MALLOC_SIZE
- size_t zmalloc_size(void *ptr) {
- void *realptr = (char*)ptr-PREFIX_SIZE;
- size_t size = *((size_t*)realptr);
- /* Assume at least that all the allocations are padded at sizeof(long) by
- * the underlying allocator. */
- if (size&(sizeof(long)-1)) size += sizeof(long)-(size&(sizeof(long)-1));
- return size+PREFIX_SIZE;
- }
- #endif
--------------------------------------------------------------------------------------------------------------------------------------------------------------
这个函数是一个条件编译的函数,通过阅读zmalloc.h文件,我们可以得知zmalloc_size()依据不同的平台,具有不同的宏定义,因为在某些平台上提供查询已分配内存实际大小的函数,可以直接
#define zmalloc_size(p):
- tc_malloc_size(p) 【tcmalloc】
- je_malloc_usable_size(p)【jemalloc】
- malloc_size(p) 【Mac系统】
当这三个平台都不存在的时候,就自定义,也就是上面的源码。
--------------------------------------------------------------------------------------------------------------------------------------------------------------
大餐
zmalloc_get_rss
获取RSS的大小,这个RSS可不是我们在网络上常常看到的RSS,而是指的Resident Set Size,表示当前进程实际所驻留在内存中的空间大小,即不包括被交换(swap)出去的空间。
了解一点操作系统的知识,就会知道我们所申请的内存空间不会全部常驻内存,系统会把其中一部分暂时不用的部分从内存中置换到swap区(装Linux系统的时候我们都知道有一个交换空间)。
该函数大致的操作就是在当前进程的
/proc/<pid>/stat 【<pid>表示当前进程id】文件中进行检索。该文件的第24个字段是RSS的信息,它的单位是pages(内存页的数目)
- size_t zmalloc_get_rss(void) {
- int page = sysconf(_SC_PAGESIZE);
- size_t rss;
- char buf[4096];
- char filename[256];
- int fd, count;
- char *p, *x;
- snprintf(filename,256,"/proc/%d/stat",getpid());
- if ((fd = open(filename,O_RDONLY)) == -1) return 0;
- if (read(fd,buf,4096) <= 0) {
- close(fd);
- return 0;
- }
- close(fd);
- p = buf;
- count = 23; /* RSS is the 24th field in /proc/<pid>/stat */
- while(p && count--) {
- p = strchr(p,' ');
- if (p) p++;
- }
- if (!p) return 0;
- x = strchr(p,' ');
- if (!x) return 0;
- *x = '\0';
- rss = strtoll(p,NULL,10);
- rss *= page;
- return rss;
- }
- int page = sysconf(_SC_PAGESIZE);
通过调用库函数sysconf()【大家可以man sysconf查看详细内容】来查询内存页的大小。
接下来:
- snprintf(filename,256,"/proc/%d/stat",getpid());
- if ((fd = open(filename,O_RDONLY)) == -1) return 0;
- if (read(fd,buf,4096) <= 0) {
- close(fd);
- return 0;
- }
- p = buf;
- count = 23; /* RSS is the 24th field in /proc/<pid>/stat */
- while(p && count--) {
- p = strchr(p,' ');
- if (p) p++;
- }
- if (!p) return 0;
- x = strchr(p,' ');
- if (!x) return 0;
- *x = '\0';
- rss = strtoll(p,NULL,10);
- rss *= page;
- return rss;
后面用rss和page相乘并返回,因为rss获得的实际上是内存页的页数,page保存的是每个内存页的大小(单位字节),相乘之后就表示RSS实际的内存大小了。
这个函数是查询内存碎片率(fragmentation ratio),即RSS和所分配总内存空间的比值。需要用zmalloc_get_rss()获得RSS的值,再以RSS的值作为参数传递进来。
zmalloc_get_fragmentation_ratio
- /* Fragmentation = RSS / allocated-bytes */
- float zmalloc_get_fragmentation_ratio(size_t rss) {
- return (float)rss/zmalloc_used_memory();
- }
-------------------------------------------------------------------------------------------------------------------------
-------------------------------------
内存碎片分为:内部碎片和外部碎片
- 内部碎片:是已经被分配出去(能明确指出属于哪个进程)却不能被利用的内存空间,直到进程释放掉,才能被系统利用;
- 外部碎片:是还没有被分配出去(不属于任何进程),但由于太小了无法分配给申请内存空间的新进程的内存空闲区域。
-------------------------------------------------------------------------------------------------------------------------
-------------------------------------
zmalloc_get_fragmentation_ratio()要获得的显然是内部碎片率。
zmalloc_get_smap_bytes_by_field
- #if defined(HAVE_PROC_SMAPS)
- size_t zmalloc_get_smap_bytes_by_field(char *field) {
- char line[1024];
- size_t bytes = 0;
- FILE *fp = fopen("/proc/self/smaps","r");
- int flen = strlen(field);
- if (!fp) return 0;
- while(fgets(line,sizeof(line),fp) != NULL) {
- if (strncmp(line,field,flen) == 0) {
- char *p = strchr(line,'k');
- if (p) {
- *p = '\0';
- bytes += strtol(line+flen,NULL,10) * 1024;
- }
- }
- }
- fclose(fp);
- return bytes;
- }
- #else
- size_t zmalloc_get_smap_bytes_by_field(char *field) {
- ((void) field);
- return 0;
- }
- #endif
- FILE *fp = fopen("/proc/self/smaps","r");
- 00400000-004ef000 r-xp 00000000 08:08 1305603 /bin/bash
- Size: 956 kB
- Rss: 728 kB
- Pss: 364 kB
- Shared_Clean: 728 kB
- Shared_Dirty: 0 kB
- Private_Clean: 0 kB
- Private_Dirty: 0 kB
- Referenced: 728 kB
- Anonymous: 0 kB
- AnonHugePages: 0 kB
- Swap: 0 kB
- KernelPageSize: 4 kB
- MMUPageSize: 4 kB
- Locked: 0 kB
- VmFlags: rd ex mr mw me dw sd
- while(fgets(line,sizeof(line),fp) != NULL) {
- if (strncmp(line,field,flen) == 0) {
- char *p = strchr(line,'k');
- if (p) {
- *p = '\0';
- bytes += strtol(line+flen,NULL,10) * 1024;
- }
- }
- }
- 利用fgets()逐行读取/proc/self/smaps文件内容
- 然后strchr()将p指针定义到字符k的位置
- 然后将p置为'\0',截断形成普通的C风格字符串
- line指向的该行的首字符,line+flen(要查询的字段的长度)所指向的位置就是字段名后面的空格处了,不必清除空格,strtol()无视空格可以将字符串转换成int类型
- strol()转换的结果再乘以1024,这是因为smaps里面的大小是kB表示的,我们要返回的是B(字节byte)表示
实际上
/proc/self目录是一个符号链接,指向/proc/目录下以当前id命名的目录。我们可以进入该目录下敲几个命令测试一下。
root@X:/proc/self# pwd -P
/proc/4152
root@X:/proc/self# ps aux|grep [4]152
root 4152 0.0 0.0 25444 2176 pts/0 S 09:06 0:00 bash
-------------------------------------------------------------------------------------------------------------------------
-------------------------------------
zmalloc_get_private_dirty
- size_t zmalloc_get_private_dirty(void) {
- return zmalloc_get_smap_bytes_by_field("Private_Dirty:");
- }
大家继续观察一下,我在上面贴出的 /proc/self/smaps文件的结构,它有很多结构相同的部分组成。其中有几个字段有如下的关系:
Rss=Shared_Clean+Shared_Dirty+Private_Clean+Private_Dirty
其中:
- Shared_Clean:多进程共享的内存,且其内容未被任意进程修改
- Shared_Dirty:多进程共享的内存,但其内容被某个进程修改
- Private_Clean:某个进程独享的内存,且其内容没有修改
- Private_Dirty:某个进程独享的内存,但其内容被该进程修改
其实所谓的共享的内存,一般指的就是Unix系统中的共享库(.so文件)的使用,共享库又叫动态库(含义同Windows下的.dll文件),它只有在程序运行时才被装入内存。这时共享库中的代码和数据可能会被多个进程所调用,于是就会产生共享(Shared)与私有(Private)、干净(Clean)与脏(Dirty)的区别了。此外该处所说的共享的内存除了包括共享库以外,还包括System V的IPC机制之一的共享内存段(shared memory)
-------------------------------------------------------------------------------------------------------------------------
-------------------------------------
关于smaps文件中Shared_Clean、Shared_Dirty、Private_Clean、Private_Dirty这几个字段含义的详细讨论,有位网友进行了深入地探究,并形成了博文,推荐阅读:
-------------------------------------------------------------------------------------------------------------------------
-------------------------------------