golang提供syscall庫對作業系統的系統調用作了封裝,比如:
func Open(path string, mode int, perm uint32) (fd int, err error)
func Read(fd int, p []byte) (n int, err error)
func Write(fd int, p []byte) (n int, err error)
對于syscall.Read()的封裝,内部調用了read(),read()是對Syscall()的封裝。 在系統調用結束時會觸發排程器的調用。
func Read(fd int, p []byte) (n int, err error) {
n, err = read(fd, p)
if race.Enabled {
if n > 0 {
race.WriteRange(unsafe.Pointer(&p[0]), n)
}
if err == nil {
race.Acquire(unsafe.Pointer(&ioSync))
}
}
if msanenabled && n > 0 {
msanWrite(unsafe.Pointer(&p[0]), n)
}
return
}
func read(fd int, p []byte) (n int, err error) {
var _p0 unsafe.Pointer
if len(p) > 0 {
_p0 = unsafe.Pointer(&p[0])
} else {
_p0 = unsafe.Pointer(&_zero)
}
r0, _, e1 := Syscall(SYS_READ, uintptr(fd), uintptr(_p0), uintptr(len(p)))
n = int(r0)
if e1 != 0 {
err = errnoErr(e1)
}
return
}
2. 系統調用接口 Syscall()
Syscall包含了三部分。
-
syscall.Entersyscall()
- 設定
=_g_.stackguard0
(-1314)。stackPreempt
- 将
與p
解綁。m
- 設定_g_狀态,
–>_Grunning
。_Gsyscall
- 如果sysmon處于等待喚醒狀态,喚醒sysmon線程。
- 設定
-
realSyscall()
-
syscall.Exitsyscall()
- 調用排程器
Gosched()
- 調用排程器
//src\syscall\asm_linux_amd64.s
TEXT ·Syscall(SB),NOSPLIT,$0-56
CALL runtime·entersyscall(SB)
MOVQ a1+8(FP), DI
MOVQ a2+16(FP), SI
MOVQ a3+24(FP), DX
MOVQ $0, R10
MOVQ $0, R8
MOVQ $0, R9
MOVQ trap+0(FP), AX // syscall entry
SYSCALL
CMPQ AX, $0xfffffffffffff001
JLS ok
MOVQ $-1, r1+32(FP)
MOVQ $0, r2+40(FP)
NEGQ AX
MOVQ AX, err+48(FP)
CALL runtime·exitsyscall(SB)
RET
ok:
MOVQ AX, r1+32(FP)
MOVQ DX, r2+40(FP)
MOVQ $0, err+48(FP)
CALL runtime·exitsyscall(SB)
RET
//src\cmd\vendor\golang.org\x\sys\unix\gccgo.go
func Syscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) {
syscall.Entersyscall()
r, errno := realSyscall(trap, a1, a2, a3, 0, 0, 0, 0, 0, 0)
syscall.Exitsyscall()
return r, 0, syscall.Errno(errno)
}
//src\runtime\proc.go
func entersyscall() {
reentersyscall(getcallerpc(), getcallersp())
}
3. Syscall()實作
3.1 準備進入系統調用 reentersyscall()
- 設定目前g進入系統調用狀态。
–>_Grunning
_Gsyscall
- 如果sysmon處于等待狀态,喚醒sysmon線程。
- 将目前的p與m解綁。但是m會記住該p,m.oldp=pp,同時設定p的狀态為處于系統調用
。_Psyscall
- 如果gc處于等待運作狀态,且目前p是最後一個轉換為
的,則喚醒gc。_Pgcstop
重點:
1. 喚醒sysmon線程。
2. 嘗試進入gc。
func reentersyscall(pc, sp uintptr) {
_g_ := getg()
// 禁止被搶占
_g_.m.locks++
// 1. 設定stackguard0=stackPreempt,用于
_g_.stackguard0 = stackPreempt
_g_.throwsplit = true
// 2. 儲存棧資訊
save(pc, sp)
// 3. 設定g狀态,_Grunning-->_Gsyscall
casgstatus(_g_, _Grunning, _Gsyscall)
// 4. 如果sysmonwait狀态不等于0,則喚醒sysmon線程
if atomic.Load(&sched.sysmonwait) != 0 {
systemstack(entersyscall_sysmon)
save(pc, sp)
}
// 5. 将p與目前m解綁。
pp.m = 0
_g_.m.p = 0
// 6. 設定p的狀态為 _Psyscall
atomic.Store(&pp.status, _Psyscall)
}
3.2 系統調用結束 exitsyscall()
-
嘗試擷取一個空閑P。
系統調用結束,優先嘗試使用進入系統調用時與m解綁的p。即oldp指向的p。
- 如果oldp還未被别的m綁定,則進行關聯。
-
如果oldp已經被别的m綁定,則嘗試新擷取一個空閑p。
如果擷取p成功,則設定g狀态
->_Gsyscall
_Grunning
。
運作g。
- 擷取空閑p失敗,則調用排程器。
- 将g狀态改為可運作。
–>_Gsyscall
。_Grunnable
- 将目前g與m解綁
。dropg()
- 再次嘗試擷取一個空閑p.
- 擷取失敗,則将g放入待運作隊列。
- 擷取到p,則将m與p關聯,并執行g。(執行的誰?)
- 停止m
。stopm()
- 将m放入空閑清單sched.midle。并進行死鎖檢測。
- 通知目前m線程挂起。
- 設定m目前下一個p。
- 将m下一個p置空。
- 調用排程器
。schedule()
- 将g狀态改為可運作。
重點:
1. 嘗試調用排程器。
func exitsyscall() {
_g_ := getg()
// 1.禁止被搶占
_g_.m.locks++ // see comment in entersyscall
_g_.waitsince = 0
oldp := _g_.m.oldp.ptr()
_g_.m.oldp = 0
// exitsyscallfast()
// 擷取一個空閑P,将P與目前M關聯。
// 如果能夠擷取到一個空閑P,則直接調用排程器。
if exitsyscallfast(oldp) {
// 2. 設定狀态,_Gsyscall-->_Grunning
casgstatus(_g_, _Gsyscall, _Grunning)
if _g_.preempt {
// 3. 如果可以被搶占,設定stackguard0 = stackPreempt
_g_.stackguard0 = stackPreempt
}
// 4. 調用排程器Gosched()
if sched.disable.user && !schedEnabled(_g_) {
// Scheduling of this goroutine is disabled.
Gosched()
}
return
}
_g_.sysexitticks = 0
if trace.enabled {
// Wait till traceGoSysBlock event is emitted.
// This ensures consistency of the trace (the goroutine is started after it is blocked).
for oldp != nil && oldp.syscalltick == _g_.m.syscalltick {
osyield()
}
// We can't trace syscall exit right now because we don't have a P.
// Tracing code can invoke write barriers that cannot run without a P.
// So instead we remember the syscall exit time and emit the event
// in execute when we have a P.
_g_.sysexitticks = cputicks()
}
_g_.m.locks--
// 調用排程器
// Call the scheduler.
mcall(exitsyscall0)
if _g_.m.mcache == nil {
throw("lost mcache")
}
// 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
// exitsyscall slow path on g0.
// Failed to acquire P, enqueue gp as runnable.
//
//go:nowritebarrierrec
func exitsyscall0(gp *g) {
_g_ := getg()
// 1. 設定g狀态, _Gsyscall->_Grunnable
casgstatus(gp, _Gsyscall, _Grunnable)
// 2. g與m解綁
dropg()
var _p_ *p
// 3. 從全局空閑p清單,擷取一個空閑的p。
if schedEnabled(_g_) {
_p_ = pidleget()
}
if _p_ == nil {
// 4. 沒有可用的p,則将g放入全局可執行隊列
globrunqput(gp)
} else if atomic.Load(&sched.sysmonwait) != 0 {
atomic.Store(&sched.sysmonwait, 0)
notewakeup(&sched.sysmonnote)
}
// 5. 找到可用的p
if _p_ != nil {
// 6. 将p與m關聯。
acquirep(_p_)
// 7. 執行g
execute(gp, false) // Never returns.
}
// 8. 停止執行目前的m,直到擷取一個新的任務
stopm()
// 9. 調用排程器
schedule() // Never returns.
}