标签:rss 内存页 procselfsmaps private_dirty reids
上一篇博文中,我介绍了zmalloc.c文件中几个常用的函数,接下来给大家介绍一下该文件中的其他函数,其实本文中的很多函数要比上一篇文章中的函数要更有趣的,并且涉及到很多操作系统的知识。前面几个函数比较简单,一笔带过,后面几个是学习的重点。
void zmalloc_enable_thread_safeness(void) { zmalloc_thread_safe = 1; }zmalloc_thread_safe是一个全局静态变量(static int)。它是操作是否是线程安全的标识。1 表示线程安全,0 表示非线程安全。
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; }该函数要完成的操作就是返回变量used_memory(已用内存)的值,所以它的功能是查询系统当前为Redis分配的内存大小。本身代码量不大,但是涉及到了线程安全模式下的查询操作。实现线程同步用到了互斥锁(mutex)。关于互斥锁的内容在上一篇文章中已经简要介绍过了。总之要记住的是加锁(pthread_mutex_lock)和解锁(pthread_mutex_unlock)。在加了互斥锁之后,就能保证之后的代码同时只能被一个线程所执行。
void zmalloc_set_oom_handler(void (*oom_handler)(size_t)) { zmalloc_oom_handler = oom_handler; }该函数的功能是给zmalloc_oom_handler赋值。zmalloc_oom_handler是一个函数指针,表示在内存不足(out of memory,缩写oom)的时候所采取的操作,它的类型是void (*) (size_t)。所以zmalloc_set_oom_handler函数的参数也是void (*) (size_t)类型,调用的时候就是传递一个该类型的函数名就可以了。
#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这段代码和我在上一篇博文中介绍的zfree()函数中的内容颇为相似。大家可以去阅读那一篇博文。这里再概括一下,zmalloc(size)在分配内存的时候会多申请sizeof(size_t)个字节大小的内存【64位系统中是8字节】,即调用malloc(size+8),所以一共申请分配size+8个字节,zmalloc(size)会在已分配内存的首地址开始的8字节中存储size的值,实际上因为内存对齐,malloc(size+8)分配的内存可能会比size+8要多一些,目的是凑成8的倍数,所以实际分配的内存大小是size+8+X【(size+8+X)%8==0 (0<=X<=7)】。然后内存指针会向右偏移8个字节的长度。zfree()就是zmalloc()的一个逆操作,而zmalloc_size()的目的就是计算出size+8+X的总大小。
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);
snprintf(filename,256,"/proc/%d/stat",getpid());getpid()就是获得当前进程的id,所以这个snprintf()的功能就是将当前进程所对应的stat文件的绝对路径名保存到字符数组filename中。【不得不称赞一下类Unix系统中“万物皆文件”的概念】
if ((fd = open(filename,O_RDONLY)) == -1) return 0; if (read(fd,buf,4096) <= 0) { close(fd); return 0; }以只读模式打开 /proc/<pid>/stat 文件。然后从中读入4096个字符到字符数组buf中。如果失败就关闭文件描述符fd,并退出(个人感觉因错误退出,还是返回-1比较好吧)。
p = buf; count = 23; /* RSS is the 24th field in /proc/<pid>/stat */ while(p && count--) { p = strchr(p,' '); if (p) p++; }RSS在stat文件中的第24个字段位置,所以就是在第23个空格的后面。观察while循环,循环体中用到了字符串函数strchr(),这个函数在字符串p中查询空格字符,如果找到就把空格所在位置的字符指针返回并赋值给p,找不到会返回NULL指针。p++原因是因为,p当前指向的是空格,在执行自增操作之后就指向下一个字段的首地址了。如此循环23次,最终p就指向第24个字段的首地址了。
if (!p) return 0; x = strchr(p,' '); if (!x) return 0; *x = '\0';因为循环结束也可能是p变成了空指针,所以判断一下p是不是空指针。接下来的的几部操作很好理解,就是将第24个字段之后的空格设置为‘\0‘,这样p就指向一个一般的C风格字符串了。
rss = strtoll(p,NULL,10); rss *= page; return rss;这段代码又用到了一个字符串函数——strtoll():顾名思义就是string to long long的意思啦。它有三个参数,前面两个参数表示要转换的字符串的起始和终止位置(字符指针类型),NULL和‘\0‘是等价的。最后一个参数表示的是“进制”,这里就是10进制了。
/* Fragmentation = RSS / allocated-bytes */ float zmalloc_get_fragmentation_ratio(size_t rss) { return (float)rss/zmalloc_used_memory(); }
#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一个条件编译的函数,我们当然要聚焦到#if defined的部分。
FILE *fp = fopen("/proc/self/smaps","r");用标准C的fopen()以只读方式打开/proc/self/smaps文件。简单介绍一下该文件,前面我们已经说过/proc目录下有许多以进程id命名的目录,里面保存着每个进程的状态信息,而/proc/self目录的内容和它们是一样的,self/ 表示的是当前进程的状态目录。而smaps文件中记录着该进程的详细映像信息,该文件内部由多个结构相同的块组成,看一下其中某一块的内容:
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除去开头和结尾两行,其他的每一行都有一个字段和该字段的值(单位kb)组成【每个字段的具体含义,各位自行百度】。注意这只是smaps文件的一小部分。
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; } } }
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
--------------------------------------------------------------------------------------------------------------------------------------------------------------size_t zmalloc_get_private_dirty(void) { return zmalloc_get_smap_bytes_by_field("Private_Dirty:"); }源代码很简单,该函数的本质就是在调用 zmalloc_get_smap_bytes_by_field("Private_Dirty:");其完成的操作就是扫描 /proc/self/smaps文件,统计其中所有 Private_Dirty字段的和。那么这个Private_Dirty是个什么意思呢?
标签:rss 内存页 procselfsmaps private_dirty reids
原文地址:http://blog.csdn.net/guodongxiaren/article/details/44783767