Оптимизация цикла while

8

Я создал мини-скрипт для перезагрузки моего Raspberry Pi по нажатию кнопки. Сценарий просто использует wiringPi (команда gpio), чтобы установить на вход вывод 0 (вывод 17 в стандартном порядке нумерации Raspberry Pi), а затем считывает значение до единицы (то есть, когда кнопка нажата или удерживается нажатой).

Вот мой сценарий:

gpio mode 0 in

while (true)
do
        if [ `gpio read 0` -eq 1 ]
        then
                echo password | sudo -S reboot
                break
        fi
done &

Скрипт работает отлично и все.

Тем не менее, для тех из вас, кто не знаком с Pi, он имеет очень ограниченные аппаратные ресурсы (включая 512 МБ памяти), которые можно легко использовать в цикле, подобном тому, который я использую.

Здесь я пытаюсь добиться другого способа, чтобы bash мог определить, когда значение изменилось с 0на 1без необходимости выделять для него более похожий на безусловный цикл. Это выполнимо? Пожалуйста, поделитесь своими идеями.

Фади Ханна АЛ-Касс
источник
3
Рассматривали ли вы использование прерываний для обработки аппаратного события или это абсолютно невозможно в вашем случае? adafruit.com/blog/2013/03/29/…
lgeorget
3
Я бы просто добавил спящий вызов, который должен ограничивать потребление памяти
Strugee
Прерывания @lgeorget были бы идеальными, хотя, вероятно, не обрабатывались из bash.
Иордания
Этот цикл не потребляет память.
OrangeDog

Ответы:

11

Анализ и современное решение

Скрипт является занятым циклом: он продолжает читать выводы GPIO снова и снова. Он не потребляет много памяти, но загружает процессор.

Вы должны установить вывод GPIO в режиме края. gpioУтилита имеет wfi(ожидание прерывания) команды , которые вы можете использовать , чтобы реагировать на спусковой крючок края. ( gpio wfiне существовало назад, когда был задан вопрос.)

set -e
gpio mode 0 in
gpio wfi 0 rising
echo password | sudo -S reboot

Решение Python

Существует библиотека Python для доступа к GPIO , которая поддерживает граничный режим. Вот некоторый полностью непроверенный код Python, который должен делать то, что вы хотите.

#!/usr/bin/env python
import os
from RPi import GPIO
GPIO.wait_for_edge(0, GPIO.RISING)
system("sudo reboot")

Дополнительные подсказки раковины

(true)может быть написано просто true. Скобки создают подпроцесс, который совершенно не нужен.

`gpio read 0`должно быть в двойных кавычках. Без кавычек вывод команды обрабатывается как список шаблонов имен файлов. В двойных кавычках вывод команды обрабатывается как строка. Всегда ставьте двойные кавычки вокруг подстановок команд и подстановок переменных: "$(some_command)", "$some_variable". Кроме того, вы должны использовать синтаксис, $(…)а не `…`: он имеет точно такое же значение, но синтаксис обратной кавычки имеет некоторые причуды синтаксического анализа, когда команда сложна. Таким образом:if [ "$(gpio read 0)" -eq 1 ]

Не помещайте пароль root в скрипт. Если скрипт выполняется от имени пользователя root, вам вообще не нужен sudo. Если сценарий не запущен от имени пользователя root, предоставьте пользователю, запускающему сценарий, разрешение на запуск sudo rebootбез указания пароля. Запустите visudoи добавьте следующую строку:

userwhorunsthescript ALL = (root) NOPASSWD: /sbin/reboot ""

Обратите внимание, что если в файле sudoers есть запись для того же пользователя, для которой требуется пароль, то NOPASSWDзапись должна идти после.

Как только вы запускаете перезагрузку, вам не нужно прерывать цикл, система все равно остановится.

Если вы решите продолжать использовать этот сценарий оболочки, а ваша версия gpioслишком старая, чтобы иметь wfiподкоманду, вот улучшенная версия, которая проверяет состояние кнопки каждую секунду. Обратите внимание, что, поскольку пин-код считывается только один раз в секунду, это означает, что вам нужно удерживать кнопку нажатой не менее одной секунды, чтобы быть уверенным, что событие зафиксировано.

gpio mode 0 in
while sleep 1; do
    if [ "$(gpio read 0)" -eq 1 ]; then
        reboot
    fi
done &
Жиль "ТАК - перестань быть злым"
источник
1
Для вашего последнего примера, вы можете спать на долю секунды . Нечто подобное 0.1или, возможно, 0.2должно быть в состоянии обнаруживать очень короткие нажатия и при этом оставлять достаточно времени ЦП для других потоков.
Боб
@Bob: хотя переносимость, вероятно, не имеет значения в этом случае, sleep(1)принятие дробного числа секунд не является стандартным.
1
Обновление: есть такая команда ожидания: gpio wfi 0 risingожидание нарастающего фронта на нулевом выводе, который не занят (в соответствии с сайтом подключения проводки ).
CodenameLambda
3

То, что у вас есть, известно как занятая петля . Ваш цикл не будет потреблять почти никакой памяти, но он будет потреблять много процессора. Обычно это смягчается добавлением sleepв тело цикла.

while (true)
do
        if [ `gpio read 0` -eq 1 ]
        then
                echo passowrd | sudo -S reboot
                break
        fi
        sleep 1
done &

Избавление от занятой петли будет зависеть от того, что gpioделает. Существуют системные вызовы, такие как select(), которые могут блокировать, пока дескриптор файла не будет готов.

Что касается эффективности, команда ()вокруг trueфактически выполняется trueв подоболочке. Это не нужно и может быть лучше выражено с помощью следующего:

while (( $(gpio read 0) != 1 )); do
    sleep 1
done
echo passowrd | sudo -S reboot
jordanm
источник
-1

Попробуйте следующее:

while ! gpio read 0 ; do
    sleep 1
done
echo password | sudo -S reboot
гость
источник