Ошибка при записи в файл в linux /sys/class / gpio

у меня самая странная ошибка, которую я когда-либо видел с системой linux прямо сейчас, и, похоже, есть только два возможных объяснения этому -

  • либо добавление sudo делает запись файла мгновенной
  • или добавление sudo приводит к короткой задержке в выполнении операторов
  • или я понятия не имею, что происходит с моей программой

хорошо, позвольте мне дать вам некоторые предпосылки. В настоящее время я пишу программу на C++ для малина pi gpio манипуляции. Насколько я знаю, в программе нет видимой ошибки, поскольку она успешно работает с sudo и с задержками. Итак, вот как работает gpio rpi -

  • сначала вы должны экспортировать один, чтобы зарезервировать его для манипуляций, он создаст новый каталог как gpio+number с несколькими файлами в нем.

    echo 17 > /sys/class/gpio/export

  • затем задайте это направление(в Значит читать и значит пишите)

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

  • затем запишите значение (0 или 1 для off и on)

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

  • в конце, unexport его обратно, каталог будет удален.

    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());
}

Сид класс constructor делает часть экспорта -echo 17 > /sys/class/gpio/export, а .on() call задает направление -echo "write" > /sys/class/gpio/gpio17/direction и выводит значение, а также -echo 1 > /sys/class/gpio/gpio17/value. Забудьте о unexport здесь, так как он обрабатывается деструктором и не играет никакой роли здесь.

Если вам интересно, эти функции обрабатывают ввод-вывод, как это -

{
    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 передает имеет uncommented // 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); все то же самое, включая устройство, структуру каталогов, build conf и все остальное. единственное, что объясняет это 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'"

редактировать - как упоминал @charles, я использовал std::flush после каждой записи, которую я сделал на операциях ввода-вывода. Все еще терпит неудачу.


Strace к спасение


давайте посмотрим на выполнение команды failing build -

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)

Okaaay, вот что объясняет, почему он проходит с 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 решает, что?

вот соответствующий вывод для судо -

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

2 ответов


существует гонка между udev и вашей программой. Когда вы пишете в /sys/class/gpio/export, запись не вернется, пока GPIO не будет полностью создан. Однако, как только он был создан, у вас есть два процесса, которые одновременно принимают меры на новом устройстве:

  • hotplug / uevent запускает udev для оценки его правил. В рамках этих правил он изменит права собственности и разрешения /sys/class/gpio/gpio17/value.
  • ваша программа продолжается. Он сразу же попытается открыть /sys/class/gpio/gpio17/value.

таким образом, есть шанс, что ваша программа откроет до udev изменил свое право собственности и разрешения. Это на самом деле очень вероятно, потому что ваш обработчик udev выполняет execve оболочки, которая затем execve chown и chmod. Но даже без этого планировщик обычно отдает приоритет задаче, которая уже была запущена при возвращении из syscall, поэтому ваша программа обычно открывает value файл перед udev имеет даже проснулся.

вставляя сон, вы позволяете 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;
}

С новой строки. The echo команда также добавляет новую строку.

if (dirStream) {
    dirStream << direction << std::endl;
} else {
    // LOG error here
    return false;
}

(в этом случае я бы явно использовал std::endl смыть. Конечно, просто добавив '\n' строительство также, но сделать flush явным делает код более надежным. Как бы то ни было, теперь вы полагаетесь на то, что поток закрывается сразу после записи-чего может и не произойти, если вы позже решите сохранить поток открытым до конца вашей программы.)

отсутствующая конечная новая строка может объяснить, почему она работает с задержкой: после этой задержки драйвер может интерпретировать данные как будто появилась новая строка и предполагает, что больше писем не ждут в поток.