【问题标题】:Serial socket - ignore POLLHUP received in non-canonical mode?串行套接字 - 忽略在非规范模式下收到的 POLLHUP?
【发布时间】:2021-11-03 20:56:09
【问题描述】:

我有一个通过 UART 连接到微控制器的 Raspberry Pi。 RPI 上的代码正在尝试读取传入的非规范 UART 数据,但随机接收到POLLHUP。我已经能够通过关闭并重新打开文件来恢复,但这并不理想。

有没有办法在 Linux 中禁用 termios 的断开连接检测行为?我不确定为什么首先要提出POLLHUP。我怀疑尽管我致电cfmakeraw(),某些控制字符仍在被解释。电缆不太可能是问题,因为规范调试输出工作正常(诚然,通过不同的引脚,但相同的波特和相同类型的电缆)。

示例代码,设置:

bool UartSocket::setup()
{
    int fd = ::open("/dev/serial0", O_RDWR | O_NOCTTY | O_NONBLOCK);
    if (fd == 0)
    {
        return false;
    }

    struct termios portSettings;
    ::memset(&portSettings, 0, sizeof(portSettings));
    if (m_rSyscalls.tcgetattr(fd, &portSettings) != 0)
    {
        m_logger.error("tcgetattr() failed, errno = %d.", errno);
        return false;
    }
    m_rSyscalls.cfsetispeed(&portSettings, 115200);
    m_rSyscalls.cfsetospeed(&portSettings, 115200);
    cfmakeraw(&portSettings);

    // Fiddling with more settings out of desperation
    portSettings.c_iflag &= ~IGNBRK; // disable break processing
    portSettings.c_lflag &= ~ICANON;
    portSettings.c_cc[VEOF] = 0;

    if (m_rSyscalls.tcsetattr(fd, TCSANOW, &portSettings) != 0)
    {
        m_logger.error("tcsetattr() failed, errno = %d.", errno);
        return false;
    }

    // Prepare to poll on recv() calls
    m_pollfd.fd = fd;
    m_pollfd.events = POLLIN;

    return true;
}

示例代码,Rx:

ssize_t UartSocket::recv(char* buf, size_t maxRead)
{
    ssize_t readResult = -1;

    int pollResult = ::poll(&m_pollfd, 1, 1000);
    if (pollResult > 0)
    {
        if (m_pollfd.revents & POLLERR)
        {
            int error = 0;
            socklen_t errlen = sizeof(error);
            if (getsockopt(
                        fd,
                        SOL_SOCKET,
                        SO_ERROR,
                        static_cast<void*>(&error),
                        &errlen))
            {
                m_logger.error(
                        "getsockopt failed when trying to diagnose an error.");
            }

            m_logger.error(
                    "Error on uart %s. Error = %d, len = %u.",
                    m_rConfig.getPath().c_str(),
                    error,
                    errlen);
            return -1;
        }

        if (m_pollfd.revents & POLLIN)
        {
            readResult = ::read( //
                    fd,
                    buf,
                    maxRead);
            m_logger.info("readResult = %d.", readResult);
            if (readResult > 0)
            {
                 // Party, we are happy
                 return readResult;
            }
            else if (readResult == 0)
            {
                // empty read..no-op
                m_logger.dump("Got an empty UART read.");
            }
            else
            {
                if (errno == EAGAIN)
                {
                    // No data was available to read; do nothing.
                    readResult = 0;
                    m_logger.dump("Got an empty UART read.");
                }
                else
                {
                    m_logger.error(
                            "Failure reading uart %s, errno = %d.",
                            m_rConfig.getPath().c_str(),
                            errno);
                }
            }
        }

        // We wait for the buffer to empty before handling any hangups
        if ((m_pollfd.revents & POLLHUP) && (readResult == 0))
        {
            m_logger.error("Hangup on uart %s.", m_rConfig.getPath().c_str());
            reopen(); // closes the fd, reopens it and repeats the termios setup
        }
    }
    else if (pollResult == 0)
    {
        // No data was available to read; do nothing.
        readResult = 0;
        m_logger.dump("Got an empty UART poll.");
    }
    else
    {
        m_logger.error("Failure polling uart 0, errno = %d.", errno);
        readResult = -1;
    }
    return readResult;
}

TL;DR:上面的代码有一个分支,它通过关闭和重新打开串行设备来处理POLLHUP。我正在与一个发送原始字节的设备交谈,如果 Linux 中的 termios 在 POLLHUP 的情况下不会使文件描述符不可用,我会更喜欢它。理想情况下,如果控制字符是控制字符,它们也应该完全忽略导致此问题的任何控制字符。有没有办法做到这一点?

【问题讨论】:

  • POLLHUP 问题的简单解决方案是不使用 poll()。您有一个事件驱动的多任务操作系统(例如,使用硬件中断),但您的程序通过使用非阻塞模式和轮询系统接收缓冲区来浪费 CPU 周期来抵消这种情况。 “出于绝望而摆弄更多设置” -- termios 原始模式的基本配置位于this answer。顺便说一句,在 Linux 中,您正在访问一个 串行终端,它位于 UART 上方几层。
  • @sawdust 我只使用poll() 作为保护,所以我没有永久阻塞的、不可中止的线程卡在read() 中。但我会尝试一下,如果它有效,那就太好了。
  • @sawdust 我使用了你的代码,它让我更接近问题 - 阻塞读取有时返回 0,这似乎意味着 EOF。我的初始化现在与您的建议相同,但以防万一明天我会尝试发送 \x04\n 以查看我的“原始”模式是否对 EOF 做出反应。如果事实证明这不是问题,您是否猜测还有什么可能导致原始 tty 上的阻塞读取返回 0?
  • "阻塞读取有时会返回 0" -- 那你就没有忠实地使用我的示例代码。 VMIN>0 和 VTIME>0 不会返回 0。原始模式忽略 VEOF 字符。发布您修改后的代码以供审查。还要研究stackoverflow.com/questions/25996171/… 注意非阻塞模式会导致 VMIN 和 VTIME 被忽略。
  • 经过反复试验,结果证明我设置的波特率不正确。我仍然有一些错误(可能是为了将来的问题..),但修复波特率解决了 POLLHUP。感谢您对 @sawdust 的关注,您的代码真的很有帮助。

标签: linux uart termios


【解决方案1】:

POLLHUP 问题已通过正确设置波特率得到解决。

我的原始代码调用了cfsetispeed(&amp;portSettings, 115200);。这是错误的,B115200 需要改为传递。 B115200 是一个常量,通常会解析为不可预测的东西 (example)。

我建议不要从我的代码中复制,而是使用this example 进行基本的原始 tty 设置。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-12-14
    • 2021-09-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-04
    • 1970-01-01
    相关资源
    最近更新 更多