20

最後のうるう秒に触発されて、POSIX 呼び出しを使用してタイミング (具体的にはインターバル タイマー) を調査してきました。

POSIX はタイマーを設定する方法をいくつか提供していますが、どれも問題があります:

  • sleepそして、nanosleepこれらは信号によって中断された後に再起動するのが面倒であり、クロック スキューが発生します。追加の作業を行うことで、このスキューのすべてではありませんが一部を回避できますが、これらの関数はリアルタイム クロックを使用するため、落とし穴がないわけではありません。
  • setitimerまたはより現代的なtimer_settimeもの — これらはインターバル タイマーとして設計されていますが、プロセスごとであるため、複数のアクティブなタイマーが必要な場合に問題になります。また、同期的に使用することもできませんが、それほど大したことではありません。
  • clock_gettimeclock_nanosleep一緒に使用すると、正しい答えのように見えCLOCK_MONOTONICます。clock_nanosleepは絶対タイムアウトをサポートしているため、スリープしてタイムアウトをインクリメントし、繰り返すことができます。中断後の再起動も簡単です。残念ながら、これらの関数は Linux 固有のものである可能性もあります。Mac OS X や FreeBSD ではサポートされていません。
  • pthread_cond_timedwaitは Mac で利用可能でありgettimeofday、無愛想な回避策として使用できますが、Mac ではリアルタイム クロックでしか機能しないため、システム クロックが設定されている場合やうるう秒が発生した場合に誤動作する可能性があります。

不足している API はありますか? UNIX ライクなシステムで適切に動作するインターバル タイマーを作成する、適度に移植性のある方法はありますか?

行儀がよく、適度に移植性があるとは、次のことを意味します。

  • クロック スキューが発生しにくい (もちろん、システム クロック自体のスキューを除く)
  • 設定中のシステム クロックまたはうるう秒の発生に対する回復力
  • 同じプロセスで複数のタイマーをサポートできる
  • 少なくとも Linux、Mac OS X、および FreeBSD で利用可能

うるう秒に関するメモR..の回答に応じて):

POSIX の 1 日の長さは正確に 86,400 秒ですが、実際の 1 日がこれより長くなったり短くなったりすることはめったにありません。システムがこの不一致を解決する方法は実装によって定義されますが、うるう秒が前の秒と同じ UNIX タイムスタンプを共有するのは一般的です。参照:うるう秒とその処理方法.

Linux カーネルのうるう秒のバグは、時計を 1 秒戻した後にハウスキーピングを実行できなかったことが原因でした: https://lkml.org/lkml/2012/7/1/203。そのバグがなくても、時計は1 秒戻っていたでしょう。

4

4 に答える 4

6

kqueuekeventこの目的のために利用することができます。OSX 10.6 と FreeBSD 8.1EVFILT_USERでは、別のスレッドからイベント ループを起動するために使用できる のサポートが追加されています。

これを使用して独自の条件と timedwait を実装する場合、キューのイベントを「見逃す」ことができないため、この優れた回答とは対照的に、競合状態を回避するためにロックは必要ないことに注意してください。

ソース:

サンプルコード

でコンパイルclang -o test -std=c99 test.c

#include <sys/types.h>
#include <sys/event.h>
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>

// arbitrary number used for the identifier property
const int NOTIFY_IDENT = 1337;

static int kq;

static void diep(const char *s) {
   perror(s);
   exit(EXIT_FAILURE);
}

static void *run_thread(void *arg) {
    struct kevent kev;
    struct kevent out_kev;
    memset(&kev, 0, sizeof(kev));
    kev.ident = NOTIFY_IDENT;
    kev.filter = EVFILT_USER;
    kev.flags = EV_ADD | EV_CLEAR;

    struct timespec timeout;
    timeout.tv_sec = 3;
    timeout.tv_nsec = 0;

    fprintf(stderr, "thread sleep\n");

    if (kevent(kq, &kev, 1, &out_kev, 1, &timeout) == -1)
        diep("kevent: waiting");

    fprintf(stderr, "thread wakeup\n");

    return NULL;
}

int main(int argc, char **argv) {
    // create a new kernel event queue
    kq = kqueue();
    if (kq == -1)
        diep("kqueue()");


    fprintf(stderr, "spawn thread\n");
    pthread_t thread;
    if (pthread_create(&thread, NULL, run_thread, NULL))
        diep("pthread_create");

    if (argc > 1) {
        fprintf(stderr, "sleep for 1 second\n");
        sleep(1);
        fprintf(stderr, "wake up thread\n");

        struct kevent kev;
        struct timespec timeout = { 0, 0 };

        memset(&kev, 0, sizeof(kev));
        kev.ident = NOTIFY_IDENT;
        kev.filter = EVFILT_USER;
        kev.fflags = NOTE_TRIGGER;

        if (kevent(kq, &kev, 1, NULL, 0, &timeout) == -1)
            diep("kevent: triggering");
    } else {
        fprintf(stderr, "not waking up thread, pass --wakeup to wake up thread\n");
    }

    pthread_join(thread, NULL);
    close(kq);
    return EXIT_SUCCESS;
}

出力

$ time ./test
spawn thread
not waking up thread, pass --wakeup to wake up thread
thread sleep
thread wakeup

real    0m3.010s
user    0m0.001s
sys 0m0.002s

$ time ./test --wakeup
spawn thread
sleep for 1 second
thread sleep
wake up thread
thread wakeup

real    0m1.010s
user    0m0.002s
sys 0m0.002s
于 2015-07-02T02:42:22.577 に答える
5

POSIX タイマー ( timer_create) はシグナルを必要としません。SIGEV_THREADタイマーの有効期限が通知タイプを介してスレッドで配信されるように手配することもできます。残念ながら、glibc の実装では実際には有効期限ごとに新しいスレッドが作成されます (これには多くのオーバーヘッドがあり、リアルタイム品質の堅牢性の希望が失われます)。ただし、標準では有効期限ごとに同じスレッドの再利用が許可されています。

それ以外では、インターバル タイマーを使用clock_nanosleepする独自のスレッドを作成することをお勧めします。一部の壊れたシステムにはこれらのインターフェイスがない可能性があると述べたので、そのようなシステムに(たとえばに基づいて)ドロップイン実装を行うだけで、単調クロックがないために品質が低下する可能性があると考えられますが、これは単にこれは、MacOSX のような低品質の実装を使用することの根本的な制限です。TIMER_ABSTIMECLOCK_MONOTONICpthread_cond_timedwait

うるう秒に関する懸念については、うるう秒が発生したときに ntpd などによってリアルタイム クロックが逆方向にジャンプする場合、それは ntpd の重大なバグです。POSIX 時間 (エポックからの秒数) は、SI 秒ではなく、標準ごとの暦秒 (正確には 1 日の 86400 分の 1) の単位であるため、うるう秒ロジックが POSIX システムに属する唯一の場所 (どこかにある場合) はmktime// gmtime/と故障時間のlocaltime間で変換するとき。time_t今回発生したバグは追跡していませんが、基本的な問題ではなく、システム ソフトウェアが多くの愚かで間違ったことを行ったことが原因のようです。

于 2012-07-05T11:36:04.487 に答える
1

ここでエミュレーションの質問を見ることができますclock_gettime。これについても回答を提供しましたが、私も助けてくれました。私は最近、POSIX 呼び出しを部分的にエミュレートする Mac OS X のタイミング用に保持している小さなリポジトリに単純なタイマーを追加しました。簡単なテストでは、タイマーを 2000Hz で実行します。レポはPosixMachTimingと呼ばれます。やってみて。

PosixMachTiming はMachに基づいています。タイミング関連の Mach API の一部は Apple のページから姿を消し、廃止されたようですが、まだソース コードの断片があちこちに散らばっています。ここにあるAbsoluteTimeユニットとカーネルの抽象化は、物事を行う新しい方法のようです。とにかく、PosixMachTimingリポジトリはまだ機能しています。

PosixMachTiming の概要

clock_gettimeCLOCK_REALTIMEと呼ばれるシステム リアルタイム クロックを利用する mach 関数呼び出しによってエミュレートされますCALENDAR_CLOCK

は、グローバル変数 ( ) を使用してclock_gettimeエミュレートされます。このクロックは、コンピュータの電源がオンになったとき、またはウェイクアップしたときに初期化されます。わからない。いずれにせよ、関数が呼び出すのはグローバル変数です。CLOCK_MONOTONICextern mach_port_t clock_portmach_absolute_time()

clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, ...)nanosleep現在時刻と絶対単調時間との差を使用してエミュレートされます。

itimer_start()ターゲット絶対単調時間の呼び出しに基づいitimer_step()ています。clock_nanosleepクロック スキューが問題にならないように、反復ごとに (現在の時間ではなく) タイム ステップでターゲット時間をインクリメントします。

これは、同じプロセスで複数のタイマーをサポートできるという要件を満たさないことに注意してください。

于 2016-04-26T19:33:10.283 に答える