Skip to content

sysmon

sysmon là một hàm bình thường, dịch thẳng là bộ giám sát hệ thống, sau khi xóa phần chú thích cũng chỉ khoảng 200 dòng code. Nó sẽ được phân phối một thread độc lập để khởi động trong giai đoạn bootstrap chương trình, sau đó sẽ liên tục giám sát trạng thái của chương trình Go ở background, và thực hiện các xử lý tương ứng. Về phần code khởi động của nó có thể xem trong hàm runtime.main:

go
func main() {
    ...
  mp := getg().m
  mainStarted = true
  systemstack(func() {
        newm(sysmon, nil, -1)
    })
    ...
}

Bản thân bộ giám sát hệ thống chỉ là một vòng lặp for, khoảng thời gian giữa mỗi vòng lặp là 20 micro giây, cùng với sự tăng lên của chỉ số idle của chương trình, khoảng thời gian này tối đa sẽ tăng lên 10 mili giây. Trong mỗi vòng lặp, nó chủ yếu làm mấy việc sau:

  • Hỗ trợ điều phối coroutine, chiếm quyền coroutine chạy thời gian dài
  • Kiểm tra tình hình bộ nhớ, và判断 xem có cần thực hiện garbage collection không
  • Thu hồi bộ nhớ không sử dụng
  • Giám sát network poller

Khởi động

Khởi động sysmon nằm trong hàm runtime.main, sau khi khởi tạo P xong sẽ gọi hàm runtime.newm để khởi động sysmon.

go
func main() {
    ...
  gp := getg()
  if gp.m != &m0 {
    throw("runtime.main called on final M")
  }

  process_environment()

  // The main goroutine has a goid of 1, which we set
  // up front. Make sure we're running as goid 1.
  if gp.goid != 1 {
    throw("runtime.main: goid != 1")
  }

  // Initialize all other goroutines.
  gp.m.locks++ // make it an error to call unlock
  gp.m.startupComplete.Store(true)

  // Start the world. startTheWorld sets sched.goid to 1.
  startTheWorld()

  // Run the main goroutine.
  mainStart()

  // Main goroutine is done. Unlock the main thread.
  gp.m.locks--

  // Wait for all other goroutines to exit.
  for atomic.Load(&sched.ngoroutines) > 1 {
    Gosched()
  }

  // Exit the program.
  exit(0)
}

Hàm runtime.newm sẽ tạo một thread mới để chạy hàm runtime.sysmon, vì sysmon không cần liên kết với P, nên tham số thứ hai truyền vào là nil.

go
func newm(fn func(), pp *p, id int64) {
  acquirem()
  mp := allocm(pp, fn, id)
  mp.nextp.set(pp)
  mp.sigmask = initSigmask
  newm1(mp)
  releasem(getg().m)
}

Vòng lặp giám sát

Code của hàm runtime.sysmon như sau, đã xóa bớt một phần code không quan trọng.

go
func sysmon() {
  var (
    waitNote  note
    shouldWait bool
    tick      = 20
  )

  initNote(&waitNote)
  for {
    // delay 20us -> 10ms
    if wait {
      if shouldWait {
        notetsleep(&waitNote, -1)
        clearNote(&waitNote)
      } else {
        usleep(tick)
      }
    }

    // retake, gc, scavenge, netpoll
    retake(now)
    if shouldRunGC() {
      startGC()
    }
    scavenge()
    netpoll()
  }
}

Có thể thấy, vòng lặp giám sát chủ yếu do bốn hàm retake, startGC, scavenge, netpoll cấu thành, tương ứng với bốn công việc đã đề cập ở trên.

Chiếm quyền coroutine

Hàm runtime.retake负责 chiếm quyền coroutine chạy thời gian dài, code của nó như sau.

go
func retake(now int64) uint32 {
  n := 0
  lock(&allpLock)
  for i := 0; i < len(allp); i++ {
    pp := allp[i]
    if pp == nil {
      continue
    }
    pd := &pp.sysmontick
    s := pp.status
    sysretake := false
    if s == _Prunning || s == _Psyscall {
      // Preempt G if it's running for too long.
      t := int64(pp.schedtick)
      if int64(pd.schedtick) != t {
        pd.schedtick = uint32(t)
        pd.schedwhen = now
      } else if pd.schedwhen+forcePreemptNS <= now {
        preemptone(pp)
        sysretake = true
      }
    }
  }
  unlock(&allpLock)
  return uint32(n)
}

Nó sẽ duyệt tất cả P, nếu thời gian thực thi của G mà P đang điều phối vượt quá 10ms, sẽ cưỡng chế kích hoạt chiếm quyền. Trong phần Điều phối chiếm quyền đã讲解 qua cơ chế chiếm quyền, ở đây không nhắc lại.

Garbage collection

Kiểm tra xem có cần thực hiện garbage collection không, nếu cần thì khởi động GC.

go
func shouldRunGC() bool {
  return memstats.heap_live >= memstats.gc_trigger.Load()
}

Khi bộ nhớ heap đạt đến ngưỡng kích hoạt GC, sẽ khởi động garbage collection.

Thu hồi bộ nhớ

Hàm runtime.scavenge负责 thu hồi bộ nhớ không sử dụng, code của nó như sau.

go
func scavenge() {
  // Try to release memory back to the OS.
  releaseOSMemory()
}

Nó sẽ thử giải phóng bộ nhớ không sử dụng trở lại cho hệ điều hành.

Giám sát network poller

Kiểm tra network poller, đánh thức các coroutine bị chặn vì IO.

go
func netpoll() {
  // Check for completed IO operations.
  if netpollinited() {
    list := netpoll(0)
    if !list.empty() {
      injectglist(&list)
    }
  }
}

Nó sẽ kiểm tra các operation IO đã hoàn thành, và đánh thức các coroutine tương ứng.

Khoảng thời gian giám sát

Khoảng thời gian giữa mỗi vòng lặp giám sát không cố định, nó sẽ dựa vào tình hình idle của chương trình để điều chỉnh.

go
if idle == 0 {
  tick = 20
} else if idle > 1000 {
  tick = 10000
} else {
  tick = 20 + idle/100
}

Khi chương trình bận, khoảng thời gian giám sát là 20 micro giây, khi chương trình idle, khoảng thời gian giám sát tối đa là 10 mili giây.

Tóm tắt

Sysmon là một component quan trọng trong runtime của Go, nó负责 giám sát trạng thái của chương trình, và thực hiện các xử lý tương ứng, bao gồm:

  • Chiếm quyền coroutine chạy thời gian dài
  • Khởi động garbage collection
  • Thu hồi bộ nhớ không sử dụng
  • Giám sát network poller

Thông qua sysmon, Go có thể tự động điều chỉnh trạng thái chạy của chương trình, nâng cao hiệu suất và ổn định của chương trình.

Golang by www.golangdev.cn edit