16-go调度器触发-syscall

22 篇文章 1 订阅
3 篇文章 0 订阅

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包含了三部分。

  1. syscall.Entersyscall()
    1. 设置_g_.stackguard0= stackPreempt(-1314)。
    2. pm解绑。
    3. 设置_g_状态,_Grunning–>_Gsyscall
    4. 如果sysmon处于等待唤醒状态,唤醒sysmon线程。
  2. realSyscall()
  3. syscall.Exitsyscall()
    1. 调用调度器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()
  1. 设置当前g进入系统调用状态。 _Grunning–>_Gsyscall
  2. 如果sysmon处于等待状态,唤醒sysmon线程。
  3. 将当前的p与m解绑。但是m会记住该p,m.oldp=pp,同时设置p的状态为处于系统调用_Psyscall
  4. 如果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()
  1. 尝试获取一个空闲P。
    系统调用结束,优先尝试使用进入系统调用时与m解绑的p。即oldp指向的p。

    1. 如果oldp还未被别的m绑定,则进行关联。
    2. 如果oldp已经被别的m绑定,则尝试新获取一个空闲p。
      如果获取p成功,则设置g状态_Gsyscall->_Grunning
      运行g。
  2. 获取空闲p失败,则调用调度器。

    1. 将g状态改为可运行。_Gsyscall–>_Grunnable
    2. 将当前g与m解绑dropg()
    3. 再次尝试获取一个空闲p.
      1. 获取失败,则将g放入待运行队列。
      2. 获取到p,则将m与p关联,并执行g。(执行的谁?)
    4. 停止mstopm()
      1. 将m放入空闲列表sched.midle。并进行死锁检测。
      2. 通知当前m线程挂起。
      3. 设置m当前下一个p。
      4. 将m下一个p置空。
    5. 调用调度器schedule()

重点:
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.
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值