嵌入式系统/IO编程
如果嵌入式系统无法与外部世界通信,它将毫无用处。为此,嵌入式系统需要采用I/O机制来接收外部数据,并将命令发送回外部世界。很少有计算机科学课程会提及IO编程,尽管它是嵌入式系统编程的核心功能。因此,本章将作为I/O编程的速成课程,适用于有C语言背景的读者,也适用于没有C语言背景的读者。
在编程IO总线控制时,有5种主要的处理方法:主线程轮询、多线程轮询、中断方法、中断+线程方法以及使用DMA控制器。
在这种方法中,无论何时有输出准备发送,您都检查总线是否空闲,然后发送它。根据总线的工作方式,发送它可能需要很长时间,在此期间您可能无法执行其他操作。输入的工作原理类似:每隔一段时间,您检查总线以查看是否有输入。
优点
- 易于理解
缺点
- 效率非常低,尤其是在需要手动将数据推送到总线上时(而不是通过DMA)。
- 如果您需要手动推送数据,那么您将无法执行其他操作,这可能会导致实时硬件出现问题。
- 根据轮询频率和输入频率,您可能会因为处理速度不够快而丢失数据。
一般来说,此系统仅应在IO仅在不频繁的间隔内发生时,或者在有更重要的事情需要处理时可以将其推迟的情况下使用。如果您的系统支持多线程或中断,您应该使用其他技术。
在这种方法中,我们启动一个特殊的线程来进行轮询。如果轮询时没有IO,它会将自己休眠预定义的时间。如果有IO,它会在IO线程上处理IO,从而允许主线程执行所需的操作。
优点
- 不会推迟主线程
- 允许您通过更改线程的优先级来定义IO的重要性
缺点
- 效率仍然有点低
- 如果IO频繁发生,您的轮询间隔可能太小,无法让您充分休眠,从而导致其他线程饥饿。
- 如果您的线程优先级太低,或者操作系统线程过多,导致操作系统无法及时唤醒线程,则可能会丢失数据。
- 需要支持线程的操作系统
此技术非常适合您的系统支持线程,但不支持中断或已用尽中断。当需要频繁的IO时,它并不适用- 如果间隔太小,操作系统可能无法正确休眠线程,并且您将添加每次轮询两个上下文切换的开销。
(中断架构使用中断,我们将在本章嵌入式系统/中断中详细讨论)。
在这种方法中,总线在IO准备就绪时向处理器发出中断。然后处理器跳转到一个特殊函数,并放弃它正在执行的其他操作。特殊函数(称为中断处理程序或中断服务例程)处理所有IO,然后返回到它正在执行的操作。
优点
- 效率很高
- 非常简单,只需要一个函数
缺点
- 如果处理IO需要很长时间,您可能会使其他操作饥饿。如果您的处理程序屏蔽中断,这尤其危险,因为这会导致您错过来自实时硬件的硬件中断。
- 如果您的处理程序花费的时间太长,以至于在您处理现有输入之前就绪了更多输入,则可能会丢失数据。
只要处理IO是一个短暂的过程,此技术就很棒,例如,您只需要设置DMA。如果这是一个漫长的过程,请使用多线程轮询或中断与线程。
- 我们在嵌入式系统/中断中更详细地讨论了此技术。
在这种技术中,您使用中断来检测何时IO准备就绪。您不是直接处理IO,而是中断向线程发出信号,告知IO已准备就绪,并让该线程处理IO。向线程发出信号通常通过信号量完成 - 信号量初始化为占用状态。IO线程尝试获取信号量,该操作会失败,操作系统会将其休眠。当IO准备就绪时,中断会触发并释放信号量。然后线程会醒来,处理IO,然后再尝试获取信号量并被重新休眠。
中断向量指向的例程是“一级中断处理程序”。操作系统随后唤醒的处理其余工作的线程是“二级中断处理程序”。
优点
- 最低延迟 - 与所有其他中断在该中断完全处理之前被禁用不同,中断会在尽可能快的时间(在一级中断处理程序结束时)重新启用。
- 不会推迟主线程
- 允许您通过更改线程的优先级来定义IO的重要性
- 效率很高 - 仅在需要时进行上下文切换,并且不会进行轮询。
- 从架构上来说这是一个非常干净的解决方案,允许您在处理IO的方式上非常灵活。
- 二级中断处理程序可以等待锁被释放 (嵌入式系统/锁和临界区).
缺点
- 需要支持线程的操作系统
- 最复杂的解决方案
此解决方案是最灵活的,也是效率最高的解决方案之一。它还最大限度地降低了使更重要的任务饥饿的风险。它可能是当今最常用的方法。
在某些特殊情况下,例如,当必须将一组数据传输到通信IO设备时,可能存在DMA控制器,它可以自动检测何时IO设备准备接收更多数据,并传输该数据。此技术可以与许多其他技术一起使用,例如,当数据传输完成时可以使用中断。
优点
- 这提供了最佳的性能,因为I/O可以与其他代码执行并行发生。
缺点
- 仅适用于有限范围的问题
- 并非所有系统都具有DMA控制器。这在更基本的8位微控制器中尤其如此。
- 并行性质可能会使系统复杂化
Dos.h头文件通常包含在许多C发行版中,尤其是在DOS和Windows系统上。此文件包含有关许多不同例程的信息,但最重要的是,它包含可用于从C程序直接提供端口输出的inp() 和 outp() 函数的原型。但是,许多嵌入式系统在其库中没有Dos.h头文件,也没有任何预编译的C例程来处理端口输入和输出。因此,本章的目的是教读者如何“自己编写”输入和输出例程。
一些 C 编译器发行版包含 <iohw.h> 接口。它允许编写相对可移植的硬件设备驱动程序代码。它用于实现标准 C++ <hardware> 接口。 [1]
x86 指令集包含 2 条指令:in 和 out,这两个函数都接受 2 个参数,一个端口号,然后是另一个参数来接收数据或发送数据(取决于你使用哪条命令)。
我们可以使用 CDECL 调用约定在汇编中定义 2 个函数,这些函数可以与我们的 C 程序链接,并从我们的 C 程序中调用来处理端口输出和输入。
数据可以同步或异步传输。同步传输是指使用时钟信号发送的传输。这样接收器就能准确地知道每个比特的开始和结束位置。这样,对噪声和抖动的敏感性就会降低。此外,同步传输通常需要发射器和接收器之间进行广泛的握手,以确保所有时序机制同步在一起。相反,异步传输是在没有时钟信号的情况下发送的,并且通常没有太多握手。
- ↑ "C++ 性能技术报告" 由 Dave Abrahams 等人撰写。2003 年