C write call和Go syscall.Write之间的区别
syscall write
返回-1并设置errno
是一个简单的例子。 如果C write
调用返回零或正数,我感兴趣的是errno
的状态。 如果errno
对于任何情况都不为零,则Go中的包装器syscall.Write
只返回err
,其中还包括write
调用返回正数的情况。
https://github.com/golang/go/blob/3cb64ea39e0d71fe2af554cbf4e99d14bc08d41b/src/syscall/zsyscall_linux_386.go#L1007
但是,如果我们编写零长度缓冲区而不解释任何细节,则C write
调用的手册页大致描述了errno
也可能被设置但未指定。
因此,以下案例似乎不清楚:
- 如果
write
调用为文件,非阻塞套接字或阻塞套接字返回0,则errno
的状态是什么? - 何时以及如何
write
调用返回0并且errno
不为0? - 如果
write
调用返回正数,则errno
的状态是什么? 它会消极吗? - 有没有其他系统调用可能会遇到相同的情况?
我认为上面的描述指出了C write
调用和Go syscall.Write
之间的区别,这对开发人员来说还不清楚,以下是我的想法:
根据手册页,在C write
调用文件和非阻塞套接字中明确定义了返回零,但是不清楚是否存在阻塞套接字的非错误条件,这会导致write()
无阻塞,如果重试,则返回0,并且(可能)稍后可能成功。
确实Go直接包装系统调用write
。 但是,以下代码片段似乎不安全,因为written
等于零的情况可能会触发err
但我们不想破坏循环:
func writeAll(fd int, buffer []byte) bool { length := len(buffer) for length > 0 { written, err := syscall.Write(fd, buffer) if err != nil { // here return false } length -= written buffer = buffer[written:] } return true }
我怀疑有什么不对吗?
使用write
,只有两种情况需要考虑:
- 如果失败,则结果为-1并
errno
。 - 如果成功,则结果为0或更大,并且未设置
errno
。
除非您对历史Unix实现感兴趣,否则没有其他情况需要考虑(请参阅: C中的write(2)的返回值是0是错误吗? )。
write
可能返回0的原因是输入缓冲区可能为空。
但是,如果我们编写零长度缓冲区而不解释任何细节,则C
write
调用的手册页大致描述了errno
也可能被设置但未指定。
所有这些意味着0长写入可能会失败。 如果失败,则返回-1并设置errno
。 如果成功,则返回0并且不设置errno
。 这与任何其他写操作的行为相同,它只是在手册页中提到的,因为人们可能会发现0长度写入可能会失败令人惊讶。
如果
write
调用为文件,非阻塞套接字或阻塞套接字返回0,则errno
的状态是什么?
在这种情况下,没有设置errno
,因为write
没有失败。 仅当输入缓冲区为零字节时才会发生这种情况。
何时以及如何
write
调用返回0并且errno
不为0?
这不会发生。 errno
并且返回值为-1,或者未设置errno
且返回值为0或更大。
如果
write
调用返回正数,则errno
的状态是什么? 它会消极吗?
不会设置errno
值。 它将具有与write
调用之前相同的值。
有没有其他系统调用可能会遇到相同的情况?
通常,系统调用将返回错误, 否则它们将成功。 他们不会做两者的混合。 查看其他手册页的“返回值”部分,您将看到它们与write
大致相同。
码
这段代码很安全。
func writeAll(fd int, buffer []byte) bool { length := len(buffer) for length > 0 { written, err := syscall.Write(fd, buffer) if err != nil { // here return false } length -= written buffer = buffer[written:] } return true }
请注意,它有点多余,我们可以这样做:
func writeAll(fd int, buf []byte) bool { for len(buf) > 0 { n, err := syscall.Write(fd, buf) if err != nil { return false } buf = buf[n:] } return true }
关于C的说明
从技术上讲, write
既是系统调用又是C函数(至少在许多系统上)。 但是,C函数只是一个调用系统调用的存根。 Go不会调用此存根,它会直接调用系统调用,这意味着此处不涉及C(嗯,直到进入内核)。
手册页显示了C stub, write
的调用约定和行为。 Go选择在自己的stub, syscall.Write
复制该行为。 实际的系统调用本身只有一个汇编语言接口。