开源改变世界!!

serial.c 中的 rx_buffer_head #264

技术 grbl 2年前 (2022-10-31) 254次浏览 0个评论
关闭
momus87 打开了这个问题 on 2 Sep 2013 · 14 条评论
关闭

serial.c 中的 rx_buffer_head#264

momus87 打开了这个问题 on 2 Sep 2013 · 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
作者

妈妈87 评论 on 2 Sep 2013

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

serial.c 中的 rx_buffer_head #264

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

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

serial.c 中的 rx_buffer_head #264
成员

尚尼特 评论 on 2 Sep 2013

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

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

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

serial.c 中的 rx_buffer_head #264

编译器中 volatile 的影响迫使编译器不依赖寄存器来获取变量的最后一个值,而是始终从内存中读取它。
因此,即使只有一个进程写入变量并从两者读取变量,它也必须是易失的。

serial.c 中的 rx_buffer_head #264

嗯,这可能是真的,但在实践中,对于长度为一个字节的共享变量并不是特别如此。

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

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

serial.c 中的 rx_buffer_head #264
作者

妈妈87 评论 2013 年 9 月 3 日

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

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

serial.c 中的 rx_buffer_head #264

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

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

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

并且记录在案 – 它产生的所有“惩罚”(即每次使用变量时强制执行新的提取)要么不适用(如果没有多次提取)或必不可少 – 将代码从火车事故中拯救出来等待发生。

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”的获取和加法+存储操作之间,那么它会很高兴地完全失去重置操作,除非该操作被编译成一个原子单指令操作,这正是人们无法知道的,除非在汇编级别研究编译代码的特定实例(每次重新编译代码!)。在这种情况下,“不稳定”限定词甚至还不够。相反,如果所有主要代码都是“x=y”,那么无论具体的最终组装形式如何,都不会出现任何错误。相反的动作正是我们正在讨论的——“y=x”会根据“x”是否导致不同的结果

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

serial.c 中的 rx_buffer_head #264

@romain145: 谢谢你这么好的建议。今后我会牢记在心。

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

 

serial.c 中的 rx_buffer_head #264
 
添加标题文本添加粗体文本,<Ctrl+b>添加斜体文本,<Ctrl+i>
添加引号,<Ctrl+Shift+.>添加代码,<Ctrl+e>添加链接,<Ctrl+k>
添加项目符号列表,<Ctrl+Shift+8>添加编号列表,<Ctrl+Shift+7>添加任务列表,<Ctrl+Shift+l>

直接提及用户或团队引用问题、拉取请求或讨论

添加已保存的回复

喜欢 (0)

您必须 登录 才能发表评论!