【发布时间】:2017-01-24 06:06:41
【问题描述】:
我现在遇到了我在 linux 系统上见过的最奇怪的错误,而且似乎只有两种可能的解释 -
- 附加 sudo 使文件立即写入
- 或附加 sudo 会在执行语句时产生短暂的延迟
- 或者我不知道我的程序发生了什么
好吧,让我给你一些背景知识。我目前正在编写一个用于 raspberry pi gpio 操作的 c++ 程序。据我所知,程序中没有明显的错误,因为它可以成功地使用 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。现在让我解释一下这个错误。
首先,这里有 3 次相同的测试 -
Normal run 失败
[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 通过
[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 已取消注释 // 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.
黑白延迟和正常运行的唯一区别是单个未注释行 - // auto b = sleep(1); 一切都相同,包括设备、目录结构、构建配置和一切。 解释这一点的唯一原因是 linux 可能会稍后创建该文件及其朋友,或者需要一些时间?在那之前我打电话给.on()。这可以解释它......
但是为什么 sudo 调用没有延迟通过呢?它是使这些写入更快/即时,还是它自己放置延迟语句?这是某种缓冲的原因吗?请说不:/
如果重要的话,我将使用以下开发规则来获得对 gpio 目录的非 sudo 访问权限 -
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'"
编辑 - 正如@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
不用等,wtf?这意味着如果当时没有创建文件,则必须拒绝权限。但是使用sudo 是如何解决这个问题的呢?
这里是 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 的情况下你是如何让它工作的?我猜这些文件需要 sudo 权限才能写入?
-
流在 C++ 中默认是缓冲的,不是吗?
-
你应该刷新流,看看是否有帮助。
-
所以你真正的问题是打开文件失败?直到现在,我们才看完了你的长篇文章和所有这些 cmets?
-
我的猜测是:在导出 GPIO 时,即创建
gpio17目录时,所有节点都是使用默认访问权限创建的。稍作延迟后,udev 会注意到新节点并应用规则,授予组gpio的写访问权限。所以root可以立即写入,其他用户只有在udev应用规则后才能写入。