時間の経過とともにタイムラグを累積することなく、非常に短い間隔で正確にunixコマンドを実行します


38

質問

長期間にわたって正確に毎秒UNIXコマンドを実行できるようにしたいと思います。

コマンド自体が実行に必要な時間のため、一定の時間後に遅れないソリューションが必要です。sleepwatch、および特定のpythonスクリプトはすべてこの点で私を失敗させました。

http://Arduino.ccなどのマイクロコントローラでは、ハードウェアクロック割り込みを介してそれを行います。同様の時間精度のシェルスクリプトソリューションがあるかどうかを知りたいです。StackExchange.com内で見つけたすべてのソリューションでは、数時間以上実行すると、顕著なタイムラグが生じました。以下の詳細を参照してください。

実用目的/アプリケーション

nc1秒ごとに(netcat)を介してタイムスタンプを送信することにより、ネットワーク接続が継続的にアップしているかどうかをテストしたいと思います。

送信者:

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

レシーバー:

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

完了したら、2つのログを比較します。

diff netcat-sender.txt netcat-receiver.txt

差分は未送信のタイムスタンプになります。これにより、LAN / WAN / ISPがいつトラブルを起こすかがわかります。


ソリューションスリープ

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%オフ

ソリューションREPEAT PYTHON

発見: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
必要な解像度、精度は何ですか?なぜそれが必要なのですか/何のために使用していますか?
ジッピー

nice眠っているプロセスがどうなるのでしょうか?
ティロヴィクランド

1
ただし、リアルタイムスケジューリングを使用して、sleep()呼び出しの遅延を最小限に抑えることをお勧めします。
mdpc

あなたのタイミングは常にボックスの現在の動作状態の影響を受けることになります。何かが気になるプログラムをキャッシュからアンロードする原因になった場合、プログラムの典型的な実行時間が必要な間隔よりも大幅に短いことを保証できない限り、時間が増加します。リアルタイムシステム、他の誰もログオンしていない大幅に簡素化されたセットアップ、またはシングルユーザーモードにしたいと思います。おそらくより良い解決策は、ループを別のプログラムから呼び出すのではなく、ループ自体を実行するように問題のプログラムを変更することです。
ハック見た

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'

1回スキップせずに45分間実行されており、a)システムの負荷が高くなりすぎてfork()に1秒以上かかる、またはb)うるう秒が挿入されない限り、継続して実行すると思われます。

ただし、ある程度のオーバーヘッドがあるため、コマンドが正確な秒間隔で実行されることを保証することはできませんが、割り込みベースのソリューションよりもはるかに悪いとは思いません。

date +%N(ナノ秒、GNU拡張)で約1時間実行し、統計を実行しました。最大の遅延は1 155マイクロ秒でした。平均(算術平均)216 µs、中央値219 µs、標準偏差42 µs。95%の時間で270 µsよりも速く動作しました。私はあなたがCプログラム以外でそれを打つことができるとは思わない。


1
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.私の保存されたスクリプト内の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を使用すると-std=c99、戻り値が欠落しているという警告は表示されません。それ以外の場合、特別なものは必要ありません。余分なゼロを誤って入力しましたか?strace ./tickそれはシステムコールの観点からやっているものを紹介します
デイブ

取得:gcc -O2 -std = c99 -o tick tick.c tick.c:関数 'main'内:tick.c:10:5:警告:関数 'ualarm'の暗黙的な宣言[-Wimplicit-function-declaration ] tick.c:関数 'tick'内:tick.c:5:10:警告:属性warn_unused_result [-Wunused-result]で宣言された 'write'の戻り値を無視します::私のシステム(Ubuntu 12.04)はそうするようですそれをサポートしていません。ただし、少なくともualarmが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"

manページから:

通常、この間隔は、コマンドの1つの実行が完了してから次の実行が開始されるまでの時間の長さとして解釈されます。ただし、-pまたは--preciseオプションを使用すると、interval秒ごとにコマンドの実行を監視することができます。ntptimeで試して、秒の小数部が連続的に増加する通常モードとは対照的に、小数秒が(ほぼ)同じままであることに注意してください。

ただし、システムでパラメーターを使用できない場合があります。

また、プログラムの実行に1秒以上必要な場合に何が起こるかを考慮する必要があります。次のスケジュールされた実行はスキップされるべきですか、それとも遅れて実行されるべきですか?

更新:しばらくスクリプトを実行しましたが、1つのステップを失うことはありませんでした。

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そのオプションをサポートしているのはどのバージョンですか?私がチェックしたどのマシンにもありませんでした。
タイラー

そのバージョン0.3.0は、Ubuntu 12.04の現在のバージョンです。procpsパッケージのバージョン3.2.8-11ubuntu6から来ています。
ダニエルクルマン

うーん、procpsソースパッケージはサポートしていません--precise。これはDebianの追加(3.2.8-9、watch_precision_time.patch)
ダニエルクルマン

1
OK ストレス(ディスクとコアに負荷をかける)と組み合わせてテストしたところ、次のようになりました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秒が次の1秒と重複する可能性があります。

質問と同じ形式で、いくつかの結果をトスに編集します:

$ 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
それ
はい

2
@hhaamu何がugいの?PC上の汎用OSは、非常に正確なタイミングが重要な操作向けに設計されていないため、さらに何を期待できますか?「エレガント」で絶対に正確なタイミングが必要な場合は、別のCPUスケジューラを使用するか、リアルタイムカーネルに切り替えるか、専用のハードウェアを使用するなどする必要があります。これは完全に正当なソリューションであり、ダウン投票。確かに、cronによる定期的な再同期を行わずに「バックグラウンドで実行する」だけのものに対する改善です。
jw013

1
さらに、停止するのは簡単です。サイクルの途中でそれを殺すことを危険にさらす必要はありません-crontabからエントリを削除し、分の終わりに自動的に終了します。
イズカタ

システム上でcron秒単位の精度が高いのは幸運なことですが、一般的にそうはありません
ドミトリーグリゴリエフ

15

あなたの問題は、あなたがプログラムを実行した後、あなたが最後に眠ってから経過した時間を考慮せずに、一定時間眠っているということです。

これを行うにはbashまたはその他のプログラミング言語を使用できますが、重要なのはクロックを使用して次のスリープをスケジュールする時間を決定することです。寝る前に、時計をチェックし、残り時間を確認して、違いを眠りましょう。

プロセススケジューリングの妥協により、クロックティックですぐにウェイクアップすることは保証されませんが、かなり近い(アンロード数ミリ秒以内、またはロード中数百ミリ秒以内)必要があります。また、スリープサイクルごとに再同期を行い、蓄積されたエラーを削除するたびに、時間の経過とともにエラーが蓄積されることはありません。

あなたが正確に時計の目盛りを打つ必要がある場合、あなたが探しているのはこの目的のために正確に設計されたリアルタイムオペレーティングシステムです。


プログラムporgが意図したプロセスの実行中にブロックをテストした可能性も非常に高いと思います-論理的には、実行中のマシンを殺さないようにするためです。
symcbean

ブロックするかどうかにかかわらず、メカニズムは正常に機能します。ブロックすると、ブロックした後の残り時間スリープします。ブロックしないと、タイミングスレッドまたはプロセスがスリープ状態になり、他方が動作します。どちらにしても、同じ結果。
タイラー

@tylerl:具体的なコマンドラインは、ソリューションにとってどのように見えますか?
ポーグ


1秒未満のdate +%S.%N精度で秒数を取得し、1秒未満の精度でスリープするために使用する必要がある@porgですがusleep、その後は数学の問題です。
タイラー

7

私はいつもちょうど実行何か持っ上に与えてくれたまさに間隔にします。Cプログラムを作成する必要があり、1秒間隔の部分が独自のコードを超えないように細心の注意を払う必要があると思います。これを機能させるには、おそらくスレッド化または複数の相互通信プロセスを使用する必要があります。スレッド開始時間またはプロセス開始時間のオーバーヘッドを避けるように注意してください。

1993年に関連する日付のように思われる1つの参照:CPU使用率の推定とコードプロファイリング用のランダム化サンプリングクロック 付録「敵のソースコード」を調べて、時間間隔を正確に測定し、「目覚めた」ことを確認してください。ちょうど正しい時間に彼らのプログラム。コードは19年前のものであるため、直接または簡単に移植できない可能性がありますが、コードを読んで理解しようとすると、関連する原則がコードをガイドする可能性があります。

編集:役立つ可能性のある別の参照を見つけました:インタラクティブでソフトなリアルタイムプロセスのスケジューリングに対するクロック解像度の影響


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

私のコンピューターでは、これにより20分で2つのエラーが発生し、1分あたり0.1でした。これは、実行中の約5倍の改善です。


問題sleep 1は、少なくとも 1秒はスリープすることが保証されていることです。したがって、エラーが蓄積されます。
ハハム

システムで元のコードを実行し、OPと同じ結果を取得していない限り、2台の異なるコンピューターからのタイミング結果を比較することはまったく意味がありません。
ドミトリーグリゴリエフ

1

glyいですが、それは動作します。このようなループが必要な場合は、おそらくプログラムの設計を再考する必要があります。基本的に、現在の秒全体が以前にチェックされた秒と等しいかどうかをチェックし、秒が変更されてからのナノ秒数を出力します。精度は、スリープ.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"が1秒未満より長くかからない場合に限ります。スリープ期間を増やすことでCPU負荷を下げることができます。または、本当に気にしない場合は、スリープコマンドをに置き換えてください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

基本的にイベントのCPUポーリングを行い、CPUサイクルを浪費するため、これは悪い習慣です。おそらく、タイマー割り込み(bashでは不可能)にアタッチするか、マイクロコントローラーのような専用ハードウェアを使用する必要があります。PCとそのオペレーティングシステムは、高いタイミング精度のために設計されていません。


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

With ksh93(浮動小数点$SECONDSと組み込みがありますsleep

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

同じスクリプトでも機能しzshますが、システムのsleepコマンドを呼び出します。zshzselect組み込みですが、解像度は1/100のみです。


0

私は小さなCプログラムで行きます:

#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

これは、非常に正確な解像度で1秒に少なくとも100回実行できます。

1分あたりのループ数のディレクトリが存在すると、スケジュールが作成されます。このバージョンは、コンピューターが処理できると仮定してマイクロ秒の解像度をサポートします。1分あたりの実行回数は、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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.