我写了一个“设备驱动程序”(请参阅此处的源代码:https://bitbucket.org/wothke/websid/src/master/raspi/websid_module/)在大多数情况下运行良好(请参阅https://www.youtube.com/watch?v=bE6nSTT_038)但似乎仍然有可能偶尔随机使设备崩溃。
“设备驱动程序”启动一个 kthread,执行一个简单但时序关键的播放循环,其中它通过多个 GPIO 引脚控制某些连接的音频芯片。该 kthread 在“隔离”CPU 核心上运行(使用 kthread_bind),该核心应该在很大程度上免于常规内核使用(请参阅下面有关内核配置的详细信息)。通过 sched_set_fifo 为 kthread 赋予高优先级。 kthread 不进行子例程调用,也不需要任何先前尚未在内核中分配的内存。 (该线程还使用 get_cpu、local_irq_save 和 local_bh_disable 暂时禁用任何可能干扰其计时的内容。但是,这些似乎并不是偶发崩溃的根本原因,因为即使未使用该禁用,也可能会重现崩溃。)
我编译了一个常规的“Raspberry OS”“Desktop”内核,但我专门激活了 NO_HZ_FULL(即“完整动态系统(无滴答)”)。另外,我还专门通过 cmdline.txt 隔离核心 #3: isolcpus=3 rcu_nocbs=3 rcu_nocb_poll=3 nohz_full=3 (这似乎使大多数 IRQ 远离 cpu 核心 #3 - 按预期,这样我上面的 kthread 应该很漂亮独自在核心#3)
最常见的嫌疑可能是“共享内核内存”缓冲区,该缓冲区用于上述“播放”kthread 与“用户空间”中数据的生产者之间的所有通信。我已经采取了我能想到的所有预防措施来避免潜在的竞争条件,但也许存在某种 CPU 缓存效应,或者我忽略的其他东西。“共享缓冲区”包含 4 个已设置的页面对齐区域/以这样的方式使用应该确保安全通信/同步。
第一页仅包含一个 32 位标志,可作为 u32 或 uint32_t 访问(这自然应该是原子的)。 kthread 仅在该标志为 0 时更新该标志,并且仅将其设置为非 0。用户态代码仅将此标志重置为 0,并且仅当它具有某个非 0 值时 - 从而确认它收到了 kthread 设置的非 0 值。
第二页包含类似的标志,如 1),但方向相反,即这里是 kthread 将从“userland”接收非 0 的内容。
然后,第三个(+后续)页面包含用于简单双缓冲方案的第一个缓冲区。该缓冲区由“用户区”生产者独占写入,并由 kthread 独占读取。通过 2 个标志实现的“ping/pong”协议旨在确保缓冲区“永远不会”同时使用:kthread 通过发信号通知其中一个缓冲区可以被填充来启动一个序列,然后“userland”信号返回它已完成填充相应的缓冲区,即 kthead 仅在看到来自生产者的信号(现在可以安全地执行此操作)后才开始从缓冲区读取(在“userland”生产者发出该信号之前,它使用 msync(start_page, len, MS_INVALIDATE) 来报告共享内存区域的哪些部分已更新。)。
然后,第 n 个(+以下)页面包含第二个缓冲区(3)中所述的所有内容也适用于此处)
但即使上面出现问题,也可能会阻塞 kthread 或相应的用户态进程..但我不明白为什么这会导致整个系统崩溃。
对我来说最合理的解释是,如果“共享缓冲区”被随机重新定位(从而导致随机内存损坏),但我认为这不应该发生在通过以下方式分配的缓冲区上:
_raw_buffer = kmalloc(AREA_SIZE + 2*PAGE_SIZE,
GFP_KERNEL & ~__GFP_RECLAIM & ~__GFP_MOVABLE);
或者,如果有一些内核函数专门进入阻塞等待来自核心 #3 的某些内容(这可能不会发生,因为我的 kthread 耗尽了该 CPU 上的其他所有内容......)..但是我会很惊讶为什么这样一个那么问题只会偶尔出现,而不是一直让机器崩溃。
有任何想法吗?
答案1
在我的代码中的每个合理点添加“内存屏障”未能改善这种情况之后,我终于找到了一种可行的解决方法。该问题似乎根本与共享内存无关。相反,它似乎是由调度程序触发的,并且在我长时间运行的 kthread 中添加对“schedule()”的调用似乎确实避免了系统冻结。
不幸的是,这个解决方法对我来说不是一个可行的解决方案,我创建了一个单独的线程来进一步探索它所采取的方向:有没有办法在不调用schedule()的情况下使用长时间运行的kthread?