嵌入式软件异步编程:冥想

时间:2022-11-18 07:56:23


异步编程可以编写出速度快、资源省的高效程序,可以在单线程环境下实现高并发,可以在没有操作系统的情况下实现TCP/IP等协议栈。又快又省可以将功耗控制在最低水平,因此异步编程是低功耗设计的最佳编程模型。

mingdu.zheng at gmail dot com

三重境界

参禅有三重境界:看山是山,看水是水;看山不是山,看水不是水;看山还是山,看水还是水。
编程也有三重境界:前后台系统;实时操作系统;异步前后台系统。

异步编程 vs 前后台系统

异步编程不会死等,会充分利用系统的每一个时钟,充分利用系统中可以并发工作的外设,可以高效地实现TCP/IP等协议栈。

异步编程 vs 操作系统

异步编程可以在单线程环境下实现并发,首先节省了大量的线程栈空间,其次不需要线程上下文切换节约了运行时间,也没有多线程的互斥、死锁等问题。

同步和异步

同步和异步是针对I/O操作而言的。同步I/O操作在发起操作请求后会等待直到I/O完成。没有操作系统的情况下(即前后台系统)I/O API会一直轮询I/O状态,直到I/O完成请求或超时;有操作系统的情况下I/O API会挂起当前线程,直到I/O完成中断或超时定时器唤醒挂起的线程。

异步I/O操作则在发起请求之后立即返回,不会等待I/O完成。程序可以继续执行其它任务,包括发起其它I/O请求。程序有两种方式获得异步I/O请求是否完成,一是程序在空闲时轮询I/O是否已经完成;二是在请求I/O操作时注册回调函数,I/O操作完成时调用该回调函数。

同步和异步在I/O API上的区别是:同步会发起请求并等待完成,异步则发起请求后立即返回。同步I/O的数据处理紧随API调用之后,异步I/O的数据处理和API调用则往往是“身首异处”。可以这么简单地理解同步和异步,同步就是I/O请求代码和数据处理代码是紧挨着的,在同一个地方的,通常在同一个函数内;异步就是I/O请求代码和数据处理代码是松散的,不在同一个地方的。

同步I/O符合人类的思维习惯,容易理解。异步I/O在没有多线程支持的情况下也能够实现高并发,而且比多线程实现的并发更省资源,也没有多线程的互斥、死锁等问题。

轮询式异步

在实现上,异步I/O可以分成轮询式异步和回调式异步。轮询式异步需要程序在发起I/O请求之后某个恰当的时机来检查I/O的完成状况,例如在程序空闲的时候。轮询式异步可能需要程序反复不断地检查I/O,直到I/O完成或超时。如果有操作系统的支持,那么可以挂起线程直到某个I/O操作完成或超时。

回调式异步

回调式异步需要在发起I/O请求时注册回调函数,当I/O完成后调用该回调函数。使用回调不需要程序频繁检查I/O状态,从执行效率角度讲回调式异步要优于轮询式异步,但是回调式异步的I/O请求和数据处理百分百是“身首异处”的,一定位于两个不同的函数内。

中断和回调

中断是回调的一个特例,中断服务函数是由硬件触发的回调函数,当硬件完成操作时可以触发中断调用中断服务函数。

回调函数绑定

回调函数可以是编译时绑定,也可以是运行时绑定。如果回调函数是确定的,那么可以在编译时绑定,调用方和被调用方约定一个函数原型,调用方实现该函数,通常被调用方以弱符号的形式实现该函数的空实现。有了弱符号空函数,无论调用方是否要用回调函数都不会有编译错误。运行时绑定则更灵活,可以为同一个操作指定不同的回调函数,运行时绑定可以使用函数指针实现。

事件驱动模型

事件驱动模型基本等同于回调式异步编程模型。为感兴趣的事件注册回调函数,当产生该事件时调用回调函数。事件驱动模型已经有一个搭好的框架在那里,这个框架实现了主循环,预定义了多种事件,可能包含某种事件调度器。可以说事件驱动模型是回调式异步编程模型的具体应用形式。

事件的分类

事件可以分为原始事件和衍生事件。原始事件是硬件产生的事件,即I/O事件或定时器事件。衍生事件是程序结合当前状态和原始事件衍生出来的事件。程序可以将多个原始事件合并成一个衍生事件,也可以由一个原始事件衍生出多个衍生事件。

事件还可以分为异步事件和同步事件。异步事件随时都会产生且数量不确定,比如用户输入、串口输入、网口输入,一般都是异步的输入事件。同步事件仅在发起I/O请求之后预期时间内产生且数量是确定的,比如主动的SPI数据发送或接收,同步通信通常会产生同步事件。异步事件不在程序的控制下产生,硬件上往往没有时钟信号或有被动的输入时钟信号;同步事件在程序的控制下产生,硬件上往往有主动的时钟输出信号。

中断和事件

中断是事件的一种,是硬件产生的事件,硬件以产生中断的方式告知程序这里有一个事件发生。

事件的来源

原始事件可以是硬件中断产生,也可以是程序轮询I/O状态产生。衍生事件由程序在处理原始事件过程中根据当前状态产生。

中断驱动模型

中断驱动模型是事件驱动模型的一个特例,中断驱动模型使用硬件中断控制器作为事件调度器,主程序在初始化完成后便进入休眠状态,从此主程序不再执行任何操作,只等中断产生进入中断服务函数。每个中断都是一个事件,每个中断服务函数都是一个回调函数。对于简单系统,中断驱动模型是最高效最省电的实现方式,非常适用于物联网传感器。