在linux / sys / class / gpio

时间:2016-09-16 05:27:22

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

我现在有一个我曾经见过的最糟糕的bug系统,而且似乎只有两种可能的解释 -

  • 附加sudo使文件写入即时
  • 或者追加sudo会导致执行语句的短暂延迟
  • 或者我不知道我的计划发生了什么

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

  
      
  • 首先,您要导出一个,以保留它进行操作,它会创建一个新目录gpio+number,其中包含多个文件。

         

    echo 17 > /sys/class/gpio/export

  •   
  • 然后设置它的方向(意思是读取和输出意味着写入)

         

    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并输出值 - {{1} }。忘掉这里的devport,因为它是由析构函数处理的,在这里不起作用。

如果您有点好奇,这些函数会像这样处理I / O -

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

表示基本的c ++文件/ io。现在让我解释一下这个错误。

首先,这里有3次相同的测试 -

{ 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; } 失败

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. PASSES 已取消注释wtf delay run

// 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. 一切都是相同的,包括设备,目录结构,构建conf和一切。 解释这一点的唯一因素是linux可能会创建该文件及其朋友有时甚至需要一些时间?在此之前我打电话给// auto b = sleep(1);那可以解释一下......

但是为什么没有延迟的sudo调用通过?它是否使这些写入更快/更快或者是否将延迟语句单独放置?这是否是某种缓冲的原因?请说不:/

如果重要,我使用以下开发规则获取非sudo访问gpio目录 -

.on()

编辑 - 正如@charles所提到的,我在每次I / O操作写入后都使用了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'" 。仍然失败。

拯救的目标

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

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

Okaaay,这里有一些东西,这解释了为什么它与sudo一起传递。但为什么它会延迟过去?我们也检查一下,

..., 0666) = -1 EACCES (Permission denied)

不等,wtf?这意味着如果当时没有创建文件,则拒绝的权限必须是。但是如何使用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

2 个答案:

答案 0 :(得分:7)

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

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

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

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

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

答案 1 :(得分:0)

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'也可以,但是使flush更明确可以使代码更健壮。实际上,你是现在依赖于在写入之后流立即关闭的事实 - 如果您以后决定保持流打开直到程序结束,则可能不会。)

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