天天看點

16-go排程器觸發-syscall

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. p

      m

      解綁。
    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. 停止m

      stopm()

      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.
}