c - C write call 和 Go syscall.Write 的区别

标签 c go system-calls

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. 何时以及如何编写 调用返回 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 可能返回 0 的原因是输入缓冲区可能为空。

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 长度写入有可能失败。如果失败,则返回 -1 并设置 errno。如果成功,则返回 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 已设置且返回值为 -1,或者 errno 未设置且返​​回值为 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?

一般来说,系统调用要么返回错误,要么成功。他们不会做两者的混合。查看其他手册页的返回值部分,您会发现它们与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 函数只是调用系统调用的 stub 。 Go 不调用这个 stub ,它直接调用系统调用,这意味着这里不涉及 C(好吧,直到你进入内核)。

手册页显示了 C stub 的调用约定和行为,write。 Go 选择在它自己的 stub syscall.Write 中复制该行为。实际的系统调用本身只有一个汇编语言接口(interface)。

关于c - C write call 和 Go syscall.Write 的区别,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52081841/

相关文章:

c - libuv 读取回调 uv_buf_t 清理

php - 在 Golang 中获取所有 Stripe 计划的数组

linux - find_task_by_vpid() 的替代方案

C exec/pipe/select 程序 - 缺少来自 child 的输入

c - 动态分配多维 float 组的正确方法是什么?瓦尔格林德错误

c - 多键快速排序崩溃

go - 在 HTML 中取消转义 css 输入

golang 转换包含 unicode 的字节数组

system-calls - 使用服务启动 Strace 守护进程

linux - 一些 linux 程序中奇怪的描述符关闭