TL;DR 60 Hz で一定のデクリメントを維持しながら (正確ではありませんが、ほぼ正確です)、同時書き込みと読み取りを可能にするタイマーを C でエミュレートする必要があります。これは、Linux CHIP8 エミュレーターの一部になります。共有メモリとセマフォでスレッドベースのアプローチを使用すると、精度の問題が発生するだけでなく、メイン スレッドがタイマーを使用する方法に応じて競合状態が発生します。
そのようなタイマーを考案して実装する最良の方法はどれですか?
エミュレーションの世界に飛び込むために、モジュールごとに C で Linux CHIP8 インタープリターを作成しています。
実装が仕様で可能な限り正確であることを望みます。その点で、タイマーは私にとって最も難しいモジュールであることが証明されています。
遅延タイマーを例にとってみましょう。仕様では、これは「特別な」レジスタであり、最初は 0 に設定されています。値を設定してレジスタから取得する特定のオペコードがあります。
ゼロ以外の値がレジスタに入力されると、60 Hz の周波数で自動的にデクリメントを開始し、ゼロに達すると停止します。
その実装に関する私の考えは次のとおりです。
を使用して約 60 Hz の周波数で自動的に減分を行う補助スレッドの使用
nanosleep()
。fork()
とりあえずスレ立てに使ってます。mmap()
タイマー レジスタを割り当て、その値を格納するための共有メモリの使用。このアプローチにより、補助スレッドとメイン スレッドの両方がレジスタから読み書きできるようになります。両方のスレッドのアクセスを同期するためのセマフォの使用。私は
sem_open()
それを作成し、共有リソースをロックおよびロック解除するためにそれぞれ使用sem_wait()
します。sem_post()
次のコード スニペットは、概念を示しています。
void *p = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0);
/* Error checking here */
sem_t *mutex = sem_open("timersem", O_CREAT, O_RDWR, 1);
/* Error checking and unlinking */
int *val = (int *) p;
*val = 120; // 2-second delay
pid_t pid = fork();
if (pid == 0) {
// Child process
while (*val > 0) { // Possible race condition
sem_wait(mutex); // Possible loss of frequency depending on main thread code
--(*val); // Safe access
sem_post(mutex);
/* Here it goes the nanosleep() */
}
} else if (pid > 0) {
// Parent process
if (*val == 10) { // Possible race condition
sem_wait(mutex);
*val = 50; // Safe access
sem_post(mutex);
}
}
このような実装で見られる潜在的な問題は、3 番目の点に依存しています。ゼロ以外の値に達した後にプログラムがたまたまタイマー レジスタを更新した場合、補助スレッドはメイン スレッドがリソースのロックを解除するのを待機してはなりません。そうしないと、60 Hz の遅延が満たされません。これは、両方のスレッドがレジスタを自由に更新および/または読み取ることができることを意味し (補助スレッドの場合は一定の書き込み)、明らかに競合状態が発生します。
私が何をしているのか、何を達成しようとしているのかを説明したら、私の質問は次のとおりです。
許容可能な固定周波数を維持しながら、同時書き込みと読み取りを可能にするタイマーを考案してエミュレートする最良の方法はどれですか?