системный вызов
Оригинальный адрес:
концепция
Одна картинка стоит тысячи слов:
+ - - - - - - - - - - - - - - - User Mode - - - - - - - - - - - - - - - - - -
|
| Application syscall library
program /src/syscall |
|
|
| +-------------------+ +----------------------+
| | |Faccessat { | |
| | | | |
| | | runtime·Syscall6 { | |
| |... | | |
|syscall.Access( | | ... | |
| | path, mode) | | SYSCALL ----------+----------------+
|... | | ... <----------+----------+-----+--------+
| | | | return; | | |
| | | } | | | |
| | | |} | | |
+-------------------+ +----------------------+ | | |
| | |
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | ^
| |
switch to kernel mode |
+ - - - - - - - - - - - - - - - - Kernel Mode - - - - - - - - - - - - - - - - - - v |
| | |
| System call Trap handler | |
service routine | | |
| +------------------+ +-----------------------+ | |
|sys_faccessat() <-+-----------+ |system call: <-------+--------+-----+ |
| |{ | | | | |
| | | | | | |
| | | | | ... | |
| | | | | | |
| | ... | +-----------+---call sys_call_table | switch to user mode
| | | | | |
| | | +-----------+-> ... | |
| return error; --+-----------+ | | | |
| |} | | -------------------+----------->-----------+
+------------------+ +-----------------------+ |
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
Вход
системный вызов имеет следующие записи, вsyscall/asm_linux_amd64.s
середина.
func Syscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno)
func Syscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err syscall.Errno)
func RawSyscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno)
func RawSyscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err syscall.Errno)
Реализация этих функций - вся сборка.Согласно спецификации вызова системных вызовов Linux, нам нужно только передать параметры в регистр по очереди в сборке и вызвать инструкцию SYSCALL для входа в логику обработки ядра.После системного вызова выполняется, возвращаемое значение помещается в RAX. :
RDI | RSI | RDX | R10 | R8 | R9 | RAX |
---|---|---|---|---|---|---|
параметр один | второй параметр | параметр три | параметр четыре | параметр пять | Параметр шесть | Номер системного вызова/возвратное значение |
Единственная разница между Syscall и Syscall6 — входящие параметры:
// func Syscall(trap int64, a1, a2, a3 uintptr) (r1, r2, err uintptr);
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
// 0xfffffffffffff001 是 linux MAX_ERRNO 取反 转无符号,http://lxr.free-electrons.com/source/include/linux/err.h#L17
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
Между этими двумя функциями нет большой разницы, так почему бы не использовать одну? Личное предположение, все параметры функций Go передаются в стек, возможно, для экономии места в стеке. . Среда выполнения будет уведомлена перед обычной операцией системного вызова, а затем я выполню операцию системного вызова.runtime·entersyscall
, который вызывается при выходеruntime·exitsyscall
.
// 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 $0, R10
MOVQ $0, R8
MOVQ $0, R9
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
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 ok2
MOVQ $-1, r1+56(FP)
MOVQ $0, r2+64(FP)
NEGQ AX
MOVQ AX, err+72(FP)
RET
ok2:
MOVQ AX, r1+56(FP)
MOVQ DX, r2+64(FP)
MOVQ $0, err+72(FP)
RET
Разница между RawSyscall и Syscall также очень мала, просто среда выполнения не уведомляется при входе в Syscall и выходе, поэтому среда выполнения теоретически не имеет возможности запланировать p of m of g, поэтому, если пользовательский код использует RawSyscall для It можно заблокировать другие г, выполнив некоторые блокирующие системные вызовы.Ниже приведены оригинальные слова официальной разработки:
Yes, if you call RawSyscall you may block other goroutines from running. The system monitor may start them up after a while, but I think there are cases where it won't. I would say that Go programs should always call Syscall. RawSyscall exists to make it slightly more efficient to call system calls that never block, such as getpid. But it's really an internal mechanism.
RawSyscall предназначен только для сохранения стоимости двух вызовов функций во время выполнения при выполнении тех системных вызовов, которые не должны блокироваться.
vdso
vdso можно рассматривать как специальный вызов, при использовании которого нет перехода из пользовательского режима в режим ядра в начале этой статьи со ссылкой на ссылку:
Используется для выполнения определенных системных вызовов, снижения накладных расходов на системные вызовы. Некоторые системные вызовы не передают параметры ядру, а просто запрашивают у ядра данные, такие как getTimeOfDay(), ядро может записать систему в фиксированное место при обработке этой части системного вызова (Это действие обновления завершено ядром для завершения этого обновления каждый раз), а MMAP сопоставляется с пользовательским пространством. Это позволит быстрее избежать контекста традиционного системного вызова режима INT 0x80/syscall и переключения контекста пользовательского пространства.
// func gettimeofday(tv *Timeval) (err uintptr)
TEXT ·gettimeofday(SB),NOSPLIT,$0-16
MOVQ tv+0(FP), DI
MOVQ $0, SI
MOVQ runtime·__vdso_gettimeofday_sym(SB), AX
CALL AX
CMPQ AX, $0xfffffffffffff001
JLS ok7
NEGQ AX
MOVQ AX, err+8(FP)
RET
ok7:
MOVQ $0, err+8(FP)
RET
Система управления вызовом
Первый — это файл определения системного вызова:
/syscall/syscall_linux.go
Системные вызовы можно разделить на три категории:
- блокировка системного вызова
- неблокирующий системный вызов
- завернутый системный вызов
Вызов системы блокировки определяется следующим образом:
//sys Madvise(b []byte, advice int) (err error)
Неблокирующий системный вызов:
//sysnb EpollCreate(size int) (fd int, err error)
Затем на основе этих комментариев сценарий mksyscall.pl создает соответствующую реализацию для конкретной платформы. mksyscall.pl — это Perl-скрипт, и заинтересованные студенты могут просмотреть его самостоятельно, поэтому я не буду здесь вдаваться в подробности.
Взгляните на сгенерированные результаты блокирующих и неблокирующих системных вызовов:
func Madvise(b []byte, advice int) (err error) {
var _p0 unsafe.Pointer
if len(b) > 0 {
_p0 = unsafe.Pointer(&b[0])
} else {
_p0 = unsafe.Pointer(&_zero)
}
_, _, e1 := Syscall(SYS_MADVISE, uintptr(_p0), uintptr(len(b)), uintptr(advice))
if e1 != 0 {
err = errnoErr(e1)
}
return
}
func EpollCreate(size int) (fd int, err error) {
r0, _, e1 := RawSyscall(SYS_EPOLL_CREATE, uintptr(size), 0, 0)
fd = int(r0)
if e1 != 0 {
err = errnoErr(e1)
}
return
}
Очевидно, системный вызов с пометкой sys использует Syscall или Syscall6, а системный вызов с пометкой sysnb использует RawSyscall или RawSyscall6.
Как насчет обернутого системного вызова?
func Rename(oldpath string, newpath string) (err error) {
return Renameat(_AT_FDCWD, oldpath, _AT_FDCWD, newpath)
}
Может быть имя системного вызова не очень, или параметров слишком много, поэтому просто заворачиваем. Ничего особенного.
Syscall во время выполнения
В дополнение к упомянутым выше блокирующим, неблокирующим и обернутым системным вызовам, существуют также некоторые низкоуровневые системные вызовы, определенные в среде выполнения, которые не доступны пользователю.
Библиотека системных вызовов, предоставляемая пользователю, при использовании заставит goroutine и p войти в состояния Gsyscall и Psyscall соответственно. Но эти системные вызовы, инкапсулированные самой средой выполнения, не будут вызывать entersyscall и exitsyscall независимо от того, блокируются они или нет. Хотя это системный вызов «низкого уровня»,
Но суть системного вызова, доступного пользователю, одна и та же. Эти коды находятся вruntime/sys_linux_amd64.s
, чтобы привести конкретный пример:
TEXT runtime·write(SB),NOSPLIT,$0-28
MOVQ fd+0(FP), DI
MOVQ p+8(FP), SI
MOVL n+16(FP), DX
MOVL $SYS_write, AX
SYSCALL
CMPQ AX, $0xfffffffffffff001
JLS 2(PC)
MOVL $-1, AX
MOVL AX, ret+24(FP)
RET
TEXT runtime·read(SB),NOSPLIT,$0-28
MOVL fd+0(FP), DI
MOVQ p+8(FP), SI
MOVL n+16(FP), DX
MOVL $SYS_read, AX
SYSCALL
CMPQ AX, $0xfffffffffffff001
JLS 2(PC)
MOVL $-1, AX
MOVL AX, ret+24(FP)
RET
Вот список всех системных вызовов, определенных средой выполнения дополнительно:
#define SYS_read 0
#define SYS_write 1
#define SYS_open 2
#define SYS_close 3
#define SYS_mmap 9
#define SYS_munmap 11
#define SYS_brk 12
#define SYS_rt_sigaction 13
#define SYS_rt_sigprocmask 14
#define SYS_rt_sigreturn 15
#define SYS_access 21
#define SYS_sched_yield 24
#define SYS_mincore 27
#define SYS_madvise 28
#define SYS_setittimer 38
#define SYS_getpid 39
#define SYS_socket 41
#define SYS_connect 42
#define SYS_clone 56
#define SYS_exit 60
#define SYS_kill 62
#define SYS_fcntl 72
#define SYS_getrlimit 97
#define SYS_sigaltstack 131
#define SYS_arch_prctl 158
#define SYS_gettid 186
#define SYS_tkill 200
#define SYS_futex 202
#define SYS_sched_getaffinity 204
#define SYS_epoll_create 213
#define SYS_exit_group 231
#define SYS_epoll_wait 232
#define SYS_epoll_ctl 233
#define SYS_pselect6 270
#define SYS_epoll_create1 291
Теоретически эти системные вызовы не будут удалены планировщиком во время выполнения, поэтому горутина будет продолжать выполняться после успешного выполнения, в отличие от пользовательской горутины, если p будет удалена, она войдет в очередь ожидания.
И интерактивное расписание
Если он хочет и взаимодействовать с отправкой, он сообщил мне, что дружелюбно с помощью Syscall: Eventerscall, я ухожу с: Exitsyscall.
Таким образом, взаимодействие здесь относится к взаимодействию между кодом пользователя и планировщиком при использовании библиотеки системных вызовов.Системный вызов в среде выполнения не следует этому процессу..
entersyscall
// syscall 库和 cgo 调用的标准入口
//go:nosplit
func entersyscall() {
reentersyscall(getcallerpc(), getcallersp())
}
//go:nosplit
func reentersyscall(pc, sp uintptr) {
_g_ := getg()
// 需要禁止 g 的抢占
_g_.m.locks++
// entersyscall 中不能调用任何会导致栈增长/分裂的函数
_g_.stackguard0 = stackPreempt
// 设置 throwsplit,在 newstack 中,如果发现 throwsplit 是 true
// 会直接 crash
// 下面的代码是 newstack 里的
// if thisg.m.curg.throwsplit {
// throw("runtime: stack split at bad time")
// }
_g_.throwsplit = true
// Leave SP around for GC and traceback.
// 保存现场,在 syscall 之后会依据这些数据恢复现场
save(pc, sp)
_g_.syscallsp = sp
_g_.syscallpc = pc
casgstatus(_g_, _Grunning, _Gsyscall)
if _g_.syscallsp < _g_.stack.lo || _g_.stack.hi < _g_.syscallsp {
systemstack(func() {
print("entersyscall inconsistent ", hex(_g_.syscallsp), " [", hex(_g_.stack.lo), ",", hex(_g_.stack.hi), "]\n")
throw("entersyscall")
})
}
if atomic.Load(&sched.sysmonwait) != 0 {
systemstack(entersyscall_sysmon)
save(pc, sp)
}
if _g_.m.p.ptr().runSafePointFn != 0 {
// runSafePointFn may stack split if run on this stack
systemstack(runSafePointFn)
save(pc, sp)
}
_g_.m.syscalltick = _g_.m.p.ptr().syscalltick
_g_.sysblocktraced = true
_g_.m.mcache = nil
_g_.m.p.ptr().m = 0
atomic.Store(&_g_.m.p.ptr().status, _Psyscall)
if sched.gcwaiting != 0 {
systemstack(entersyscall_gcwait)
save(pc, sp)
}
_g_.m.locks--
}
Видно, что G, входящий в системный вызов, точно не будет вытеснен.
exitsyscall
// g 已经退出了 syscall
// 需要准备让 g 在 cpu 上重新运行
// 这个函数只会在 syscall 库中被调用,在 runtime 里用的 low-level syscall
// 不会用到
// 不能有 write barrier,因为 P 可能已经被偷走了
//go:nosplit
//go:nowritebarrierrec
func exitsyscall(dummy int32) {
_g_ := getg()
_g_.m.locks++ // see comment in entersyscall
if getcallersp(unsafe.Pointer(&dummy)) > _g_.syscallsp {
// throw calls print which may try to grow the stack,
// but throwsplit == true so the stack can not be grown;
// use systemstack to avoid that possible problem.
systemstack(func() {
throw("exitsyscall: syscall frame is no longer valid")
})
}
_g_.waitsince = 0
oldp := _g_.m.p.ptr()
if exitsyscallfast() {
if _g_.m.mcache == nil {
systemstack(func() {
throw("lost mcache")
})
}
// 目前有 p,可以运行
_g_.m.p.ptr().syscalltick++
// 把 g 的状态修改回 running
casgstatus(_g_, _Gsyscall, _Grunning)
// 垃圾收集未在运行(因为我们这段逻辑在执行)
// 所以清理掉 syscallsp 是安全的
_g_.syscallsp = 0
_g_.m.locks--
if _g_.preempt {
// 防止在 newstack 中清理掉 preemption 标记
_g_.stackguard0 = stackPreempt
} else {
// 否则恢复在 entersyscall/entersyscallblock 中破坏掉的正常的 _StackGuard
_g_.stackguard0 = _g_.stack.lo + _StackGuard
}
_g_.throwsplit = false
return
}
_g_.sysexitticks = 0
_g_.m.locks--
// 调用 scheduler
mcall(exitsyscall0)
if _g_.m.mcache == nil {
systemstack(func() {
throw("lost mcache")
})
}
// 调度器返回了,所以我们可以清理掉在 syscall 期间为垃圾收集器
// 准备的 syscallsp 信息了
// 需要一直等待到 gosched 返回,我们不确定垃圾收集器是不是在运行
_g_.syscallsp = 0
_g_.m.p.ptr().syscalltick++
_g_.throwsplit = false
}
Здесь также называются exitsyscallfastrfast и exitsyscall0.
exitsyscallfast
//go:nosplit
func exitsyscallfast() bool {
_g_ := getg()
// Freezetheworld sets stopwait but does not retake P's.
if sched.stopwait == freezeStopWait {
_g_.m.mcache = nil
_g_.m.p = 0
return false
}
// Try to re-acquire the last P.
if _g_.m.p != 0 && _g_.m.p.ptr().status == _Psyscall && atomic.Cas(&_g_.m.p.ptr().status, _Psyscall, _Prunning) {
// There's a cpu for us, so we can run.
exitsyscallfast_reacquired()
return true
}
// Try to get any other idle P.
oldp := _g_.m.p.ptr()
_g_.m.mcache = nil
_g_.m.p = 0
if sched.pidle != 0 {
var ok bool
systemstack(func() {
ok = exitsyscallfast_pidle()
})
if ok {
return true
}
}
return false
}
Короче говоря, попробуйте получить P, чтобы выполнить логику после Syscall. Если для нас нет P, то перейдите к exitsyscall0.
mcall(exitsyscall0)
Когда вызывается exitsyscall0, он переключается на стек g0.
exitsyscall0
// 在 exitsyscallfast 中吃瘪了,没办法,慢慢来
// 把 g 的状态设置成 runnable,先进 runq 等着
//go:nowritebarrierrec
func exitsyscall0(gp *g) {
_g_ := getg()
casgstatus(gp, _Gsyscall, _Grunnable)
dropg()
lock(&sched.lock)
_p_ := pidleget()
if _p_ == nil {
// 如果 P 被人偷跑了
globrunqput(gp)
} else if atomic.Load(&sched.sysmonwait) != 0 {
atomic.Store(&sched.sysmonwait, 0)
notewakeup(&sched.sysmonnote)
}
unlock(&sched.lock)
if _p_ != nil {
// 如果现在还有 p,那就用这个 p 执行
acquirep(_p_)
execute(gp, false) // Never returns.
}
if _g_.m.lockedg != 0 {
// 设置了 LockOsThread 的 g 的特殊逻辑
stoplockedm()
execute(gp, false) // Never returns.
}
stopm()
schedule() // Never returns.
}
entersyscallblock
Зная, что могу заблокировать, я сдал p напрямую.
// 和 entersyscall 一样,就是会直接把 P 给交出去,因为知道自己是会阻塞的
//go:nosplit
func entersyscallblock(dummy int32) {
_g_ := getg()
_g_.m.locks++ // see comment in entersyscall
_g_.throwsplit = true
_g_.stackguard0 = stackPreempt // see comment in entersyscall
_g_.m.syscalltick = _g_.m.p.ptr().syscalltick
_g_.sysblocktraced = true
_g_.m.p.ptr().syscalltick++
// Leave SP around for GC and traceback.
pc := getcallerpc()
sp := getcallersp(unsafe.Pointer(&dummy))
save(pc, sp)
_g_.syscallsp = _g_.sched.sp
_g_.syscallpc = _g_.sched.pc
if _g_.syscallsp < _g_.stack.lo || _g_.stack.hi < _g_.syscallsp {
sp1 := sp
sp2 := _g_.sched.sp
sp3 := _g_.syscallsp
systemstack(func() {
print("entersyscallblock inconsistent ", hex(sp1), " ", hex(sp2), " ", hex(sp3), " [", hex(_g_.stack.lo), ",", hex(_g_.stack.hi), "]\n")
throw("entersyscallblock")
})
}
casgstatus(_g_, _Grunning, _Gsyscall)
if _g_.syscallsp < _g_.stack.lo || _g_.stack.hi < _g_.syscallsp {
systemstack(func() {
print("entersyscallblock inconsistent ", hex(sp), " ", hex(_g_.sched.sp), " ", hex(_g_.syscallsp), " [", hex(_g_.stack.lo), ",", hex(_g_.stack.hi), "]\n")
throw("entersyscallblock")
})
}
// 直接调用 entersyscallblock_handoff 把 p 交出来了
systemstack(entersyscallblock_handoff)
// Resave for traceback during blocked call.
save(getcallerpc(), getcallersp(unsafe.Pointer(&dummy)))
_g_.m.locks--
}
У этой функции есть только один вызывающий объект, notesleepg, поэтому я не буду здесь вдаваться в подробности.
entersyscallblock_handoff
func entersyscallblock_handoff() {
handoffp(releasep())
}
Полегче. .
entersyscall_sysmon
func entersyscall_sysmon() {
lock(&sched.lock)
if atomic.Load(&sched.sysmonwait) != 0 {
atomic.Store(&sched.sysmonwait, 0)
notewakeup(&sched.sysmonnote)
}
unlock(&sched.lock)
}
entersyscall_gcwait
func entersyscall_gcwait() {
_g_ := getg()
_p_ := _g_.m.p.ptr()
lock(&sched.lock)
if sched.stopwait > 0 && atomic.Cas(&_p_.status, _Psyscall, _Pgcstop) {
_p_.syscalltick++
if sched.stopwait--; sched.stopwait == 0 {
notewakeup(&sched.stopnote)
}
}
unlock(&sched.lock)
}
Суммировать
Системный вызов, предоставленный пользователю, в основном уведомляет среду выполнения в форме entersyscall и exitsyscall.Когда системный вызов заблокирован, среда выполнения определяет, следует ли освободить P для другого использования M. Отмена привязки относится к отвязке между M и P. Если привязка не привязана, при возврате системного вызова этот g будет помещен в очередь выполнения runq.
При этом рантайм сохраняет свои привилегии, при выполнении собственной логики мой P не будет передан, что гарантирует, что эти системные вызовы, используемые собственным «нижним слоем» Go, могут быть обработаны сразу после возврата.
Таким образом, это тоже epollwait.Среда выполнения использует его и не может быть прервана другими.Используемый вами syscall.EpollWait, очевидно, не имеет этой привилегии.
использованная литература
-
the linux programming interface