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 也可能被设置但未指定。

因此,以下案例似乎不清楚:

  1. 如果write调用为文件,非阻塞套接字或阻塞套接字返回0,则errno的状态是什么?
  2. 何时以及如何write调用返回0并且errno不为0?
  3. 如果write调用返回正数,则errno的状态是什么? 它会消极吗?
  4. 有没有其他系统调用可能会遇到相同的情况?

我认为上面的描述指出了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. 如果失败,则结果为-1并errno
  2. 如果成功,则结果为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复制该行为。 实际的系统调用本身只有一个汇编语言接口。