标签:参数设置 linux系统 enter mic 存在 看到了 线程 round amp
概念:系统调用为用户态进程提供了硬件的抽象接口。并且是用户空间访问内核的唯一手段,除异常和陷入外,它们是内核唯一的合法入口。保证系统的安全和稳定。
调用号:在Linux中,每个系统调用被赋予一个独一无二的系统调用号。当用户空间的进程执行一个系统调用时,会使用调用号指明系统调用。
syscall指令:因为用户代码特权级较低,无权访问需要最高特权级才能访问的内核地址空间的代码和数据。所以需要特殊指令,在golang中是syscall。
x86-64中通过syscall指令执行系统调用的参数设置
给个简单的例子。
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 函数,但执行系统调用的防止并不知道它一个。它们的定义如下:
// 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,reentersyscall的主要功能:
本节及后面会涉及到一些之前分析过的函数,这里给出链接,就不重复分析了。
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--
}
主要功能是:
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,分为两种情况。
第一:如果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
}
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,继续调度了。
}
主要内容是将 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。
在系统调用之后调用:exitsyscall。
exitsyscallfast:尝试为当前M绑定一个P,成功了会return退出exitsyscall。
exitsyscall0:进入调度循环
角色:家长(M)与房子(P)和孩子们(G)。
规则:家长必须要在房子里才能抚养孩子们(运行)。但房子并不固定属于某个家长,孩子也并不固定属于某个家长。
家长张三要带着一个孩子(m.curg)小明出去打猎(syscall),他们就离家出走(_Gsyscall/_Psyscall)了,家长和房子就互相断了归属,但是他们还留着(m.oldp)房子的地址(天字一号房)。
这期间其他没有房子的家长(李四)看到天字一号没有家长,可能会占据这个房子,并且抚养房子里的孩子。
家长带小明打猎回来后,如果天字一号没有被其他家长占据,那么继续原来的生活(P和M绑定,P/G变为_Prunning/_Grunning)。
如果天字一号被李四占据,那么张三会寻找任何一个空闲房子(可能李四也是这么丢的房子吧)。继续原来的生活。
但是,如果张三没有找到任何一个房子,那么张三就要和小明分离了(dropg),小明被放到孤儿院(globrunqput)等待领养,张三被放在养老院(mput)睡觉(futex系统调用)。
可能有一天有房子空出来了,张三被放在房子里,然后唤醒,继续抚养孩子(schedule)。
标签:参数设置 linux系统 enter mic 存在 看到了 线程 round amp
原文地址:https://www.cnblogs.com/flhs/p/12709962.html