gpt4 book ai didi

c++ - 在 linux/sys/class/gpio 中写入文件的错误

转载 作者:IT王子 更新时间:2023-10-29 00:07:06 32 4
gpt4 key购买 nike

我现在遇到了我在 Linux 系统上见过的最奇怪的错误,似乎只有两种可能的解释 -

  • 要么附加 sudo 使文件立即写入
  • 或者附加 sudo 会在执行语句时产生短暂的延迟
  • 或者我不知道我的程序发生了什么

好吧,让我给你一些背景知识。我目前正在编写一个用于 raspberry pi gpio 操作的 c++ 程序。据我所知,程序中没有明显的错误,因为它成功地与 sudo 一起工作并且也成功地延迟了。所以这里是 rpi 的 gpio 是如何工作的 -

  • First you've to export one, to reserve it for manipulation, it will create a new directory as gpio+number with several files in it.

    echo 17 > /sys/class/gpio/export

  • Then set it's direction(in means read and out means write)

    echo "out" > /sys/class/gpio/gpio17/direction

  • Then write the value (0 or 1 for off and on)

    echo 1 > /sys/class/gpio/gpio17/value

  • At the end, unexport it back, the directory will get deleted.

    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 次运行 -

正常运行 失败

[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

使用 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); 一切都是一样的,包括设备、目录结构、构建 conf 和一切。 唯一能解释这一点的是 linux 有时可能会稍后创建该文件及其 friend ,或者需要一些时间?我在那之前调用了 .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。仍然失败。


Strace 助您一臂之力


让我们看看失败的构建命令的执行 -

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(权限被拒绝)

好的,这里有一些东西可以解释为什么它通过 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

最佳答案

udev 和您的程序之间存在竞争。当您写入 /sys/class/gpio/export 时,在完全创建 GPIO 之前写入不会返回。然而,一旦它被创建,您就有两个进程同时对新设备采取行动:

  • 热插拔/uevent 触发 udev 评估其规则。作为这些规则的一部分,它将更改 /sys/class/gpio/gpio17/value 的所有权和权限。
  • 你的程序继续。它将立即尝试打开 /sys/class/gpio/gpio17/value

因此您的程序有可能在 udev 更改其所有权和权限之前打开 value 文件。这实际上很有可能,因为您的 udev 处理程序执行 shell 的 execve,然后 execve 的 chown 和 chmod。但即使没有它,调度程序通常也会优先考虑从系统调用返回时已经在运行的任务,因此您的程序通常会在 udev 唤醒之前打开 value 文件。

通过插入一个 sleep ,你允许 udev 做它的事情。因此,为了使其健壮,您可以在打开文件之前使用 access() 轮询文件。

给 udev 更高的优先级也会有所帮助。例如。 chrt -f -p $(pidof systemd-udevd) 3。这赋予了 udev 实时优先权,这意味着它总是在你的程序之前运行。它还可能使您的系统无响应,因此请多加小心。

关于c++ - 在 linux/sys/class/gpio 中写入文件的错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39524234/

32 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com