|
操作系统内存管理
操作系统管理内存的存储单元是页(page),在 linux 中一般是 4KB。而且,操作系统还会使用 虚拟内存 来管理内存,在用户程序中,我们看到的内存是不是真实的内存,而是虚拟内存。当访问或者修改内存的时候,操作系统会将虚拟内存映射到真实的内存中。申请内存的组件是 Page Table 和 MMU(Memory Management Unit)。因为这个性能很重要,所以在 CPU 中专门有一个 TLB(Translation Lookaside Buffer)来缓存 Page Table 的内容。
为什么要用虚拟内存?
- 保护内存,每个进程都有自己的虚拟内存,不会相互干扰。防止修改和访问别的进程的内存。
- 减少内存碎片,虚拟内存是连续的,而真实的内存是不连续的。
- 当内存不够时,可以把虚拟内存映射到硬盘上,这样就可以使用硬盘的空间来扩展内存。
如上图所示,如果直接使用真实的内存,想要连续的内存肯定是申请不到的,这就是内存碎片的问题。而使用虚拟内存,通过 Page 映射的方式,保证内存连续。
Go 内存管理单元
page
在 go 中,管理内存的存储单元也是页(Page), 每个页的大小是 8KB。Go 内存管理是由 runtime 来管理的,runtime 会维护一个内存池,用来分配和回收内存。这样可以避免频繁的系统调用申请内存,提高性能。
mspan
mspan 是 go 内存管理基本单元,一个 mspan 包含一个或者多个 page。go 中有多种 mspan,每种 mspan 给不同的内存大小使用。
classbytes/objbytes/spanobjectstail wastemax wastemin align1881921024087.50%82168192512043.75%163248192341829.24%84328192256021.88%3254881921703231.52%166648192128023.44%6478081921023219.07%168968192853215.95%3291128192731613.56%16.....................6424576245761011.45%8192652726481920312810.00%128662867257344204.91%40966732768327681012.50%8192
- class 是 mspan 的类型,每种类型对应不同的内存大小。
- obj 是每个对象的大小。
- span 是 mspan 的大小。
- objects 是 mspan 中对象的个数。
- tail waste 是 mspan 中最后一个对象的浪费空间。(不能整除造成的)
- max waste 是 mspan 中最大的浪费空间。(比如第一个中 每个都使用 1 byte,那么就所有都浪费 7 byte,7 / 8 = 87.50%)
- min align 是 mspan 中对象的对齐大小。如果超过这个就会分配下一个 mspan。
数据结构
mspan
type mspan struct { // 双向链表 下一个 mspan 和 上一个 mspan next *mspan prev *mspan // debug 使用的 list *mSpanList // 起始地址和页数 当 class 太大 要多个页组成 mspan startAddr uintptr npages uintptr // 手动管理的空闲对象链表 manualFreeList gclinkptr // 下一个空闲对象的地址 如果小于它 就不用检索了 直接从这个地址开始 提高效率 freeindex uint16 // 对象的个数 nelems uint16 // GC 扫描使用的空闲索引 freeIndexForScan uint16 // bitmap 每个 bit 对应一个对象 标记是否使用 allocCache uint64 // ... // span 的类型 spanclass spanClass // size class and noscan (uint8) // ...}spanClass
type spanClass uint8func makeSpanClass(sizeclass uint8, noscan bool) spanClass { return spanClass(sizeclass<<1) | spanClass(bool2int(noscan))}//go:nosplitfunc (sc spanClass) sizeclass() int8 { return int8(sc >> 1)}//go:nosplitfunc (sc spanClass) noscan() bool { return sc&1 != 0}spanClass 是 unint8 类型,一共有 8 位,前 7 位是 sizeclass,也就是上边 table 中的内容,一共有 (67 + 1) * 2 种类型, +1 是 0 代表比 67 class 的内存还大。最后一位是 noscan,也就是表示这个对象中是否含有指针,用来给 GC 扫描加速用的(无指针对象就不用继续扫描了),所以要 * 2。
mspan 详解
如果所示
- mspan 是一个双向链表,如果不够用了,在挂一个就行了。
- startAddr 是 mspan 的起始地址,npages 是 page 数量。根据 startAddr + npages * 8KB 就可以得到 mspan 的结束地址。
- allocCache 是一个 bitmap,每个 bit 对应一个对象,标记是否使用。使用了 ctz(count trailing zero)。
- freeindex 是下一个空闲对象的地址,如果小于它,就不用检索了,直接从这个地址开始,提高效率。
mcache
mache 是每个 P (processor)的结构体中都有的,是用来缓存的,因为每个 P 同一时间只有一个 goroutine 在执行,所以 mcache 是不需要加锁的。这也是 mcache 的设计初衷,减少锁的竞争,提高性能。
type p struct { // ... mcache *mcache // ...}// 每个 P 的本队缓存type mcache struct { // 不在 gc 的堆中分配 _ sys.NotInHeap // The following members are accessed on every malloc, // so they are grouped here for better caching. nextSample uintptr // trigger heap sample after allocating this many bytes scanAlloc uintptr // bytes of scannable heap allocated // 微对象分配器(<16B 不含指针) tiny uintptr // 内存的其实地址 tinyoffset uintptr // 偏移量 tinyAllocs uintptr // 分配了多少个 tiny 对象 // span缓存数组,按大小类别索引 alloc [numSpanClasses]*mspan // spans to allocate from, indexed by spanClass // 用于不同大小的栈内存分配 go 的 堆上分配栈内存 stackcache [_NumStackOrders]stackfreelist // 控制 mcache 的刷新 flushGen atomic.Uint32}mcentral
mcentral 也是一种缓存,只不过在中心而不是在每个 P 上。mcentral 存在的意义也是减少锁竞争,如果没有 mcentral,那么只要从中心申请 mspan 就需要加锁。现在加上了 mcentral,申请时就需要加特别力度的锁就可以了,比如申请 class = 1 的 mspan 加 class = 1 的锁就可以了,不影响别人申请 class = 2 的 mspan。这样就可以较少锁竞争,提高性能。
type mcentral struct { _ sys.NotInHeap // mspan 的类别 spanclass spanClass // 部分使用的span列表 // 使用两个集合交替角色 // [0] -> 已清扫的spans // [1] -> 未清扫的spans partial [2]spanSet // list of spans with a free object // 完全使用的 mspan full [2]spanSet // list of spans with no free objects}type spanSet struct { // spanSet是一个两级数据结构,由一个可增长的主干(spine)指向固定大小的块组成。 // 访问spine不需要锁,但添加块或扩展spine时需要获取spine锁。 // // 因为每个mspan至少覆盖8K的堆内存,且在spanSet中最多占用8字节, // 所以spine的增长是相当有限的。 // 锁 spineLock mutex // 原子指针,指向一个动态数组 spine atomicSpanSetSpinePointer // *[N]atomic.Pointer[spanSetBlock] // 当前spine数组中实际使用的长度 原子类型 spineLen atomic.Uintptr // Spine array length // spine数组的容量 spineCap uintptr // Spine array cap, accessed under spineLock // index是spanSet中的头尾指针,被压缩在一个字段中。 // head和tail都表示所有块的逻辑连接中的索引位置,其中head总是在tail之后或等于tail // (等于tail时表示集合为空)。这个字段始终通过原子操作访问。 // // head和tail各自的宽度为32位,这意味着在需要重置之前,我们最多支持2^32次push操作。 // 如果堆中的每个span都存储在这个集合中,且每个span都是最小尺寸(1个运行时页面,8 KiB), // 那么大约需要32 TiB大小的堆才会导致无法表示的情况。 // 头部索引 index atomicHeadTailIndex}type mheap struct { central [numSpanClasses]struct { mcentral mcentral // 填充字节 一般不能整除的时候 末尾的余数就不用了 pad [(cpu.CacheLinePadSize - unsafe.Sizeof(mcentral{})%cpu.CacheLinePadSize) % cpu.CacheLinePadSize]byte }}mheap
mheap 是全局的内存管理器,申请内存是 mcentral 不满足要求的时候,就会从 mheap 中申请,要加全局锁。如果 mheap 还不能满足,就会系统调用从操作系统申请,每次申请的最小单位是 Arena,也就是 64M。
type mheap struct { _ sys.NotInHeap // 全局锁 lock mutex // page 分配器 管理所有的page pages pageAlloc sweepgen uint32 // sweep 代数 gc时候使用 // 所有的 mspan allspans []*mspan // 正在使用的 page 数 pagesInUse atomic.Uintptr // ...... // 用于定位内存地址是哪个 mspan 的 // 二维数组 1 << arenaL1Bits = 1 1 << arenaL2Bits = 4194304 arenas [1 << arenaL1Bits]*[1 << arenaL2Bits]*heapArena spanalloc fixalloc // span 分配器 cachealloc fixalloc // mcache 分配器 specialfinalizeralloc fixalloc // finalizer 分配器 // ......}heapArena
// A heapArena stores metadata for a heap arena. heapArenas are stored// outside of the Go heap and accessed via the mheap_.arenas index.type heapArena struct { _ sys.NotInHeap // page 对应的 mspan // pagesPerArena 8192 一个 page 8KB 所以一个 heapArena 可以存储 64M 的内存 spans [pagesPerArena]*mspan // 标记哪个 page 是在使用的 // /8 是 uint8 可以表示 8 个 page pageInUse [pagesPerArena / 8]uint8 // 标记哪些span包含被标记的对象 用于 gc 加速 pageMarks [pagesPerArena / 8]uint8 // 标记哪些span包含特殊对象 pageSpecials [pagesPerArena / 8]uint8 checkmarks *checkmarksMap // arena中第一个未使用(已归零)页面的起始字节 zeroedBase uintptr}pageAlloc
分配 page 的结构体,是一个 radix tree 的结构,一共有 5 层,每一层都是一个 summary 数组,用于快速查找空闲页面。
type pageAlloc struct { // 基数树 一共有 summaryLevels=5 层 // 基数树的摘要数组,用于快速查找空闲页面 summary [summaryLevels][]pallocSum // 二级页面位图结构 // 使用二级结构而不是一个大的扁平数组,是因为在64位平台上总大小可能非常大(O(GiB)) chunks [1 << pallocChunksL1Bits]*[1 << pallocChunksL2Bits]pallocData // 搜索起始地址 searchAddr offAddr // start 和 end 表示 pageAlloc 知道的块索引范围 start, end chunkIdx // ......}type pallocSum uint64// pallocSum 被划分成几个部分:// 63位 62-42位 41-21位 20-0位// [标志位] [end值] [max值] [start值]// 1 21位 21位 21位func (p pallocSum) start() uint { // 检查第63位是否为1 if uint64(p)&uint64(1<<63) != 0 { return maxPackedValue } // 否则,取最低21位 return uint(uint64(p) & (maxPackedValue - 1))}func (p pallocSum) max() uint { if uint64(p)&uint64(1<<63) != 0 { return maxPackedValue } // 右移21位,然后取21位 return uint((uint64(p) >> logMaxPackedValue) & (maxPackedValue - 1))}func (p pallocSum) end() uint { if uint64(p)&uint64(1<<63) != 0 { return maxPackedValue } // 右移42位,然后取21位 return uint((uint64(p) >> (2 * logMaxPackedValue)) & (maxPackedValue - 1))}
内存分配流程
流程
go 中把 对象分成三类 tiny ,small 和 large。tiny 是小于 16B 的对象,small 是大于等于 16B 小于 32KB 的对象,large 是大于 32KB 的对象。tiny 分配器主要是为了减少内存碎片。
- 如果是 tiny object,直接使用 tiny 分配器分配。如果 tiny 分配器中的空间不够(定长位16B),就从 mchunk 中获取一个新的 16B 的对象作为 tiny 分配器的空间使用。
- 如果是 small object,根据所属的 class, 从 mcache 获取对应 mspan 中的内存。
- 如果 mspan 中的内存不够,根据所属的 class 从 mcentral 中获取新的 mspan ,从 mspan 中获取内存。(要 class 力度的锁)
- 如果 mcentral 中的 mspan 也不够,就从 mheap 中获取对应数量的 page 组装成 mspan,然后从新的 mspan 中获取内存。(全局锁)
- 如果 mheap 中的 mspan 也不够,就系统调用从操作系统获取新的 Arena。把内存 page 分配好,然后继续第四步。
- 如果是 large object,直接从第四部开始。
mallocgc
// 在 heap 上分配内存函数 size 对象大小 typ 对象类型 needzero 是否需要清零func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer { // gc 终止阶段不允许分配 这个是一个检查 正常情况下不会出现 if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") } // 处理分类为 0 的情况 if size == 0 { return unsafe.Pointer(&zerobase) } // ...... // Set mp.mallocing to keep from being preempted by GC. mp := acquirem() if mp.mallocing != 0 { throw("malloc deadlock") } if mp.gsignal == getg() { throw("malloc during signal") } mp.mallocing = 1 shouldhelpgc := false dataSize := userSize // 获取 M 和 M 所属 P 的 mcache c := getMCache(mp) if c == nil { throw("mallocgc called without a P or outside bootstrapping") } var span *mspan var header **_type var x unsafe.Pointer // 对象总是不是含有指针 如果不含有 就不用往下扫描了 用来 gc 加速 noscan := typ == nil || !typ.Pointers() // 是不是小对象 (< 32k - 8) if size <= maxSmallSize-mallocHeaderSize { // 如果对象大小小于 16B 且不含有指针 则使用 tiny 分配器 if noscan && size < maxTinySize { off := c.tinyoffset // 内存对齐一下 if size&7 == 0 { off = alignUp(off, 8) } else if goarch.PtrSize == 4 && size == 12 { off = alignUp(off, 8) } else if size&3 == 0 { off = alignUp(off, 4) } else if size&1 == 0 { off = alignUp(off, 2) } // 如果剩余空间足够 使用 tiny 分配器 if off+size <= maxTinySize && c.tiny != 0 { x = unsafe.Pointer(c.tiny + off) c.tinyoffset = off + size c.tinyAllocs++ mp.mallocing = 0 releasem(mp) return x } // 重新 分配一个 tiny 使用 span = c.alloc[tinySpanClass] v := nextFreeFast(span) if v == 0 { v, span, shouldhelpgc = c.nextFree(tinySpanClass) } x = unsafe.Pointer(v) (*[2]uint64)(x)[0] = 0 (*[2]uint64)(x)[1] = 0 if !raceenabled && (size < c.tinyoffset || c.tiny == 0) { c.tiny = uintptr(x) c.tinyoffset = size } size = maxTinySize } else { // 处理小对象 // 处理对象头部 主要加入一些头部信息帮助 GC 加速 hasHeader := !noscan && !heapBitsInSpan(size) if hasHeader { size += mallocHeaderSize } // 根据不同的对象大小 使用不同的mspan var sizeclass uint8 if size <= smallSizeMax-8 { sizeclass = size_to_class8[divRoundUp(size, smallSizeDiv)] } else { sizeclass = size_to_class128[divRoundUp(size-smallSizeMax, largeSizeDiv)] } size = uintptr(class_to_size[sizeclass]) spc := makeSpanClass(sizeclass, noscan) span = c.alloc[spc] // 使用缓存从 mspan 中获取空闲对象 v := nextFreeFast(span) if v == 0 { // 先从本地获取 span 如果本地没获取到 升级到 mcenral 获取 v, span, shouldhelpgc = c.nextFree(spc) } x = unsafe.Pointer(v) // 如果需要清零 处理一下 if needzero && span.needzero != 0 { memclrNoHeapPointers(x, size) } // 设置头 if hasHeader { header = (**_type)(x) x = add(x, mallocHeaderSize) size -= mallocHeaderSize } } } else { // 大对象分配 直接从 mheap 中获取 class = 0 的 mspan shouldhelpgc = true span = c.allocLarge(size, noscan) span.freeindex = 1 span.allocCount = 1 size = span.elemsize x = unsafe.Pointer(span.base()) if needzero && span.needzero != 0 { delayedZeroing = true } if !noscan { // Tell the GC not to look at this yet. span.largeType = nil header = &span.largeType } } // ...... return x}nextFreeFast
func nextFreeFast(s *mspan) gclinkptr { // 使用 ctz64 (amd64 中是 tzcnt 指令 ) 获取末尾的 0(以分配) 的个数 如果是 64 说明没有空闲对象 theBit := sys.TrailingZeros64(s.allocCache) // 如果找到了空闲位置(theBit < 64) if theBit < 64 { result := s.freeindex + uint16(theBit) if result < s.nelems { freeidx := result + 1 if freeidx%64 == 0 && freeidx != s.nelems { return 0 } // 分配了 cache 移动一下 s.allocCache >>= uint(theBit + 1) s.freeindex = freeidx s.allocCount++ // result * elemsize:计算对象的偏移量 // base():获取span的起始地址 return gclinkptr(uintptr(result)*s.elemsize + s.base()) } } return 0}nextFree
// nextFree 从 mcache 中获取下一个空闲对象func (c *mcache) nextFree(spc spanClass) (v gclinkptr, s *mspan, shouldhelpgc bool) { s = c.alloc[spc] shouldhelpgc = false // 从 mcache 对象空位的偏移量 freeIndex := s.nextFreeIndex() if freeIndex == s.nelems { // mcache 没有靠你先对象 从 mcentral,mheap 获取 c.refill(spc) shouldhelpgc = true s = c.alloc[spc] freeIndex = s.nextFreeIndex() } if freeIndex >= s.nelems { throw("freeIndex is not valid") } v = gclinkptr(uintptr(freeIndex)*s.elemsize + s.base()) s.allocCount++ if s.allocCount > s.nelems { println("s.allocCount=", s.allocCount, "s.nelems=", s.nelems) throw("s.allocCount > s.nelems") } return}一组一组获取空闲对象
func (s *mspan) nextFreeIndex() uint16 { sfreeindex := s.freeindex snelems := s.nelems if sfreeindex == snelems { return sfreeindex } if sfreeindex > snelems { throw("s.freeindex > s.nelems") } aCache := s.allocCache bitIndex := sys.TrailingZeros64(aCache) for bitIndex == 64 { // Move index to start of next cached bits. sfreeindex = (sfreeindex + 64) &^ (64 - 1) if sfreeindex >= snelems { s.freeindex = snelems return snelems } whichByte := sfreeindex / 8 // Refill s.allocCache with the next 64 alloc bits. s.refillAllocCache(whichByte) aCache = s.allocCache bitIndex = sys.TrailingZeros64(aCache) // nothing available in cached bits // grab the next 8 bytes and try again. } result := sfreeindex + uint16(bitIndex) if result >= snelems { s.freeindex = snelems return snelems } s.allocCache >>= uint(bitIndex + 1) sfreeindex = result + 1 if sfreeindex%64 == 0 && sfreeindex != snelems { whichByte := sfreeindex / 8 s.refillAllocCache(whichByte) } s.freeindex = sfreeindex return result}
// 给 mcache 添加一个新的 mspan 一般是申请内存 mcache 中没有空闲对象了func (c *mcache) refill(spc spanClass) { s := c.alloc[spc] if s.allocCount != s.nelems { throw("refill of span with free space remaining") } if s != &emptymspan { // ...... // 如果不是空的 而且没有空闲对象 就把这个 span 放到 mcentral 中 mcache 使用不了这个 span 了 mheap_.central[spc].mcentral.uncacheSpan(s) // ...... } // 从 mcentral 获取新的 span 如果没有就从 mheap 再没有就系统调用申请内存 s = mheap_.central[spc].mcentral.cacheSpan() // ..... c.alloc[spc] = s}cacheSpan
func (c *mcentral) cacheSpan() *mspan { // ...... // 尝试从以清扫的部分获取 sg := mheap_.sweepgen if s = c.partialSwept(sg).pop(); s != nil { goto havespan } // 如果以清扫的没有 就马上开始主动清扫 sl = sweep.active.begin() if sl.valid { // 尝试从未清扫的部分使用的 span 列表中获取 for ; spanBudget >= 0; spanBudget-- { s = c.partialUnswept(sg).pop() if s == nil { break } // 尝试获取 span if s, ok := sl.tryAcquire(s); ok { // 清扫它 并使用 s.sweep(true) sweep.active.end(sl) goto havespan } } // 尝试从未清扫的已满 span 列表中获取 for ; spanBudget >= 0; spanBudget-- { s = c.fullUnswept(sg).pop() if s == nil { break } if s, ok := sl.tryAcquire(s); ok { s.sweep(true) // 清扫之后 看有无可用的 没有就下一个 freeIndex := s.nextFreeIndex() if freeIndex != s.nelems { s.freeindex = freeIndex sweep.active.end(sl) goto havespan } c.fullSwept(sg).push(s.mspan) } } sweep.active.end(sl) } trace = traceAcquire() if trace.ok() { trace.GCSweepDone() traceDone = true traceRelease(trace) } // mcentral 中没有可用的 span 了 从 mheap 中获取 s = c.grow() if s == nil { return nil } // 获取到了 span 了 上边会 goto 到这havespan: // ...... // 处理 allocCache 缓存 freeByteBase := s.freeindex &^ (64 - 1) whichByte := freeByteBase / 8 s.refillAllocCache(whichByte) s.allocCache >>= s.freeindex % 64 return s}grow
func (c *mcentral) grow() *mspan { npages := uintptr(class_to_allocnpages[c.spanclass.sizeclass()]) size := uintptr(class_to_size[c.spanclass.sizeclass()]) // 申请内存 s := mheap_.alloc(npages, c.spanclass) if s == nil { return nil } // 计算这个 span 可以容纳多少个对象 和 偏移量等 n := s.divideByElemSize(npages << _PageShift) s.limit = s.base() + size*n s.initHeapBits(false) return s}func (h *mheap) alloc(npages uintptr, spanclass spanClass) *mspan { var s *mspan systemstack(func() { // 先清扫一些页 防止一直增长 if !isSweepDone() { h.reclaim(npages) } s = h.allocSpan(npages, spanAllocHeap, spanclass) }) return s}func (h *mheap) allocSpan(npages uintptr, typ spanAllocType, spanclass spanClass) (s *mspan) { // 检查内存对其 ...... if !needPhysPageAlign && pp != nil && npages < pageCachePages/4 { // 尝试从缓存直接获取 *c = h.pages.allocToCache() } // 加锁 lock(&h.lock) if needPhysPageAlign { // Overallocate by a physical page to allow for later alignment. extraPages := physPageSize / pageSize // 尝试从 pageAlloc 获取页 base, _ = h.pages.find(npages + extraPages) } if base == 0 { // 尝试分配所需页数 base, scav = h.pages.alloc(npages) if base == 0 { var ok bool // 空间不足,尝试扩展 growth, ok = h.grow(npages) if !ok { unlock(&h.lock) return nil } base, scav = h.pages.alloc(npages) if base == 0 { throw("grew heap, but no adequate free space found") } } } unlock(&h.lock)HaveSpan: // ...... // 组装成 mspan h.initSpan(s, typ, spanclass, base, npages) return s}// 向操作系统申请内存func (h *mheap) grow(npage uintptr) (uintptr, bool) { // 每次申请 4 M ask := alignUp(npage, pallocChunkPages) * pageSize // ...... av, asize := h.sysAlloc(ask, &h.arenaHints, true) // ......}// sysAlloc -> sysReserve -> sysReserveOSfunc sysReserveOS(v unsafe.Pointer, n uintptr) unsafe.Pointer { p, err := mmap(v, n, _PROT_NONE, _MAP_ANON|_MAP_PRIVATE, -1, 0) if err != 0 { return nil } return p}func mmap(addr unsafe.Pointer, n uintptr, prot, flags, fd int32, off uint32) (unsafe.Pointer, int) { // ...... return sysMmap(addr, n, prot, flags, fd, off)}stack 内存
// newproc1if newg == nil { newg = malg(stackMin)}//newproc1 -> malg -> stackallocfunc stackalloc(n uint32) stack { thisg := getg() // ...... var v unsafe.Pointer // 小栈 linux 下是 32k if n < fixedStack<<_NumStackOrders && n < _StackCacheSize { order := uint8(0) n2 := n for n2 > fixedStack { order++ n2 >>= 1 } var x gclinkptr // 以下情况直接从全局池分配: // 1. 禁用栈缓存 // 2. 没有关联的 P // 3. 禁用抢占 if stackNoCache != 0 || thisg.m.p == 0 || thisg.m.preemptoff != "" { lock(&stackpool[order].item.mu) x = stackpoolalloc(order) unlock(&stackpool[order].item.mu) } else { // 从 P 的本地缓存分配 c := thisg.m.p.ptr().mcache x = c.stackcache[order].list // 如果本地缓存为空,则重新填充 if x.ptr() == nil { stackcacherefill(c, order) x = c.stackcache[order].list } c.stackcache[order].list = x.ptr().next c.stackcache[order].size -= uintptr(n) } v = unsafe.Pointer(x) } else { // 大栈 var s *mspan npage := uintptr(n) >> _PageShift log2npage := stacklog2(npage) // Try to get a stack from the large stack cache. lock(&stackLarge.lock) if !stackLarge.free[log2npage].isEmpty() { s = stackLarge.free[log2npage].first stackLarge.free[log2npage].remove(s) } unlock(&stackLarge.lock) lockWithRankMayAcquire(&mheap_.lock, lockRankMheap) if s == nil { // 从堆中分配新的栈空间 s = mheap_.allocManual(npage, spanAllocStack) if s == nil { throw("out of memory") } osStackAlloc(s) s.elemsize = uintptr(n) } v = unsafe.Pointer(s.base()) } // ... return stack{uintptr(v), uintptr(v) + uintptr(n)}}stackpoolalloc stackpool
var stackpool [_NumStackOrders]struct { item stackpoolItem _ [(cpu.CacheLinePadSize - unsafe.Sizeof(stackpoolItem{})%cpu.CacheLinePadSize) % cpu.CacheLinePadSize]byte}type stackpoolItem struct { _ sys.NotInHeap mu mutex span mSpanList}func stackpoolalloc(order uint8) gclinkptr { list := &stackpool[order].item.span s := list.first if s == nil { // 从 mheap 中申请 class = 0 对应页数的 s = mheap_.allocManual(_StackCacheSize>>_PageShift, spanAllocStack) // ... } // 分配内存 x := s.manualFreeList // ... return x}stackcache
type mcache struct { stackcache [_NumStackOrders]stackfreelist}type stackfreelist struct { list gclinkptr size uintptr }type gclinkptr uintptrfunc (p gclinkptr) ptr() *gclink { return (*gclink)(unsafe.Pointer(p))}stackcacherefill
func stackcacherefill(c *mcache, order uint8) { for size < _StackCacheSize/2 { x := stackpoolalloc(order) x.ptr().next = list list = x size += fixedStack << order } unlock(&stackpool[order].item.mu) c.stackcache[order].list = list c.stackcache[order].size = size}回收
//Goexit -> goexit1 -> goexit0 -> gdestroyfunc gdestroy(gp *g) { // ...... // 修改状态 casgstatus(gp, _Grunning, _Gdead) // 把 gp 的变量制空 ....... // 把 m 上的 g 制空 dropg() gfput(pp, gp)}func gfput(pp *p, gp *g) { // ...... stksize := gp.stack.hi - gp.stack.lo // 如果栈不是默认大小 直接释放掉 只有默认大小才去复用 if stksize != uintptr(startingStackSize) { // non-standard stack size - free it. stackfree(gp.stack) gp.stack.lo = 0 gp.stack.hi = 0 gp.stackguard0 = 0 } // 将 goroutine 放入空闲队列 pp.gFree.push(gp) pp.gFree.n++ // 如果到达 64 个 goroutine 就把一部分放到全局队列中 if pp.gFree.n >= 64 { var ( inc int32 stackQ gQueue noStackQ gQueue ) for pp.gFree.n >= 32 { gp := pp.gFree.pop() pp.gFree.n-- if gp.stack.lo == 0 { noStackQ.push(gp) } else { stackQ.push(gp) } inc++ } lock(&sched.gFree.lock) sched.gFree.noStack.pushAll(noStackQ) sched.gFree.stack.pushAll(stackQ) sched.gFree.n += inc unlock(&sched.gFree.lock) }}stackfree
func stackfree(stk stack) { // ...... if n < fixedStack<<_NumStackOrders && n < _StackCacheSize { // 小栈(< 32k) 留着复用一下 order := uint8(0) n2 := n for n2 > fixedStack { order++ n2 >>= 1 } x := gclinkptr(v) // 如果不使用缓存或当前处理器被抢占,使用全局栈池 if stackNoCache != 0 || gp.m.p == 0 || gp.m.preemptoff != "" { lock(&stackpool[order].item.mu) stackpoolfree(x, order) unlock(&stackpool[order].item.mu) } else { // 否则,使用本地缓存 c := gp.m.p.ptr().mcache if c.stackcache[order].size >= _StackCacheSize { stackcacherelease(c, order) } x.ptr().next = c.stackcache[order].list c.stackcache[order].list = x c.stackcache[order].size += n } } else { // 如果栈大小不适合缓存,检查其 span 状态并相应处理 s := spanOfUnchecked(uintptr(v)) if s.state.get() != mSpanManual { println(hex(s.base()), v) throw("bad span state") } if gcphase == _GCoff { // 如果 GC 未运行,立即释放栈 osStackFree(s) mheap_.freeManual(s, spanAllocStack) } else { // 如果 GC 运行中,将栈添加到大栈缓存,避免与 GC 竞态 log2npage := stacklog2(s.npages) lock(&stackLarge.lock) stackLarge.free[log2npage].insert(s) unlock(&stackLarge.lock) } }} |
|