如何在Linux GPIO中使用boost :: asio

Flo*_*ian 8 c++ epoll boost-asio gpio

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

在边沿触发的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
Run Code Online (Sandbox Code Playgroud)

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

我不是boost::asio内部专家,但原因可能是boost::asioepoll反应器是水平触发而不是边缘触发的情况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;
}
Run Code Online (Sandbox Code Playgroud)

Tan*_*ury 5

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

Boost.Asio的epoll反应器是边缘触发的(请参阅Boost.Asio 1.43 修订历史记录)。在某些条件1下,Boost.Asio将在启动功能(例如async_read())的上下文中尝试I / O操作。如果I / O操作完成(成功或失败),则通过将完成处理程序按io_service原样发布到io_service.post()。否则,文件描述符将添加到事件多路分解器以进行监视。该文档暗示了这种行为:

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

对于构成操作,例如async_read()EOF被视为一个错误,因为它表明在操作的合同违反(即完成条件永远不会满足,因为没有更多的数据将可用)。在这种特殊情况下,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();
}
Run Code Online (Sandbox Code Playgroud)

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