我对这个命题思考了很久的时间,也做了比较多的尝试。尝试分析了几个小的嵌入式RTOS的code。但是仍然感觉是比较模糊,如果让我说个1234,恐怕是比较难了。在这里写点我自己的想法。
在这里,我考虑的可移植性结构专门说的是在嵌入式的平台,就拿uboot来说,比较大的一堆代码,功能也比较丰富,说到可移植性,我会想它的目的就是想通过最少的修改,使得这个代码可以运行在新的平台。它至少:
1. 可以很容易的添加新的代码或者功能,而不需要或者少量的修改原有的代码 (比如说添加一个新的command,或者是添加一个新的driver)
2. 可以很快的根据新的平台的特性修剪适当的功能,比如说(有的平台资源拮据,仅仅只需要一个console 就可以了,而不需要其他的诸如ethernet或者usb功能)
uboot在这个上面做得很好,你只需要添加相应的board文件、配置相关变量,就基本上可以实现这个code的移植了。u-boot 是如何组织这些代码使得它具有良好的可移植性的呢? 换句话说,这种设计思想是不是一个固定的方式?采用这种思想后,可以使得您的软件结构做到强可移植性呢? 我猜想,这也许是一个经验问题。
又比如,当我在ARM9上尝试一个最简单的uart 功能,几个简单的函数如下:
-
/* init hardware */
-
void s3c_uart_init(void);
-
-
/* put a char */
-
int s3c_putc(char c);
-
-
/* get a char */
-
int s3c_getch();
-
-
/* put a string */
-
int s3c_putstring(char* string);
-
-
/* read a stirng*/
-
char* read();
每一个硬件程序员都能写出着几个函数; 但是再往深处想一点,发现这几个函数几乎不能动,它只能用于这个特定的芯片上,离开了这个平台,换一个新的平台后,我仍然需要从头到尾完成上述几个函数。这里只有4个Function,实现起来还是相对比较简单的。但是如果我把它变成一个Console,用来做后台terminal的输入输出,那改动的代码将更多。
-
// a console simple code
-
-
printf( ...)
-
{
-
s3c_putstring(str)...
-
-
}
-
-
-
scanf( )
-
{
-
-
s3c_getch()
-
....
-
}
如此一来,一旦代码太多,就成为了platform 专用code了,几乎不存在任何可移植性的特性; 又比如在我们常用的SOC上,一般都有多个UART ; 3-4个UART 在SOC上完全不足为奇; 如果为每个UART都声明一组全局function, 于调用者来说,若需要修改一个输入输出uart,他需要将所有调用uart的地方的function都需要重新改一遍。增加了维护的难度...如此一来,那作为一个uart的driver,应该怎么写,才能够让调用者觉得舒心?如何设计,才能够使得当需求发生变化时,code的修改量最少,甚至不用修改,仅需配置一些参数就够了。
从调用者的角度上来考虑,它关心的是用哪个UART,波特率为多少; 其他的一概不关心。它更希望不需要去修改function,而仅仅就是几个参数的修改就能随心所欲的指定port发送数据。其他的问题,它想都懒得想。于是,作为下游的开发者,应该考虑为上层提供一个通用的调用方式,为它们提供一组固定的functions, 只要这组functions的形式没有发生变化,那么上层的code都不用去动。于是,这就是一组通用的接口。
-
void uart_init(int portnum, ...)
-
-
void uart_putc(int portnum,char c)
-
-
void uart_getc(int portnum,char& c)
-
-
void uart_sendstring(int portnum,char* string)
-
-
void uart_getstring(int portnum,char* string)
有了这组通用的接口(Inteface),则上层代码可以完全通过调用这几个function, 而不同的platform 则可以对这几个function进行overwrite ;从而达到上层与下层之间解耦的能力。同样的,这一组接口又统一了多个UART口的operate,将对不同的UART的实现屏蔽到这组通用function 之下,使得上层调用者完全不用理会这些细节问题。混乱的代码似乎变得有些清晰了。
======》
然而这样就算完成了吗?并非如此,当有一天你的cosole 突然需要通过Ethernet的方式与远程用户进行交互,那....该怎么办呢? 结果是你还是得修改这一批code,因为它们只support 串口这一层。。。归根结底的愿意,是因为我们的接口没有想到以后会扩展成如此的样子,或者说,我们的通用的接口并没有做到与上层完全独立。我们只是站在了uart的角度上来思考,我们都在考虑不同的UART怎么才能便捷的为console提供服务。接下来我们应该反过来想想,站在console的角度上来,它希望为他提供输入输出接口的地方应该是个什么样子,或许就应该是这样:
-
struct console_port
-
{
-
void (*init)(void);
-
void (*writeline)(char* c,int len)
-
char* (*readline)(int length);
-
void (*shutdown)(void);
-
.....
-
}
console 提供一组Generel接口,它不管是什么设备在为它提供i/o 服务,只要以这种接口注册在它的输入输出接口上,那么他们就调用这个接口去实现信息的传递。如此一来,不管是UART1,UART2也好,还是Ethernet 什么的也好; 只要实现了,然后注册给了console 服务,那就一切貌似又恢复平静了....
实际上,上面说的那些,无疑都逃不过软件可复用设计的概念, 在软件可复用设计上,代码的设计原则是低耦合,高内聚,模块化。在嵌入式系统设计中,尤其如此,引入面向接口编程的方式,将能够降低模块与模块之间的耦合度。面对可移植性的难题,事实上,我觉得从根本上采用面向对象、面向接口编程的方式或许是最好的方法。就上述而言,当我们希望自己写的console 可以复用到很多环境中,那Console中就只能做与console 相关的事情,尽量降低它与其他事情的关系,比如说,Console不应该太关系它的输入输出应该是面向哪里,或许,它在某一个平台上是以Ethnet 作为输入输出,在另一个平台上是uart, 或许到了某一天,它希望直接将信息输出到LCD上等等....
阅读(1018) | 评论(0) | 转发(0) |