第三节 空闲列表页

空闲列表页中主要包含三个部分:所有已经可以重新利用的空闲页列表ids、将来很快被释放掉的事务关联的页列表pending、页id的缓存。详细定义在freelist.go文件中,下面给大家展示其空闲页的定义。

  1. freelist.go
  2. // freelist represents a list of all pages that are available for allocation.
  3. // It also tracks pages that have been freed but are still in use by open transactions.
  4. type freelist struct {
  5. // 已经可以被分配的空闲页
  6. ids []pgid // all free and available free page ids.
  7. // 将来很快能被释放的空闲页,部分事务可能在读或者写
  8. pending map[txid][]pgid // mapping of soon-to-be free page ids by tx.
  9. cache map[pgid]bool // fast lookup of all free and pending page ids.
  10. }
  11. // newFreelist returns an empty, initialized freelist.
  12. func newFreelist() *freelist {
  13. return &freelist{
  14. pending: make(map[txid][]pgid),
  15. cache: make(map[pgid]bool),
  16. }
  17. }

下图展示的是空闲列表的存储方式。

../imgs/空闲列表存储.png

1. freelist->page

将空闲列表转换成页信息,写到磁盘中,此处需要注意一个问题,页头中的count字段是一个uint16,占两个字节,其最大可以表示2^16 即65536个数字,当空闲页的个数超过65535时时,需要将p.ptr中的第一个字节用来存储其空闲页的个数,同时将p.count设置为0xFFFF。否则不超过的情况下,直接用count来表示其空闲页的个数

  1. // write writes the page ids onto a freelist page. All free and pending ids are
  2. // saved to disk since in the event of a program crash, all pending ids will
  3. // become free.
  4. //将 freelist信息写入到p中
  5. func (f *freelist) write(p *page) error {
  6. // Combine the old free pgids and pgids waiting on an open transaction.
  7. // Update the header flag.
  8. // 设置页头中的页类型标识
  9. p.flags |= freelistPageFlag
  10. // The page.count can only hold up to 64k elements so if we overflow that
  11. // number then we handle it by putting the size in the first element.
  12. lenids := f.count()
  13. if lenids == 0 {
  14. p.count = uint16(lenids)
  15. } else if lenids < 0xFFFF {
  16. p.count = uint16(lenids)
  17. // 拷贝到page的ptr中
  18. f.copyall(((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[:])
  19. } else {
  20. // 有溢出的情况下,后面第一个元素放置其长度,然后再存放所有的pgid列表
  21. p.count = 0xFFFF
  22. ((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[0] = pgid(lenids)
  23. // 从第一个元素位置拷贝
  24. f.copyall(((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[1:])
  25. }
  26. return nil
  27. }
  28. // copyall copies into dst a list of all free ids and all pending ids in one sorted list.
  29. // f.count returns the minimum length required for dst.
  30. func (f *freelist) copyall(dst []pgid) {
  31. // 首先把pending状态的页放到一个数组中,并使其有序
  32. m := make(pgids, 0, f.pending_count())
  33. for _, list := range f.pending {
  34. m = append(m, list...)
  35. }
  36. sort.Sort(m)
  37. // 合并两个有序的列表,最后结果输出到dst中
  38. mergepgids(dst, f.ids, m)
  39. }
  40. // mergepgids copies the sorted union of a and b into dst.
  41. // If dst is too small, it panics.
  42. // 将a和b按照有序合并成到dst中,a和b有序
  43. func mergepgids(dst, a, b pgids) {
  44. if len(dst) < len(a)+len(b) {
  45. panic(fmt.Errorf("mergepgids bad len %d < %d + %d", len(dst), len(a), len(b)))
  46. }
  47. // Copy in the opposite slice if one is nil.
  48. if len(a) == 0 {
  49. copy(dst, b)
  50. return
  51. }
  52. if len(b) == 0 {
  53. copy(dst, a)
  54. return
  55. }
  56. // Merged will hold all elements from both lists.
  57. merged := dst[:0]
  58. // Assign lead to the slice with a lower starting value, follow to the higher value.
  59. lead, follow := a, b
  60. if b[0] < a[0] {
  61. lead, follow = b, a
  62. }
  63. // Continue while there are elements in the lead.
  64. for len(lead) > 0 {
  65. // Merge largest prefix of lead that is ahead of follow[0].
  66. n := sort.Search(len(lead), func(i int) bool { return lead[i] > follow[0] })
  67. merged = append(merged, lead[:n]...)
  68. if n >= len(lead) {
  69. break
  70. }
  71. // Swap lead and follow.
  72. lead, follow = follow, lead[n:]
  73. }
  74. // Append what's left in follow.
  75. _ = append(merged, follow...)
  76. }

2. page->freelist

从磁盘中加载空闲页信息,并转为freelist结构,转换时,也需要注意其空闲页的个数的判断逻辑,当p.count为0xFFFF时,需要读取p.ptr中的第一个字节来计算其空闲页的个数。否则则直接读取p.ptr中存放的数据为空闲页ids列表

  1. //从磁盘中的page初始化freelist
  2. // read initializes the freelist from a freelist page.
  3. func (f *freelist) read(p *page) {
  4. // If the page.count is at the max uint16 value (64k) then it's considered
  5. // an overflow and the size of the freelist is stored as the first element.
  6. idx, count := 0, int(p.count)
  7. if count == 0xFFFF {
  8. idx = 1
  9. // 用第一个uint64来存储整个count的值
  10. count = int(((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[0])
  11. }
  12. // Copy the list of page ids from the freelist.
  13. if count == 0 {
  14. f.ids = nil
  15. } else {
  16. ids := ((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[idx:count]
  17. f.ids = make([]pgid, len(ids))
  18. copy(f.ids, ids)
  19. // Make sure they're sorted.
  20. sort.Sort(pgids(f.ids))
  21. }
  22. // Rebuild the page cache.
  23. f.reindex()
  24. }
  25. // reindex rebuilds the free cache based on available and pending free lists.
  26. func (f *freelist) reindex() {
  27. f.cache = make(map[pgid]bool, len(f.ids))
  28. for _, id := range f.ids {
  29. f.cache[id] = true
  30. }
  31. for _, pendingIDs := range f.pending {
  32. for _, pendingID := range pendingIDs {
  33. f.cache[pendingID] = true
  34. }
  35. }
  36. }

3. allocate

开始分配一段连续的n个页。其中返回值为初始的页id。如果无法分配,则返回0即可

  1. // allocate returns the starting page id of a contiguous list of pages of a given size.
  2. // If a contiguous block cannot be found then 0 is returned.
  3. // [5,6,7,13,14,15,16,18,19,20,31,32]
  4. // 开始分配一段连续的n个页。其中返回值为初始的页id。如果无法分配,则返回0即可
  5. func (f *freelist) allocate(n int) pgid {
  6. if len(f.ids) == 0 {
  7. return 0
  8. }
  9. var initial, previd pgid
  10. for i, id := range f.ids {
  11. if id <= 1 {
  12. panic(fmt.Sprintf("invalid page allocation: %d", id))
  13. }
  14. // Reset initial page if this is not contiguous.
  15. // id-previd != 1 来判断是否连续
  16. if previd == 0 || id-previd != 1 {
  17. // 第一次不连续时记录一下第一个位置
  18. initial = id
  19. }
  20. // If we found a contiguous block then remove it and return it.
  21. // 找到了连续的块,然后将其返回即可
  22. if (id-initial)+1 == pgid(n) {
  23. // If we're allocating off the beginning then take the fast path
  24. // and just adjust the existing slice. This will use extra memory
  25. // temporarily but the append() in free() will realloc the slice
  26. // as is necessary.
  27. if (i + 1) == n {
  28. // 找到的是前n个连续的空间
  29. f.ids = f.ids[i+1:]
  30. } else {
  31. copy(f.ids[i-n+1:], f.ids[i+1:])
  32. f.ids = f.ids[:len(f.ids)-n]
  33. }
  34. // Remove from the free cache.
  35. // 同时更新缓存
  36. for i := pgid(0); i < pgid(n); i++ {
  37. delete(f.cache, initial+i)
  38. }
  39. return initial
  40. }
  41. previd = id
  42. }
  43. return 0
  44. }

考虑是否需要补充其他的freelist的方法