Skip to content

Commit bcccb8e

Browse files
author
wziww
committed
RawSyscall Syscall 区别补充
1 parent c88e584 commit bcccb8e

File tree

1 file changed

+87
-1
lines changed

1 file changed

+87
-1
lines changed

syscall.md

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,12 +175,98 @@ ok2:
175175
RET
176176
```
177177

178-
RawSyscall 和 Syscall 的区别也非常微小,就只是在进入 Syscall 和退出的时候没有通知 runtime,这样 runtime 理论上是没有办法通过调度把这个 g 的 m 的 p 调度走的,所以如果用户代码使用了 RawSyscall 来做一些阻塞的系统调用,是有可能阻塞其它的 g 的,下面是官方开发的原话:
178+
golang 旧版本中 RawSyscall 和 Syscall 的区别也非常微小,就只是在进入 Syscall 和退出的时候没有通知 runtime,这样 runtime 理论上是没有办法通过调度把这个 g 的 m 的 p 调度走的,所以如果用户代码使用了 RawSyscall 来做一些阻塞的系统调用,是有可能阻塞其它的 g 的,下面是官方开发的原话:
179179

180180
> 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.
181181

182182
RawSyscall 只是为了在执行那些一定不会阻塞的系统调用时,能节省两次对 runtime 的函数调用消耗。
183183

184+
#### 新版本抢占式调度中的 RawSyscall 和 Syscall
185+
由于 `RawSyscall` 相较于 `Syscall` 缺少了 `runtime·entersyscall(SB)` 以及 `runtime·exitsyscall(SB)` 的调用,当 `g` 执行的是阻塞性质的系统调用的时候,当前 `g` 会维持 `running` 状态,runtime 系统监控在进行全局调度的时候一旦发现运行超过 10ms 的 `g` 就会执行抢占操作(1.14.3 版本, linux_amd64 下为例),通过发送信号量给 `g` 对应的线程,而由于线程在初始化的时候进行了信号量的监听以及设置了相应的 `sa_flags` 参数,会导致在收到信号量的时候对正在阻塞的系统调用产生中断,这种行为往往会给使用者带来意料之外的情况,以下通过一个简单的阻塞性系统调用案例来演示(futex):
186+
```go
187+
// futex
188+
package main
189+
190+
import (
191+
"fmt"
192+
"os"
193+
"syscall"
194+
"time"
195+
"unsafe"
196+
)
197+
198+
const (
199+
SYS_futex = 202
200+
_FUTEX_PRIVATE_FLAG = 128
201+
_FUTEX_WAIT = 0
202+
_FUTEX_WAKE = 1
203+
_FUTEX_WAIT_PRIVATE = _FUTEX_WAIT | _FUTEX_PRIVATE_FLAG
204+
_FUTEX_WAKE_PRIVATE = _FUTEX_WAKE | _FUTEX_PRIVATE_FLAG
205+
)
206+
207+
func main() {
208+
var futexVar uint32 = uint32(os.Getegid())
209+
futexVar++
210+
for i := 0; i < 3; i++ {
211+
go func() { // 启动 3 个线程进行阻塞系统调用
212+
fmt.Println("sleep start")
213+
type timespec struct {
214+
tv_sec int64
215+
tv_nsec int64
216+
}
217+
/*
218+
* 设置 10 秒唤醒 futex
219+
* 通过调节该参数大小可以测试:
220+
* 当执行时间小于 runtime 调度器的判定时间的时候,阻塞调用可正常进行
221+
*/
222+
var ns int64 = 1e9 * 10
223+
ts := timespec{
224+
tv_sec: ns / 1e9,
225+
tv_nsec: ns % 1e9,
226+
}
227+
// var ts timespec
228+
// ts.setNsec(ns)
229+
fmt.Println(syscall.RawSyscall6(
230+
SYS_futex, // trap AX 202
231+
uintptr(unsafe.Pointer(&futexVar)), // a1 DI 1
232+
uintptr(_FUTEX_WAIT), // a2 SI 0
233+
1, // a3 DX
234+
// futex 超时参数,经测试,在设置为 NULL 的情况下(永不超时)
235+
// 再起一个线程(下方注释部分代码)修改 futexVar 的值可以发现另一个系统调用异常,感兴趣的读者可自行测试验证
236+
uintptr(unsafe.Pointer(&ts)), // a4 R10
237+
0, // a5 R8
238+
0), // a6 R9
239+
)
240+
fmt.Println("sleep end")
241+
}()
242+
}
243+
// go func () {
244+
// select {
245+
// case <-time.After(time.Second):
246+
// futexVar = 2
247+
// }
248+
// }
249+
for {
250+
select {
251+
case <-time.After(time.Hour):
252+
}
253+
}
254+
}
255+
256+
```
257+
执行结果:
258+
```shell
259+
sleep start
260+
sleep start
261+
sleep start
262+
18446744073709551615 0 interrupted system call # 线程收到 SIGURG ,系统调用被信号中断
263+
sleep end
264+
18446744073709551615 0 interrupted system call
265+
sleep end
266+
18446744073709551615 0 interrupted system call
267+
sleep end
268+
```
269+
综上,在使用 `RawSyscall` 的时候需要自行根据 golang 版本确定是否符合你的语气
184270
## vdso
185271

186272
vdso 可以认为是一种特殊的调用,在使用时,没有本文开头的用户态到内核态的切换,引用一段参考资料:

0 commit comments

Comments
 (0)