本文是嵌入式软件系列:嵌入式C开发人员的ADA
我们已经在以前的文章中看到了如何使用ADA来描述高级语义和体系结构。然而,语言的优点在于,它可以一直使用到开发的最低级别,包括嵌入式装配代码或比特数据管理。
表示条款
该语言的一个非常有趣的功能是,与C不同,除非开发人员指定,否则没有数据表示约束。这意味着编译器可以自由选择代表与性能的最佳权衡。让我们从以下ADA示例开始:
R类是记录V:整数范围0 .. 255;b1:布尔人;b2:布尔人;带包的结束记录;
和C示例:
struct r {unsigned int v:8;Bool B1;Bool B2;};
上面的ADA和C ++代码都代表着创建尽可能小的对象的努力。在Java中无法控制数据大小,但是该语言确实指定了原始类型的值大小。
尽管在此示例中,C ++和ADA代码是等效的,但存在有趣的语义差异。在C ++中,需要指定每个字段所需的位数。在这里,我们指出V仅为8位,有效地表示为0到255的值。
在ADA中,这是另一种方式:开发人员指定所需的值范围,编译器决定如何表示事物,对速度或尺寸进行优化。记录结束时声明的包装方面规定,即使以访问记录组件的速度降低速度,编译器也应优化尺寸。我们将在各节中查看有关包装方面的更多详细信息钻头操作和将结构映射到位。在第6章中(来)。
也可以指定其他表示条款,以及根据可用值和指定尺寸的要求之间的编译时间一致性检查。当需要特定布局时,这特别有用。例如,与硬件,驱动程序或通信协议接口时。这是根据上一个示例指定特定数据布局的方法:
R类是记录V:整数范围0 .. 255;b1:布尔人;b2:布尔人;结束记录;对于r,使用记录 - 占据第一个字节的第一位。B1在0范围0 .. 0;- 占据第一个字节的最后7位。- 以及第二个字节的第一位。v在0范围1 .. 8;- 占据第二个字节的第二位。 B2 at 1 range 1 .. 1; end record;
我们省略了“用包装”指令,而是在记录声明之后使用记录表示条款。编译器被指示可将R型的对象传播到两个字节上。我们在这里指定的布局效率相当低,无法在任何机器上使用。但是,您可以让编译器构造最有效的访问方法,而不是手动编码自己的机器依赖性比特方法。
嵌入式装配代码
在执行低级开发(例如内核或硬件驱动程序级别)时,有时可能需要使用汇编代码实现功能。
每个ADA编译器都有基于硬件平台和受支持的汇编程序的嵌入组件代码的约定。我们的示例将在X86体系结构上与GNAT和GCC合作。
所有X86处理器自英特尔奔腾提供RDTSC指令以来,它告诉我们自上次处理器重置以来的周期数。它不需要输入,并且在EDX和EAX寄存器之间分配了无符号的64位值。
GNAT提供了一个名为System.machine_code.asm的子程序,可用于汇编代码插入。您可以指定一个字符串以传递到汇编程序以及可用于输入和输出的源级变量:
- get_processor_cycles.adb with ssytem.machine_code;使用system.machine_code;与接口;使用接口;函数get_processor_cycles返回unsigned_64很低,高:unsigned_32;计数器:unsigned_64;开始ASM(“ rdtsc”,outputs =>(unsigned_32’asm_output(“ = a”,high),unsigned_32’asm_output(“ = d”,low)),volatile => true);计数器:= UNSIGNED_64(高) * 2 ** 32 + unsigned_64(low);返回计数器;end get_processor_cycles;
上面的unsigned_32'asm_output子句提供了要更新的机器寄存器和源级变量之间的关联。= a和= d分别指EAX和EDX机器寄存器。来自软件包接口的Unsigned_32和Unsigned_64类型的使用可确保数据的正确表示。我们组装两个32位值以形成单个64位值。
我们将挥发性参数设置为True,以告诉编译器多次使用相同输入调用此指令可能会导致不同的输出。这消除了编译器将多个调用优化到单个呼叫中的可能性。
通过优化,GNAT编译器足够聪明,可以使用EAX和EDX寄存器来实现高和低变量。这导致组装接口的开销为零。
机器代码插入界面提供了此处显示的许多功能。更多信息可以在GNAT用户指南和GNAT参考手册中找到。
中断处理
在编程嵌入设备时,处理中断是一个重要方面。例如,使用中断来表明发生了硬件或软件事件。因此,通过处理中断,应用程序可以对外部事件做出反应。
ADA为处理中断提供内置支持。我们可以通过将处理程序(必须是受保护的程序)附加处理来处理中断。在声明受保护程序的声明中,我们使用附加_Handler方面,并指示我们要处理的中断。
让我们来看看一个代码示例陷阱Linux上的退出中断(sigquit):
-signal_handlers.ads with system.os_interface;Package signal_handlers是受保护的类型quit_handler请求的函数返回布尔值;私人quit_request:boolean:= false;- - 声明“退出”中断的中断处理程序 - end quit_handler;end Signal_hander;-signal_handlers.adb带有ada.text_io;使用ada.text_io;软件包Body Signal_handler受保护的身体quit_handler是函数请求返回布尔值(quit_request);procedure handle_quit_signal是开始put_line(“检测到退出请求!”);quit_request:= true; end Quit_Handler; end Signal_Handler; -- test_quite_handler.adb with Ada.Text_IO; use Ada.Text_IO; with Signal_Handlers; procedure Test_Quit_Handler is Quit : Signal_Handlers.Quit_Handler; begin while True loop delay 1.0; exit when Quit.Requested; end loop; Put_Line (“Exiting application...”); end Test_Quit_Handler;
来自此示例的Signal_Handlers软件包的规范包含Quit_handler的声明,这是一个受保护的类型。在该受保护类型的私有部分中,我们声明handle_quit_signal过程。通过在handle_quit_signal声明中使用附加_handler方面并指示戒烟中断(system.os_interface.sigquit),我们正在指示操作系统以任何退出请求来调用此过程。因此,例如,当用户在键盘上按CTRL+\时,该应用程序的行为将如下:
- 操作系统调用handle_quit_signal过程,该过程向用户显示一条消息(“检测到退出请求!”),并设置一个布尔值变量-Quit_request,quit_request在quit_handler type中声明:
- 主要应用程序通过将请求的函数拨打为true循环的一部分来检查退出处理程序的状态:
*退出时,此呼叫在退出中。
*在这种情况下,请求的函数返回true,因为quit_request标志是由handle_quit_signal过程设置的。
- 主要应用程序将退出循环,显示消息并完成。
请注意,上面的代码示例无法移植,因为它利用了Linux操作系统中断。在编程嵌入设备时,我们将使用这些特定设备上可用的中断。
另请注意,在上面的示例中,我们在编译时声明了一个静态处理程序。如果您需要使用可以在运行时配置的动态处理程序,则可以利用ADA.Intruck软件包中的子程序。该软件包不仅包含actact_handler的版本作为一个过程,还包括其他过程,例如:
- Exchange_handler,可以在运行时交换当前处理程序与其他处理程序中断相关联的当前处理程序。
- distach_handler,我们可以用来删除当前与给定中断关联的处理程序。
有关ADA.Intructs软件包的详细信息不超出本课程的范围。将来,我们将在单独的,更高级的课程中讨论它们。您可以在中断ADA参考手册的附录。