内容全部是自我理解梳理而成,可能存在错误内容。

涉及文件

  • sds.h
  • sds.c

具体了解sds的可以看 http://github.com/antirez/sds

结构体定义

  1. struct test {
  2. int age;
  3. int len;
  4. unsigned char flags;
  5. char name[];
  6. } test;

内存分布

image.png
通过这个图,(如果不考虑结构体对齐)可知

dd->name 下移一位 就是flags的地址, unsigned char flags = dd->name[-1] printf(“输出输出flags:%c\n”, dd->name[-1]);===> output: 【输出flags:B】

获取结构体, 通过结构体获取其他属性 struct test sh = (void ) ((dd->name) - sizeof(struct test));
printf(“name:%s\n”, sh->name); ===> output: 【name址:chenshun001】

而实际上会出现内存对齐,导致flags也会占用4个字节,从而导致移动内存获取结构体指针失败。

概念

  • 结构体内存对齐: 按照int 4个字节对齐,此时通过sizeof(struct test)进行计算,结构体test占用12个字节。

    1. struct test {
    2. int age;
    3. int len;
    4. unsigned char flags;
    5. char name[];
    6. } test;
  • 结构体属性压缩: 只占用对应的内存长度,此时通过sizeof(struct test)进行计算,结构体test占用9个字节。

    1. struct __attribute__ ((__packed__)) test {
    2. int age;
    3. int len;
    4. unsigned char flags;
    5. char name[];
    6. } test;

    实际案例

    ```c

    include

    include

    include

    include

struct attribute ((packed)) test { int age; int len; unsigned char flags; char name[]; } test;

int main() { struct test dd = malloc(sizeof(struct test)); if (dd == NULL) { return 1; } char f = “chenshun001”; dd->age = 456; dd->len = 456; dd->flags = 66;

  1. printf("输出flags:%d\n", dd->name[-1]);
  2. printf("结构体地址:%p\n", &dd);
  3. printf("结构体指向的地址:%p\n", *&dd);
  4. printf("结构体age地址:%p\n", &dd->age);
  5. printf("结构体len地址:%p\n", &dd->len);
  6. printf("结构体flags地址:%p\n", &dd->flags);
  7. memcpy(dd->name, f, strlen(f));
  8. printf("结构体name地址:%p\n", &dd->name);
  9. //如果加上内存对齐,那么这里获取的内存地址是错误的
  10. struct test *sh = (void *) ((dd->name) - sizeof(struct test));
  11. printf("name址:%s\n", sh->name);
  12. free(dd);
  13. return 0;

} //output //输出flags:66 //结构体地址:0x7ffee97a48c0 //结构体指向的地址:0x7fe937c05a20 //结构体age地址:0x7fe937c05a20 //结构体len地址:0x7fe937c05a24 //结构体flags地址:0x7fe937c05a28 //结构体name地址:0x7fe937c05a29 //name址:chenshun001

  1. <a name="cKGFu"></a>
  2. ### Redis SDS结构体
  3. ```c
  4. // sdshdr 结构 低版本
  5. struct sdshdr {
  6. int len;
  7. int free;
  8. char buf[];
  9. };
  10. //=============================================================================
  11. //高版本
  12. struct __attribute__ ((__packed__)) sdshdr5 {
  13. //低3个字节表示类型 高5个字节表示长度 2^5=32,必须小于32
  14. unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
  15. char buf[];
  16. };
  17. struct __attribute__ ((__packed__)) sdshdr8 {
  18. uint8_t len; /* used */
  19. uint8_t alloc; /* excluding the header and null terminator */
  20. unsigned char flags; /* 3 lsb of type, 5 unused bits */
  21. char buf[];
  22. };
  23. struct __attribute__ ((__packed__)) sdshdr16 {
  24. uint16_t len; /* used */
  25. uint16_t alloc; /* excluding the header and null terminator */
  26. unsigned char flags; /* 3 lsb of type, 5 unused bits */
  27. char buf[];
  28. };
  29. struct __attribute__ ((__packed__)) sdshdr32 {
  30. uint32_t len; /* used */
  31. uint32_t alloc; /* excluding the header and null terminator */
  32. unsigned char flags; /* 3 lsb of type, 5 unused bits */
  33. char buf[];
  34. };
  35. struct __attribute__ ((__packed__)) sdshdr64 {
  36. uint64_t len; /* used */
  37. uint64_t alloc; /* excluding the header and null terminator */
  38. unsigned char flags; /* 3 lsb of type, 5 unused bits */
  39. char buf[];
  40. };

老版本的SDS内存图如上图所示
高版本的如下图所示
image.png

Redis 内部操作

创建一个SDS

  1. /*sds本身就是一个字符指针,使用sds可以直接在上层使用c标准库的函数*/
  2. sds sdsnewlen(const void *init, size_t initlen) {
  3. void *sh;
  4. sds s;
  5. //确定sds结构体类型SDS_TYPE_5/SDS_TYPE_8/SDS_TYPE_16/SDS_TYPE_32/SDS_TYPE_64
  6. char type = sdsReqType(initlen);
  7. //...移除一点点代码
  8. //获取结构体需要的内存 sizeof(struct 对应的结构体)
  9. int hdrlen = sdsHdrSize(type);
  10. //char指针
  11. unsigned char *fp; /* flags pointer. */
  12. //分配内存 = 结构体需要的内存+初始化的长度内存+1('\0'需要的内存)
  13. sh = s_malloc(hdrlen + initlen + 1);
  14. //分配内存失败
  15. if (sh == NULL) return NULL;
  16. //结构体指针的起始地址+hdrlen的地址==sds char柔性数组的开始地址
  17. s = (char *) sh + hdrlen;
  18. //char指针赋值,这样下边的*fp就是指向sds结构体的flags字段了
  19. fp = ((unsigned char *) s) - 1;
  20. switch (type) {
  21. case SDS_TYPE_5: {
  22. //设置fp指针的数据高5位 就是initlen,字符长度
  23. *fp = type | (initlen << SDS_TYPE_BITS);
  24. break;
  25. }
  26. case SDS_TYPE_8: {
  27. //调用SDS_HDR_VAR获取到sh的结构体指针, 初始化结构体数据
  28. struct sdshdr8* sh = (void*)(s - sizeof(struct sdshdr8));
  29. sh->len = initlen;
  30. sh->alloc = initlen;
  31. *fp = type;
  32. break;
  33. }
  34. case SDS_TYPE_16: {
  35. //调用SDS_HDR_VAR获取到sh的结构体指针, 初始化结构体数据
  36. struct sdshdr16* sh = (void*)(s - sizeof(struct sdshdr16));
  37. sh->len = initlen;
  38. sh->alloc = initlen;
  39. *fp = type;
  40. break;
  41. }
  42. case SDS_TYPE_32: {
  43. //调用SDS_HDR_VAR获取到sh的结构体指针, 初始化结构体数据
  44. struct sdshdr32* sh = (void*)(s - sizeof(struct sdshdr32));
  45. sh->len = initlen;
  46. sh->alloc = initlen;
  47. *fp = type;
  48. break;
  49. }
  50. case SDS_TYPE_64: {
  51. //调用SDS_HDR_VAR获取到sh的结构体指针, 初始化结构体数据
  52. struct sdshdr64* sh = (void*)(s - sizeof(struct sdshdr64));
  53. sh->len = initlen;
  54. sh->alloc = initlen;
  55. *fp = type;
  56. break;
  57. }
  58. }
  59. //使用memcpy进行数据拷贝
  60. if (initlen && init)
  61. memcpy(s, init, initlen);
  62. //字符串结尾部分放置位'\0', 直接利用c标准库的函数
  63. s[initlen] = '\0';
  64. return s;
  65. }

获取SDS信息

  1. //sds内存的起始地址
  2. #define SDS_HDR(T, s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))
  3. //返回sds剩余可用空间 = sds.alloc - sds.len;
  4. static inline size_t sdsavail(const sds s) {
  5. unsigned char flags = s[-1];
  6. switch (flags & SDS_TYPE_MASK) {
  7. case SDS_TYPE_5: {
  8. return 0;
  9. }
  10. case SDS_TYPE_8: {
  11. struct sdshdr8 *sh = SDS_HDR(8, s);
  12. return sh->alloc - sh->len;
  13. }
  14. case SDS_TYPE_16: {
  15. struct sdshdr16 *sh = SDS_HDR(16, s);
  16. return sh->alloc - sh->len;
  17. }
  18. case SDS_TYPE_32: {
  19. struct sdshdr32 *sh = SDS_HDR(32, s);
  20. return sh->alloc - sh->len;
  21. }
  22. case SDS_TYPE_64: {
  23. struct sdshdr64 *sh = SDS_HDR(64, s);
  24. return sh->alloc - sh->len;
  25. }
  26. }
  27. return 0;
  28. }

SDS扩容

image.png

  1. sds sdsMakeRoomFor(sds s, size_t addlen) {
  2. //2个sds的结构体指针,这里的sds都特指为struct sds,而不是char* sds.
  3. void *sh, *newsh;
  4. //获取free大小free=avail (available)
  5. size_t avail = sdsavail(s);
  6. size_t len, newlen, reqlen;
  7. //获取sds的结构体类型
  8. char type, oldtype = s[-1] & SDS_TYPE_MASK;
  9. //结构体字节数大小
  10. int hdrlen;
  11. /* 如果可用字节数> 待分配字节数 Return ASAP if there is enough space left. */
  12. if (avail >= addlen) return s;
  13. //获取已使用字节数,如果是3.2版本的,直接可用通过 (struct sds* (void*)(s-sizeof(struct sds)))->len 获取
  14. //这里必须根据type 去判断一下header
  15. len = sdslen(s);
  16. //sds的开始内存地址向下移动 sdsHdrSize(oldtype) 位就是sds结构体的开始地址, 结构体指针*sh指向sds结构体内存地址,完美
  17. sh = (char *) s - sdsHdrSize(oldtype);
  18. //需要的内存数 = len(已使用) + addlen(待添加的字节数)
  19. reqlen = newlen = (len + addlen);
  20. assert(newlen > len); /* Catch size_t overflow */
  21. //如果小于1M,进行翻倍,后续拓展就不需要重新分配内存了
  22. if (newlen < SDS_MAX_PREALLOC)
  23. newlen *= 2;
  24. else
  25. //大于1M,每次只加一M,和Netty的内存分配策略有异曲同工之妙
  26. newlen += SDS_MAX_PREALLOC;
  27. //获取新的sds type
  28. type = sdsReqType(newlen);
  29. /* Don't use type 5: the user is appending to the string and type 5 is
  30. * not able to remember empty space, so sdsMakeRoomFor() must be called
  31. * at every appending operation. */
  32. if (type == SDS_TYPE_5) type = SDS_TYPE_8;
  33. //type对应的结构体指针内存大小
  34. hdrlen = sdsHdrSize(type);
  35. assert(hdrlen + newlen + 1 > reqlen); /* Catch size_t overflow */
  36. //内存没有发生变化
  37. if (oldtype == type) {
  38. //分配内存
  39. newsh = s_realloc(sh, hdrlen + newlen + 1);
  40. if (newsh == NULL) return NULL;
  41. s = (char *) newsh + hdrlen;
  42. } else {
  43. /* Since the header size changes, need to move the string forward,
  44. * and can't use realloc */
  45. //新分配一块地址
  46. newsh = s_malloc(hdrlen + newlen + 1);
  47. //分配地址失败
  48. if (newsh == NULL) return NULL;
  49. //将sds的数据拷贝到新生成的sds,拷贝数据为真实数据+1('\0')
  50. memcpy((char *) newsh + hdrlen, s, len + 1);
  51. //释放内存
  52. s_free(sh);
  53. //指向新的地址
  54. s = (char *) newsh + hdrlen;
  55. //设置结构体sds的type
  56. s[-1] = type;
  57. //设置新的长度
  58. sdssetlen(s, len);
  59. }
  60. //设置总的内存大小
  61. sdssetalloc(s, newlen);
  62. return s;
  63. }