When you have seen one ant, one bird, one tree, you have not seen them all. – E. O. Wilson
前一段时间由于工作需要,参与设计了HAL代码,实现 application 和 hardware driver 的分离,在此期间学习并且总结了一些HAL的相关知识,在这里简单梳理与延伸。
这个HAL系列的文章主要想通过以下几个方面讨论与HAL有关的一些知识:
- HAL的介绍
- 如何设计好HAL
- 如何利用设计模式设计HAL
- 回调函数的使用
- …
HAL(Hardware Abstraction Layer)硬件抽象层
许多早期的计算机系统没有任何形式的硬件抽象。这意味着为该系统编写程序的任何人都必须知道每个硬件设备如何与系统的其余部分进行通信。这对软件开发人员来说是一个巨大的挑战,因为他们必须知道系统中每个硬件设备如何工作才能确保软件的兼容性。使用硬件抽象,而不是直接与硬件设备通信的程序,它将程序传达给操作系统该设备应执行的操作,然后,操作系统会向该设备生成硬件相关的指令。这意味着程序员不需要知道特定设备的工作方式,就能使他们的程序与设备兼容。
由于部分硬件厂商不想把自己的核心代码公开,如果把代码放在内核空间里就需要遵循GUN License,会损害厂家的利益。所以,Google为了响应厂家在Android的架构里提出HAL的概念,把对硬件的支持分为用户空间和内核空间,而HAL层就属于这里面的用户空间,该部分代码遵循Apache License,所以厂家可以把核心的代码实现在HAL层,无需对外开放源代码[3]。
一个好的比喻是交通运输的抽象。骑自行车和开车都是一种交通方式,它们都有共同点,比如需要使用脚、都有轮子。因此人们可以指定抽象的“drive”,然后让实现者决定是骑自行车还是开车。抽象了“轮式地面运输”功能,并封装了“如何驾驶”等详细信息。
所以抽象和封装是软件设计中很重要的概念,在计算机中,芯片中都可以抽象出很多模块。硬件抽象层(HAL)位于软件堆栈中的应用程序编程接口(API)之下,而应用程序层位于API之上,并通过调用API中的函数与硬件进行交互。硬件抽象层(HAL)可以很好地解决软件和硬件的“冲突”。
所以:
- **使用HAL可以帮助开发人员减少开发时间并提高代码架构质量;
- HAL的实现可以作为动态库或模块加载,实现了对硬件的抽象,同时也可以隐藏代码(便捷性+安全性)。**
HAL实例
HAL架构
这个例子参考高焕堂的HAL框架API介绍[2]。
在Android的HAL框架使用通用的321架构,也就是三个结构体,两个常量,一个函数。所有的硬件抽象模块都遵循321架构,在此基础上扩展自有的功能。(以下代码由C实现)
三个结构体 (基类)
/*** Every hardware module must have a data structure named HAL_MODULE_INFO_SYM* and the fields of this data structure must begin with hw_module_t* followed by module specific information.*/typedef struct hw_module_t{ uint32_t tag; uint16_t version_major; uint16_t version_minor; cost char* id; const char* author; struct hw_module_methods_t* methods; void* dso; uint32_t reserved[10];}hw_module_t;/*** Create a function list*/typedef struct hw_module_methods_t{ /** Open a specific device */ int (*open)(const struct hw_module_t* module, const char* id, struct hw_device_t** device);} hw_module_methods_t; /*** Every device data structure must begin with hw_device_t* followed by module specific public methods and attributes.*/typedef struct hw_device_t{ /** tag must be initialized to HARDWARE_DEVICE_TAG */ uint32_t tag; uint32_t version; struct hw_module_t* module; uint32_t reserved[12]; int (*close)(struct hw_device_t* device)}hw_device_t;
两个常量
/*** Name of the hal_module_info*/#define HAL_MODULE_INFO_SYM HMI/*** Name of the hal_module_info as a string*/#define HAL_MODULE_INFO_SYM_AS_STR "HMI"
一个函数
公共API,根据module_id去查找注册相对应的硬件对象,然后载入相应的HAL层驱动模块的so文件。
/*** Get the module info associated with a module by id.* @return: 0 == success, <0 == error and *module == NULL*/int hw_get_module(const char *id, const struct hw_module_t **module);
使用HAL操作LED驱动
根据基类设计LED驱动子类
typedef struct led_module_t{ hw_module_t common; int status;}led_module_t;typedef struct led_device_t{ hw_device_t common; int (*set_on)(led_device_t* dev); int (*set_off)(led_device_t* dev);}led_device_t;static int led_open(const hw_module_t* module, const char* name, hw_device_t** device){ led_device_t led_device; led_device.common.tag = HARDWARE_DEVICE_TAG; led_device.common.version = 0; led_device.common.module = module; led_device.common.close = led_device_close; led_device.set_on = led_set_on; led_device.set_off = led_set_off; *device = (hw_device_t*)&led_device;}static int led_device_close(hw_device_t* device){ led_device_t* dev = (led_device_t*) device; if(dev) free(dev); return 0;}static int led_set_on(led_device_t *dev) { //call to led HAL-Driver LOGI("led_set_on"); return 0; }static int led_set_off(led_device_t *dev){ //call to led HAL-Driver LOGI("led_set_off"); return 0;}
构造Methods代码
- 创建methods函数表
- 创建module对象
- 设定open() 函数指针
static hw_module_methods_t led_module_methods ={ .open = led_open,}const led_module_t HAL_MODULE_INFO_SYM ={ .common = { .methods = &led_module_methods, .tag = HARDWARE_MODULE_TAG, .version_major = 1, .version_minor = 0, .id = LED_HARDWARE_MODULE_ID, .name = "led HAL module", .author = "Balabala", }, .status = -1,}
上层程序调用HAL
static int load_led_interface(const char *if_name, audio_device_t **dev){ const hw_module_t *mod; int rc; rc = hw_get_module(LED_HARDWARE_MODULE_ID, if_name, &mod); ALOGE_IF(rc, "%s couldn't load LED hw module %s.%s (%s)", __func__, LED_HARDWARE_MODULE_ID, if_name, strerror(-rc)); if (rc) { return -1; } rc = mod->methods->open(mod, LED_HARDWARE_MODULE_ID, dev); if (rc) { return -1; } return 0;}
以上的HAL实例是在广泛应用于 Android 中的HAL架构,开发者可以根据自己的实际项目需求进行自己项目的HAL设计。采用类似这种思路,我会在后续文章中解释我是如何学习/设计自己的HAL。
最后
最早使用HAL的经历是使用 ST 的 CubeMX 的 HAL 库,但是并没有深入了解 HAL 的设计意图和具体实现方法。其实,HAL 的设计在工程上有十分重要的意义,所以借此机会整理相关内容也希望加深自己的理解。
在 HAL 的设计过程中,我更加体会到抽象和封装这两个概念在软件设计中的重要性。我们在阅读或者使用他人的代码时会有十分直观的感受,“好”的代码给人的感觉总是 simple and intuitive。汉语里有一个词叫做“见微知著”,类似的一句英文可能更加直观表示出这种感觉,叫做“see the forest for the trees”,“forest”未尝不是对“trees”的一种抽象。抽象其实就是提供了一个类似的思路,从各种“微”的硬件寄存器,到“著”的上层API,充满着“大”和“小”的博弈。抽象这种方式或者说能力,更加方便我们大脑对事物的理解与认知,更符合我们的逻辑,也能让我们的工作也好生活也好更加有条不紊。
我想用一个具体的例子展示“抽象”的魅力:比如理解计算机是如何工作的。我们都知道CPU是一台计算机的“心脏”,但如果把整个CPU拆成无数个晶体管摆在我们的眼前,恐怕冯诺依曼也要挠挠头。我们都知道CPU最基本的单元是晶体管。利用晶体管能够控制电流通断(on/off)的特性实现对电流的控制,从而得到 true/false,这些二进制的“0”和“1”就构成计算机运行的基本单位。一个“0”或者“1”在计算机中被叫做一个 bit(位)。
- 由晶体管向上抽象,我们可以得到一些基本的逻辑门电路,包括与、或、非等基本的逻辑门实现对一个 bit 的逻辑运算。
- 使用多个逻辑门电路向上抽象一层,我们可以得到一个全加器,实现对两个 bits 的求和运算。
- 使用多个全加器再向上抽象一层,我们可以得到ALU(算术逻辑单元)实现对多个 bits 的算术运算和逻辑运算。
但是只是有运算能力还不够,我们需要一个地方去存储这些运算数据。
- 因此同样从逻辑门电路向上抽象,我们可以搭建一个简单的锁存器和触发器,实现对1个 bit 的信息保存,也就是可以保存一个 bit 的电平状态。
- 由多个锁存器,再向上抽象我们可以得到一个寄存器,实现对多个 bit 二进制数据的存储。
- 由多个寄存器再向上抽象我们就可以得到内存(memory) 也叫随机存取存储器(RAM)。
就这样,我们就由一堆晶体管创造了CPU的两个重要成员,ALU 和 Memory。他们加在一起就构成了可以运算与存储信息的CPU。用一个图片展示大概是这样。
所以,抽象是一个很神奇的概念,既可以把简单的事情变复杂(比如单个晶体管不能运算,但由大量晶体管组成的CPU可以),又可以把复杂的事情变简单(比如使用简单的指令可以让CPU运算,而不需要用复杂的指令操作每一个晶体管)。
生活中、工作中很多的事物都有着相似的 pattern。不管是软件设计中的抽象,还是“以小见大”“见微知著”这些耳熟能详的词语,很多概念或者知识就像一座座山峰一直存在着。而科学仿佛是一根环绕在我们周围隐形的线,将不同领域甚至不同维度的山峰巧妙连接,并引导着我们攀上一座山,看到下一座。朋友们,期待在科学的路上与你相遇。
to be continued…
参考
[1] https://en.wikipedia.org/wiki/Hardware_abstraction
[2] https://edu.51cto.com/center/course/lesson/index?id=41601
[3] 【Android】HAL层浅析_冇二哥的专栏-CSDN博客?