【问题标题】:Differences between C write call and Go syscall.WriteC write call 和 Go syscall.Write 的区别
【发布时间】:2019-02-04 12:03:17
【问题描述】:

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

但是,man page of C write call 大致描述了errno 可能如果我们写零长度缓冲区而不解释任何细节,也可以设置但未指定。

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

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

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

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

Go 确实直接包装了系统调用write。但是,下面的代码 sn-p 似乎并不安全,因为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
}

我的怀疑有什么不对吗?

【问题讨论】:

  • 这里要注意的一点是,库函数永远不会重置/清除 errno - 这是调用者的责任。
  • @Antti:此外,在 Go 中,库函数根本不会触及 errno。

标签: c go system-calls


【解决方案1】:

使用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 长度写入可能会失败。

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

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

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

这不会发生。要么设置了errno,返回值为-1,要么没有设置errno,返回值为0或更大。

如果write 调用返回肯定,errno 的状态是什么?会不会是负数?

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

是否有任何其他系统调用可能遇到相同的情况?

一般来说,系统调用要么返回错误,要么成功。他们不会将两者混合使用。查看其他手册页的 Return Value 部分,您会发现它们与 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 中复制该行为。实际的系统调用本身只有一个汇编语言接口。

【讨论】:

  • 非常感谢您的体面回答,它解决了我对 write call 的所有疑问 :)
  • 也许是一个后续问题,如果 writeAllwrite 调用被 CPU 中断并且另一个系统调用被安排(可能在另一个 goroutine 中调用)并且失败,是否有可能在下一个 write 调用中接收到从另一个 goroutine 触发的 errno
  • 不,这是不可能的。
  • 您是否正在检查读取是否完成?没有源代码,我无话可说。
猜你喜欢
  • 2011-10-22
  • 1970-01-01
  • 2019-05-07
  • 2015-04-21
  • 2010-10-06
  • 2013-07-10
  • 2014-01-31
  • 1970-01-01
  • 2016-12-27
相关资源
最近更新 更多