十一放假在家,没有更新博客,现在忙里偷闲,记录一下这几天源码的总结,redis的源码还是比较容易理解的,至少目前是,它有自己的lis实现和字符串的管理办法,也有自己的内存管理策略。现在就从这三部分一一总结一下。
内存管理:
内存管理源码只有zmalloc.h 和zmalloc,c这两个文件。主要函数是通过zmalloc来申请和zfree来释放。zmalloc的时候,他会额外的申请PREFIX_SIZE个空间。这是个long long的大小,为的是存储申请内存的大小,其实和malloc的现实也是一样的。malloc也有头部对吧。然后它将除去头部大小的实际内存返回给用户。
而calloc 将内存初始化为1而不是0,这个倒是挺奇怪的。zrealloc函数是首选判断之前的内存是不是NULL的,假如是NULL的直接调用zmalloc就好了,否则的话,在得到之前的真实内存起始地址,然后冲洗realloc,这个的现实其实和系统的realloc差不多。然后这个文件中有一些统计的信息,是用一个宏来实现的,我们自己做的项目其实也是用宏来实现的,宏的好处就是减少函数调用开销,有一个技巧,为了防止宏受到别的语句的影响,宏使用了do while方式,while的条件是0,也就是只执行一次。
然后是计算一下系统的信息,比如RSS。redis一个优点就是跨平台性,所以定义了不同系统得到系统信息的方式,比如rss,有的系统通过stat文件得到,有的系统通过stask结构体得到,有的系统没有,那么就直接使用统计信息。而对于得到系统总的内存大小,更是针对不同的系统进行了计算。内存管理这部分的一个额外收获是原来平台对malloc的真实实现有不同的版本,有的是用的是jemalloc有的使用的tcmalloc。
list的实现是通过双向链表来实现的。这个本身就是一段优美的代码。里面定义了复制和释放的函数指针,用来方便深度复制和拷贝吧.还纪录了list的长度大小插入部分 分别定义 了从头部插入,从尾部插入,然后是从中间插入。从任意插入,从任意节点插入又分为从节点的前面插入和后面插入,是通过after这个参数区分的。另一个需要提到的是list还设置了迭代器的概念,基于next指针实现的。
第三部分就是最有意思的字符串部分了,这部分封装了char*这个基本的数据结构,和内存管理一样,设置了一个头部信息,用来存放字符的长度,以及分配的空间大小。而根据不同的字符大小,有不同的头部大小,这样可以更加有效的使用内存。而且为了内存更加轻便,使用了pack关键字,就是强制1字节对齐,而不是默认的对齐方式,这样可以更加节省内存。这部分具体如下:
这部分学到了一个比较好玩的语法结构,就是attribute关键字的使用还有一个宏的使用方法:
struct __attribute__ ((__packed__)) sdshdr5 {
unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
char buf[];
};
#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T)));
使用## 作为拼接的语法。
sds这部分是自己封装了char*,为了更好的进行字符的改变,扩展,修改等。它借鉴了vector的内存管理方式。使用sdsMakeRoomFor进行字符串的扩张。扩展方式是每次加入还有做够的空闲区域的话就直接返回。否则检查需要扩展的大小是否超过一个系统默认值,假如没有吵过,那么就2倍扩张,否则的话,就扩展系统默认值大小。这样既能够有效的扩展大小,又不至于扩展的特别严重。代码如下:
sds sdsMakeRoomFor(sds s, size_t addlen) {
void *sh, *newsh;
size_t avail = sdsavail(s);
size_t len, newlen;
char type, oldtype = s[-1] & SDS_TYPE_MASK;
int hdrlen;
/* Return ASAP if there is enough space left. */
if (avail >= addlen) return s;
len = sdslen(s);
sh = (char*)s-sdsHdrSize(oldtype);
newlen = (len+addlen);
if (newlen < SDS_MAX_PREALLOC)
newlen *= 2;
else
newlen += SDS_MAX_PREALLOC;
type = sdsReqType(newlen);
/* Don't use type 5: the user is appending to the string and type 5 is
* not able to remember empty space, so sdsMakeRoomFor() must be called
* at every appending operation. */
if (type == SDS_TYPE_5) type = SDS_TYPE_8;
hdrlen = sdsHdrSize(type);
if (oldtype==type) {
newsh = s_realloc(sh, hdrlen+newlen+1);
if (newsh == NULL) return NULL;
s = (char*)newsh+hdrlen;
} else {
/* Since the header size changes, need to move the string forward,
* and can't use realloc */
newsh = s_malloc(hdrlen+newlen+1);
if (newsh == NULL) return NULL;
memcpy((char*)newsh+hdrlen, s, len+1);
s_free(sh);
s = (char*)newsh+hdrlen;
s[-1] = type;
sdssetlen(s, len);
}
sdssetalloc(s, newline);
return s;
}
因为是在char*的基础上封装的,所以很多信息都需要手动的去设置,在你改变了一个字符串的大小的时候,需要去重新设置字符的长度大小。
我觉得里面有几个函数写的很不错,拿出来分享一下:
一个是 格式化写入的函数,里面对于堆栈上数组的使用比较有意思:
/* Like sdscatprintf() but gets va_list instead of being variadic. */
sds sdscatvprintf(sds s, const char *fmt, va_list ap) {
va_list cpy;
char staticbuf[1024], *buf = staticbuf, *t;
size_t buflen = strlen(fmt)*2;
/* We try to start using a static buffer for speed.
* If not possible we revert to heap allocation. */
if (buflen > sizeof(staticbuf)) {
buf = s_malloc(buflen);
if (buf == NULL) return NULL;
} else {
buflen = sizeof(staticbuf);
}
/* Try with buffers two times bigger every time we fail to
* fit the string in the current buffer size. */
while(1) {
buf[buflen-2] = '\0';
va_copy(cpy,ap);
vsnprintf(buf, buflen, fmt, cpy);
va_end(cpy);
if (buf[buflen-2] != '\0') {
if (buf != staticbuf) s_free(buf);
buflen *= 2;
buf = s_malloc(buflen);
if (buf == NULL) return NULL;
continue;
}
break;
}
/* Finally concat the obtained string to the SDS string and return it. */
t = sdscat(s, buf);
if (buf != staticbuf) s_free(buf);
return t;
}
sds sdscatfmt(sds s, char const *fmt, ...)
sds sdstrim(sds s, const char *cset) {
char *start, *end, *sp, *ep;
size_t len;
sp = start = s;
ep = end = s+sdslen(s)-1;
while(sp <= end && strchr(cset, *sp)) sp++;
while(ep > sp && strchr(cset, *ep)) ep--;
len = (sp > ep) ? 0 : ((ep-sp)+1);
if (s != sp) memmove(s, sp, len);
s[len] = '\0';
sdssetlen(s,len);
return s;
}
sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count) {
int elements = 0, slots = 5, start = 0, j;
sds *tokens;
if (seplen < 1 || len < 0) return NULL;
tokens = s_malloc(sizeof(sds)*slots);
if (tokens == NULL) return NULL;
if (len == 0) {
*count = 0;
return tokens;
}
for (j = 0; j < (len-(seplen-1)); j++) {
/* make sure there is room for the next element and the final one */
if (slots < elements+2) {
sds *newtokens;
slots *= 2;
newtokens = s_realloc(tokens,sizeof(sds)*slots);
if (newtokens == NULL) goto cleanup;
tokens = newtokens;
}
/* search the separator */
if ((seplen == 1 && *(s+j) == sep[0]) || (memcmp(s+j,sep,seplen) == 0)) {
tokens[elements] = sdsnewlen(s+start,j-start);
if (tokens[elements] == NULL) goto cleanup;
elements++;
start = j+seplen;
j = j+seplen-1; /* skip the separator */
}
}
/* Add the final element. We are sure there is room in the tokens array. */
tokens[elements] = sdsnewlen(s+start,len-start);
if (tokens[elements] == NULL) goto cleanup;
elements++;
*count = elements;
return tokens;
cleanup:
{
int i;
for (i = 0; i < elements; i++) sdsfree(tokens[i]);
s_free(tokens);
*count = 0;
return NULL;
}
}
1. 测试的使用使用 宏来做,自动进行测试,可以显示哪些测试用例通过了,哪些没有通过,我之前使用assert做的。
2.写完一个模块,一定要给这个模块写一些测试用例,阿里和百度都挺重视这个问题的。