CHIP8 in C - 如何正确处理延时定时器?

log*_*ter 2 c linux multithreading emulation chip-8

TL;DR我需要用 C 语言模拟一个计时器,允许并发写入和读取,同时保持 60 Hz 的恒定递减(不完全准确,但大致准确)。它将成为 Linux CHIP8 模拟器的一部分。使用具有共享内存和信号量的基于线程的方法会引发一些准确性问题,以及取决于主线程如何使用计时器的竞争条件。

设计和实现这种计时器的最佳方法是什么?


我正在用 C 语言逐个模块地编写一个 Linux CHIP8 解释器,以便深入了解仿真的世界。

我希望我的实现尽可能准确地符合规范。在这方面,计时器已被证明对我来说是最困难的模块。

以延迟计时器为例。在规范中,它是一个“特殊”寄存器,最初设置为 0。有特定的操作码可以向寄存器设置值并从寄存器中获取值。

如果寄存器中输入的值不为零,它将自动开始以 60 Hz 的频率递减,一旦达到零就停止。

我对其实施的想法包括以下内容:

  1. 使用辅助线程,通过使用以接近 60 Hz 的频率自动进行递减nanosleep()。我fork()暂时用来创建线程。

  2. 使用共享内存 viammap()来分配定时器寄存器并在其上存储其值。这种方法允许辅助线程和主线程读取和写入寄存器。

  3. 使用信号量来同步两个线程的访问。我使用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);
    }
}
Run Code Online (Sandbox Code Playgroud)

我认为这种实现的一个潜在问题依赖于第三点。如果程序碰巧在计时器寄存器达到非零值时更新计时器寄存器,则辅助线程不得等待主线程解锁资源,否则将无法满足 60 Hz 延迟。这意味着两个线程都可以自由地更新和/或读取寄存器(在辅助线程的情况下不断写入),这显然会引入竞争条件。

一旦我解释了我正在做的事情以及我试图实现的目标,我的问题是:

设计和模拟允许并发写入和读取同时保持可接受的固定频率的计时器的最佳方法是什么?

小智 5

不要为此使用线程和同步原语(信号量、共享内存等)。事实上,我什至想说:除非您明确需要多处理器并发,否则不要使用线程。同步很难做到正确,如果同步错误,调试起来就更困难。

相反,找出一种在单线程中实现这一点的方法。我推荐以下两种方法之一:

  1. 跟踪最后一个值写入定时器寄存器的时间。从寄存器读取时,计算它被写入的时间,并从结果中减去适当的值。

  2. 跟踪总共执行了多少条指令,并每 N 条指令从定时器寄存器中减 1,其中 N 是一个很大的数字,使得 N 条指令约为 1/60 秒。