C 中设备驱动程序的程序组织
Program organization with device drivers in C
假设我有两个设备 driver,我希望它们共享同一个接口,这样调用者就不知道它正在与哪个 driver 通话。我将如何在 C 中组织这个?我想到了几个方法:
首先:为具有相同接口的driver创建一对.c/.h文件,并在调用者中创建一个开关:
//main.c:
#ifdef USING_DRIVER_1
#include "driver_1.h"
#else
#include "driver_2.h"
#endif // USING_DRIVER_1
第二:使用单个 header 并在 driver 源文件中创建一个 file-long 开关,如下所示:
//driver_1.c:
#ifdef USING_DRIVER_1
#include "driver.h"
bool func(uint32_t var)
{
foo(var);
}
#endif // USING_DRIVER_1
//driver_2.c:
#ifndef USING_DRIVER_1
#include "driver.h"
bool func(uint32_t var)
{
bar(var);
}
#endif // !USING_DRIVER_1
第三:这个很像第二个,但不是在文件本身中使用 switch 语句,而是在 makefile 中选择特定的 driver 或 IDE 等价物:
#makefile:
SRC = main.c
#SRC += driver_1.c
SRC += driver_2.c
我确信其中之一优于其他,并且可能还有一些我没有想到。它在实践中是如何实现的?
编辑:
关于我的特定系统的详细信息:我的目标是 ARM 微控制器和我的开发人员。环境是 IDE。设备 driver 用于两个不同的修订版,绝不会同时使用,因此每个构建应仅包含一个版本。设备本身是通过 AT 命令运行的调制解调器。
我建议使用指向函数的指针。例如:
struct driver_api {
bool (*pFunc)(uint32_t);
} DriverApi;
void initializeApi(struct driver_api *pApi);
// driver1.c:
void initializeApi(struct driver_api *pApi)
{
pApi->pFunc = bar;
}
// driver2.c:
void initializeApi(struct driver_api *pApi)
{
pApi->pFunc = foo;
}
您可能会考虑的另一件事是从源文件中删除 #ifndef USING_DRIVER_1
检查。使用构建系统(例如 make)并指定项目中应包含哪些源文件。然后,基于某些编译时选项(例如命令行参数)包括 driver1.c
或 driver2.c
,但绝不会同时包含两者。
指针的 "advantage" 是您可以编译这两个 API,然后在 运行 时间决定(甚至在 运行 中更改它,无论出于何种原因)。
这三种变体实际上都很有用。选择哪个取决于您的实际需要:
- 从调用方选择 driver 会将两个 driver 添加到代码中。只有在 run-time 处切换 drivers 才有意义。那么这将是(唯一的)方法。使用例如函数指针或两个相同的
const struct
提供接口(函数指针和可能的其他数据)。
- 全局开关非常丑陋,并且不可能跨函数和声明。更好的方法是使用
#if .. #elif #end
进行条件编译。如果两个 driver 之间只有细微差别,例如不同的 SPI 接口(SPI1 与 SPI2 ...)。那么这就是要走的路。通过 build-tool 中的一些努力,您甚至可以将其用于案例 1。(一个文件用于两个不同的 driver,但我不推荐)。
- 如果两个 driver 在实现上有很大不同,但具有相同的接口,则采用第三种方法,但使用一个 header 或两个 driver(参见下面)。
注意除第一种方法外的所有方法,两种driver都必须为应用程序提供相同的接口。第一种方法实际上允许存在差异,但这实际上需要用户代码将它们区别对待,而这可能不是您想要的。
对两个 driver 使用单个 header 文件(例如:"spi_memory.h" 和 "spi_flash.c" 与 "spi_eeprom.c")确实确保应用程序看不到实际差异 -当然,只要 driver 也 表现 相同。界面中的变量(例如 extern size_t memory_size;
)或函数(更好的方法)可以捕获细微差别。
假设我有两个设备 driver,我希望它们共享同一个接口,这样调用者就不知道它正在与哪个 driver 通话。我将如何在 C 中组织这个?我想到了几个方法:
首先:为具有相同接口的driver创建一对.c/.h文件,并在调用者中创建一个开关:
//main.c:
#ifdef USING_DRIVER_1
#include "driver_1.h"
#else
#include "driver_2.h"
#endif // USING_DRIVER_1
第二:使用单个 header 并在 driver 源文件中创建一个 file-long 开关,如下所示:
//driver_1.c:
#ifdef USING_DRIVER_1
#include "driver.h"
bool func(uint32_t var)
{
foo(var);
}
#endif // USING_DRIVER_1
//driver_2.c:
#ifndef USING_DRIVER_1
#include "driver.h"
bool func(uint32_t var)
{
bar(var);
}
#endif // !USING_DRIVER_1
第三:这个很像第二个,但不是在文件本身中使用 switch 语句,而是在 makefile 中选择特定的 driver 或 IDE 等价物:
#makefile:
SRC = main.c
#SRC += driver_1.c
SRC += driver_2.c
我确信其中之一优于其他,并且可能还有一些我没有想到。它在实践中是如何实现的?
编辑:
关于我的特定系统的详细信息:我的目标是 ARM 微控制器和我的开发人员。环境是 IDE。设备 driver 用于两个不同的修订版,绝不会同时使用,因此每个构建应仅包含一个版本。设备本身是通过 AT 命令运行的调制解调器。
我建议使用指向函数的指针。例如:
struct driver_api {
bool (*pFunc)(uint32_t);
} DriverApi;
void initializeApi(struct driver_api *pApi);
// driver1.c:
void initializeApi(struct driver_api *pApi)
{
pApi->pFunc = bar;
}
// driver2.c:
void initializeApi(struct driver_api *pApi)
{
pApi->pFunc = foo;
}
您可能会考虑的另一件事是从源文件中删除 #ifndef USING_DRIVER_1
检查。使用构建系统(例如 make)并指定项目中应包含哪些源文件。然后,基于某些编译时选项(例如命令行参数)包括 driver1.c
或 driver2.c
,但绝不会同时包含两者。
指针的 "advantage" 是您可以编译这两个 API,然后在 运行 时间决定(甚至在 运行 中更改它,无论出于何种原因)。
这三种变体实际上都很有用。选择哪个取决于您的实际需要:
- 从调用方选择 driver 会将两个 driver 添加到代码中。只有在 run-time 处切换 drivers 才有意义。那么这将是(唯一的)方法。使用例如函数指针或两个相同的
const struct
提供接口(函数指针和可能的其他数据)。 - 全局开关非常丑陋,并且不可能跨函数和声明。更好的方法是使用
#if .. #elif #end
进行条件编译。如果两个 driver 之间只有细微差别,例如不同的 SPI 接口(SPI1 与 SPI2 ...)。那么这就是要走的路。通过 build-tool 中的一些努力,您甚至可以将其用于案例 1。(一个文件用于两个不同的 driver,但我不推荐)。 - 如果两个 driver 在实现上有很大不同,但具有相同的接口,则采用第三种方法,但使用一个 header 或两个 driver(参见下面)。
注意除第一种方法外的所有方法,两种driver都必须为应用程序提供相同的接口。第一种方法实际上允许存在差异,但这实际上需要用户代码将它们区别对待,而这可能不是您想要的。
对两个 driver 使用单个 header 文件(例如:"spi_memory.h" 和 "spi_flash.c" 与 "spi_eeprom.c")确实确保应用程序看不到实际差异 -当然,只要 driver 也 表现 相同。界面中的变量(例如 extern size_t memory_size;
)或函数(更好的方法)可以捕获细微差别。