C 中的 CHIP8 - 如何正确处理延迟计时器?

CHIP8 in C - How to correctly handle the delay timer?

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

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


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

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

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

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

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

  1. 使用辅助线程自动递减,使用nanosleep()以接近60赫兹的频率。我暂时用fork()创建线程

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

  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);
    }
}

我看到这种实现的一个潜在问题取决于第三点。如果一个程序碰巧 更新 定时器寄存器一旦达到 不同于零 的值,那么辅助线程 不能 等待主线程解锁资源,否则60Hz延迟将无法完成。这意味着两个线程都可以自由更新 and/or 读取寄存器(在辅助线程的情况下不断写入),这显然引入了竞争条件。

一旦我解释了我在做什么以及我试图实现什么,我的问题是:

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

不要为此使用线程和同步原语(信号量、共享内存等)。事实上,我什至会说:不要将线程用于 任何东西,除非您明确需要多处理器并发。同步很难做对,做错了更难调试

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

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

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