linux 进程 内存原理

1
2
3
# 查看页表大小
getconf PAGE_SIZE
# 答案是 4096  ,单位是 k

image-20211129204739656

虚拟内存 和 物理内存 会有映射关系

linux 使用的是多级索引页表

Linux采用了一种同时适用于32bit和64bit系统的分页模型。32bit系统一般采用两级页表就足够了,但64bit系统需要更多的分页。Linux 2.6.10版本采用三级分页,从2.6.11版本开始采用了四级分页。

使用 多级页表的好处

CPU 对内存的 访问

  • CPU 有个 Memory Management Unit (MMu)单元
  • cpu 把虚拟地址 给 MMU ,MMU 去物理内存 查询页表,得到实际的物理地址
  • CPU 维护一份缓存 Translation Lookaside buffer (TLB), 缓存虚拟地址和物理地址 的映射关系

image-20211129215936113

goroutine

go语言 基于 GMP 模型 实现 用户态线程

  • Goroutine: 表示协程,每个 goroutine 都有自己的栈空间,定时器,初始化栈空间为 2k,空间会随着需求增加
  • Machine: 抽象化代表内核线程,记录内核线程栈信息,当 goroutine 调度到线程时, 使用该goroutine 自己的栈信息
  • Process , 代表调度器,负责 goroutine,维护一个 本地 goroutine 队列,M从P上获得Goroutine 并执行,同时还负责部分内存管理

image-20211129221245640

GMP 模型细节

image-20211129221734801

以一定的几率,比如说 1/6 去全局队列中获得任务执行,如果 全局队列没有任务,局部空闲队列也没有任务,就采用工作窃取的机制 去其他的队列中获取任务来执行

G状态 G的主要几种状态:

本文基于Go1.13,具体代码见(/src/runtime/runtime2.go)

_Gidle:刚刚被分配并且还没有被初始化,值为0,为创建goroutine后的默认值

_Grunnable: 没有执行代码,没有栈的所有权,存储在运行队列中,可能在某个P的本地队列或全局队列中(如上图)。

_Grunning: 正在执行代码的goroutine,拥有栈的所有权(如上图)。

_Gsyscall:正在执行系统调用,拥有栈的所有权,与P脱离,但是与某个M绑定,会在调用结束后被分配到运行队列(如上图)。

_Gwaiting:被阻塞的goroutine,阻塞在某个channel的发送或者接收队列(如上图)。

_Gdead: 当前goroutine未被使用,没有执行代码,可能有分配的栈,分布在空闲列表gFree,可能是一个刚刚初始化的goroutine,也可能是执行了goexit退出的goroutine(如上图)。

_Gcopystac:栈正在被拷贝,没有执行代码,不在运行队列上,执行权在

_Gscan : GC 正在扫描栈空间,没有执行代码,可以与其他状态同时存在

P的状态 _Pidle :处理器没有运行用户代码或者调度器,被空闲队列或者改变其状态的结构持有,运行队列为空

_Prunning :被线程 M 持有,并且正在执行用户代码或者调度器(如上图)

_Psyscall:没有执行用户代码,当前线程陷入系统调用(如上图)

_Pgcstop :被线程 M 持有,当前处理器由于垃圾回收被停止

_Pdead :当前处理器已经不被使用

M的状态 自旋线程:处于运行状态但是没有可执行goroutine的线程(如下图),数量最多为GOMAXPROC,若是数量大于GOMAXPROC就会进入休眠。

非自旋线程:处于运行状态有可执行goroutine的线程。

golang 内存回收原理

  1. 引用计数 (python,PHP,Swift)

  2. 标记-清除(golang)

    1. 从根变量遍历所有应用到对象,没有被标记的对象进行回收
    2. 缺点:需要 stop the world ,暂停程序运行
    3. golang 具体使用的是 三色标记算法
  3. 分代收集 (java)

go语言垃圾回收触发机制

  • 内存分配量达到阈值触发 GC

    • 每次内存分配时候会检查当前内存分配量是否已达到阈值,达到阈值立即启动GC
    • 定期触发GC
      • 默认情况下,最长 2分钟触发一次 GC, 这个间隔在 src/runtime/proc.go:forcegcperiod 变量中声明
    • 手动触发
      • 调用 runtime.GC() 来手动触发GC 。这个主要用于GC 性能测试和统计

golang GC 工作流程

  • Mark:
    • 先 mark prepare: 初始化 GC 任务,包括开启写屏障(write barrier) 和 辅助 GC (Mutator assist ),统计 GC root 对象的任务数量,这个过程需要短暂的 STW
    • GC drains: 扫描 所有 root 对象, 包括全局 指针, goroutine(G) 栈上的指针,将其加入标记队列(灰色队列),并循环处理灰色队列的对象,知道灰色队列为空,这个过程后台并行执行
  • Mark termination: 完成标记,要重新 扫描 全局指针,因为 mark 和 用户程序是并行的,所以这个过程中 可能会有新的对象分配和制作赋值,需要通过写屏障(write barrier) 记录下来, re-scan 再检查一下会 STW
  • Sweep: 标记结果 将 白色对象 回收,这个过程是并行执行的
  • Sweep termination , 未清扫的span 进行清扫,只有上一轮 GC 的清扫工作完成后才能开始新一轮的 GC

调优命令

参考文档

1
2
perf top -p <pid>
# perf 是一个 比较特殊的命令,可以用来分析进程信息