嵌入式程序完成后会发生什么?


29

当执行到达最后一条return语句时,嵌入式处理器中会发生什么?一切都照原样冻结吗?功耗等,天空中永远只有一个NOP?还是连续执行NOP,或者处理器将完全关闭?

我问的部分原因是,我想知道处理器是否需要在完成执行之前关闭电源,并且如果确实关闭了电源,那么它将如何完成执行?


22
这取决于您的信念。有人说它将轮回。
Telaclavo

9
是导弹吗?
Lee Kowalkowski

6
一些系统支持HCF(暂停和着火)指令。:)
Stefan Paul Noack

1
它将转至自我销毁例程

Answers:


41

这是我父亲一直问我的问题。“ 为什么不只执行所有指令并在最后停止?

让我们看一个病理例子。以下代码是在Microchip的PIC18的C18编译器中编译的:

void main(void)
{

}

它产生以下汇编输出:

addr    opco     instruction
----    ----     -----------
0000    EF63     GOTO 0xc6
0002    F000     NOP
0004    0012     RETURN 0
.
. some instructions removed for brevity
.
00C6    EE15     LFSR 0x1, 0x500
00C8    F000     NOP
00CA    EE25     LFSR 0x2, 0x500
00CC    F000     NOP
.
. some instructions removed for brevity
.
00D6    EC72     CALL 0xe4, 0            // Call the initialisation code
00D8    F000     NOP                     //  
00DA    EC71     CALL 0xe2, 0            // Here we call main()
00DC    F000     NOP                     // 
00DE    D7FB     BRA 0xd6                // Jump back to address 00D6
.
. some instructions removed for brevity
.

00E2    0012     RETURN 0                // This is main()

00E4    0012     RETURN 0                // This is the initialisation code

如您所见,main()被调用,并且最后包含一个return语句,尽管我们自己没有明确地将它放在那里。当main返回时,CPU执行下一条指令,该指令只是GOTO以返回到代码的开头。main()被简单地反复调用。

话虽如此,这不是人们通常会做的事情。我从未写过任何允许main()退出的嵌入式代码。通常,我的代码如下所示:

void main(void)
{
    while(1)
    {
        wait_timer();
        do_some_task();
    }    
}

因此,我通常不会让main()退出。

你说“好吧好”。所有这些非常有趣,编译器确保永远不会有最后一个return语句。但是,如果我们强制执行该问题会怎样?如果我手动编码了我的汇编程序,但又没有跳回开头怎么办?

好吧,很明显,CPU只会继续执行下一条指令。这些看起来像这样:

addr    opco     instruction
----    ----     -----------
00E6    FFFF     NOP
00E8    FFFF     NOP
00EA    FFFF     NOP
00EB    FFFF     NOP
.
. some instructions removed for brevity
.
7EE8    FFFF     NOP
7FFA    FFFF     NOP
7FFC    FFFF     NOP
7FFE    FFFF     NOP

main()中最后一条指令之后的下一个内存地址为空。在具有闪存的微控制器上,空指令包含值0xFFFF。至少在PIC上,该操作码被解释为“ nop”或“ no operation”。它根本什么也没做。CPU将从内存一直一直执行到所有结束。

那是什么

在最后一条指令中,CPU的指令指针为0x7FFe。当CPU将2加到其指令指针时,它得到0x8000,这在只有32k FLASH的PIC上被认为是溢出,因此它回绕回0x0000,CPU高兴地在代码开始处继续执行指令,就好像它已被重置一样。


您还询问了是否需要关闭电源。基本上,您可以做任何您想做的事,这取决于您的应用程序。

如果您确实有一个应用程序,该应用程序在开机后只需要做一件事,然后什么也不做,那么您可以放一会儿(1); 在main()的末尾,以便CPU停止执行任何明显的操作。

如果应用程序要求CPU断电,则取决于CPU,可能会有各种可用的睡眠模式。但是,CPU有重新唤醒的习惯,因此您必须确保睡眠没有时间限制,并且没有活动的Watch Dog Timer等。

您甚至可以组织一些外部电路,这些电路将允许CPU在完成后完全切断自身的电源。看到以下问题:使用瞬时按钮作为闩锁开/关拨动开关


20

对于已编译的代码,它取决于编译器。我使用的Rowley CrossWorks gcc ARM编译器跳转到具有无限循环的crt0.s文件中。适用于16位dsPIC和PIC24器件(也基于gcc)的Microchip C30编译器会复位处理器。

当然,大多数嵌入式软件永远不会那样终止,而是连续不断地执行代码。


13

这里有两点要指出:

  • 严格来说,嵌入式程序无法“完成”。
  • 很少需要运行嵌入式程序一段时间,然后“完成”。

在嵌入式环境中,通常不存在程序关闭的概念。在较低级别上,CPU将在可能的情况下执行指令。没有“最终回报声明”之类的东西。如果CPU遇到不可恢复的错误或显式暂停(进入睡眠模式,低功耗模式等),则可能会停止执行,但是请注意,即使是睡眠模式或不可恢复的错误也通常不能保证不再有更多的代码要执行。被执行。您可以从睡眠模式中唤醒(这是它们通常的使用方式),甚至锁定的CPU仍可以执行NMI处理程序(Cortex-M就是这种情况)。看门狗也仍然会运行,并且一旦启用,您可能无法在某些微控制器上将其禁用。架构之间的细节差异很大。

如果固件是用C或C ++等语言编写的,则main()退出时发生的情况由启动代码确定。例如,这是STM32标准外设库中启动代码的相关部分(对于GNU工具链,注释是我的):

Reset_Handler:  
  /*  ...  */
  bl  main    ; call main(), lr points to next instruction
  bx  lr      ; infinite loop

尽管main()返回时,此代码将进入无限循环,尽管以一种非显而易见的方式(bl main加载lr下一条指令的地址,实际上是对其自身的跳转)。不会尝试停止CPU或使其进入低功耗模式,等等。如果您在应用程序中有对此的合法需要,您必须自己做。

请注意,如ARMv7-M ARM A2.3.1中所指定,链接寄存器在复位时设置为0xFFFFFFFF,并且分支到该地址将触发故障。因此,Cortex-M的设计师决定将重置处理程序的返回视为异常,因此很难与他们争论。

谈到在固件完成后停止CPU的合法需求,很难想象有任何方法可以通过关闭设备电源来解决。(如果您确实永久禁用了CPU,则只能对设备执行电源循环或外部硬件复位操作。)您可以取消DC / DC转换器的ENABLE信号置为有效,或者关闭电源。其他方式,例如ATX PC。


1
“您可以从睡眠模式中唤醒(这是它们通常的使用方式),甚至被锁定的CPU仍可以执行NMI处理程序(Cortex-M就是这种情况。)“-听起来像是一本书或电影情节。:)
马克·艾伦

“ bl main”将使用以下指令的地址(“ bx lr”)加载“ lr”,不是吗?执行“ bx lr”时,是否有任何理由期望“ lr”包含其他内容?
2012年

@supercat:您当然是对的。我编辑了答案,以消除错误并将其扩大一点。考虑到这一点,他们实现此循环的方式非常奇怪。他们本可以很容易做到的loop: b loop。我不知道他们是否真的打算退货却忘了保存lr
索恩2012年

很好奇 我希望很多ARM代码会在LR保持与输入时保持相同的值的情况下退出,但不知道这是可以保证的。这样的保证通常不会有用,但是要坚持下去,需要向例程添加一条指令,该例程将r14复制到其他寄存器,然后调用其他例程。如果在返回时认为lr为“未知”,则可以“ bx”保存已保存副本的寄存器。但是,这将导致所指示的代码非常奇怪的行为。
supercat

实际上,我非常确定非叶函数可以节省lr。这些通常将lr推入序言中的堆栈,然后通过将保存的值弹出到pc中返回。例如,这就是C或C ++ main()会执行的操作,但是有问题的库的开发人员显然没有在Reset_Handler中执行任何此类操作。
索恩2012年

9

当询问时return,您认为水平过高。C代码被翻译成机器代码。因此,如果您反而想到处理器盲目地将指令从内存中拉出并执行它们,则不知道哪一个是“最终”指令return。因此,处理器没有固有的目的,而是由程序员来处理最终情况。正如Leon在他的答案中指出的那样,编译器已编写了默认行为,但程​​序员常常可能希望自己关闭程序(我做了很多事情,例如进入低功耗模式并暂停,或者等待USB电缆插入进入,然后重新启动)。

许多微处理器都有停止指令,这会使处理器停止运行而不会影响特性。其他处理器可以通过简单地重复跳到同一地址来依靠“停止”。有可能的选择,但这取决于程序员,因为处理器即使不打算将内存用作指令,也只会从内存中读取指令。


7

这个问题不是嵌入式的(嵌入式系统可以运行Linux甚至是Windows,也可以是Windows),而是独立的或裸机的:(编译的)应用程序是计算机上唯一运行的东西(如果它是微控制器或微处理器)。

对于大多数语言,该语言未定义当“主”终止且没有操作系统可返回时发生的情况。对于C,它取决于启动文件中的内容(通常为crt0.s)。在大多数情况下,用户可以(甚至必须)提供启动代码,所以最终的答案是:您编写的是启动代码,或者指定的启动代码中发生了什么。

实际上,有3种方法:

  • 无需采取特殊措施。未确定主要收益时会发生什么。

  • 跳到0,或使用任何其他方式重新启动应用程序。

  • 进入紧密循环(或禁用中断并执行暂停指令),从而永远锁定处理器。

合适的取决于应用程序。精美的贺卡和制动控制系统(仅提及两个嵌入式系统)可能应该重新启动。重新启动的不利之处在于该问题可能不会引起注意。


5

前几天,我正在看一些反汇编的ATtiny45代码(由AVR-GCC编译的C ++),代码末尾的代码跳到0x0000。基本上是进行重置/重启。

如果编译器/汇编器忽略了最后一次跳转到0x0000的操作,则程序存储器中的所有字节均被解释为“有效”机器代码,并且一直运行到程序计数器翻转到0x0000为止。

在AVR上,一个00字节(单元为空时为默认值)为NOP = No Operation。因此,它运行得非常快,只花了一些时间就什么也不做。


1

通常,编译后的main代码随后与启动代码链接(它可能集成到工具链中,由芯片供应商提供,由您自己编写,等等)。

然后,链接程序将所有应用程序代码和启动代码放置在内存段中,因此对您的问题的答案取决于:1.启动代码,因为它可以例如:

  • 以空循环(bl lrb .)结束,这类似于“程序结束”,但先前启用的中断和外设仍将运行,
  • 以跳转到程序的开头(完全重新运行启动或jsut到main)结束。
  • 只需在调用mainreturn 之后忽略“下一步将是什么” 。

    1. 在第三个项目符号中,从main行为返回后程序计数器仅增加时,将取决于您的链接器(和/或链接期间使用的链接器脚本)。
  • 如果其他函数/代码放在您的后面main,则将使用无效/未定义的参数值执行该代码,

  • 如果后续存储器以错误的指令异常开始,则可能会生成异常,MCU最终将复位(如果异常产生了复位)。

如果启用了看门狗,尽管您处于所有无限循环中,它最终仍将重置MCU(当然,如果不重新加载它)。


-1

停止嵌入式设备的最佳方法是永远等待NOP指令。

第二种方法是使用设备本身来关闭设备。如果可以按照说明控制继电器,则只需打开为嵌入式设备供电的开关即可,而嵌入式设备却不消耗任何电能。


那真的不能回答问题。
马特·杨

-4

手册中对此进行了明确说明。通常,CPU将引发一般异常,因为它将访问堆栈段外部的内存位置。[内存保护异常]。

您所说的嵌入式系统是什么意思?微处理器还是微控制器?无论哪种方式,都在手册中定义。

在x86 CPU中,我们通过将命令发送到ACIP控制器来关闭计算机。进入系统管理模式。因此,该控制器是I / O芯片,您无需手动将其关闭。

阅读ACPI规范以获取更多信息。


3
-1:TS没有提及任何特定的CPU,因此不要承担太多。不同的系统以非常不同的方式处理这种情况。
Wouter van Ooijen
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.