C write call和Go syscall.Write之间的区别

问题描述 投票:1回答:1

系统调用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 ,并且(可能)如果重试可能会在以后成功。

确实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
}

我怀疑有什么不对吗?

c go system-calls
1个回答
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 stub,write的调用约定和行为。 Go选择在自己的存根中复制该行为,syscall.Write。实际的系统调用本身只有一个汇编语言接口。

© www.soinside.com 2019 - 2024. All rights reserved.