首页 科技正文

allbet gaming代理:Golang源码学习:调剂逻辑(四)系统挪用

admin 科技 2020-05-28 53 0

Linux系统挪用

观点:系统挪用为用户态历程提供了硬件的抽象接口。而且是用户空间接见内核的唯一手段,除异常和陷入外,它们是内核唯一的正当入口。保证系统的平安和稳固。

挪用号:在Linux中,每个系统挪用被赋予一个举世无双的系统挪用号。当用户空间的历程执行一个系统挪用时,会使用挪用号指明系统挪用。

syscall指令:由于用户代码特权级较低,无权接见需要最高特权级才气接见的内核地址空间的代码和数据。以是需要特殊指令,在golang中是syscall。

参数设置

x86-64中通过syscall指令执行系统挪用的参数设置

  • rax存放系统挪用号,挪用返回值也会放在rax中
  • 当系统挪用参数小于即是6个时,参数则须按顺序放到寄存器 rdi,rsi,rdx,r10,r8,r9中。
  • 若是系统挪用的参数数目大于6个,需将参数保存在一块延续的内存中,并将地址存入rbx中。

Golang中挪用系统挪用

给个简朴的例子。

package main

import (
	"fmt"
	"os"
)

func main() {
	f, _ := os.Open("read.go")
	buf := make([]byte, 1000)
	f.Read(buf)
	fmt.Printf("%s", buf)
}

通过 IDE 跟踪获得挪用路径:

os/file.go:(*File).Read() -> os/file_unix.go:(*File).read() -> internal/poll/fd_unix.go:(*File).pfd.Read()

->syscall/syscall_unix.go:Read() -> syscall/zsyscall_linux_amd64.go:read() -> syscall/syscall_unix.go:Syscall()

// syscall/zsyscall_linux_amd64.go
func read(fd int, p []byte) (n int, err error) {
        ......
	r0, _, e1 := Syscall(SYS_READ, uintptr(fd), uintptr(_p0), uintptr(len(p)))
        ......
}

可以看到 f.Read(buf) 最终挪用了 syscall/syscall_unix.go 文件中的 Syscall 函数。我们忽略中心的详细执行逻辑。

SYS_READ 界说的是 read 的系统挪用号,界说在 syscall/zsysnum_linux_amd64.go。

package syscall

const (
	SYS_READ                   = 0
	SYS_WRITE                  = 1
	SYS_OPEN                   = 2
	SYS_CLOSE                  = 3
	SYS_STAT                   = 4
	SYS_FSTAT                  = 5
        ......
)

Syscall系列函数

虽然在上面看到了 Syscall 函数,但执行系统挪用的防止并不知道它一个。它们的界说如下:

// src/syscall/syscall_unix.go

func Syscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno)
func Syscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err Errno)
func RawSyscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno)
func RawSyscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err Errno)

Syscall 与 Syscall6 的区别:只是参数个数的差别,其他都相同。

Syscall 与 RawSyscall 的区别:Syscall 最先会挪用 runtime·entersyscall ,竣事时会挪用 runtime·exitsyscall;而 RawSyscall 没有。这意味着 Syscall 是受调剂器控制的,RawSyscall不受。因此 RawSyscall 可能会造成壅闭。

下面来看一下源代码:

// src/syscall/asm_linux_amd64.s
// func Syscall(trap int64, a1, a2, a3 uintptr) (r1, r2, err uintptr);
// Trap # in AX, args in DI SI DX R10 R8 R9, return in AX DX
// Note that this differs from "standard" ABI convention, which
// would pass 4th arg in CX, not R10.

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

// func Syscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, err uintptr)
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

// func RawSyscall(trap, a1, a2, a3 uintptr) (r1, r2, err uintptr)
TEXT ·RawSyscall(SB),NOSPLIT,$0-56
	MOVQ	a1+8(FP), DI
	MOVQ	a2+16(FP), SI
	MOVQ	a3+24(FP), DX
	MOVQ	trap+0(FP), AX	// syscall entry
	SYSCALL
	CMPQ	AX, $0xfffffffffffff001
	JLS	ok1
	MOVQ	$-1, r1+32(FP)
	MOVQ	$0, r2+40(FP)
	NEGQ	AX
	MOVQ	AX, err+48(FP)
	RET
ok1:
	MOVQ	AX, r1+32(FP)
	MOVQ	DX, r2+40(FP)
	MOVQ	$0, err+48(FP)
	RET

// func RawSyscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, err uintptr)
TEXT ·RawSyscall6(SB),NOSPLIT,$0-80
        ......
	RET

系统挪用前函数(entersyscall -> reentersyscall)

在执行系统挪用前挪用 entersyscall 和 reentersyscall,reentersyscall的主要功能:

  1. 由于要最先系统挪用,以是当前G和和P的状态划分变为了 _Gsyscall 和 _Psyscall
  2. 而P不会守候M,以是P和M相互解绑
  3. 然则M会保留P到 m.oldp 中,在系统挪用竣事后实验与P重新绑定。

本节及后面会涉及到一些之前剖析过的函数,这里给出链接,就不重复剖析了。

  • wirep
  • mcall
func entersyscall() {
	reentersyscall(getcallerpc(), getcallersp())
}
func reentersyscall(pc, sp uintptr) {
	_g_ := getg()
	_g_.m.locks++
	_g_.stackguard0 = stackPreempt
	_g_.throwsplit = true

	// Leave SP around for GC and traceback.
	save(pc, sp)
	_g_.syscallsp = sp
	_g_.syscallpc = pc
	casgstatus(_g_, _Grunning, _Gsyscall)	// 当前g的状态由 _Grunning 改为 _Gsyscall
	......
	_g_.m.syscalltick = _g_.m.p.ptr().syscalltick
	_g_.sysblocktraced = true
	_g_.m.mcache = nil
	pp := _g_.m.p.ptr()
	pp.m = 0				// 当前 p 解绑 m
	_g_.m.oldp.set(pp)			// 将当前 p 赋值给 m.oldp。会在 exitsyscall 中用到。
	_g_.m.p = 0				// 当前 m 解绑 p
	atomic.Store(&pp.status, _Psyscall)	// 将当前 p 的状态改为 _Psyscall
        ......
	_g_.m.locks--
}

系统挪用退出后函数(exitsyscall)

主要功能是:

  1. 先实验绑定oldp,若是不允许,则绑定随便空闲P
  2. 未能绑定P,则解绑G和M;睡眠事情线程;重新调剂。
func exitsyscall() {
	_g_ := getg()
        ......
	_g_.waitsince = 0
	oldp := _g_.m.oldp.ptr()	// reentersyscall 函数中存储的P
	_g_.m.oldp = 0
	if exitsyscallfast(oldp) {	// 实验给当前M绑定个P,下有剖析。绑定乐成后执行 if 中的语句。
		_g_.m.p.ptr().syscalltick++
		casgstatus(_g_, _Gsyscall, _Grunning) // 更改G的状态
		_g_.syscallsp = 0
		_g_.m.locks--
		if _g_.preempt {
			_g_.stackguard0 = stackPreempt
		} else {
			_g_.stackguard0 = _g_.stack.lo + _StackGuard
		}
		_g_.throwsplit = false
		return
	}
	......
	mcall(exitsyscall0)	// 下有剖析
	......
}

实验为当前M绑定P(exitsyscallfast)

该函数的主要目的是实验为当前M绑定一个P,分为两种情形。

第一:若是oldp(也就是当前M的元配)存在,而且状态可以从 _Psyscall 变更到 _Pidle,则此P与M相互绑定,返回true。

第二:oldp条件不允许,则实验获取任何空闲的P并与当前M绑定。详细实现是:exitsyscallfast_pidle 挪用 pidleget,不为nil,则挪用 acquirep。

func exitsyscallfast(oldp *p) bool {
	_g_ := getg()
	// 实验与oldp绑定
	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
	}
	// 实验获取任何空闲的P
	if sched.pidle != 0 {
		var ok bool
		systemstack(func() {
			ok = exitsyscallfast_pidle()
                         ......
		})
		if ok {
			return true
		}
	}
	return false
}

M解绑G,重新调剂(mcall(exitsyscall0))

func exitsyscall0(gp *g) {
	_g_ := getg()	// g0
	casgstatus(gp, _Gsyscall, _Grunnable)
	dropg()	// 解绑 gp 与 M
	lock(&sched.lock)
	var _p_ *p
	if schedEnabled(_g_) {
		_p_ = pidleget()
	}
	if _p_ == nil {
		globrunqput(gp)	// 未获取到空闲P,将gp放入sched.runq
	} else if atomic.Load(&sched.sysmonwait) != 0 {
		atomic.Store(&sched.sysmonwait, 0)
		notewakeup(&sched.sysmonnote)
	}
	unlock(&sched.lock)
	if _p_ != nil {
		acquirep(_p_)
		execute(gp, false) // 有P,与当前M绑定,执行gp,进入调剂循环。
	}
	if _g_.m.lockedg != 0 {
		// Wait until another thread schedules gp and so m again.
		stoplockedm()
		execute(gp, false) // Never returns.
	}
	stopm()		// 没有新事情之前住手M的执行。睡眠事情线程。在获得P而且叫醒之后会继续执行
	schedule()	// 能走到这里说明M以获得P,而且被叫醒,可以寻找一个G,继续调剂了。
}

exitsyscall0 -> stopm

主要内容是将 M 放回 sched.midle,并通过futex系统挪用挂起线程。

func stopm() {
	_g_ := getg()

	if _g_.m.locks != 0 {
		throw("stopm holding locks")
	}
	if _g_.m.p != 0 {
		throw("stopm holding p")
	}
	if _g_.m.spinning {
		throw("stopm spinning")
	}

	lock(&sched.lock)
	mput(_g_.m)		// M 放回 sched.midle
	unlock(&sched.lock)
	notesleep(&_g_.m.park)	// notesleep->futexsleep->runtime.futex->futex系统挪用。
	noteclear(&_g_.m.park)
	acquirep(_g_.m.nextp.ptr())
	_g_.m.nextp = 0
}

总结

在系统挪用之前挪用:entersyscall

  • 更改P和G的状态为_Psyscall和_Gsyscall
  • 解绑P和M
  • 将P存入m.oldp

在系统挪用之后挪用:exitsyscall

  • exitsyscallfast:实验为当前M绑定一个P,乐成了会return退出exitsyscall。

    • 若是oldp相符条件则wirep
    • 否则实验获取任何空闲的P并与当前M绑定
  • exitsyscall0:进入调剂循环

    • 更改gp状态为_Grunnable
    • dropg解绑gp和M
    • 实验获取p,获取到则acquirep绑定P和M;execute进入调剂循环。
    • 未获取到则globrunqput将gp放入sched.runq;stopm将M放入sched.midle、挂起事情线程;此M被叫醒后schedule进入调剂循环。

不太适当的比喻

靠山设定

角色:家长(M)与屋子(P)和孩子们(G)。
规则:家长必须要在屋子里才气抚育孩子们(运行)。但屋子并不牢固属于某个家长,孩子也并不牢固属于某个家长。

出门狩猎:

家长张三要带着一个孩子(m.curg)小明出去狩猎(syscall),他们就离家出走(_Gsyscall/_Psyscall)了,家长和屋子就相互断了归属,然则他们还留着(m.oldp)屋子的地址(天字一号房)。

狩猎时代:

这时代其他没有屋子的家长(李四)看到天字一号没有家长,可能会占有这个屋子,而且抚育屋子里的孩子。

打完回家:

家长带小明狩猎回来后,若是天字一号没有被其他家长占有,那么继续原来的生涯(P和M绑定,P/G变为_Prunning/_Grunning)。
若是天字一号被李四占有,那么张三会寻找任何一个空闲屋子(可能李四也是这么丢的屋子吧)。继续原来的生涯。
然则,若是张三没有找到任何一个屋子,那么张三就要和小明分离了(dropg),小明被放到孤儿院(globrunqput)守候领养,张三被放在养老院(mput)睡觉(futex系统挪用)。

张三的运气:

可能有一天有屋子空出来了,张三被放在屋子里,然后叫醒,继续抚育孩子(schedule)。

,

Sunbet

Sunbet www.6358917.com Sunbet(www.sunbet.in)是进入Sunbet的主力站点。Sunbet开放Sunbet会员开户网址、Sunbet代理开户、Sunbet手机版下载、Sunbet电脑客户端下载等业务。

版权声明

本文仅代表作者观点,
不代表本站Allbet的立场。
本文系作者授权发表,未经许可,不得转载。

评论