如何使用 boost::asio 和 Linux GPIO
How to use boost::asio with Linux GPIOs
我有一个单线程 Linux 应用程序使用 boost::asio 异步 input/output。现在我需要扩展此应用程序以读取 /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
的调用总是立即调用处理程序(当然只在 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;
}
据我所知,Boost.Asio 无法获得这种特殊行为。虽然内核将 procfs 和 sysfs 上的某些文件标记为可轮询,但它们不提供 boost::asio::posix::stream_descriptor
及其操作所期望的类似流的行为。
Boost.Asio 的 epoll 反应器是边缘触发的(参见 Boost.Asio 1.43 revision history notes). Under certain conditions1, Boost.Asio will attempt the I/O operation within the context of the initiating function (e.g. async_read()
)。如果 I/O 操作完成(成功或失败),则完成处理程序将由 io_service.post()
发布到 io_service
中。否则,文件描述符将被添加到事件多路分解器中进行监控。文档暗示了这种行为:
Regardless of whether the asynchronous operation completes immediately or not, the handler will not be invoked from within this function. Invocation of the handler will be performed in a manner equivalent to using 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 对象上既没有相同类型的挂起操作,也没有挂起的带外操作。没有自定义挂钩可以防止这种行为。
我有一个单线程 Linux 应用程序使用 boost::asio 异步 input/output。现在我需要扩展此应用程序以读取 /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
我设法编写了一个基于 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;
}
据我所知,Boost.Asio 无法获得这种特殊行为。虽然内核将 procfs 和 sysfs 上的某些文件标记为可轮询,但它们不提供 boost::asio::posix::stream_descriptor
及其操作所期望的类似流的行为。
Boost.Asio 的 epoll 反应器是边缘触发的(参见 Boost.Asio 1.43 revision history notes). Under certain conditions1, Boost.Asio will attempt the I/O operation within the context of the initiating function (e.g. async_read()
)。如果 I/O 操作完成(成功或失败),则完成处理程序将由 io_service.post()
发布到 io_service
中。否则,文件描述符将被添加到事件多路分解器中进行监控。文档暗示了这种行为:
Regardless of whether the asynchronous operation completes immediately or not, the handler will not be invoked from within this function. Invocation of the handler will be performed in a manner equivalent to using
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 对象上既没有相同类型的挂起操作,也没有挂起的带外操作。没有自定义挂钩可以防止这种行为。