Запустіть команду unix точно через дуже короткі проміжки часу, БЕЗ накопичення затримки часу


38

Питання

Я хотів би мати можливість виконувати команду UNIX точно кожну секунду протягом тривалого періоду .

Мені потрібно рішення, яке не відстає від певного часу через час, який команда сама потребує для виконання. сон , дивитись , і певний сценарій пітона все це мене провалило.

На мікроконтролері, такому як http://Arduino.cc, я б це зробив через перерви апаратних годин. Мені хотілося б знати, чи існує подібне в часі рішення скрипту оболонки. Всі рішення, які я знайшов в StackExchange.com, призвели до помітного затримки часу, якщо працювати протягом кількох годин. Дивіться деталі нижче.

Практичне призначення / застосування

Я хочу перевірити, чи постійно працює моє мережне з'єднання, надсилаючи часові позначки через nc(netcat) кожні 1 секунду.

Відправник:

precise-timestamp-generator | tee netcat-sender.txt | nc $receiver $port

Одержувач:

nc -l -p $port > netcat-receiver.txt

Після завершення порівняйте два журнали:

diff netcat-sender.txt netcat-receiver.txt

Відмінністю будуть непередавані часові позначки. З цього я б знав, в який час моя локальна мережа / WAN / ISP створює проблеми.


Розв’язання SLEEP

while [ true ]; do date "+%Y-%m-%d %H:%M:%S" ; sleep 1; done | tee timelog-sleep.txt

З часом отримує певний зсув, оскільки команда в циклі також займає небагато часу.

Точність

cat timelog-sleep.txt

2012-07-16 00:45:16
[...]
2012-07-16 10:20:36

Минуло секунд: 34520

wc -l timelog-sleep.txt

Рядки у файлі: 34243

Точність узагальнена:

  • 34520-34243 = 277 проблем із термінами
  • 34520/34243 = 1.008 = 0,8% знижки

Розчин ПОВТОРИТИ ПІТОН

Знайдено за адресою: Повторюйте команду Unix кожні x секунд назавжди

repeat.py 1 "date '+%Y-%m-%d %H:%M:%S'" >> timelog-repeat-py.txt

Передбачається, щоб уникнути компенсації часу, але цього не вдалося.

Точність

wc -l timelog-repeat-py.txt

2012-07-16 13:42:44
[...]
2012-07-16 16:45:24

Минуло секунд: 10960

wc -l timelog-repeat-py.txt

Рядки у файлі: 10859

Точність узагальнена:

  • 10960-10859 = 101 проблема з термінами
  • 10960/10859 = 1.009 = 0,9% знижки

Рішення ГЛЕД

watch -n 1 "date '+%Y-%m-%d %H:%M:%S' >> ~/Desktop/timelog-watch.txt"

Точність

wc -l timelog-watch.txt
2012-07-16 11:04:08
[...]
2012-07-16 13:25:47

Минуло секунд: 8499

wc -l timelog-watch.txt

Рядки у файлі: 8366

Точність узагальнена:

  • 8499-8366 = 133 задачі з терміном.
  • 8499/8366 = 1,016 = 1,6% знижки.

5
Для чого потрібна роздільна здатність, точність і навіщо вона вам потрібна / для чого ви її використовуєте?
jippie

Що станеться, якщо у вас niceпроцес, який спить?
Тіло Віклунд

1
Чи хочете Ви скористатися графіком РЕАЛЬНОГО ЧАСУ, щоб сподіватись мінімізувати затримку виклику в режимі сну ().
mdpc

Мене вражає, що на ваш час завжди впливатиме поточний робочий стан коробки. Якщо щось змушує завантажувати програму, яку ви хочете отримати, з кешу, ви отримаєте збільшення часу, якщо ви не зможете гарантувати, що типовий час виконання програми значно менший за потрібний інтервал. Я хотів би бути в режимі реального часу, у значно знищеному режимі, коли ніхто більше не ввійшов, або в режимі одиночного користувача. Ймовірно, краще рішення - змінити програму, про яку йдеться, зробити цикл сам, а не викликати його з іншої програми.
Hack Saw

2
Запустіть кожну команду у власному потоці, тому будь-яке блокування, пов'язане з IO, не скидає ваш час.
Джоель Корнетт

Відповіді:


12

Як працює цей сценарій Perl, який я щойно зіткнув?

#!/usr/bin/perl

use strict;
use warnings;
use Time::HiRes qw/time sleep/;

sub launch {
    return if fork;
    exec @_;
    die "Couldn't exec";
}

$SIG{CHLD} = 'IGNORE';

my $interval = shift;
my $start = time();
while (1) {
    launch(@ARGV);
    $start += $interval;
    sleep $start - time();
}

Використання: perl timer.pl 1 date '+%Y-%m-%d %H:%M:%S'

Він працює 45 хвилин без жодного пропуску, і я підозрюю, що це буде продовжуватись робити, якщо а) завантаження системи не стане настільки високим, що fork () займає більше секунди або b) вставлена ​​високосна секунда.

Однак це не може гарантувати, що команда працює з точними другими інтервалами, оскільки є накладні витрати, але я сумніваюся, що це набагато гірше рішення на основі переривання.

Я запускав його близько години date +%N(наносекунд, розширення GNU) і провів деякі статистичні дані про нього. Найбільше відставання було 1 155 мікросекунд. Середнє значення (середнє арифметичне) 216 мкс, медіана 219 мкс, стандартне відхилення 42 мкс. Він пробіг швидше, ніж 270 мкс 95% часу. Я не думаю, що ти можеш це перемогти, за винятком програми C.


1
Я запускав його протягом ночі без жодних інших активних програм користувача з інтервалом в 1 сек, і він пробіг 29241 сек, без жодної пропущеної секунди! Це буде відповідати моєму призначенню. Тоді я побіг знову це ранок з інтервалом 0,1 сек, GNU dateз +%Nі тільки після 3 хвилин він кинув цю помилку: Time::HiRes::sleep(-0.00615549): negative time not invented yet at ~/bin/repeat.pl line 23.Line 23 в моєму збереженому сценарії:sleep $start - time();
PORG

Якщо ви запускаєте його з інтервалом 0,01 сек або 0,001 сек, це лише декілька секунд або менше, поки програма не припинить помилку "негативний час". Але для моєї мети це підходить!
порг

28

Функція POSIX ualarm()дозволяє намітити ядро, щоб періодично сигналізувати про ваш процес, з мікросекундною точністю.

Підключіть просту програму:

 #include<unistd.h>
 #include<signal.h>
 void tick(int sig){
     write(1, "\n", 1);
 }
 int main(){
     signal(SIGALRM, tick);
     ualarm(1000000, 1000000); //alarm in a second, and every second after that.
     for(;;)
         pause();
 }

Складіть

 gcc -O2 tick.c -o tick

Потім прикріпіть його до того, що вам потрібно періодично робити так:

./tick | while read x; do
    date "+%Y-%m-%d %H:%M:%S"
done | tee timelog-sleep.txt

Мені потрібна спеціальна оболонка або C-std для цього? Я склав це (що дало невелике попередження про відсутнє повернення), але жодного результату не було отримано.
математика

@math With -std=c99, ви не отримаєте попередження про відсутнє повернення. Інакше вам нічого особливого не потрібно. Ви ввели неправильний нуль? strace ./tickпокаже вам, що це робить з точки зору систематичного виклику
Дейв

Я отримую: gcc -O2 -std = c99 -o галочку tick.c tick.c: У функції 'main': tick.c: 10: 5: попередження: неявне оголошення функції 'ualarm' [-Wimplicit-function -кларація ] tick.c: У функції 'галочка': tick.c: 5: 10: попередження: ігнорування повернення значення 'написати', оголошене атрибутом warn_unused_result [-Wunused-result] :: Здається, що робить моя система (Ubuntu 12.04) не підтримують. Однак, принаймні, існує сторінка, на якій уalarm має бути unistd.h. (gcc - 4.6.3)
математика

28

Ви пробували watchз параметром --precise?

watch -n 1 --precise "date '+%Y-%m-%d %H:%M:%S.%N' >> ~/Desktop/timelog-watch.txt"

На чоловіковій сторінці:

Зазвичай цей інтервал інтерпретується як проміжок часу між завершенням одного запуску команди та початком наступного виконання. Однак, використовуючи опцію -p або - точність, ви можете робити спробу перегляду команд виконувати команду кожні проміжки секунд. Спробуйте це з ntptime і помітьте, як дробові секунди залишаються (майже) однаковими, на відміну від звичайного режиму, де вони постійно збільшуються.

Цей параметр може бути недоступним у вашій системі.

Ви також повинні врахувати, що має статися, коли на виконання вашої програми потрібно більше однієї секунди. Чи слід пропустити наступне заплановане виконання, або його слід запізнити?

Оновлення : я деякий час запускав сценарій, і він не втратив жодного кроку:

2561 lines
start: 2012-07-17 09:46:34.938805108
end:   2012-07-17 10:29:14.938547796

Оновлення:--precise прапор є доповненням Debian, патч, однак , досить просто: http://patch-tracker.debian.org/patch/series/view/procps/1:3.2.8-9squeeze1/watch_precision_time.patch


Точно шлях. Бажаю, щоб я міг +10 цього.
krlmlr

Яка версія watchпідтримує цю опцію? Це було на жодній із машин, які я перевіряв.
tylerl

Його версія 0.3.0, яка є поточною версією Ubuntu 12.04. Він походить з версії 3.2.8-11ubuntu6 пакету propps.
daniel kullmann

Хм, вихідний пакет propps не підтримує --precise. Це додаток Debian (3.2.8-9, watch_precision_time.patch)
daniel kullmann

1
Гаразд, але те саме, що mdpc у коментарях до питання, зазначене: Це може також вийти з ладу, коли ваша система перебуває під великим навантаженням. Я щойно перевірив це у поєднанні зі стресом (навантаження на диск та сердечники), і зрозумів це: 2012-07-24 07:20:21.864818595 2012-07-24 07:20:22.467458430 2012-07-24 07:20:23.068575669 2012-07-24 07:20:23.968415439 речі в реальному часі (ядро тощо) є там не просто так!
математика

18

crontabмає дозвіл 1 хвилину. Якщо у вас все добре, якщо час затримки за цю хвилину накопичується, а потім перезавантаження на наступну хвилину, ця основна ідея може спрацювати:

* * * * * for second in $(seq 0 59); do /path/to/script.sh & sleep 1s;done

Зауважте, що script.shтакож виконується у фоновому режимі. Це повинно допомогти мінімізувати відставання, що накопичуються з кожною ітерацією циклу.

Залежно від кількості затримок sleep, однак є ймовірність перекриття другої 59 другою хвилиною наступної хвилини.

EDIT, щоб підкинути деякі результати у тому ж форматі, що і в питанні:

$ cat timelog-cron
2012-07-16 20:51:01
...
2012-07-16 22:43:00

1 година 52 хвилини = 6720 секунд

$ wc -l timelog-cron
6720 timelog-cron

0 проблем із термінами, знижка 0%. Будь-який час накопичення скидається щохвилини.


1
Чи можу я запитати, чому це було знято?
Ізката

2
Це некрасивий хак
hhaamu

2
@hhaamu Що негарного в цьому? ОС загального призначення на ПК не розраховані на дуже точні операції, що мають найважливіший час, тому що ще можна очікувати? Якщо ви хочете "елегантні" та абсолютно точні терміни, вам доведеться використовувати інший планувальник процесора, або перейти на ядро ​​в реальному часі, або використовувати спеціальне обладнання тощо. Це ідеально законне рішення, і я не бачу причин для будь-якого низовини. Це, безумовно, поліпшення того, у якого було лише "запуск у фоновому режимі" без періодичної повторної синхронізації через cron.
jw013

1
Плюс зупинити це легко. Не потрібно ризикувати вбити її в середині циклу - видаліть запис із кронтабу, і він закінчується самостійно в кінці хвилини.
Ізката

Ви просто пощастило , що у вашій системі cronє точною на другий, але це не так взагалі.
Дмитро Григор'єв

15

Ваша проблема полягає в тому, що ви спите протягом певного часу після запуску програми, не враховуючи кількість часу, який минув з останнього разу, коли ви спали.

Ви можете це зробити bash або будь-якою іншою мовою програмування, але ключовим є використання годинника, щоб визначити, як довго планувати наступний сон. Перед сном перевірити годинник, побачити, скільки часу у вас залишилось, і заснути різницю.

Через компроміси планування процесу вам не гарантується прокинутися прямо на галочку годинника, але ви повинні бути досить близько (протягом декількох завантажених мс або під навантаженням протягом декількох сотень мс). І ви не накопичуватимете помилок з часом, оскільки кожен раз, коли ви повторно синхронізуєтесь на кожному циклі сну та видаляєте накопичену помилку.

Якщо вам потрібно точно натиснути на галочку годинника, тоді ви шукаєте операційну систему в реальному часі , розроблену саме для цієї мети.


Я думаю, що також дуже ймовірно, що програма porg перевірила блок під час виконання наміченого процесу - що логічно вони повинні зробити, щоб уникнути вбивства машини, на якій вони працюють.
symcbean

Незалежно від того, блокуєте ви його чи ні, механізм працює просто чудово. Якщо ви блокуєте, ви спите час, що залишився після блокування. Якщо ви не блокуєте, ваша тимчасова нитка або процес спить, поки інший працює. У будь-якому випадку, однаковий результат.
tylerl

@tylerl: Як би виглядав конкретний командний рядок для вашого рішення?
порг

Я думаю, ви мали на увазі те саме, що і @lynxlynxlynx
porg

@porg вам потрібно скористатися, date +%S.%Nщоб отримати кількість секунд з точністю usleepдо другої секунди, і спати з точністю до другої секунди, але після цього це лише питання математики.
tylerl

7

Я завжди просто відмовився від того, щоб щось запустити рівно з інтервалом. Я думаю, що вам доведеться написати програму C і дуже уважно звертати увагу на те, щоб не перевищувати частину інтервалу 1 секунди своїм кодом. Вам, мабуть, доведеться використовувати нитки чи декілька процесів взаємозв’язку, щоб це працювало. Слідкуйте за тим, щоб уникнути накладних витрат часу або запуску потоку.

Одне посилання, яке здається актуальним, відноситься до 1993 року: рандомізований годинник вибірки для оцінки використання процесора та кодування профілю Вам потрібно буде ознайомитись з додатком "Вихідний код супротивника", щоб побачити, як вони точно вимірювали часові інтервали та "прокинулися" їх програма в правильний час. Оскільки коду виповнилося 19 років, він, ймовірно, не буде переноситися прямо чи легко, але якщо ви його прочитаєте та спробуєте зрозуміти, принципи, які стосуються цього, можуть керувати вашим кодом.

EDIT: Знайдено ще одне посилання, яке може допомогти: Вплив роздільної здатності годин на планування інтерактивних та м'яких процесів у режимі реального часу, які повинні допомогти вам з будь-яким теоретичним підґрунтям.


4

Погляньте на nanosleep () (з http://linux.about.com/library/cmd/blcmdl2_nanosleep.htm ). Замість того, щоб програма спала 1 секунду, спайте (1 - кількість, яку потрібно запустити) секунд. Ви отримаєте набагато кращу роздільну здатність.


Можна зробити те ж саме з регулярними sleep, тобто sleep 0.99. Проблема полягає в тому, що кількість часу, який потрібно запустити, далеко не постійне, навіть його середнє значення може коливатися з часом.
Дмитро Григор’єв

3

Спробуйте запустити свою команду у фоновому режимі, щоб це не вплинуло на терміни циклу так сильно, але навіть цього буде недостатньо, якщо ви не хочете накопичувати протягом тривалого періоду часу, оскільки з цим, безумовно, є кілька мілісекундних витрат.

Отже, це, мабуть, краще, але також, ймовірно, все ще недостатньо добре:

while [ true ]; do date "+%Y-%m-%d %H:%M:%S" & sleep 1; done | 
tee timelog-sleep.txt

На моєму комп’ютері це дало 2 помилки за 20 хвилин або 0,1 в хвилину, що приблизно в п'ять разів покращило ваш пробіг.


Проблема sleep 1полягає в тому, що гарантовано спати хоча б одну секунду - ніколи менше. Отже, помилка накопичується.
hhaamu

Порівнювати результати синхронізації з двох різних комп'ютерів є абсолютно безглуздим, якщо ви не запустили оригінальний код у вашій системі та не отримали такий же результат, як і ОП.
Дмитро Григор’єв

1

Некрасиво, але це працює. Ймовірно, ви повинні переосмислити дизайн своєї програми, якщо вам потрібна така петля. В основному він перевіряє, чи поточна ціла секунда дорівнює попередній перевіреній і друкує кількість наносекунд з моменту зміни другої. На точність впливає сон .001.

while true; do T=$( date +%s ); while [[ $T -eq $( date +%s ) ]]; do sleep .001; done; date "+%N nanoseconds late"; done

Точність в мілісекундах, за умови, що «корисне навантаження» date "+%N nanoseconds late"не займе більше ніж секунду. Ви можете знизити завантаження процесора, збільшивши період сну або якщо ви дійсно не проти просто замінити команду сну на true.

002112890 nanoseconds late
001847692 nanoseconds late
002273652 nanoseconds late
001317015 nanoseconds late
001650504 nanoseconds late
002180949 nanoseconds late
002338716 nanoseconds late
002064578 nanoseconds late
002160883 nanoseconds late

Це погана практика, оскільки ви в основному проводите опитування процесора на подію, і ви витрачаєте цикли процесора. Напевно, ви хочете приєднати до переривання таймера (неможливо від bash) або використовувати спеціальне обладнання, наприклад мікроконтролер. ПК та його операційна система не розроблені для високої точності синхронізації.


1

Іншим методом було б використання призупинення в циклі та надсилання SIGCONT з точної зовнішньої програми. Надсилання сигналу дуже легке і матиме набагато меншу затримку, ніж щось виконувати. Ви також можете заздалегідь встановити ряд команд із командою "at", навряд чи хтось більше використовує "at". Я не впевнений, наскільки це точно.

Якщо точність є критичною, і ви хочете серйозно поставитися до цього, це виглядає як те саме додаток, де ти зазвичай використовуєш RTOS, який можна зробити під Linux з патч-ядром RT-Preempt, що дасть точність та деяку міру переривання контролю, але це може бути більше турбувати, ніж це варто.

https://rt.wiki.kernel.org/index.php/RT_PREEMPT_HOWTO

Xenomai також може бути корисним, це повна реалізація RTOS і портується для x86 та x86_64, але тут є якесь програмування.

http://www.xenomai.org/index.php/Main_Page


1

З ksh93(у якого плаваюча точка $SECONDSі вбудований sleep)

typeset -F SECONDS=0
typeset -i i=0
while true; do
   cmd
   sleep "$((++i - SECONDS))"
done

З тим самим сценарієм буде працювати і той самий сценарій, zshале він буде викликати sleepкоманду вашої системи . zshмає zselectвбудований, але лише з роздільною здатністю 1/100.


0

Я б поїхав з невеликою програмою на С:

#include <sys/time.h>
#include <unistd.h>

int main(int argc, char **argv, char **envp)
{
    struct timeval start;
    int rc = gettimeofday(&start, NULL);
    if(rc != 0)
            return 1;

    for(;;)
    {
        struct timeval now;
        rc = gettimeofday(&now, NULL);
        useconds_t delay;
        if(now.tv_usec < start.tv_usec)
            delay = start.tv_usec - now.tv_usec;
        else
            delay = 1000000 - now.tv_usec + start.tv_usec;
        usleep(delay);
        pid_t pid = fork();
        if(pid == -1)
            return 1;
        if(pid == 0)
            _exit(execve(argv[1], &argv[1], envp));
    }
}

Ця програма очікує, що програма викличе повний шлях в якості першого аргументу, і передає будь-які інші аргументи. Він не чекатиме завершення команди, тому він із задоволенням запустить кілька примірників.

Також стиль кодування тут справді неохайний, і зроблено ряд припущень, які можуть бути, а може і не бути гарантовані чинними стандартами, тобто якість цього коду "працює для мене".

Ця програма отримає дещо довші або коротші інтервали, коли годинник регулюється NTP або вручну встановлюючи його. Якщо програма повинна впоратися з цим, POSIX забезпечує те, на timer_create(CLOCK_MONOTONIC, ...)що це не впливає.


0

Ви повинні слідкувати за поточним часом і порівнювати його з початковим часом. Таким чином, ви спите розраховану кількість часу на кожну ітерацію, а не на фіксовану суму. Таким чином, ви не будете накопичувати помилки синхронізації і відсуватися від місця, де ви повинні бути, тому що ви скидаєте таймінги кожного циклу до абсолютного часу від початку.

Також деякі функції сну повертаються рано, якщо виникає перерва, тому в цьому випадку вам доведеться знову зателефонувати до способу сну, поки не пройде повний проміжок часу.


0

Ось такий, що баш сценарій і дуже точний. Він використовує usleep для мікросекундної точності.

http://wiki.junkemailfilter.com/index.php/How_to_run_a_Linux_script_every_few_seconds_under_cron

Там є 3 сценарії. Подивіться на ту, що внизу. Зробитиме близько 2400 страт за хвилину з розумною точністю. І це дійсно просто.


0

Цей може працювати щонайменше 100 разів на секунду з дуже точною роздільною здатністю.

Наявність у каталозі кількості циклів за хвилину створює графік. Ця версія підтримує роздільну здатність мікросекунди, передбачаючи, що ваш комп'ютер може впоратися з цим. Кількість страт за хвилину не повинна рівномірно ділитися на 60, а також не обмежуватись 60 Я перевірив її до 6000, і вона працює.

Цю версію можна встановити в каталозі /etc/init.d і запустити як службу.

#! /bin/sh

# chkconfig: 2345 91 61
# description: This program is used to run all programs in a directory in parallel every X times per minute. \
#              Think of this program as cron with microseconds resolution.

# Microsecond Cron
# Usage: cron-ms start
# Copyright 2014 by Marc Perkel
# docs at http://wiki.junkemailfilter.com/index.php/How_to_run_a_Linux_script_every_few_seconds_under_cron"
# Free to use with attribution

# The scheduling is done by creating directories with the number of"
# executions per minute as part of the directory name."

# Examples:
#   /etc/cron-ms/7      # Executes everything in that directory  7 times a minute
#   /etc/cron-ms/30     # Executes everything in that directory 30 times a minute
#   /etc/cron-ms/600    # Executes everything in that directory 10 times a second
#   /etc/cron-ms/2400   # Executes everything in that directory 40 times a second

basedir=/etc/cron-ms

case "$1" in

   start|restart|reload)
   $0 stop
   mkdir -p /var/run/cron-ms
   for dir in $basedir/* ; do
      $0 ${dir##*/} &
   done
   exit
   ;;

   stop)
   rm -Rf /var/run/cron-ms
   exit
   ;;

esac

# Loops per minute is passed on the command line

loops=$1
interval=$((60000000/$loops))

# Just a heartbeat signal that can be used with monit to verify it's alive

touch /var/run/cron-ms

# After a restart the PIDs will be different allowing old processes to terminate

touch /var/run/cron-ms/$$

# Sleeps until a specific part of a minute with microsecond resolution. 60000000 is full minute

usleep $(( $interval - 10#$(date +%S%N) / 1000 % $interval ))

# Deleting the PID files exit the program

if [ ! -f /var/run/cron-ms/$$ ]
then
   exit
fi

# Run all the programs in the directory in parallel

for program in $basedir/$loops/* ; do
   if [ -x $program ] 
   then
      $program &> /dev/null &
   fi
done

exec $0 $loops
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.