标签归档:HAL

浅谈HAL设计(3)- 回调函数

在设计HAL,或者把代码结构分层的时候我们不可避免会遇到这种情况,一般函数调用顺序总是高级别函数调用低级别函数,比如HAL调用dirver层,或者app调用middleware。但是总有一些情况需要我们在较低级别的函数中调用较高级别的函数,这时候回调函数(callback)就十分有用了。

什么是回调

举一个例子,老板让张三去参加一个培训,培训时间要1000秒:

void start_training(char* name)
{
    delay(1000);
}

老板还想要让张三去给自己买一杯咖啡,时间需要100秒

void buy_coffee(char* name)
{
    delay(100)
}

老板同时执行了两个并行的任务线程:

execute_task_1()
{
    start_training("ZhangSan")
}
execute_task_2()
{
    buy_cooffee("ZhangSan")
}

张三不能去买咖啡,因为他还在参加培训。于是,老板只能一遍一遍发这个命令,或者等到张三结束培训之后再告诉张三。老板很恼火,觉得自己浪费了很多时间。

张三建议老板使用“回调”:

typedef void(*boss_cb)(char* name);
void start_training(char* name, boss_cb callback)
{
    delay(1000);
    callback(name);
}
void buy_coffee(char* name)
{
    delay(100)
}

于是老板只需要一条指令:

execute_task_1()
{
    start_training("ZhangSan", buy_coffee)
}

张三在培训完会直接去执行买咖啡的指令,老板不用一直等着或者反复催促,节省了老板很多时间,老板很满意,给张三加了薪。

张三在培训时候使用的就是回调函数,他不需要知道回调函数的具体内容,只提供这个接口。具体的内容由老板实现,老板让干什么就干什么,可以培训完去买咖啡,可以培训完去开车,也可以培训完去参加会议。

所以,回调函数是对某段代码的引用,该代码作为参数传递给其他代码,该代码允许较低级别的软件层调用较高级别的层中定义的函数[1]。在软件设计中,回调这种机制允许dirver这种较低层代码中设计某种接口,然后将具体如何实现这个接口功能留给上层的应用程序层。C语言中可以通过传递函数指针实现。

最简单的回调函数只是作为参数传递给另一个函数的函数指针。 在大多数情况下,回调包含三部分:

  • 回调函数
  • 注册回调函数
  • 执行回调函数

下图显示了这三个部分在回调实现中是如何工作的[2]:

一个简单的例子

在这个例子中有三个文件:

  • callback.c
  • reg_callback.h
  • reg_callback.c
/* callback.c */
#include<stdio.h>
#include"reg_callback.h"
/* callback function definition goes here */
void my_callback(void)
{
    printf("inside my_callback\n");
}
int main(void)
{
    /* initialize function pointer to
    my_callback */
    callback ptr_my_callback=my_callback;                           
    printf("This is a program demonstrating function callback\n");
    /* register our callback function */
    register_callback(ptr_my_callback);                             
    printf("back inside main program\n");
    return 0;
}
/* reg_callback.h */
typedef void (*callback)(void);
void register_callback(callback ptr_reg_callback);
/* reg_callback.c */
#include<stdio.h>
#include"reg_callback.h"
/* registration goes here */
void register_callback(callback ptr_reg_callback)
{
    printf("inside register_callback\n");
    /* calling our callback function my_callback */
    (*ptr_reg_callback)();                                  
}

编译运行后可以得到输出:

This is a program demonstrating function callback
inside register_callback
inside my_callback
back inside main program

正如图片里所展示的关系,较高层的函数将较低层的函数作为普通调用来调用,并且回调机制允许较低层的函数通过指向回调函数的指针来调用较高层的函数。使用回调函数,相当于把两个异步的任务关联了起来。

所以,在C语言中回调函数只不过是将函数指针传递到需要调用回调函数的代码,使用这种方法,我们可以实现一些比如,错误处理,退出前清理内存等很有实用性的功能。

Reference

[1] https://en.wikipedia.org/wiki/Callback_%28computer_programming%29

[2] https://www.beningo.com/embedded-basics-callback-functions/

浅谈HAL设计(2)- 设计模式与实例

为什么使用设计模式

在软件工程中,设计模式(design pattern)是解决软件设计中常见问题的通用可重复解决方案。 设计模式不是可以直接转换为代码的最终设计。 它是关于如何解决可以在许多不同情况下使用的问题的描述或模板。

设计模式可以通过提供经过测试的,经过验证的开发范例来加快开发过程。 有效的软件设计需要考虑直到实施的后期才变得可见的问题。 重用设计模式有助于防止可能引起重大问题的细微问题,并提高熟悉模式的开发人员和架构师的代码可读性。

通常,人们只了解如何将某些软件设计技术应用于某些特定的问题。 这些技术很难应用于更广泛的问题。 而设计模式提供了一种更通用的解决方案,并且易于移植和使用。

此外,使用设计模式允许开发人员使用众所周知的,易于理解的名称进行交流,减小了沟通成本。常见的设计模式也可以随着时间的流逝而得到改进,使其比开发人员的临时设计更为可靠。

工程实例

设计思路

设计目的是要实现一个通用的 HAL,适用于各种不同的单板(board)项目,以及不同的芯片(platform)。各种 boards 和 platforms 具有类似的属性和功能。因此这里参考了抽象工厂设计模式,设计如下的包含 HAL 的工程。整个工程的文件结构的组成:

├─ include
│    └─ hal
├─ main
├─ app
├─ hal
├─ board
│    └─ hal
└─ platform
      ├─ hal
      ├─ cpu
      └─ driver

工程主要由四部分组成:

  • app:整个工程的上层 application 应用软件;
  • hal:上下层连接的硬件抽象层 HAL;
  • board:针对不同的硬件单板的定制化配置;
  • platform:针对不同的平台芯片的底层代码,对于所有的 board 都适用。例如,可以包含STM系列,TI的TMS,ADI的SHARC,NXP的S32等等不同的MCU、DSP芯片的驱动层代码。

其中,app 只需要调用 HAL 的 API 即可实现对硬件的使用。board 包含对单板的各种位置和初始化代码。platform 包含底层基础代码,操作硬件寄存器,适用于各种 board 单板。

HAL 相关的代码由于使用了设计模式,所以使用 C++实现,其他代码均使用 C 实现。

使用这种设计思路,使 HAL 这部分代码不需要被频繁修改,用户在使用的时候,只需要按照HAL所规定的API,添加对应 platform 类型(比如C2000)和 board 类型(比如 demodemo)的实现代码即可。这样,不同层之间的代码耦合度可以大大降低,可读性、可维护性也会有很大提高。

这种设计层次清晰,调用简单,但是不足之处就是对于内存有一些要求。因为使用了很多动态成员变量,和一些C++特性,对于RAM有一些要求,所以对于一些RAM空间有限的小型MCU/SOC并不适用。

对于内存有限的MCU,我设计了一套相似的 HAL lite 版本,全部由C语言实现,并且使用静态成员注册modules,避免程序半途因为RAM不够而崩溃。由于整体思路类似,就不再赘述。

HAL 的初始化与构建

我们假设一个简单的使用情景:

  • 一块名字叫做 demodemo 的单板;
  • 使用 TI 的 C2000 芯片;
  • 需要使用这个芯片的 ADC 的功能。

main函数中通过调用 hal_platform_init(); 实现对所对应 platform(也就是demodemo + C2000)的初始化,文件位置 /board/hal/demodemo_c2000_platform.cpp。部分代码:

bool DemodemoPlatform::Init()
{
    bool error = false;

    error = bsp_init();

    for (std::list<Driver *>::iterator it = mpDriverList.begin();
            it != mpDriverList.end();
            ++it)
    {
        error = (*it)->Init();
    }

    return error;
}

uint32_t hal_platform_init(void)
{
    ErrorCode_t status = NO_ERROR;
    Platform::PlatformBuilder *pPlatformBuilder = nullptr;

    /*construct the platform*/
    pPlatformBuilder = new DemodemoMcuPlatform::DemodemoMcuPlatformBuilder();
    if (nullptr != pPlatformBuilder)
    {
        gpPlatform = pPlatformBuilder->Construct();
        /* Initialize the specific platform*/
        gpPlatform->Init();
    }
    else
    {
        status = ERROR_NO_MEMORY;
    }

    return status;
}

这里使用了抽象工厂模式,可以针对不同的 platform 生成相应的实例。其中,首先通过:

Platform *DemodemoMcuPlatform::DemodemoMcuPlatformBuilder::Construct()
{
    DemodemoMcuPlatform *pPlatform = nullptr;
    pPlatform = new DemodemoMcuPlatform(this);
    HardwareFactory *pHardwarefactory = nullptr;
    pHardwarefactory = new DemodemoMcuHardwareFactory();
    Soc *pSoc = pHardwarefactory->CreateSoc();
    pPlatform->AddHardware(pSoc, pPlatform);

#if HAVE_ADC
    Adc *pAdc = pHardwarefactory->CreateAdc();
    pPlatform->AddHardware(pAdc, pPlatform);
#endif /* HAVE_ADC */

    delete pHardwarefactory;

    return pPlatform;
}

生成 demodemo 工厂实例,并通过CreateXxx(),AddHardware(),注册所需要使用的外设。

然后使用 gpPlatform->Init() 初始化 platform:

  1. 单板初始化:调用bsp_init() 做一些 demodemo单板一些必要的初始化,比如电源、时钟、中断、全局变量等的初始化。
  2. driver 初始化:通过
for (std::list<Driver *>::iterator it = mpDriverList.begin();
        it != mpDriverList.end();
        ++it)
{
    error = (*it)->Init();
}

实现对这个 platform 注册的所有外设的初始化,比如timer,UART,CAN,I2C等,这个例子只使用了一个ADC作参考。

这样,所有的与 HAL 有关的外设都创建了一个适用于 demodemo 单板的实例,当我们需要调相应的 HAL 的时候,可以在对应的 hal_xxx.cpp 中通过:

static inline Adc* getAdcInstance()
{
    static Adc* spAdc = nullptr;

    if (nullptr == spAdc)
    {
        Platform* pPlatform = static_cast<Platform*>(hal_platform_getInstance());
        Driver* pDriver = const_cast<Driver*>(pPlatform->GetHardware("Adc"));
        spAdc = static_cast<Adc*>(pDriver);
    }

    return spAdc;
}

得到所注册的外设(比如ADC)。然后再对外设模块具体操作即可:

uint32_t hal_adc_init(void)
{
    Adc* pAdc = getAdcInstance();

    if (nullptr != pAdc)
    {
        pAdc->Init();
        return NO_ERROR;
    }
    else
    {
        return ERROR_NO_MEMORY;
    }
}

Driver 如何被 App 调用

根据设计,app 在使用的过程中不需要知道底层驱动如何实现,而是可以调用 HAL API 实现对相应 driver 的使用。例如,可以这样使用 hal_adc_read() 读取 ADC 的数值:

/**
 * app.c
 */
void app_run(void)
{
    /* read ADC channel 1 value */
    printf("APP: Task triggered \n");
    hal_adc_read(1); // read ADC channel 1 value
}

这个 API 在文件 hal_adc.h 中 (位置 /include/hal),这个API是面向所有HAL的使用者,因此放在 /include/ 这个文件夹中。

对应文件 hal_adc.cpp 实现了具体操作,文件位置 /hal/src/hal_adc.cpp,并不允许 HAL API 使用者修改。

/**
 * hal_adc.cpp
 */
#include "../../include/hal/hal_adc.h"

#include <stdio.h>

#include "../../include/hal/hal_platform.h"
#include "../../hal/include/platform_interface.h"

#if HAVE_ADC

static inline Adc* getAdcInstance()
{
    static Adc* spAdc = nullptr;

    if (nullptr == spAdc)
    {
        Platform* pPlatform = static_cast<Platform*>(hal_platform_getInstance());
        Driver* pDriver = const_cast<Driver*>(pPlatform->GetHardware("Adc"));
        spAdc = static_cast<Adc*>(pDriver);
    }

    return spAdc;
}

uint32_t hal_adc_init(void)
{
    Adc* pAdc = getAdcInstance();

    if (nullptr != pAdc)
    {
        pAdc->Init();
        return NO_ERROR;
    }
    else
    {
        return ERROR_NO_MEMORY;
    }
}

uint16_t hal_adc_read(uint8_t Channel)
{
    Adc* pAdc = getAdcInstance();

    if (NULL != pAdc)
    {
        printf("HAL: ADC read triggered \n");

        return pAdc->Read(Channel);
    }

    return 0;
}

#endif /* HAVE_ADC */

HAL 通过调用 driver_interface 中定义的不同 CPU 的 ADC read 函数,实现对底层 driver 的调用,位置 /hal/include/driver_interface.h。

/**
 * driver_interface.h
 */

#ifndef HAL_INCLUDE_DRIVER_INTERFACE_H_
#define HAL_INCLUDE_DRIVER_INTERFACE_H_

/* INCLUDE FILES */
#include <string>
#include <stdint.h>

#include "../../board/include/board.h"

#if HAVE_ADC
//#include "../../include/hal/hal_adc.h"
#endif

/* GLOBAL FUNCTIONS */
/**
 * @brief Base class for all HAL drivers.
 *
 */
class Driver
{
public:
    virtual bool Init() = 0;

    virtual ~Driver() { };

    virtual const std::string &GetName() const
    {
        return mName;
    };

protected:
    Driver(): mName("Driver") {};
    std::string mName;
};

/**
 * @brief SoC chip type abstract class, derived from Driver.
 *
 */
class Soc : public Driver
{
public:
    virtual bool Init() = 0;

    virtual ~Soc() { };

protected:
    Soc() { };
};

#if HAVE_ADC
/**
 * @brief ADC driver abstract class, derived from Driver.
 *
 */
class Adc : public Driver
{
public:
    virtual bool Init() = 0;

    virtual uint16_t Read(uint8_t channel) = 0;

    virtual ~Adc() { };

protected:
    Adc() { };
};
#endif /* HAVE_ADC */

#endif /* HAL_INCLUDE_DRIVER_INTERFACE_H_ */

对上述HAL的driver 接口,可以用以下方式实现。根据MCU种类不同,用户可以定义自己的实现内容。例如,对于C2000 ADC 的 source 代码,可以这样实现,位置 /platform/hal/c2000_drivers.cpp:

/**
 * c2000_drivers.cpp
 */

 /* INCLUDE FILES */
#include "c2000_drivers.h"

#if HAVE_ADC
#include "../../platform/driver/c2000_driver_adc.h"
#endif

/* MODULE FUNCTIONS */
/**
 * S32K SOC class implementation
 */
C2000Soc::C2000Soc()
{
    mName.assign("Soc");
}

bool C2000Soc::Init()
{
    bool error = false;

    //error = s32k_driver_soc_init();

    return error;
}

C2000Soc::~C2000Soc()
{
}

#if HAVE_ADC
/**
 * S32K ADC class implementation
 */
C2000Adc::C2000Adc()
{
    mName.assign("Adc");
}

bool C2000Adc::Init()
{
    c2000_driver_adc_init();
    return true;
}

uint16_t C2000Adc::Read(uint8_t channel)
{
    return c2000_driver_adc_read(channel);
}

C2000Adc::~C2000Adc()
{
}
#endif /* HAVE_ADC */

这样,一个衔接上层的 app 与 底层的 ADC driver 的 HAL 就实现了。实例中只给出了 ADC 的实现,实际应用中可以根据需要添加 timer,UART,DAI等外设和服务。

执行 main.c 主函数,读取三次C2000 ADC 的数值:

int main(int argc, char **argv)
{
    printf("HAL DEMO STARTS...\n");
    printf("\n***Stage 1: INIT All***\n");
    hal_platform_init();
    app_init();

    printf("\n***Stage 2: START All***\n");
    hal_platform_start();
    app_start();

    printf("\n***Stage 3: RUN Application***\n");
    while (Global.system_running)
    {
        app_run();

        if (++Global.time > 2) break;
    }

    return 0;
}

我们可以得到结果:

总结

这个 HAL 的设计思路就是通过把整个工程的代码分为 app, board, hal, platform 这4层,实现 app->hal->driver 的调用,并且易于移植和调用。

优点:

  • 更好的可读性:更多抽象,更清楚的逻辑;
  • 更好的可维护性:每部分的代码可以单独维护,出现问题根据错误码方便查找追溯;
  • 更好的可移植性:对于不同的单板,只需要修改 board 层代码有针对地修改即可,其他层代码可以完全共用;
  • 更标准化:减少不同工程师的冲突。

缺点:

  • 文件数量多,对内存的动态操作较多,会降低一定的稳定性;
  • 代码量更大,需要的flash RAM空间会增多;
  • 执行速度更慢:因为要调用的函数嵌套更多,入栈出栈的次数会更多;
  • 对公共代码的修改要谨慎,保证其可靠性,避免影响其他 platform。

to be continued…

参考

https://en.wikipedia.org/wiki/Software_design_pattern

Design Patterns and Refactoring

浅谈HAL设计(1)- 介绍

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博客?