当前位置:   article > 正文

ceph bufferlist

ceph bufferlist

文章转载于:http://bean-li.github.io/bufferlist-in-ceph/

前言

buffer list是ceph中的一个基础的数据结构,代码中大量的使用。

buffer::raw

介绍buffer list之前,我们必须先介绍buffer::raw和buffer::ptr。相对于buffer list,这两个数据结构相对比较容易理解。

  1. class buffer::raw {
  2. public:
  3. char *data;
  4. unsigned len;
  5. atomic_t nref;
  6. mutable simple_spinlock_t crc_spinlock;
  7. map<pair<size_t, size_t>, pair<uint32_t, uint32_t> > crc_map;
  8. ...
  9. }

注意,这个数据结构,data,len,nref,这些成员变量不难猜测出其含义,相信我们设计数据结构也会有这几个变量,data是个指针,指向真正的数据,而len记录了该buffer::raw数据区数据的长度,nref表示引用计数。

注意,data指针指向的数据可能有不同的来源,最容易想到的当然是malloc,其次我们以可以使用mmap通过创建匿名内存映射来分配空间,甚至我们可以通过pipe管道+splice实现零拷贝获取空间。有些时候,分配的空间时,会提出对齐的要求,比如按页对齐。

这是因为这些来源不同,要求不同,buffer::raw也就有了一些变体:

buffer:raw_malloc

这个变体数据来源源自malloc,因此,创建的时候,需要通过malloc分配长度为len的空间,,而不意外,析构的时候,会掉用free释放空间。

  1. class buffer::raw_malloc : public buffer::raw {
  2. public:
  3. explicit raw_malloc(unsigned l) : raw(l) {
  4. if (len) {
  5. data = (char *)malloc(len);
  6. if (!data)
  7. throw bad_alloc();
  8. } else {
  9. data = 0;
  10. }
  11. inc_total_alloc(len);
  12. inc_history_alloc(len);
  13. bdout << "raw_malloc " << this << " alloc " << (void *)data << " " << l << " " << buffer::get_total_alloc() << bendl;
  14. }
  15. raw_malloc(unsigned l, char *b) : raw(b, l) {
  16. inc_total_alloc(len);
  17. bdout << "raw_malloc " << this << " alloc " << (void *)data << " " << l << " " << buffer::get_total_alloc() << bendl;
  18. }
  19. ~raw_malloc() {
  20. free(data);
  21. dec_total_alloc(len);
  22. bdout << "raw_malloc " << this << " free " << (void *)data << " " << buffer::get_total_alloc() << bendl;
  23. }
  24. raw* clone_empty() {
  25. return new raw_malloc(len);
  26. }
  27. };

buffer::raw_mmap_pages

顾名思义,也能够猜到,这个数据的来源是通过mmap分配的匿名内存映射。因此析构的时候,毫不意外,掉用munmap解除映射,归还空间给系统。

  1. class buffer::raw_mmap_pages : public buffer::raw {
  2. public:
  3. explicit raw_mmap_pages(unsigned l) : raw(l) {
  4. data = (char*)::mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0);
  5. if (!data)
  6. throw bad_alloc();
  7. inc_total_alloc(len);
  8. inc_history_alloc(len);
  9. bdout << "raw_mmap " << this << " alloc " << (void *)data << " " << l << " " << buffer::get_total_alloc() << bendl;
  10. }
  11. ~raw_mmap_pages() {
  12. ::munmap(data, len);
  13. dec_total_alloc(len);
  14. bdout << "raw_mmap " << this << " free " << (void *)data << " " << buffer::get_total_alloc() << bendl;
  15. }
  16. raw* clone_empty() {
  17. return new raw_mmap_pages(len);
  18. }
  19. };

buffer::raw_posix_aligned

看名字也看出来了,对空间有对齐的要求。Linux下posix_memalign函数用来分配有对齐要求的内存空间。这种分配方式分配的空间,也是用free函数来释放,将空间归还给系统。

  1. class buffer::raw_posix_aligned : public buffer::raw {
  2. unsigned align;
  3. public:
  4. raw_posix_aligned(unsigned l, unsigned _align) : raw(l) {
  5. align = _align;
  6. assert((align >= sizeof(void *)) && (align & (align - 1)) == 0);
  7. #ifdef DARWIN
  8. data = (char *) valloc (len);
  9. #else
  10. data = 0;
  11. int r = ::posix_memalign((void**)(void*)&data, align, len);
  12. if (r)
  13. throw bad_alloc();
  14. #endif /* DARWIN */
  15. if (!data)
  16. throw bad_alloc();
  17. inc_total_alloc(len);
  18. inc_history_alloc(len);
  19. bdout << "raw_posix_aligned " << this << " alloc " << (void *)data << " l=" << l << ", align=" << align << " total_alloc=" << buffer::get_total_alloc() << bendl;
  20. }
  21. ~raw_posix_aligned() {
  22. ::free((void*)data);
  23. dec_total_alloc(len);
  24. bdout << "raw_posix_aligned " << this << " free " << (void *)data << " " << buffer::get_total_alloc() << bendl;
  25. }
  26. raw* clone_empty() {
  27. return new raw_posix_aligned(len, align);
  28. }
  29. };

后面还有基于pipe和splice的零拷贝方式,我们不赘述。从上面的函数不难看出,buffer::raw系列,就像他的名字一样,真的是很原生,并没有太多的弯弯绕,就是利用系统提供的API来达到分配空间的目的。

buffer::ptr

buffer::ptr是在buffer::raw系列的基础上,这个类也别名bufferptr。

  1. src/include/buffer_fwd.h
  2. #ifndef BUFFER_FWD_H
  3. #define BUFFER_FWD_H
  4. namespace ceph {
  5. namespace buffer {
  6. class ptr;
  7. class list;
  8. class hash;
  9. }
  10. using bufferptr = buffer::ptr;
  11. using bufferlist = buffer::list;
  12. using bufferhash = buffer::hash;
  13. }
  14. #endif

这个类的成员变量如下,这个类是raw这个类的包装升级版本,它的_raw就是指向buffer::raw类型的变量。

  1. class CEPH_BUFFER_API ptr {
  2. raw *_raw;
  3. unsigned _off, _len;
  4. ......
  5. }

很多操作都是很容易想到的:

  1. buffer::ptr& buffer::ptr::operator= (const ptr& p)
  2. {
  3. if (p._raw) {
  4. p._raw->nref.inc();
  5. bdout << "ptr " << this << " get " << _raw << bendl;
  6. }
  7. buffer::raw *raw = p._raw;
  8. release();
  9. if (raw) {
  10. _raw = raw;
  11. _off = p._off;
  12. _len = p._len;
  13. } else {
  14. _off = _len = 0;
  15. }
  16. return *this;
  17. }
  18. buffer::raw *buffer::ptr::clone()
  19. {
  20. return _raw->clone();
  21. }
  22. void buffer::ptr::swap(ptr& other)
  23. {
  24. raw *r = _raw;
  25. unsigned o = _off;
  26. unsigned l = _len;
  27. _raw = other._raw;
  28. _off = other._off;
  29. _len = other._len;
  30. other._raw = r;
  31. other._off = o;
  32. other._len = l;
  33. }
  34. const char& buffer::ptr::operator[](unsigned n) const
  35. {
  36. assert(_raw);
  37. assert(n < _len);
  38. return _raw->get_data()[_off + n];
  39. }
  40. char& buffer::ptr::operator[](unsigned n)
  41. {
  42. assert(_raw);
  43. assert(n < _len);
  44. return _raw->get_data()[_off + n];
  45. }
  46. int buffer::ptr::cmp(const ptr& o) const
  47. {
  48. int l = _len < o._len ? _len : o._len;
  49. if (l) {
  50. int r = memcmp(c_str(), o.c_str(), l);
  51. if (r)
  52. return r;
  53. }
  54. if (_len < o._len)
  55. return -1;
  56. if (_len > o._len)
  57. return 1;
  58. return 0;
  59. }

bufferlist

bufferlist才是我们的目的地,前两个类其实是比较容易理解的,但是bufferlist相对复杂一点。

bufferlist是buffer::list的别名:

  1. #ifndef BUFFER_FWD_H
  2. #define BUFFER_FWD_H
  3. namespace ceph {
  4. namespace buffer {
  5. class ptr;
  6. class list;
  7. class hash;
  8. }
  9. using bufferptr = buffer::ptr;
  10. using bufferlist = buffer::list;
  11. using bufferhash = buffer::hash;
  12. }
  13. #endif
  14. class CEPH_BUFFER_API list {
  15. // my private bits
  16. std::list<ptr> _buffers;
  17. unsigned _len;
  18. unsigned _memcopy_count; //the total of memcopy using rebuild().
  19. ptr append_buffer; // where i put small appends

多个bufferptr形成一个list,这就是bufferlist。成员变量并无太多难以理解的地方,比较绕的是bufferlist的迭代器 ,理解迭代器,就不难理解bufferlist各个操作函数。

要理解bufferlist 迭代器,,首先需要理解迭代器成员变量的含义。

  1. bl_t* bl;
  2. list_t* ls; // meh.. just here to avoid an extra pointer dereference..
  3. unsigned off; // in bl
  4. list_iter_t p;
  5. unsigned p_off; // in *p
  • bl:指针,指向bufferlist
  • ls:指针,指向bufferlist的成员 _buffers
  • p: 类型是std::list::iterator,用来迭代遍历bufferlist中的bufferptr
  • p_off : 当前位置在对应的bufferptr的偏移量
  • off: 如果将整个bufferlist看成一个buffer::raw,当前位置在整个bufferlist的偏移量

这个递进关系比较明显,从宏观的bufferlist,递进到内部的某个bufferptr,再递进到bufferptr内部raw数据区的某个偏移位置。 此外还包含了当前位置在整个bufferlist的偏移量off。

注意p_off和off容易产生误解,请阅读seek函数仔细揣摩

seek(unsigned o),顾名思义就是将位置移到o处,当然o指的是整个bufferlist的o处。ceph实现了一个更通用的advance,接受一个int型的入参。

如果o>0,表示向后移动,如果o小于0,表示想前移动。移动的过程中可能越过当前的bufferptr之指向的数据区。

  1. template<bool is_const>
  2. void buffer::list::iterator_impl<is_const>::advance(int o)
  3. {
  4. //cout << this << " advance " << o << " from " << off << " (p_off " << p_off << " in " << p->length() << ")" << std::endl;
  5. if (o > 0) {
  6. p_off += o;
  7. while (p_off > 0) {
  8. if (p == ls->end())
  9. throw end_of_buffer();
  10. if (p_off >= p->length()) {
  11. // skip this buffer
  12. p_off -= p->length();
  13. p++;
  14. } else {
  15. // somewhere in this buffer!
  16. break;
  17. }
  18. }
  19. off += o;
  20. return;
  21. }
  22. while (o < 0) {
  23. if (p_off) {
  24. unsigned d = -o;
  25. if (d > p_off)
  26. d = p_off;
  27. p_off -= d;
  28. off -= d;
  29. o += d;
  30. } else if (off > 0) {
  31. assert(p != ls->begin());
  32. p--;
  33. p_off = p->length();
  34. } else {
  35. throw end_of_buffer();
  36. }
  37. }
  38. }
  39. template<bool is_const>
  40. void buffer::list::iterator_impl<is_const>::seek(unsigned o)
  41. {
  42. p = ls->begin();
  43. off = p_off = 0;
  44. advance(o);
  45. }

除此以外,获取当前位置的ptr也很有意思,理解该函数也有帮助理解迭代器五个成员的含义。

  1. template<bool is_const>
  2. buffer::ptr buffer::list::iterator_impl<is_const>::get_current_ptr() const
  3. {
  4. if (p == ls->end())
  5. throw end_of_buffer();
  6. return ptr(*p, p_off, p->length() - p_off);
  7. }

相当于多个bufferptr对应的buffer::raw组成了一个可能不连续的buffer列表,因此使用起来可能不方便,ceph处于这种考虑,提供了rebuild的函数。该函数的作用是,干脆创建一个buffer::raw,来提供同样的空间和内容。

  1. void buffer::list::rebuild()
  2. {
  3. if (_len == 0) {
  4. _buffers.clear();
  5. return;
  6. }
  7. ptr nb;
  8. if ((_len & ~CEPH_PAGE_MASK) == 0)
  9. nb = buffer::create_page_aligned(_len);
  10. else
  11. nb = buffer::create(_len);
  12. rebuild(nb);
  13. }
  14. void buffer::list::rebuild(ptr& nb)
  15. {
  16. unsigned pos = 0;
  17. for (std::list<ptr>::iterator it = _buffers.begin();
  18. it != _buffers.end();
  19. ++it) {
  20. nb.copy_in(pos, it->length(), it->c_str(), false);
  21. pos += it->length();
  22. }
  23. _memcopy_count += pos;
  24. _buffers.clear();
  25. if (nb.length())
  26. _buffers.push_back(nb);
  27. invalidate_crc();
  28. last_p = begin();
  29. }

从下面测试代码中不难看出rebuild的含义,就是划零为整,重建一个buffer::raw来提供空间

  1. {
  2. bufferlist bl;
  3. const std::string str(CEPH_PAGE_SIZE, 'X');
  4. bl.append(str.c_str(), str.size());
  5. bl.append(str.c_str(), str.size());
  6. EXPECT_EQ((unsigned)2, bl.buffers().size());
  7. bl.rebuild();
  8. EXPECT_EQ((unsigned)1, bl.buffers().size());
  9. }

理解了上述的内容,bufferlist剩余的上千行代码,基本也就变成了流水账了,不难理解了,在此就不再赘述了。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/601689
推荐阅读
相关标签
  

闽ICP备14008679号