主要函數(shù)
void *zmalloc(size_t size);
void *zcalloc(size_t size);
void *zrealloc(void *ptr, size_t size);
void zfree(void *ptr);
char *zstrdup(const char *s);
size_t zmalloc_used_memory(void);
void zmalloc_set_oom_handler(void (*oom_handler)(size_t));
size_t zmalloc_get_rss(void);
int zmalloc_get_allocator_info(size_t *allocated, size_t *active, size_t *resident);
size_t zmalloc_get_private_dirty(long pid);
size_t zmalloc_get_smap_bytes_by_field(char *field, long pid);
size_t zmalloc_get_memory_size(void);
void zlibc_free(void *ptr);
void *zmalloc(size_t size)
void *zmalloc(size_t size) {
void *ptr = malloc(size+PREFIX_SIZE);
if (!ptr) zmalloc_oom_handler(size);
#ifdef HAVE_MALLOC_SIZE
update_zmalloc_stat_alloc(zmalloc_size(ptr));
return ptr;
#else
*((size_t*)ptr) = size;
update_zmalloc_stat_alloc(size+PREFIX_SIZE);
return (char*)ptr+PREFIX_SIZE;
#endif
}
參數(shù)size是需要分配的空間大小。事實上我們需要分配的空間大小為size+PREFIX_SIZE。PREFIX_SIZE是根據(jù)平臺的不同和HAVE_MALLOC_SIZE宏定義控制的。如果malloc()函數(shù)調(diào)用失敗致盟,就會調(diào)用zmalloc_oom_handler()函數(shù)來打印異常矮台,并且會終止函數(shù)阻问,zmalloc_oom_handler其實是一個函數(shù)指針啊终,真正調(diào)用的函數(shù)是zmalloc_default_oom(),zmalloc_default_oom()函數(shù)源碼如下:
static void zmalloc_default_oom(size_t size) {
fprintf(stderr, "zmalloc: Out of memory trying to allocate %zu bytes\n",
size);
fflush(stderr);
abort();
}
static void (*zmalloc_oom_handler)(size_t) = zmalloc_default_oom;
內(nèi)存分配成功之后,會依據(jù)HAVE_MALLOC_SIZE的控制對前八個字節(jié)操作份企,用以記錄分配內(nèi)存的長度,會在update_zmalloc_stat_alloc()宏定義函數(shù)中更新used_memory這個靜態(tài)變量的值巡莹,update_zmalloc_stat_alloc()源碼如下:
#define update_zmalloc_stat_alloc(__n) do { \
size_t _n = (__n); \
if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \
atomicIncr(used_memory,__n); \
} while(0)
if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1));
這一行是為了將不為sizeof(long)的_n對sizeof(long)補齊
atomicIncr(used_memory,__n);會調(diào)用__atomic_add_fetch(&var,(count),__ATOMIC_RELAXED);用于保證更新used_memory變量的操作是一個原子操作司志。
void *zcalloc(size_t size)
void *zcalloc(size_t size) {
void *ptr = calloc(1, size+PREFIX_SIZE);
if (!ptr) zmalloc_oom_handler(size);
#ifdef HAVE_MALLOC_SIZE
update_zmalloc_stat_alloc(zmalloc_size(ptr));
return ptr;
#else
*((size_t*)ptr) = size;
update_zmalloc_stat_alloc(size+PREFIX_SIZE);
return (char*)ptr+PREFIX_SIZE;
#endif
}
zcalloc函數(shù)和zmalloc函數(shù)處理的思路很是相似,就不做太多的解釋了
void *zrealloc(void *ptr, size_t size)
void *zrealloc(void *ptr, size_t size) {
// 如果沒有定義HAVE_MALLOC_SIZE降宅,就說明PREFIX_SIZE宏定義不為0骂远,那么ptr并不是該段內(nèi)存真正的開始地址
#ifndef HAVE_MALLOC_SIZE
void *realptr;
#endif
size_t oldsize;
void *newptr;
if (ptr == NULL) return zmalloc(size);
// 根據(jù)HAVE_MALLOC_SIZE宏定義,oldsize,newptr獲取方式不一樣腰根,以及更新used_memory的細節(jié)
#ifdef HAVE_MALLOC_SIZE
oldsize = zmalloc_size(ptr);
newptr = realloc(ptr,size);
if (!newptr) zmalloc_oom_handler(size);
update_zmalloc_stat_free(oldsize);
update_zmalloc_stat_alloc(zmalloc_size(newptr));
return newptr;
#else
realptr = (char*)ptr-PREFIX_SIZE;
oldsize = *((size_t*)realptr);
newptr = realloc(realptr,size+PREFIX_SIZE);
if (!newptr) zmalloc_oom_handler(size);
*((size_t*)newptr) = size;
update_zmalloc_stat_free(oldsize+PREFIX_SIZE);
update_zmalloc_stat_alloc(size+PREFIX_SIZE);
return (char*)newptr+PREFIX_SIZE;
#endif
}
大致思路就是根據(jù)新的size進行重新分配內(nèi)存激才,并且對used_memory變量進行更新。只不過獲取原內(nèi)存大小方式不一樣额嘿,根據(jù)HAVE_MALLOC_SIZE進行區(qū)分瘸恼。
void zfree(void *ptr)
void zfree(void *ptr) {
#ifndef HAVE_MALLOC_SIZE
void *realptr;
size_t oldsize;
#endif
if (ptr == NULL) return;
#ifdef HAVE_MALLOC_SIZE
update_zmalloc_stat_free(zmalloc_size(ptr));
free(ptr);
#else
realptr = (char*)ptr-PREFIX_SIZE;
oldsize = *((size_t*)realptr);
update_zmalloc_stat_free(oldsize+PREFIX_SIZE);
free(realptr);
#endif
}
其實zfree函數(shù)和zrealloc函數(shù)做法差不到太多,都是對oldsize和realptr對HAVE_MALLOC_SIZE有無聲明分別進行操作册养。
char *zstrdup(const char *s)
char *zstrdup(const char *s) {
size_t l = strlen(s)+1;
char *p = zmalloc(l);
memcpy(p,s,l);
return p;
}
該函數(shù)是創(chuàng)建一個字符串副本
size_t zmalloc_used_memory(void)
size_t zmalloc_used_memory(void) {
size_t um;
atomicGet(used_memory,um);
return um;
}
獲取used_memory變量的值东帅,主要保證原子操作(在atomicGet(used_memory,um);中保證)
void zmalloc_set_oom_handler(void (*oom_handler)(size_t))
void zmalloc_set_oom_handler(void (*oom_handler)(size_t)) {
zmalloc_oom_handler = oom_handler;
}
主要用來設(shè)置內(nèi)存分配失敗處理函數(shù)指針zmalloc_oom_handler的值
size_t zmalloc_get_rss(void)
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 zmalloc_get_allocator_info(size_t *allocated, size_t *active, size_t *resident)
#if defined(USE_JEMALLOC)
int zmalloc_get_allocator_info(size_t *allocated,
size_t *active,
size_t *resident) {
uint64_t epoch = 1;
size_t sz;
*allocated = *resident = *active = 0;
/* Update the statistics cached by mallctl. */
sz = sizeof(epoch);
je_mallctl("epoch", &epoch, &sz, &epoch, sz);
sz = sizeof(size_t);
/* Unlike RSS, this does not include RSS from shared libraries and other non
* heap mappings. */
je_mallctl("stats.resident", resident, &sz, NULL, 0);
/* Unlike resident, this doesn't not include the pages jemalloc reserves
* for re-use (purge will clean that). */
je_mallctl("stats.active", active, &sz, NULL, 0);
/* Unlike zmalloc_used_memory, this matches the stats.resident by taking
* into account all allocations done by this process (not only zmalloc). */
je_mallctl("stats.allocated", allocated, &sz, NULL, 0);
return 1;
}
#else
int zmalloc_get_allocator_info(size_t *allocated,
size_t *active,
size_t *resident) {
*allocated = *resident = *active = 0;
return 1;
}
#endif
獲取分配器的信息,主要在使用jemalloc前提下使用球拦,獲取jemalloc分配的信息靠闭,詳細信息可在http://jemalloc.net/jemalloc.3.html查閱
size_t zmalloc_get_smap_bytes_by_field(char *field, long pid)
#if defined(HAVE_PROC_SMAPS)
size_t zmalloc_get_smap_bytes_by_field(char *field, long pid) {
char line[1024];
size_t bytes = 0;
int flen = strlen(field);
FILE *fp;
if (pid == -1) {
// /proc/pid/smaps反應(yīng)了運行時的進程的內(nèi)存影響,系統(tǒng)的運行時庫(so)坎炼,堆愧膀,棧信息均可在其中看到。
fp = fopen("/proc/self/smaps","r");
} else {
char filename[128];
snprintf(filename,sizeof(filename),"/proc/%ld/smaps",pid);
fp = fopen(filename,"r");
}
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, long pid) {
((void) field);
((void) pid);
return 0;
}
#endif
獲取/proc/pid/smaps中某一個field的字節(jié)大小
size_t zmalloc_get_private_dirty(long pid)
size_t zmalloc_get_private_dirty(long pid) {
return zmalloc_get_smap_bytes_by_field("Private_Dirty:",pid);
}
獲取Rss中已改寫的私有頁面頁面大小
size_t zmalloc_get_memory_size(void)
size_t zmalloc_get_memory_size(void) {
#if defined(__unix__) || defined(__unix) || defined(unix) || \
(defined(__APPLE__) && defined(__MACH__))
#if defined(CTL_HW) && (defined(HW_MEMSIZE) || defined(HW_PHYSMEM64))
int mib[2];
mib[0] = CTL_HW;
#if defined(HW_MEMSIZE)
mib[1] = HW_MEMSIZE; /* OSX. --------------------- */
#elif defined(HW_PHYSMEM64)
mib[1] = HW_PHYSMEM64; /* NetBSD, OpenBSD. --------- */
#endif
int64_t size = 0; /* 64-bit */
size_t len = sizeof(size);
if (sysctl( mib, 2, &size, &len, NULL, 0) == 0)
return (size_t)size;
return 0L; /* Failed? */
#elif defined(_SC_PHYS_PAGES) && defined(_SC_PAGESIZE)
/* FreeBSD, Linux, OpenBSD, and Solaris. -------------------- */
return (size_t)sysconf(_SC_PHYS_PAGES) * (size_t)sysconf(_SC_PAGESIZE);
#elif defined(CTL_HW) && (defined(HW_PHYSMEM) || defined(HW_REALMEM))
/* DragonFly BSD, FreeBSD, NetBSD, OpenBSD, and OSX. -------- */
int mib[2];
mib[0] = CTL_HW;
#if defined(HW_REALMEM)
mib[1] = HW_REALMEM; /* FreeBSD. ----------------- */
#elif defined(HW_PHYSMEM)
mib[1] = HW_PHYSMEM; /* Others. ------------------ */
#endif
unsigned int size = 0; /* 32-bit */
size_t len = sizeof(size);
if (sysctl(mib, 2, &size, &len, NULL, 0) == 0)
return (size_t)size;
return 0L; /* Failed? */
#else
return 0L; /* Unknown method to get the data. */
#endif
#else
return 0L; /* Unknown OS. */
#endif
}
獲取物理內(nèi)存的字節(jié)數(shù)
總結(jié)
看了redis內(nèi)存分配的源碼后点弯,其實沒有相信中的那么難以理解扇调,或許只是心理上的作用,當然也說明redis源碼寫得真的是好抢肛,讓我這種渣渣都能輕而易舉的看懂狼钮,并且注釋也很少碳柱,這里的函數(shù)幾乎都是對glibc的malloc中的函數(shù)進行了一層包裝,并且維護了一個叫做used_memory的全局變量熬芜,并且每一次對全局變量的操作都是原子操作莲镣。也對一些常用的函數(shù)進行了封裝,例如:獲取rss的大小涎拉,獲取/proc/pid/smaps文件中某一field占用字節(jié)數(shù)的大小瑞侮,獲取物理內(nèi)存字節(jié)數(shù)等等,總的來說鼓拧,收益匪淺半火,沒想到內(nèi)存操作可以做到這樣簡單。