Contact me: hankecnc@gmail.com

serial.c 中的 rx_buffer_head #264

推推 grbl 3年前 (2023-01-21) 210次浏览

关闭
momus87 开启了这个问题 2013 年 9 月 2 日 · 14条评论
关闭

serial.c 中的 rx_buffer_head#264

momus87 开启了这个问题 2013 年 9 月 2 日 · 14条评论

注释

serial.c 中的 rx_buffer_head #264

你好,

当我试图为 atmega 编写自己的串行库时,我一直潜伏在 Grbl 存储库中。

在 serial.c 中:
由于 rx_buffer_head 被 ISR(SERIAL_RX) 修改,它不应该声明为 volatile 吗?

serial.c 中的 rx_buffer_head #264

只有当您计划从 ISR以外volatile的其他地方访问该变量时才需要。它只是告诉编译器不要对您的代码做出执行顺序假设(因为从编译器的角度来看,ISR 永远不会被调用)。

serial.c 中的 rx_buffer_head #264
作者

的确,但是 rx_buffer_head 也可以从 serial_read() 访问。

serial.c 中的 rx_buffer_head #264

那么你可能是对的。它似乎并没有在实践中发生,但gcc确实可以得出结论,因为 ISR 永远不会被调用,所以值rx_buffer_head永远不会改变,因此可以用常量替换。

我认为,就像你说的,它应该被标记volatile,只是为了安全起见。

serial.c 中的 rx_buffer_head #264
成员

根据我的经验,只有当两个不同的进程更改并写入变量时才需要分配一个 volatile。例如,主程序和一个 ISR 都写入它或两个 ISR。

当只有一个写入变量时,您不需要分配一个 volatile。任何数量的进程都可以毫无问题地读取它(至少从我所看到的。)我相信编译器只需要知道何时从 RAM 中检索变量的新副本,这样它就不会优化和使用旧的处理器寄存器中的本地一个。

有时,当我看到两个进程写入一个变量时,我也不需要显式声明 volatile。只有当变量写入的编码方式总是从 ram 中新鲜读取变量时,才会发生这种情况。

serial.c 中的 rx_buffer_head #264

volatile 在编译中的作用强制编译器不依赖寄存器获取变量的最后一个值,而是始终从内存中读取它。
因此,即使只有一个进程写入变量并且从两个进程读取变量,它也必须是易变的。

serial.c 中的 rx_buffer_head #264
成员

好吧,这可能是真的,但实际上并不是特别适用于长度为一个字节的共享变量。

我不太确定 AVR 处理器在内部是如何工作的,但不久前我对挥发物的处理方式进行了一些实验。我不是盲目地分配 volatiles 并可能影响编译器优化和代码性能,而是检查哪些有效,哪些无效。在由一个进程写入的单字节变量的情况下,任何其他进程都可以在更改后立即毫无问题地检索它。我不能说这将如何随着其他编译器或其他类型的处理器(如 ARM)而改变,但到目前为止它运行良好。

如果您遇到此问题,请告诉我您使用的是什么编译器和什么芯片。

serial.c 中的 rx_buffer_head #264
作者

看看使用 avr-gcc 访问/修改常规或易失性 1 字节变量之间是否存在性能差异会很有趣。我会尝试检查一下。使用预分频器设置为 1 的计数器应该很容易。

但是如果你不介意的话,我现在有几乎相反的问题要问:
为什么 tx_buffer_tail 声明为 volatile ?:-P
它从 serial_write() 访问并从 ISR(SERIAL_UDRE) 修改,就像 rx_buffer_head 一样,
除非我遗漏了什么。

serial.c 中的 rx_buffer_head #264

@chamnit: 请理解这纯粹是靠运气。根据定义,任何编译器的“volatile”存在的原因只有一个:告诉编译器不要重新使用已经获取的变量副本,因为它可能同时被更改,并且编译器再次根据定义,无法预见,因为这是一个运行时事件,而不是编译时事件。

显然,这只会影响正在测试的代码,如果变量的意外更改恰好发生在原始获取点和重新使用获取值的点之间,即使原始值已更改 – 这通常相当靠近 – 因此机会通常很小。没有任何 MCU 能够幸免于此,包括 AVR。

显然,在中断之外使用一次的值(因此每次执行该部分代码时都会获取)不会有这个问题,但关键是除非亲自彻底检查生成的代码,否则永远无法确定是什么或不会发生在引擎盖下。对于任何类型的适当编码,任何非零机会都是不可接受的,这就是存在“volatile”的原因。

郑重声明 – 它所招致的所有“惩罚”(即每次使用变量时都强制执行新的提取)要么不适用(如果没有多次提取)要么不可或缺 – 避免代码成为火车残骸等待发生。

serial.c 中的 rx_buffer_head #264
成员

@blinkenlight: “trainwreck”这个说法有点过于夸张了。请理解,这并非纯粹靠运气,而是对 AVR 中发生的事情进行了彻底测试。如果没有将变量分配为 volatile 有任何问题,我们所要做的就是分配它。如果它工作正常,那么就没有理由灌输这种“惩罚”,特别是如果快速且高频地调用 volatile 变量。grbl 代码库并没有那么复杂或那么长,但是无论何时何地我们都可以改进它,性能很重要。

这绝不是说 grbl 代码是完美的,从缺乏规划器的覆盖处理可以看出(我们说话时正在处理)。我不明白我们为什么要为此争论不休,因为事情似乎运行良好,而且我们的用户还没有报告任何“奇怪的事情”。我相信我们会在 ARM 处理器上重新审视这一切。我知道@aldenhart已经解决了过去几个月的问题。

serial.c 中的 rx_buffer_head #264

恐怕你试图在最坏的情况下节省 1 个周期(在大多数情况下什么都没有)……这不是我所说的性能改进。我认为你应该专注于更耗时的事情:除法、取模、数据排序、内联等……
如果你不想遵循“良好实践”规则,因为你“认为”它可以工作,好吧,让我们开始吧,但是在你的私人项目中。
在公共项目中,您必须意识到做出这样的决定的后果,这是不可接受的。因为没有人能预料到一切,所以制作可运行软件的唯一方法就是遵守规则。想想一个人试图为不同的体系结构编译源代码……

serial.c 中的 rx_buffer_head #264
成员

@romain145: 哇。这个不稳定的问题已经失控,几乎变成了一场口水战。

我看到你是新来的。我假设您已经查看了代码。相信我,在过去三年中我已经做了很多工作来优化代码,如您所说,删除除法、取模等。如果您对进一步改进它有任何建议,除了这里的主题,请将您的想法和改进发送给我们。

我不太明白的是为什么你对不稳定的“良好做法”如此坚定。我从来没有说过我们不会。我已经说过很多次,现在没有理由这样做。如果它没有坏,就不要修理它。当我提出 ARM 问题和编译 grbl 时,我们将在到达那里时跨过那座桥。

serial.c 中的 rx_buffer_head #264

火焰战争 ;-)
我在上一个回答中可能有点过分,但 volatile 确实很重要,因为你可能在 100 年内有 1 个错误,但每个人都知道墨菲定律 :)
我遇到了那种问题,非常非常难调试。所以现在,每次我发现一个可疑的错误原因,我都会解决它。
我以前在µC上教编程,volatile是学生最难理解的东西之一,因为看不到效果。这就是为什么经过几十年或 C 编程后,出现了良好的实践并且必须遵循,即使您不想要 :)。
有一天我学到了一件事:永远不要认为你比编译器更聪明。

serial.c 中的 rx_buffer_head #264

@chamnit:请不要把这个当成个人问题——我不是在评论该指令的这种特定用途,而是在评论它在这种情况下的一般不可或缺性。我没有研究代码在这里被编译成什么,这个特定用例完全有可能不构成和/或没有被编译成危险代码。

然而,对于在中断中修改变量并且也在代码中的其他地方读取或写入的变量的一般情况,“volatile”不是可选的,“trainwreck”是完全合理的——更重要的是,“volatile”甚至还不够:对于确定性行为,需要在处理相关变量时完全禁用中断——我想说最常见的用例正是缓冲区指针之一。我认为我不需要解释其中一个由于不幸的中断发生而意外跳来跳去的结果;当这种情况发生时,人们跳过/重新解析缓冲区内容和/或指针可能会完全耗尽缓冲区,具体取决于具体实现。如果存在这样的错误,我总是认为代码具有不可接受的危险性,无一例外——不管它实际出现故障的频率如何。

一些由此产生的错误可能会偶然地自我纠正,并且没有明显的影响,但有些错误肯定不会. 例如,主代码中一个简单的“x=x+N”被“x=0”打断,如果它恰好发生在获取“x”和加法+存储操作之间,将很高兴地完全失去重置动作,除非该操作被编译成一个原子单指令操作,这正是人们无法知道的,除非在汇编级别研究编译代码的特定实例(每次代码被重新编译!)。在这种情况下,“volatile”限定符甚至还不够。相反,如果所有的主要代码都是“x=y”,那么无论具体的最终汇编形式如何,都不会出现错误。相反的行动正是我们正在讨论的——“y=x”会导致不同的结果,这取决于“x”是否

同样,我不能说哪种情况适用于上面提出的问题,我是第一个承认我没有专门研究它的人。我只是想指出,在这种情况下,某些行为不仅仅是最佳实践,它们是可靠代码与随机触发的严重错误代码之间的唯一区别——而且这种问题无法通过任何数量的“测试”消除研究代码如何执行或编译器通常倾向于生成哪种代码,但只能通过查看 C 代码的特定用例和/或生成的每个版本的二进制文件的汇编列表,除非有人采取可以保证这种情况不会发生的适当预防措施。

serial.c 中的 rx_buffer_head #264
成员

@romain145: 谢谢你的好建议。我会牢记这一点。

@blinkenlight: 也许我需要进一步澄清我之前的评论。我知道“易失性”对于长度超过一个字节的变量非常重要,并且已经看到您注意到对代码造成灾难性或“火车失事”行为的影响。但是我没有看到的是易失性变量中同样的灾难性行为,其中一个字节被一个进程写入并被其他进程读取。也许,是 AVR 或编译器始终正确且一致地从内存中检索字节。我不确定 100%。为了以防万一,我会查看问题线程以确保这不是某些用户问题的原因,但我认为这是孤立的。

喜欢 (0)