概念:系統調用爲用戶態進程提供了硬件的抽象接口。而且是用戶空間訪問內核的惟一手段,除異常和陷入外,它們是內核惟一的合法入口。保證系統的安全和穩定。html
調用號:在Linux中,每一個系統調用被賦予一個獨一無二的系統調用號。當用戶空間的進程執行一個系統調用時,會使用調用號指明系統調用。linux
syscall指令:由於用戶代碼特權級較低,無權訪問須要最高特權級才能訪問的內核地址空間的代碼和數據。因此須要特殊指令,在golang中是syscall。golang
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 跟蹤獲得調用路徑:ui
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 函數。咱們忽略中間的具體執行邏輯。this
SYS_READ 定義的是 read 的系統調用號,定義在 syscall/zsysnum_linux_amd64.go。atom
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 的區別:只是參數個數的不一樣,其餘都相同。unix
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)。