如何检测有人在Linux中打开pty(伪终端)的从属端?

时间:2021-02-04 21:01:23

Having more than one process read from a serial device (/dev/ttyXX) makes it so that both processes can't get all of the data -- the data will be split between them in some way. I'd like to write a program that reads from a serial device, creates several master/slave pty pairs, and then allows programs that were made to read from the serial device to instead read from the ptys so that all reading processes receive the data from the serial device and have the ptys act like the serial device in the sense that when they start reading from the pty they get only the most recent data. In other words, you won't get any data that was written before you started to read (it's my experience that this is how /dev/ttyXX devices work, or at least the RS-232 anemometer I'm reading from). Named pipes can mimic these semantics by trapping SIGPIPE to determine that there is no reader and thus we can choose not to write to that particular named pipe. However, some binaries that were written to use terminals may fail when talking to named pipes, as checks for isatty() and the errno condition on calls like tcsetattr() can cause failing conditions. The key here is to be able to use existing binaries that were written for a terminal.

从串行设备(/ dev / ttyXX)读取多个进程使得两个进程都无法获取所有数据 - 数据将以某种方式在它们之间进行分割。我想编写一个从串行设备读取的程序,创建几个主/从pty对,然后允许从串行设备读取的程序从ptys读取,以便所有读取进程接收数据从串行设备开始,ptys就像串行设备一样,当他们从pty开始读取时,他们只得到最新的数据。换句话说,在开始阅读之前你不会得到任何数据(这是我的经验,这就是/ dev / ttyXX设备的工作方式,或者至少是我正在阅读的RS-232风速计)。命名管道可以通过捕获SIGPIPE来模仿这些语义,以确定没有读取器,因此我们可以选择不写入该特定命名管道。但是,一些写入使用终端的二进制文件在与命名管道通信时可能会失败,因为对isatty()的检查和对tcsetattr()等调用的错误条件可能会导致失败的情况。这里的关键是能够使用为终端编写的现有二进制文件。

So, if I can detect when the slave side of the pty is opened for reading, this should give me roughly the same semantics as there being no SIGPIPE in the named pipe case. I notice that HP-UX has TIOCTRAP as an ioctl() command which seems to do exactly what I want, but sadly it is not available on Linux.

因此,如果我能够检测到pty的slave端何时打开以进行读取,那么这应该给出与命名管道情况中没有SIGPIPE大致​​相同的语义。我注意到HP-UX将TIOCTRAP作为ioctl()命令,它似乎完全符合我的要求,但遗憾的是它在Linux上不可用。

I've been reading references for days and the number of options for this type of thing is staggering. The answer might lie in the terminal settings, blocking/non-blocking behavior, setting buffer sizes somewhere, conditions reported from poll()/select(), or some combination. I can't seem to find anything, though. I'm wondering if it's possible that I need to write my own device driver, but it seems like I should be able to do this without going that far.

我已经阅读了几天的参考文献,这种类型的东西的选项数量是惊人的。答案可能在于终端设置,阻塞/非阻塞行为,在某处设置缓冲区大小,从poll()/ select()报告的条件,或某些组合。但我似乎找不到任何东西。我想知道是否有可能我需要编写自己的设备驱动程序,但似乎我应该能够做到这一点而不用那么远。

So, for clarification:
- The question is: How can I detect when someone opens the slave side of a pty (pseudo-terminal) in Linux?
- I want a reader opening the slave side of the pty to receive data written strictly after the reader opens the pty (if my multi-writing process just writes data for a while before the reader opens the slave side, the data will buffer up and eventually the writer will block and the slave reader, upon opening, will immediately get all the buffered data -- this is not desirable as I want it to get only data generated in the immediate temporal vicinity)
- It must be a pty, not a named pipe, socket, etc, as isatty() and tcsetattr(), etc need to be OK so that existing binaries work

因此,为了澄清: - 问题是:如何检测有人在Linux中打开pty(伪终端)的从属端? - 我希望读者打开pty的slave端接收读取器打开pty后严格写入的数据(如果我的多次写入过程只是在读取器打开从属端之前写入数据一段时间,数据将缓冲并且最终编写器将阻塞,并且从属读取器在打开时将立即获得所有缓冲数据 - 这是不可取的,因为我希望它只获得在紧邻时间附近生成的数据) - 它必须是一个pty,而不是命名管道,套接字等,因为isatty()和tcsetattr()等需要正常,以便现有的二进制文件工作

2 个解决方案

#1


10  

The reason you can't find this is because there's no documented interface specifically to allow it. However, there is a trick that allows you to do it. After opening the pseudo-terminal master (assumed here to be file descriptor ptm), you open and immediately close the slave side:

你找不到这个的原因是因为没有特定的文档接口来允许它。但是,有一个技巧可以让你这样做。打开伪终端主站(假设这里是文件描述符ptm)后,打开并立即关闭从站侧:

close(open(ptsname(ptm), O_RDWR | O_NOCTTY));

This sets the HUP flag on the tty master. You now poll the HUP flag regularly with poll() (say, whenever data comes in from your data source):

这会在tty主服务器上设置HUP标志。现在,您可以使用poll()定期轮询HUP标志(例如,每当数据来自您的数据源时):

struct pollfd pfd = { .fd = ptm, .events = POLLHUP };
poll(&pfd, 1, 10 /* or other small timeout */);

if (!(pfd.revents & POLLHUP))
{
    /* There is now a reader on the slave side */
}

If the reader ever goes away, POLLHUP will be set again.

如果读者消失,将再次设置POLLHUP。

In your case, you probably don't even need to remember from one loop to the next whether a given pty has a reader - just block on read() on your data source, then when data is available, simultaneously poll() all of your master ttys and send the data to any of them that do not have POLLHUP set.

在你的情况下,你可能甚至不需要记住从一个循环到下一个循环是否给定的pty有一个阅读器 - 只是阻塞数据源上的read(),然后当数据可用时,同时poll()所有你的主人ttys并将数据发送给没有设置POLLHUP的任何人。

#2


3  

Add an inotify watch on the slave pty and poll on that. You can get an inotify event on open. You can then poll on the inotify file descriptor and the master pty file descriptor. You can get an inotify event for open (IN_OPEN). This will unblock the poll when the slave side is opened.

在slave pty上添加一个inotify监视并对其进行轮询。您可以在打开时获得inotify事件。然后,您可以在inotify文件描述符和主pty文件描述符上进行轮询。您可以获得open(IN_OPEN)的inotify事件。这将在从属侧打开时取消阻止轮询。

#1


10  

The reason you can't find this is because there's no documented interface specifically to allow it. However, there is a trick that allows you to do it. After opening the pseudo-terminal master (assumed here to be file descriptor ptm), you open and immediately close the slave side:

你找不到这个的原因是因为没有特定的文档接口来允许它。但是,有一个技巧可以让你这样做。打开伪终端主站(假设这里是文件描述符ptm)后,打开并立即关闭从站侧:

close(open(ptsname(ptm), O_RDWR | O_NOCTTY));

This sets the HUP flag on the tty master. You now poll the HUP flag regularly with poll() (say, whenever data comes in from your data source):

这会在tty主服务器上设置HUP标志。现在,您可以使用poll()定期轮询HUP标志(例如,每当数据来自您的数据源时):

struct pollfd pfd = { .fd = ptm, .events = POLLHUP };
poll(&pfd, 1, 10 /* or other small timeout */);

if (!(pfd.revents & POLLHUP))
{
    /* There is now a reader on the slave side */
}

If the reader ever goes away, POLLHUP will be set again.

如果读者消失,将再次设置POLLHUP。

In your case, you probably don't even need to remember from one loop to the next whether a given pty has a reader - just block on read() on your data source, then when data is available, simultaneously poll() all of your master ttys and send the data to any of them that do not have POLLHUP set.

在你的情况下,你可能甚至不需要记住从一个循环到下一个循环是否给定的pty有一个阅读器 - 只是阻塞数据源上的read(),然后当数据可用时,同时poll()所有你的主人ttys并将数据发送给没有设置POLLHUP的任何人。

#2


3  

Add an inotify watch on the slave pty and poll on that. You can get an inotify event on open. You can then poll on the inotify file descriptor and the master pty file descriptor. You can get an inotify event for open (IN_OPEN). This will unblock the poll when the slave side is opened.

在slave pty上添加一个inotify监视并对其进行轮询。您可以在打开时获得inotify事件。然后,您可以在inotify文件描述符和主pty文件描述符上进行轮询。您可以获得open(IN_OPEN)的inotify事件。这将在从属侧打开时取消阻止轮询。