您好,登錄后才能下訂單哦!
本篇內(nèi)容主要講解“Go調(diào)度器學(xué)習(xí)之系統(tǒng)調(diào)用的方法是什么”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實(shí)用性強(qiáng)。下面就讓小編來帶大家學(xué)習(xí)“Go調(diào)度器學(xué)習(xí)之系統(tǒng)調(diào)用的方法是什么”吧!
下面,我們將以一個(gè)簡單的文件打開的系統(tǒng)調(diào)用,來分析一下Go
調(diào)度器在系統(tǒng)調(diào)用時(shí)做了什么。
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)) }
如上簡單的代碼,讀取一個(gè)名為file
的本地文件,然后打印其數(shù)據(jù),我們通過匯編代碼來分析一下其調(diào)用過程:
$ go build -gcflags "-N -l" -o main main.go
$ objdump -d main >> main.i
可以發(fā)現(xiàn),在main.i
中,從main.main
函數(shù),對(duì)于文件Open
操作的調(diào)用關(guān)系為: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
可以發(fā)現(xiàn),系統(tǒng)調(diào)用最終會(huì)進(jìn)入到runtime.entersyscall
函數(shù):
func entersyscall() { reentersyscall(getcallerpc(), getcallersp()) }
runtime.entersyscall
函數(shù)會(huì)調(diào)用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) // 存儲(chǔ)一下舊P _g_.m.p = 0 atomic.Store(&pp.status, _Psyscall) if sched.gcwaiting != 0 { systemstack(entersyscall_gcwait) save(pc, sp) } _g_.m.locks-- }
可以發(fā)現(xiàn),runtime.reentersyscall
除了做一些保障性的工作外,最重要的是做了以下三件事:
保存當(dāng)前goroutine
的PC和棧指針SP的內(nèi)容;
將當(dāng)前goroutine
的狀態(tài)置為_Gsyscall
;
將當(dāng)前P的狀態(tài)置為_Psyscall
,并解綁P和M,讓當(dāng)前M陷入內(nèi)核的系統(tǒng)調(diào)用中,P被釋放,可以被其他找工作的M找到并且執(zhí)行剩下的goroutine
。
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() // 拿到開始存儲(chǔ)的舊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
函數(shù)有以下個(gè)分支:
如果舊的P還沒有被其他M占用,依舊處于_Psyscall
狀態(tài),那么直接通過wirep
函數(shù)獲取這個(gè)P,返回true;
如果舊的P被占用了,那么調(diào)用exitsyscallfast_pidle
去獲取空閑的P來執(zhí)行,返回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 } // 如果上一個(gè)P沒有被其他M占用,還處于_Psyscall狀態(tài),那么直接通過wirep函數(shù)獲取此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 }
當(dāng)exitsyscallfast
函數(shù)返回false后,則會(huì)調(diào)用exitsyscall0
函數(shù)去處理:
func exitsyscall0(gp *g) { casgstatus(gp, _Gsyscall, _Grunnable) dropg() // 因?yàn)楫?dāng)前m沒有找到p,所以先解開g和m lock(&sched.lock) var _p_ *p if schedEnabled(gp) { _p_ = pidleget() // 還是嘗試找一下有沒有空閑的p } var locked bool if _p_ == nil { // 如果還是沒有空閑p,那么把g扔到全局隊(duì)列去等待調(diào)度 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,那么就去執(zhí)行,這個(gè)分支永遠(yuǎn)不會(huì)返回 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的條件,停止這個(gè)m,因?yàn)闆]有p,所以m應(yīng)該要開始找工作了 schedule() // Never returns. // 通過schedule函數(shù)進(jìn)行調(diào)度 }
exitsyscall0
函數(shù)還是會(huì)嘗試找一個(gè)空閑的P,沒有的話就把goroutine
扔到全局隊(duì)列,然后停止這個(gè)M,并且調(diào)用schedule
函數(shù)等待調(diào)度;如果找到了空閑P,則會(huì)利用這個(gè)P去執(zhí)行此goroutine
。
通過以上分析,可以發(fā)現(xiàn)goroutine
有關(guān)系統(tǒng)調(diào)用的調(diào)度還是比較簡單的:
在發(fā)生系統(tǒng)調(diào)用時(shí)會(huì)將此goroutine
設(shè)置為_Gsyscall
狀態(tài);
并將P設(shè)置為_Psyscall
狀態(tài),并且解綁M和P,使得這個(gè)P可以去執(zhí)行其他的goroutine
,而M就陷入系統(tǒng)內(nèi)核調(diào)用中了;
當(dāng)該M從內(nèi)核調(diào)用中恢復(fù)到用戶態(tài)時(shí),會(huì)優(yōu)先去獲取原來的舊P,如果該舊P還未被其他M占用,則利用該P(yáng)繼續(xù)執(zhí)行本goroutine
;
如果沒有獲取到舊P,那么會(huì)嘗試去P的空閑列表獲取一個(gè)P來執(zhí)行;
如果空閑列表中沒有獲取到P,就會(huì)把goroutine
扔到全局隊(duì)列中,等到繼續(xù)執(zhí)行。
可以發(fā)現(xiàn),如果系統(tǒng)發(fā)生著很頻繁的系統(tǒng)調(diào)用,很可能會(huì)產(chǎn)生很多的M,在IO密集型的場(chǎng)景下,甚至?xí)l(fā)生線程數(shù)超過10000的panic事件。
到此,相信大家對(duì)“Go調(diào)度器學(xué)習(xí)之系統(tǒng)調(diào)用的方法是什么”有了更深的了解,不妨來實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。