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
=stackPreempt
(-1314)。 - 将
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是最后一个转换为
_Pgcstop
的,则唤醒gc。
重点:
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.
}