1

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

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


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

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

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

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

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

  1. 使用辅助线程自动递减,频率接近 60 Hz,使用nanosleep(). 我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);
    }
}

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

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

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

4

1 回答 1

2

不要为此使用线程和同步原语(信号量、共享内存等)。事实上,我想说的是:除非您明确需要多处理器并发,否则不要将线程用于任何事情。同步很难做到正确,当你出错时更难调试。

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

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

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

于 2016-07-12T23:17:37.793 回答