【问题标题】:How to use boost::asio with Linux GPIOs如何在 Linux GPIO 中使用 boost::asio
【发布时间】:2015-08-08 03:39:20
【问题描述】:

我有一个使用 boost::asio 进行异步输入/输出的单线程 Linux 应用程序。现在我需要扩展这个应用程序以读取/sys/class/gpio/gpioXX/value 上的 GPIO 输入。

可以在边沿触发的 GPIO 输入上使用 boost::asio::posix::stream_descriptor 吗?

我将 GPIO 输入配置如下:

echo XX >/sys/class/gpio/export
echo in >/sys/class/gpio/gpioXX/direction
echo both >/sys/class/gpio/gpioXX/edge

我设法编写了一个基于 epoll 的测试应用程序,该应用程序会阻塞 GPIO 文件描述符,直到 GPIO 信号发生变化,但 boost::asio 似乎无法正确阻塞。对 boost::asio::async_read 的调用总是立即使用 EOF 调用处理程序(当然只在 io_service.run() 内),或者 - 如果文件指针被设置回 - 2 字节数据。

我不是boost::asio 内部的专家,但原因可能是boost::asio epoll 反应器是水平触发的,而不是posix::stream_descriptor 的边缘触发?

这是我的代码:

#include <fcntl.h>

#include <algorithm>
#include <iterator>
#include <stdexcept>

#include <boost/asio.hpp>

boost::asio::io_service io_service;
boost::asio::posix::stream_descriptor sd(io_service);
boost::asio::streambuf streambuf;

void read_handler(const boost::system::error_code& error, std::size_t bytes_transferred)
{
    if (error.value() == boost::asio::error::eof) {
        // If we don't reset the file pointer we only get EOFs
        lseek(sd.native_handle(), 0, SEEK_SET);
    } else if (error)
        throw std::runtime_error(std::string("Error ") + std::to_string(error.value()) + " occurred (" + error.message() + ")");

    std::copy_n(std::istreambuf_iterator<char>(&streambuf), bytes_transferred, std::ostreambuf_iterator<char>(std::cout));
    streambuf.consume(bytes_transferred);
    boost::asio::async_read(sd, streambuf, &read_handler);
}

int main(int argc, char *argv[])
{
    if (argc != 2)
        return 1;

    int fd = open(argv[1], O_RDONLY);
    if (fd < 1)
        return 1;

    try {
        sd.assign(fd);
        boost::asio::async_read(sd, streambuf, &read_handler);
        io_service.run();
    } catch (...) {
        close(fd);
        return 1;
    }

    close(fd);
    return 0;
}

【问题讨论】:

  • 好吧,谢谢你的链接,但正如在 grep 输出中看到的那样,EPOLLET 在任何情况下都没有使用。
  • 如何“在任何情况下都不使用”? EPOLLET在各个地方都有使用。
  • 抱歉,任何 -> 每个 ;)

标签: c++ boost-asio gpio epoll


【解决方案1】:

据我所知,Boost.Asio 无法实现这种特殊行为。虽然内核将 procfs 和 sysfs 上的某些文件标记为可轮询,但它们不提供 boost::asio::posix::stream_descriptor 及其操作所期望的类似流的行为。

Boost.Asio 的 epoll 反应器是边缘触发的(参见 Boost.Asio 1.43 revision history notes)。在某些条件下1,Boost.Asio 会在初始化函数的上下文中尝试 I/O 操作(例如async_read())。如果 I/O 操作完成(成功或失败),则完成处理程序被io_service.post() 发送到io_service 中。否则,文件描述符将被添加到事件解复用器中进行监控。文档暗示了这种行为:

无论异步操作是否立即完成,都不会在此函数中调用处理程序。处理程序的调用将以等同于使用boost::asio::io_service::post() 的方式执行。

对于组合操作,例如async_read()EOF is treated as an error,因为它表示违反了操作的合同(即永远不会满足完成条件,因为没有更多数据可用)。在这种特殊情况下,I/O 系统调用将发生在 async_read() 初始化函数中,从文件开头(偏移量 0)读取到文件结尾,导致操作失败并返回 boost::asio::error::eof。操作完成后,永远不会将其添加到事件解复用器中以进行边沿触发监控:

boost::asio::io_service io_service;
boost::asio::posix::stream_descriptor stream_descriptor(io_service);

void read_handler(const boost::system::error_code& error, ...)
{
  if (error.value() == boost::asio::error::eof)
  {
    // Reset to start of file.
    lseek(sd.native_handle(), 0, SEEK_SET);
  }

  // Same as below.  ::readv() will occur within this context, reading
  // from the start of file to end-of-file, causing the operation to
  // complete with failure.
  boost::asio::async_read(stream_descriptor, ..., &read_handler);
}

int main()
{
  int fd = open( /* sysfs file */, O_RDONLY);

  // This would throw an exception for normal files, as they are not
  // poll-able.  However, the kernel flags some files on procfs and
  // sysfs as pollable.
  stream_descriptor.assign(fd);

  // The underlying ::readv() system call will occur within the
  // following function (not deferred until edge-triggered notification
  // by the reactor).  The operation will read from start of file to
  // end-of-file, causing the operation to complete with failure.
  boost::asio::async_read(stream_descriptor, ..., &read_handler);

  // Run will invoke the ready-to-run completion handler from the above
  // operation.
  io_service.run();
}

1。在内部,Boost.Asio 将此行为称为推测操作。这是一个实现细节,但如果操作可能不需要事件通知(例如,它可以立即尝试非阻塞 I/O 调用),并且没有挂起的 I/O 操作,则将在启动函数中尝试 I/O 操作I/O 对象上的相同类型的操作或未决的带外操作。没有自定义挂钩可以防止这种行为。

【讨论】:

  • 感谢您的回答,它帮助我更好地理解了 boost::asio 的一些概念。我发现使用 null_buffers 可以避免 EOF 情况。
  • 但是,深入挖掘 asios epoll reactor 的源代码后,我发现所描述行为的原因不是 推测性操作,而是 epoll 的情况reactor 为每个异步操作调用 epoll_ctl,这使得 epoll_wait 立即返回。
  • 我在基于epoll 的实现中证明,在开始时仅使用一次epoll_ctl 会使后续的epoll_wait 调用(除了紧随其后的调用之外)阻塞,直到发生GPIO 事件。不幸的是,正如您所说,似乎不可能从 boost::asio 获得这种特殊行为。
  • @Florian 很高兴它有帮助。 Boost.Asio 没有具体说明 epoll 的具体用法,当前的实现肯定不会提供你想要的行为。但是,即使实现没有针对每个操作发出 epoll_ctl,文档允许的推测操作行为仍然会阻止通过异步读取操作获得所需的行为。
猜你喜欢
  • 2016-11-15
  • 1970-01-01
  • 2017-09-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多