Pty 没有关闭,尽管调用了 close() 系统调用

Noa*_*ick 7 linux terminal go linux-kernel pty

我正在实现 ssh 服务器的功能,因此根据 shell 请求,我打开一个 pty-tty 对。
一个片段:

import (
    "github.com/creack/pty"
    ...
)

func attachPty(channel ssh.Channel, shell *exec.Cmd) {
    mypty, err := pty.Start(shell)
    go func() {
        io.Copy(channel, mypty) // (1) ; could also be substituted with read() syscall, same problem
    }
    go func() {
        io.Copy(mypty, channel) // (2) - this returns on channel exit with eof, so let's close mypty
        if err := syscall.Close(int(mypty.Fd())); err != nil {
            fmt.Printf("error closing fd") // no error is printed out, /proc/fd shows it's successfuly closed
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

一旦 ssh 通道关闭,我就关闭 pty。我预期的行为是它应该向 shell 发送 SIGHUP。

如果我注释掉(1)副本(src:mypty,dst:channel),它就可以了!
但是 - 当它没有被注释掉时:

  • 副本(1)不返回,这意味着read系统调用mypty仍然阻塞,并且不返回 eof => 主设备没有关闭?
  • shell 没有收到 SIGHUP

我不确定为什么如果我注释掉(1)它有效的副本,也许内核引用会对读取进行计数?

我的线索:

去注释:

  • 我直接关闭 fd,因为否则使用通常的方法os.File.close()实际上并不会因为某种原因关闭 fd,它会保持打开状态/proc/<pid>/fd

  • (1)用直接系统调用替换副本read将导致相同的结果

谢谢你!

Nic*_*hen 0

我自己也遇到过这个问题,并花了一些时间进行调查。

事实证明,这是 Go 运行时的故意行为。具体来说,它可以防止文件在被另一个 goroutine 读取时被关闭。引入这一点是为了缓解竞争条件,即如果文件描述符被另一个 goroutine 关闭并重用,则 goroutine 可能会从错误的文件中读取。

对于 SSHD 用例,我没有好的解决方案。我稍后可能会更新这个答案。您自己想出了一些解决方法吗?