Как использовать boost::asio с Linux GPIO

У меня есть однопоточное приложение для Linux, использующее boost::asio для асинхронного ввода/вывода. Теперь мне нужно расширить это приложение для чтения входов GPIO на /sys/class/gpio/gpioXX/value.

Можно ли сделать это с помощью boost::asio::posix::stream_descriptor на входах GPIO, запускаемых фронтом?

Я настроил вход 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 всегда немедленно вызывает обработчик (конечно, только внутри io_service.run()) либо с EOF, либо — в случае, если указатель файла был установлен обратно — с 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;
}

person Florian    schedule 26.05.2015    source источник
comment
comment
Что ж, спасибо за ссылку, но, как видно из вывода grep, EPOLLET не используется ни в коем случае.   -  person Florian    schedule 26.05.2015
comment
как это не используется в любом случае? EPOLLET используется в различных местах.   -  person m.s.    schedule 26.05.2015
comment
Извините, любой -› каждый ;)   -  person Florian    schedule 26.05.2015


Ответы (1)


Насколько я знаю, добиться такого поведения с помощью Boost.Asio невозможно. Хотя ядро ​​помечает некоторые файлы в procfs и sysfs как доступные для опроса, они не обеспечивают потокового поведения, ожидаемого от boost::asio::posix::stream_descriptor и его операций.

Реактор epoll Boost.Asio срабатывает по фронту (см. Boost.Asio 1.43 примечания к истории изменений). При определенных условиях1 Boost.Asio попытается выполнить операцию ввода-вывода в контексте инициирующей функции (например, async_read()). Если операция ввода-вывода завершена (успешно или неудачно), то обработчик завершения отправляется в io_service как-если io_service.post(). В противном случае файловый дескриптор будет добавлен в демультиплексор событий для мониторинга. Документация ссылается на это поведение:

Независимо от того, завершается ли асинхронная операция немедленно или нет, обработчик не будет вызываться из этой функции. Вызов обработчика будет выполнен способом, эквивалентным использованию boost::asio::io_service::post().

Для составных операций, таких как async_read(), EOF рассматривается как ошибка, так как указывает на нарушение контракта операции (т. е. условие завершения никогда не будет выполнено, поскольку больше не будет доступных данных). В этом конкретном случае системный вызов ввода-вывода будет выполняться внутри инициирующей функции 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 такое поведение называется спекулятивными операциями. Это деталь реализации, но операция ввода-вывода будет предпринята внутри инициирующей функции, если операция может не нуждаться в уведомлении о событии (например, она может немедленно попытаться выполнить неблокирующий вызов ввода-вывода), и если нет ни ожидающих операций того же типа, а также ожидающих внеполосных операций над объектом ввода-вывода. Нет никаких настраивающих крючков, чтобы предотвратить такое поведение.

person Tanner Sansbury    schedule 26.05.2015
comment
Спасибо за ваш ответ, он очень помог мне лучше понять некоторые концепции boost::asio. Я понял, что случая EOF можно избежать, используя null_buffers. - person Florian; 28.05.2015
comment
Однако, покопавшись в исходниках asios epoll Reactor, я обнаружил, что причиной описанного поведения является не спекулятивная операция, а случай, когда epoll Reactor вызывает epoll_ctl для каждой асинхронной операции, которая заставляет epoll_wait немедленно вернуться. - person Florian; 28.05.2015
comment
Я доказал в реализации на основе epoll, что использование epoll_ctl только один раз в самом начале блокирует последующие вызовы epoll_wait (кроме непосредственно следующего) до тех пор, пока не произойдет событие GPIO. К сожалению, как вы сказали, получить такое конкретное поведение от boost::asio не представляется возможным. - person Florian; 28.05.2015
comment
@ Флориан Рад, что это помогло. Точное использование epoll в Boost.Asio не указано, и текущая реализация определенно не обеспечит желаемого поведения. Однако даже если реализация не выдает epoll_ctl за операцию, спекулятивное поведение операции, разрешенное документацией, все равно не позволит получить желаемое поведение с операциями асинхронного чтения. - person Tanner Sansbury; 29.05.2015