91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Go調度器學習之系統調用的方法是什么

發布時間:2023-04-07 11:30:23 來源:億速云 閱讀:94 作者:iii 欄目:開發技術

本篇內容主要講解“Go調度器學習之系統調用的方法是什么”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“Go調度器學習之系統調用的方法是什么”吧!

1. 系統調用

下面,我們將以一個簡單的文件打開的系統調用,來分析一下Go調度器在系統調用時做了什么。

1.1 場景

package main

import (
   "fmt"
   "io/ioutil"
   "os"
)

func main() {
   f, err := os.Open("./file")
   if err != nil {
      panic(err)
   }
   defer f.Close()

   content, err := ioutil.ReadAll(f)
   if err != nil {
      panic(err)
   }
   fmt.Println(string(content))
}

如上簡單的代碼,讀取一個名為file的本地文件,然后打印其數據,我們通過匯編代碼來分析一下其調用過程:

$ go build -gcflags "-N -l" -o main main.go
$ objdump -d main >> main.i

可以發現,在main.i中,從main.main函數,對于文件Open操作的調用關系為:main.main -> os.Open -> os.openFile -> os.openFileNolog -> syscall.openat -> syscall.Syscall6.abi0 -> runtime.entersyscall.abi0,而Syscall6的匯編如下:

TEXT ·Syscall6(SB),NOSPLIT,$0-80
   CALL   runtime·entersyscall(SB)
   MOVQ   a1+8(FP), DI
   MOVQ   a2+16(FP), SI
   MOVQ   a3+24(FP), DX
   MOVQ   a4+32(FP), R10
   MOVQ   a5+40(FP), R8
   MOVQ   a6+48(FP), R9
   MOVQ   trap+0(FP), AX // syscall entry
   SYSCALL
   CMPQ   AX, $0xfffffffffffff001
   JLS    ok6
   MOVQ   $-1, r1+56(FP)
   MOVQ   $0, r2+64(FP)
   NEGQ   AX
   MOVQ   AX, err+72(FP)
   CALL   runtime·exitsyscall(SB)
   RET
ok6:
   MOVQ   AX, r1+56(FP)
   MOVQ   DX, r2+64(FP)
   MOVQ   $0, err+72(FP)
   CALL   runtime·exitsyscall(SB)
   RET

1.2 陷入系統調用

可以發現,系統調用最終會進入到runtime.entersyscall函數:

func entersyscall() {
   reentersyscall(getcallerpc(), getcallersp())
}

runtime.entersyscall函數會調用runtime.reentersyscall

func reentersyscall(pc, sp uintptr) {
   _g_ := getg()

   // Disable preemption because during this function g is in Gsyscall status,
   // but can have inconsistent g->sched, do not let GC observe it.
   _g_.m.locks++

   // Entersyscall must not call any function that might split/grow the stack.
   // (See details in comment above.)
   // Catch calls that might, by replacing the stack guard with something that
   // will trip any stack check and leaving a flag to tell newstack to die.
   _g_.stackguard0 = stackPreempt
   _g_.throwsplit = true

   // Leave SP around for GC and traceback.
   save(pc, sp)  // 保存pc和sp
   _g_.syscallsp = sp
   _g_.syscallpc = pc
   casgstatus(_g_, _Grunning, _Gsyscall)
   if _g_.syscallsp < _g_.stack.lo || _g_.stack.hi < _g_.syscallsp {
      systemstack(func() {
         print("entersyscall inconsistent ", hex(_g_.syscallsp), " [", hex(_g_.stack.lo), ",", hex(_g_.stack.hi), "]\n")
         throw("entersyscall")
      })
   }

   if trace.enabled {
      systemstack(traceGoSysCall)
      // systemstack itself clobbers g.sched.{pc,sp} and we might
      // need them later when the G is genuinely blocked in a
      // syscall
      save(pc, sp)
   }

   if atomic.Load(&sched.sysmonwait) != 0 {
      systemstack(entersyscall_sysmon)
      save(pc, sp)
   }

   if _g_.m.p.ptr().runSafePointFn != 0 {
      // runSafePointFn may stack split if run on this stack
      systemstack(runSafePointFn)
      save(pc, sp)
   }

   // 一下解綁P和M
   _g_.m.syscalltick = _g_.m.p.ptr().syscalltick
   _g_.sysblocktraced = true
   pp := _g_.m.p.ptr()
   pp.m = 0
   _g_.m.oldp.set(pp)  // 存儲一下舊P
   _g_.m.p = 0
   atomic.Store(&pp.status, _Psyscall)
   if sched.gcwaiting != 0 {
      systemstack(entersyscall_gcwait)
      save(pc, sp)
   }

   _g_.m.locks--
}

可以發現,runtime.reentersyscall除了做一些保障性的工作外,最重要的是做了以下三件事:

  • 保存當前goroutine的PC和棧指針SP的內容;

  • 將當前goroutine的狀態置為_Gsyscall

  • 將當前P的狀態置為_Psyscall,并解綁P和M,讓當前M陷入內核的系統調用中,P被釋放,可以被其他找工作的M找到并且執行剩下的goroutine

1.3 從系統調用恢復

func exitsyscall() {
   _g_ := getg()

   _g_.m.locks++ // see comment in entersyscall
   if getcallersp() > _g_.syscallsp {
      throw("exitsyscall: syscall frame is no longer valid")
   }

   _g_.waitsince = 0
   oldp := _g_.m.oldp.ptr()  // 拿到開始存儲的舊P
   _g_.m.oldp = 0
   if exitsyscallfast(oldp) {
      if trace.enabled {
         if oldp != _g_.m.p.ptr() || _g_.m.syscalltick != _g_.m.p.ptr().syscalltick {
            systemstack(traceGoStart)
         }
      }
      // There's a cpu for us, so we can run.
      _g_.m.p.ptr().syscalltick++
      // We need to cas the status and scan before resuming...
      casgstatus(_g_, _Gsyscall, _Grunning)

      ...

      return
   }

   ...

   // Call the scheduler.
   mcall(exitsyscall0)

   // Scheduler returned, so we're allowed to run now.
   // Delete the syscallsp information that we left for
   // the garbage collector during the system call.
   // Must wait until now because until gosched returns
   // we don't know for sure that the garbage collector
   // is not running.
   _g_.syscallsp = 0
   _g_.m.p.ptr().syscalltick++
   _g_.throwsplit = false
}

其中,exitsyscallfast函數有以下個分支:

  • 如果舊的P還沒有被其他M占用,依舊處于_Psyscall狀態,那么直接通過wirep函數獲取這個P,返回true;

  • 如果舊的P被占用了,那么調用exitsyscallfast_pidle去獲取空閑的P來執行,返回true;

  • 如果沒有空閑的P,則返回false;

//go:nosplit
func exitsyscallfast(oldp *p) bool {
   _g_ := getg()

   // Freezetheworld sets stopwait but does not retake P's.
   if sched.stopwait == freezeStopWait {
      return false
   }

   // 如果上一個P沒有被其他M占用,還處于_Psyscall狀態,那么直接通過wirep函數獲取此P
   // Try to re-acquire the last P.
   if oldp != nil && oldp.status == _Psyscall && atomic.Cas(&oldp.status, _Psyscall, _Pidle) {
      // There's a cpu for us, so we can run.
      wirep(oldp)
      exitsyscallfast_reacquired()
      return true
   }

   // Try to get any other idle P.
   if sched.pidle != 0 {
      var ok bool
      systemstack(func() {
         ok = exitsyscallfast_pidle()
         if ok && trace.enabled {
            if oldp != nil {
               // Wait till traceGoSysBlock event is emitted.
               // This ensures consistency of the trace (the goroutine is started after it is blocked).
               for oldp.syscalltick == _g_.m.syscalltick {
                  osyield()
               }
            }
            traceGoSysExit(0)
         }
      })
      if ok {
         return true
      }
   }
   return false
}

exitsyscallfast函數返回false后,則會調用exitsyscall0函數去處理:

func exitsyscall0(gp *g) {
   casgstatus(gp, _Gsyscall, _Grunnable)
   dropg() // 因為當前m沒有找到p,所以先解開g和m
   lock(&sched.lock)
   var _p_ *p
   if schedEnabled(gp) {
      _p_ = pidleget() // 還是嘗試找一下有沒有空閑的p
   }
   var locked bool
   if _p_ == nil { // 如果還是沒有空閑p,那么把g扔到全局隊列去等待調度
      globrunqput(gp)

      // Below, we stoplockedm if gp is locked. globrunqput releases
      // ownership of gp, so we must check if gp is locked prior to
      // committing the release by unlocking sched.lock, otherwise we
      // could race with another M transitioning gp from unlocked to
      // locked.
      locked = gp.lockedm != 0
   } else if atomic.Load(&sched.sysmonwait) != 0 {
      atomic.Store(&sched.sysmonwait, 0)
      notewakeup(&sched.sysmonnote)
   }
   unlock(&sched.lock)
   if _p_ != nil { // 如果找到了空閑p,那么就去執行,這個分支永遠不會返回
      acquirep(_p_)
      execute(gp, false) // Never returns.
   }
   if locked {
      // Wait until another thread schedules gp and so m again.
      //
      // N.B. lockedm must be this M, as this g was running on this M
      // before entersyscall.
      stoplockedm()
      execute(gp, false) // Never returns.
   }
   stopm() // 這里還是沒有找到空閑p的條件,停止這個m,因為沒有p,所以m應該要開始找工作了
   schedule() // Never returns. // 通過schedule函數進行調度
}

exitsyscall0函數還是會嘗試找一個空閑的P,沒有的話就把goroutine扔到全局隊列,然后停止這個M,并且調用schedule函數等待調度;如果找到了空閑P,則會利用這個P去執行此goroutine

2. 小結

通過以上分析,可以發現goroutine有關系統調用的調度還是比較簡單的:

  • 在發生系統調用時會將此goroutine設置為_Gsyscall狀態;

  • 并將P設置為_Psyscall狀態,并且解綁M和P,使得這個P可以去執行其他的goroutine,而M就陷入系統內核調用中了;

  • 當該M從內核調用中恢復到用戶態時,會優先去獲取原來的舊P,如果該舊P還未被其他M占用,則利用該P繼續執行本goroutine

  • 如果沒有獲取到舊P,那么會嘗試去P的空閑列表獲取一個P來執行;

  • 如果空閑列表中沒有獲取到P,就會把goroutine扔到全局隊列中,等到繼續執行。

可以發現,如果系統發生著很頻繁的系統調用,很可能會產生很多的M,在IO密集型的場景下,甚至會發生線程數超過10000的panic事件。

到此,相信大家對“Go調度器學習之系統調用的方法是什么”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

go
AI

康保县| 茶陵县| 静乐县| 舟曲县| 和平县| 康乐县| 高雄县| 三江| 承德县| 萨嘎县| 亚东县| 天峨县| 察隅县| 长葛市| 健康| 灵武市| 百色市| 黄浦区| 德安县| 潼关县| 吴忠市| 社会| 太谷县| 平谷区| 林西县| 化德县| 井陉县| 云林县| 阳西县| 浦江县| 定州市| 正蓝旗| 沙雅县| 霞浦县| 土默特左旗| 乐业县| 安图县| 汉阴县| 庆云县| 米林县| 土默特右旗|