C写调用和Go syscall.Write之间的区别

时间:2018-08-29 16:06:21

标签: c go system-calls

syscall write返回-1,而设置errno的情况很简单。如果C errno调用返回零或正值,我对write的状态感兴趣。如果syscall.Write在任何情况下都不为零,则Go中的包装器err仅返回errno,这还包括write调用返回正数的情况。

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

但是,man page of C write call粗略地描述了errno 也可以设置,但如果我们写零长度缓冲区而不进行任何详细说明,则可以未指定。

因此,以下情况似乎不清楚:

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

我认为以上描述指出了C write调用和Go syscall.Write之间的区别,这对于开发人员来说尚不清楚,这是我的想法:

根据手册页,在对文件和非阻塞套接字的C write调用中明确定义了返回零,但不清楚阻塞套接字是否存在非错误条件,这将导致write()没有阻塞,返回0,并且(大概)在重试后可能会成功。

Indeed 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
}

我的怀疑有什么不对吗?

1 个答案:

答案 0 :(得分:6)

对于write,只有两种情况需要考虑:

  1. 如果失败,则结果为-1,并且设置了errno
  2. 如果成功,则结果为0或更大,并且未设置errno

没有其他情况可以考虑,除非您对历史上的Unix实现感兴趣(请参阅:Is a return value of 0 from write(2) in C an error?)。

write返回0的原因是因为输入缓冲区可能为空。

  

但是,C write调用的手册页大致描述了errno也可以设置,但是如果我们写零长度缓冲区而不进行任何详细说明,则未指定。

这意味着0长度的写入可能会失败。如果失败,则返回-1并设置errno。如果成功,则返回0,并且不设置errno。手册页中仅提到了这种行为,因为人们可能会发现0长度的写入可能会失败。

  

如果errno调用返回文件,非阻塞套接字或阻塞套接字的0,则write的状态是什么?

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

  

write何时以及如何调用返回0而errno返回0?

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

  

如果errno呼叫返回正值,write的状态如何?会否定?

将不会设置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存根write的调用约定和行为。 Go选择将其行为复制到自己的存根syscall.Write中。实际的系统调用本身仅具有汇编语言界面。