【问题标题】:Bug with writing to file in linux /sys/class/gpio在 linux /sys/class/gpio 中写入文件的错误
【发布时间】: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应用规则后才能写入。

标签: c++ linux file c++11 sudo


【解决方案1】:

udev 和您的程序之间存在竞争。当您写入/sys/class/gpio/export 时,写入将不会返回,直到完全创建 GPIO。但是,一旦它被创建,您将有两个进程同时在新设备上执行操作:

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

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

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

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

【讨论】:

    【解决方案2】:

    来自您的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' 也可以,但是使刷新显式使代码更加健壮。事实上,您现在依赖于事实上,流在写入后立即关闭——如果您后来决定将流保持打开直到程序结束,则可能不会。)

    缺少的尾随换行符可以解释为什么它会延迟工作:在延迟之后,驱动程序可能会将数据解释为好像有一个换行符,并假设流中没有更多的字母在等待。

    【讨论】:

    • 关闭流也会刷新它(这将在函数返回时发生)
    • 我刚刚说过,显式刷新它会使代码更加健壮。也许稍后他决定让流保持打开状态(可能是value 而不是direction),这样他就可以随着时间的推移写入多个值。在这种情况下,仍然需要显式刷新。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-25
    • 1970-01-01
    • 1970-01-01
    • 2017-03-15
    • 2021-08-22
    • 1970-01-01
    相关资源
    最近更新 更多