实用的嵌入式编码技巧:第一部分

嵌入式开发 专栏收录该内容
104 篇文章 0 订阅

几乎每个嵌入式系统都使用中断。许多支持多任务或多线程操作。这些类型的应用程序可以期望程序的控制流几乎在任何时候更改上下文。当该中断到来时,将暂停当前操作,并开始运行另一个功能或任务。如果函数和任务共享变量会怎样?如果一个例程破坏了对方的数据,那么灾难肯定会隐现。
通过仔细控制数据的共享方式,我们创建了可重入 函数,这些函数允许多个并发调用而不会相互干扰。“纯”一词有时可与“折返”互换使用。
Reentrancy最初是为大型机发明的,当时内存是一种有价值的商品。系统操作员注意到,几个大程序的十几个或数百个相同副本将随时存储在计算机的内存阵列中。在马里兰大学,骇人听闻的骇客之地,怪兽Univac 1108拥有最早进入市场的FORTRAN编译器之一。
它烧毁了惊人的(当时)32 kW的系统内存,但是可重入,即使50个用户运行它也只需要32 k。每个人都从相同的地址集执行相同的代码。每个人都有自己的数据区,但是每个运行编译器的人实际上都执行相同的代码。当操作系统在用户之间更改上下文时,它交换数据区域,因此一个人的工作不会影响其他任何人。共享代码,但不共享数据。
嵌入式世界中,例程必须满足以下条件才能重新进入:
规则1。 它以原子方式使用所有共享变量,除非每个变量都分配给函数的特定实例。
规则2。 它不调用不可重入函数。
规则3. 它不会以非原子方式使用硬件。
原子变量
第一个规则和最后一个规则都使用“原子”一词,该词源于希腊语,意思是“不可分割”。在计算机世界中,“原子”是指不能中断的操作。考虑汇编语言说明:
mov ax,bx
由于只有复位才能停止或中断该指令,所以它是原子的。它会启动并完成,而不会受到其他任务或中断的干扰。第一部分规则1需要原子使用共享变量。假设两个函数共享全局变量“ foobar”。函数A包含:
temp = foobar; temp + = 1; foob​​ar = temp;
这段代码不是可重入的,因为foobar是非原子使用的,也就是说,它需要三个语句来更改其值,而不是一个。foob​​ar的处理不是不可分割的。这些语句之间可能会发生中断,将上下文切换到其他函数,然后该函数也可能尝试并更改foobar。
显然存在冲突,foobar将以错误的值结束,自动驾驶仪将崩溃,成千上万的尖叫者会惊叹道:“为什么他们不教那些开发人员关于可重入性?”相反,假设函数A看起来像:
foob​​ar + = 1;
现在该操作是原子操作,中断将不会在foobar处于部分更改状态的情况下暂停处理,因此例程是可重入的。
除了 。。。您真的知道C编译器生成什么吗?在x86处理器上,代码可能如下所示:
movax,[foobar]
incax mov [foobar],ax
这显然不是原子的,因此也不是可重入的。atomicversion是:
inc [foobar]
道德上要警惕编译器。假设它生成了atomiccode,您可能会发现60分钟敲门。
第一条重入规则的第二部分显示为。。。除非每个分配给该函数的特定实例。” 这是绕过共享变量问题的原子规则的一个例外。
一个实例 是通过代码的路径。没有理由不能从许多其他地方调用单个函数。在多任务环境中,该功能的多个副本确实确实有可能同时执行。(假设例程是一个从队列中检索数据的驱动程序;代码的许多不同部分可能或多或少同时需要排队的数据。)每个执行路径都是代码的“实例”。考虑:
intfoo;
无效some_function(void){ foo ++; }
foo是一个全局变量,其作用域超出了函数的作用域。即使没有其他例程使用foo,如果变量的多个实例在任何时间运行,some_function也会破坏该变量。C和C ++可以避免这种危险。使用自动变量。也就是说,在函数内部声明foo。然后,例程的每个实例将使用从堆栈创建的foo的新版本,如下所示:
voidsome_function(void){
int foo; foo ++; }
另一个选择是动态分配内存(使用malloc),因此每个化身都使用唯一的数据区。这样就避免了基本的重入问题,因为多个实例不可能在该变量的通用版本上加盖戳记。
另外两个规则
其余规则非常简单。
规则2 告诉我们调用函数继承了被调用方的重入问题。如果函数内的其他代码破坏了共享变量,那就太有意义了。该系统将崩溃。
但是,使用编译语言存在一个隐患。您确定(真的确定)运行时程序包是可重入的吗?显然,字符串操作和许多其他复杂的事情都使用运行时调用来完成实际工作。大量的编译器还会生成运行时调用,以进行较长的数学运算,甚至是整数乘法和除法运算。
如果某个函数必须是可重入的,请与编译器供应商联系以确保整个运行时包都是纯净的。如果您购买了可能在多个地方调用的软件包(如协议栈),请采取类似的预防措施,以确保所购买的例程也可重入。
规则3 是唯一嵌入的警告。硬件看起来很像一个变量,如果要用一个I / O操作来处理设备多于一个,就会产生重入问题。
考虑Zilog的SCC串行控制器。访问设备的任何内部寄存器都需要两个步骤:首先将寄存器的地址写入端口,然后从相同的端口(具有相同的I / O地址)读取或写入寄存器。如果在设置端口和访问寄存器之间出现中断,则另一个功能可能会接管并访问设备。当控制返回到第一个功能时,您设置的寄存器地址将不正确。
保持代码可重入
消除非重入代码的最佳选择是什么?经验法则是避免共享变量。全局变量是调试麻烦和失败代码的源泉。使用自动变量或动态分配的内存。
然而,全局变量还是传递数据的最快方法。从实时系统中完全消除它们是不可能的。因此,在使用共享资源(变量或硬件)时,我们必须采取不同的操作。
最常见的方法是在非重入代码期间禁用中断。随着中断的关闭,系统突然变成了单一进程环境。将没有上下文切换。禁用中断,执行不可重入的工作,然后重新打开中断。
大多数情况下,此类代码如下:
我长
无效do_something(void){ disable_interrupts();
i + = 0x1234; enable_interrupts(); }
此解决方案不起作用。如果do_something()是通用例程(可能已在许多地方调用)并且在禁用了中断的情况下调用,则将其重新打开后将返回。机器的上下文已更改,可能是非常危险的方式。
不要使用旧的借口“是的,但是我写了代码,我很小心。只有知道中断将要发生时,我才会调用例程。” 将来的程序员可能不知道此限制,并且可能将do_something()视为解决其他问题所需的票证。。。也许在中断关闭时。
更好的代码如下所示:
我长 无效do_so 方法(无效){ 推送中断状态;
disable_interrupts();
i + = 0x1234; 弹出中断状态
}
关闭中断确实会增加系统延迟,从而降低了其及时响应外部事件的能力。更为温和的方法是使用信号量指示资源何时繁忙。信号量是简单的开关状态指示器,其固有的处理是原子的,通常在共享资源不可用时用作“使用中”标志以使例程空闲。
几乎每个商业实时操作系统都包括信号量。如果这是您获得可重入代码的方式,则一定要使用RTOS。没有RTOS?有时我会看到分配使用中标志以保护共享资源的代码,如下所示:
while(使用中); //等待耕种资源免费
in_use = TRUE; //设置资源繁忙的
Donon可重入内容 in_use = FALSE; //设置可用资源
如果某个其他例程可以访问该资源,则会将其设置为in_usetrue,从而使该例程处于空闲状态,直到释放in_use为止。看似简单,但不起作用。在while语句之后发生的中断将抢先执行。这个例程感觉到它现在拥有对该资源的独占访问权,但是还没有机会将in_use设置为true。
现在,其他一些例程可以访问该资源。某些处理器具有测试设置指令,其作用类似于使用中的标志,但这是中断安全的。它将始终有效。该指令看起来像:
Tset变量; 如果(变量== 0){
; 变量= 1; ; 返回TRUE;} ; 否则{返回FALSE;}
如果您不太幸运可以进行测试,请尝试以下操作:
循环:mov al,0; 0表示“正在使用” xchg al,变量 cmp al,0 je循环; 如果使用则循环
如果al = 0,我们将0交换为零;没什么改变,但是代码循环是因为其他人正在使用资源。如果al = 1,则将0放入“使用中”变量,将资源标记为繁忙。我们脱离了循环,现在可以控制资源了。每次都会工作。
递归
如果没有提及递归,就不会有关于递归的完整讨论,这仅仅是因为两者之间有太多的混淆。如果调用自身,则该函数是递归的。这是从多种算法中删除迭代的经典方法。
给定足够的堆栈空间,这是一种非常有效的方法,尽管很难调试,但仍然可以编写代码。由于递归函数本身会进行调用,因此很明显,它必须是可重入的,以免浪费其变量。因此,所有递归函数都必须是可重入的,但并非所有可重入函数都是递归的。
相关实战:https://www.99qibang.cn/information/b03c7fb0867346a5a4138a0cc4d2b154.html
https://www.99qibang.cn/information/f9e767e732ec441eb0d014bdc7abec6c.html
https://www.99qibang.cn/information/a1d19d2a3ec8497bba93472d4d3e8e5b.html
https://www.99qibang.cn/information/e150ed86f9c045a6b3d6b55136c20e1f.html

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值