C 写调用和 Go 之间的差异 syscall.Write

Differences between C write call and Go syscall.Write

syscall write returns -1 并设置 errno 是一个微不足道的案例。如果 C write 调用返回零或正数,我对 errno 的状态感兴趣。 Go 中的包装器 syscall.Write 只是 returns err 如果 errno 在任何情况下都不为零,这也包括 write 调用 returns 的情况阳性。

https://github.com/golang/go/blob/3cb64ea39e0d71fe2af554cbf4e99d14bc08d41b/src/syscall/zsyscall_linux_386.go#L1007

然而,man page of C write call大致描述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 实现感兴趣(参见:Is a return value of 0 from write(2) in C an error?)。

write可能return0的原因是输入缓冲区可能为空。

However, the man page of C write call roughly describes errno may also be set but unspecified if we write zero length buffer without explaining any detail.

这意味着 0 长度写入可能会失败。如果失败,它 returns -1 并设置 errno。如果成功,它 returns 0 并且不设置 errno。这与任何其他写入的行为相同,它只是在手册页中提到,因为人们可能会惊讶于 0 长度写入可能会失败。

What is the status of errno if write call returning 0 for a file, a non-blocking socket, or a blocking socket?

在这种情况下,errno没有设置,因为write没有失败。只有当输入缓冲区为零字节时才会发生这种情况。

When and how write call returning 0 and errno is not 0?

这不会发生。 errno 已设置且 return 为 -1,或者 errno 未设置且 return 为 0 或更大。

What is the status of errno if write call returning positive? Will it be negative?

errno 值将不会被设置。它将具有与 write 调用之前相同的值。

Is there any other syscall may encounter the same situation?

一般来说,系统调用会return一个错误或者它们会成功。他们不会做两者的混合。查看其他手册页的 Return 值部分,您会发现它们大部分与 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 存根 write 的调用约定和行为。 Go 选择在自己的存根 syscall.Write 中复制该行为。实际系统调用本身只有汇编语言接口。