在linux /sys/class/gpio中写入文件的错误
Bug with writing to file in linux /sys/class/gpio
我现在遇到了我在linux系统中见过的最奇怪的错误,似乎只有两种可能的解释-
- 附加sudo使文件写入立即
- 或者附加sudo在执行语句时会产生短暂的延迟
- 或者我不知道我的程序发生了什么
让我给你介绍一些背景知识。我目前正在编写一个c++程序的树莓派gpio操作。据我所知,程序中没有明显的错误&因为它可以成功地与sudo一起工作,也可以成功地与延迟一起工作。下面是rpi的gpio是如何工作的
首先你必须导出一个,为了保留操作,它将创建一个新目录
gpio+number
,其中包含几个文件。
echo 17 > /sys/class/gpio/export
然后设置方向(in表示读,out表示写)
echo "out" > /sys/class/gpio/gpio17/direction
然后写入值(0或1关闭和打开)
echo 1 > /sys/class/gpio/gpio17/value
最后取消导出,目录将被删除。
echo 17 > /sys/class/gpio/unexport
无论您是通过bash命令还是通过c/c++或任何其他语言的IO来执行此操作,因为在unix中这些只是文件,您只需要对它们进行读/写操作。到现在为止一切都很好。我已经手动测试了这个,它可以工作,所以我的手动测试通过了。
现在我为我的程序编写了一个简单的测试,看起来像这样-
TEST(LEDWrites, LedDevice)
{
Led led1(17, "MyLED");
// auto b = sleep(1);
EXPECT_EQ(true, led1.on());
}
Led类constructor
执行导出部分- echo 17 > /sys/class/gpio/export
,而.on()
调用设置方向- echo "write" > /sys/class/gpio/gpio17/direction
并输出值- echo 1 > /sys/class/gpio/gpio17/value
。忘记这里的unexport吧,因为它是由析构函数处理的,在这里不起作用。
如果你很好奇,这些函数是这样处理I/O的-
{
const std::string direction = _dir ? "out" : "in";
const std::string path = GPIO_PATH + "/gpio" + std::to_string(powerPin) + "/direction";
std::ofstream dirStream(path.c_str(), std::ofstream::trunc);
if (dirStream) {
dirStream << direction;
} else {
// LOG error here
return false;
}
return true;
}
表示基本的c++文件/io。现在让我来解释一下这个bug。
首先,这里有3个相同的测试运行-
Normal run
FAILS
[isaac@alarmpi build]$ ./test/testexe
Running main() from gtest_main.cc
[==========] Running 2 tests from 2 test cases.
[----------] Global test environment set-up.
[----------] 1 test from LEDConstruction
[ RUN ] LEDConstruction.LedDevice
[ OK ] LEDConstruction.LedDevice (1 ms)
[----------] 1 test from LEDConstruction (1 ms total)
[----------] 1 test from LEDWrites
[ RUN ] LEDWrites.LedDevice
../test/test.cpp:20: Failure
Value of: led1.on()
Actual: false
Expected: true
[ FAILED ] LEDWrites.LedDevice (2 ms)
[----------] 1 test from LEDWrites (3 ms total)
[----------] Global test environment tear-down
[==========] 2 tests from 2 test cases ran. (6 ms total)
[ PASSED ] 1 test.
[ FAILED ] 1 test, listed below:
[ FAILED ] LEDWrites.LedDevice
1 FAILED TEST
run with sudo
PASSES
[isaac@alarmpi build]$ sudo ./test/testexe
[sudo] password for isaac:
Running main() from gtest_main.cc
[==========] Running 2 tests from 2 test cases.
[----------] Global test environment set-up.
[----------] 1 test from LEDConstruction
[ RUN ] LEDConstruction.LedDevice
[ OK ] LEDConstruction.LedDevice (1 ms)
[----------] 1 test from LEDConstruction (2 ms total)
[----------] 1 test from LEDWrites
[ RUN ] LEDWrites.LedDevice
[ OK ] LEDWrites.LedDevice (2 ms)
[----------] 1 test from LEDWrites (2 ms total)
[----------] Global test environment tear-down
[==========] 2 tests from 2 test cases ran. (5 ms total)
[ PASSED ] 2 tests.
wtf delay run
PASSES has uncomment // auto b = sleep(1);
[isaac@alarmpi build]$ ./test/testexe
Running main() from gtest_main.cc
[==========] Running 2 tests from 2 test cases.
[----------] Global test environment set-up.
[----------] 1 test from LEDConstruction
[ RUN ] LEDConstruction.LedDevice
[ OK ] LEDConstruction.LedDevice (1 ms)
[----------] 1 test from LEDConstruction (2 ms total)
[----------] 1 test from LEDWrites
[ RUN ] LEDWrites.LedDevice
[ OK ] LEDWrites.LedDevice (1001 ms)
[----------] 1 test from LEDWrites (1003 ms total)
[----------] Global test environment tear-down
[==========] 2 tests from 2 test cases ran. (1005 ms total)
[ PASSED ] 2 tests.
唯一的区别b/w延迟和正常运行是一个没有注释的行- // auto b = sleep(1);
一切都是一样的,包括设备,目录结构,构建配置和一切。唯一能解释这一点的是linux可能会稍后创建该文件和它的朋友,或者需要一些时间?在此之前我调用.on()
。这就可以解释了…
但是为什么sudo调用没有延迟通过?它是使那些写更快/即时还是单独放置延迟语句?这是某种缓冲的原因吗?请说不:/
如果有关系,我使用以下dev规则来获得非sudo访问gpio目录-
SUBSYSTEM=="bcm2835-gpiomem", KERNEL=="gpiomem", GROUP="gpio", MODE="0660"
SUBSYSTEM=="gpio", KERNEL=="gpiochip*", ACTION=="add", PROGRAM="/bin/sh -c 'chown root:gpio /sys/class/gpio/export /sys/class/gpio/unexport ; chmod 220 /sys/class/gpio/export /sys/class/gpio/unexport'"
SUBSYSTEM=="gpio", KERNEL=="gpio*", ACTION=="add", PROGRAM="/bin/sh -c 'chown root:gpio /sys%p/active_low /sys%p/direction /sys%p/edge /sys%p/value ; chmod 660 /sys%p/active_low /sys%p/direction /sys%p/edge /sys%p/value'"
EDIT -正如@charles所提到的,我在每次写I/O操作后都使用std::flush
。仍然失败。
紧急救援
让我们看看失败的构建命令的执行-
open("/sys/class/gpio/export", O_WRONLY|O_CREAT|O_TRUNC|O_LARGEFILE, 0666) = 3
open("/sys/class/gpio/unexport", O_WRONLY|O_CREAT|O_TRUNC|O_LARGEFILE, 0666) = 3
open("/sys/class/gpio/export", O_WRONLY|O_CREAT|O_TRUNC|O_LARGEFILE, 0666) = 3
open("/sys/class/gpio/gpio17/value", O_WRONLY|O_CREAT|O_TRUNC|O_LARGEFILE, 0666) = -1 EACCES (Permission denied)
open("/sys/class/gpio/gpio17/direction", O_WRONLY|O_CREAT|O_TRUNC|O_LARGEFILE, 0666) = -1 EACCES (Permission denied)
open("/sys/class/gpio/unexport", O_WRONLY|O_CREAT|O_TRUNC|O_LARGEFILE, 0666) = 3
..., 0666) = -1 EACCES (Permission denied)
好的,这里有一些东西,解释了为什么它是用sudo传递的。但为什么它迟迟没有通过呢?我们再检查一下
open("/sys/class/gpio/export", O_WRONLY|O_CREAT|O_TRUNC|O_LARGEFILE, 0666) = 3
open("/sys/class/gpio/unexport", O_WRONLY|O_CREAT|O_TRUNC|O_LARGEFILE, 0666) = 3
open("/sys/class/gpio/export", O_WRONLY|O_CREAT|O_TRUNC|O_LARGEFILE, 0666) = 3
open("/sys/class/gpio/gpio17/value", O_WRONLY|O_CREAT|O_TRUNC|O_LARGEFILE, 0666) = 3
open("/sys/class/gpio/gpio17/direction", O_WRONLY|O_CREAT|O_TRUNC|O_LARGEFILE, 0666) = 4
open("/sys/class/gpio/unexport", O_WRONLY|O_CREAT|O_TRUNC|O_LARGEFILE, 0666) = 3
不,等等,怎么了?这意味着,如果当时没有创建文件,则必须拒绝该权限。但如何使用sudo
解决这个问题?
open("/sys/class/gpio/export", O_WRONLY|O_CREAT|O_TRUNC|O_LARGEFILE, 0666) = 3
open("/sys/class/gpio/unexport", O_WRONLY|O_CREAT|O_TRUNC|O_LARGEFILE, 0666) = 3
open("/sys/class/gpio/export", O_WRONLY|O_CREAT|O_TRUNC|O_LARGEFILE, 0666) = 3
open("/sys/class/gpio/gpio17/value", O_WRONLY|O_CREAT|O_TRUNC|O_LARGEFILE, 0666) = 3
open("/sys/class/gpio/gpio17/direction", O_WRONLY|O_CREAT|O_TRUNC|O_LARGEFILE, 0666) = 4
open("/sys/class/gpio/unexport", O_WRONLY|O_CREAT|O_TRUNC|O_LARGEFILE, 0666) = 3
udev和您的程序之间存在竞争。当您写入/sys/class/gpio/export
时,在GPIO完全创建之前,写操作不会返回。然而,一旦它被创建,你有两个进程同时对新设备采取行动:
- 一个热插拔/uevent触发udev评估它的规则。作为这些规则的一部分,它将改变
- 程序继续。它将立即尝试打开
/sys/class/gpio/gpio17/value
。
/sys/class/gpio/gpio17/value
的所有权和权限。因此,在 udev更改其所有权和权限之前,您的程序有可能打开value
文件。这实际上是很有可能的,因为您的udev处理程序执行shell,然后执行chown和chmod。但是,即使没有这样做,调度程序通常也会在从系统调用返回时优先考虑已经在运行的任务,因此您的程序通常会在udev唤醒之前打开value
文件。
通过插入一个sleep,你允许udev做它的事情。因此,为了使其健壮,您可以在打开文件之前使用access()轮询该文件。
给udev更高的优先级也会有所帮助。例:chrt -f -p $(pidof systemd-udevd) 3
。这给了udev实时优先级,这意味着它总是在程序之前运行。
从您的strace
输出
open("/sys/class/gpio/gpio17/value", O_WRONLY|O_CREAT|O_TRUNC|O_LARGEFILE, 0666) = -1 EACCES (Permission denied)
open("/sys/class/gpio/gpio17/direction", O_WRONLY|O_CREAT|O_TRUNC|O_LARGEFILE, 0666) = -1 EACCES (Permission denied)
您首先写value
,然后 direction
。当然,在写入值之前,您应该首先设置正确的方向。
同时,你应该结束你的输出
if (dirStream) {
dirStream << direction;
} else {
// LOG error here
return false;
}
加换行符。echo
命令还附加了一个换行符。
if (dirStream) {
dirStream << direction << std::endl;
} else {
// LOG error here
return false;
}
(在本例中,我将显式地使用std::endl
进行刷新。当然,只添加'n'
也可以,但是显式刷新会使代码更加健壮。实际上,您现在依赖于这样一个事实,即流在写入后立即关闭——如果您以后决定将流保持打开状态直到程序结束,则可能不会关闭。
后面缺少换行符可以解释为什么它与延迟一起工作:在该延迟之后,驱动程序可能会将数据解释为好像有换行符,并假设流中没有更多的字母等待。
- C++系统找不到指定的文件错误
- C++语法头文件错误
- C++生成文件错误"implicit entry/start for main executable"
- C++/生成文件错误:未定义对"main"的引用
- VS2019:资源文件错误:中性(默认)(未知子语言:0x8)
- C++程序不制作文件(错误)
- 安卓工作室 |CPP 文件错误错误: 位图库中对"AndroidBitmap_unlockPixels"的未定义引用
- "what does ":*** [可执行文件] 错误 1 " mean ?"
- 如何修复使用VScode调试器gcc调试时的"找不到文件"错误
- 在课堂上创建了一个朋友,给出"无输入文件"错误
- Qt .exe文件错误
- c++ 添加具有写入文件错误的文本变量
- 无法转换 .CATPart 文件.错误:输入文件路径似乎包含不支持的字符
- 写入文本文件错误,QT
- C++写入文件错误
- Windows Subsystem for Linux (WSL) 下的间歇性随机"找不到文件"错误
- 犰狳读取MAT文件错误
- 无法求解FullPathForFilename:COCOS2DX中可能缺少文件错误
- C Wininet FTP列出了许多文件错误
- 生成文件错误 - 找不到文件 - *.cpp