(2006年04月5日 星期三 20:03)设备驱动的porting
一些驱动porting中要注意的问题。
1、编译选项
交叉编译时,要加入一些和机器相关的编译选项,才能完成正确的链接。这些选项可以在gcc的手册中查看到其意义。
编译kernel目录外的驱动模块,需要由-I选项告诉编译器内核头文件的具体位置,如-I${LINUX_BASE}/include/asm-mips/am5120/。
好的驱动程序,会使用宏的方式来定义程序的一些特性,而许多特性是和机器类型相关的,因此,建议在交叉编译之前,搜索程序中所有的#define/#ifdef,正确设置特定机器需要的宏。
编译选项处理的一般做法,是对现有的Makefile进行少量修改,设置正确的交叉编译器,指定内核头文件的位置,以及加入机器相关的编译选项。尽量保持原有Makefile的结构,避免带来不可预料的麻烦。
对于驱动模块,不建议对其使用strip方法,否则模块可能不可用。
编译完成之后,使用file命令,查看是否正确交叉编译。
2、汇编代码的改写
一些移植性不好的驱动程序会内嵌汇编代码,需要对其进行改写,而这个过程是很难也很麻烦的。
建议?换个驱动再porting!
3、字节顺序
字节顺序是指占内存多于一个字节类型的数据在内存中的存放顺序,有小端、大端两种字节顺序。小端字节序指低字节数据存放在内存低地址处,高字节数据存放在内存高地址处;大端字节序是高字节数据存放在低地址处,低字节数据存放在高地址处。基于X86平台的PC机是小端字节序的,而有些嵌入式平台则是大端字节序的,这个就成为porting驱动中的一个问题。
大多数驱动会在头文件里,给出一个宏定义,定义机器的字节顺序,对于这种驱动只要正确设置宏值即可。
4、字节对齐
有些嵌入式处理器的寻址方式决定了在内存中占2字节的int16、uint16等类型数据只能存放在偶数内存地址处,占4字节的int32 、uint32 等类型数据只能存放在4的整数倍的内存地址处,占8字节的类型数据只能存放在8的整数倍的内存地址处,而在内存中只占1字节的类型数据可以存放在任意地址处。由于这些限制,在这些平台上编程时有很大的不同。往往对齐问题会使得结构体成员之间会有空洞。
对于通信双方都是对结构成员操作的,这种情况不会出错,但如果有一方是逐字节读取内容的,就会错误地读到其它字节的内容。另外,若对内存中数据以强制类型转换的方式读取,字节对齐的不同会引起数据读取的错误。因为假如指针指在基数内存地址处,我们想取得占内存两个字节的数据存放在uint16型的变量中,强制类型转换的结果是取得了该指针所指地址与前一地址处的数据并没有按照我们的愿望取该指针所指地址与后一地址处的数据,这样就导致了数据读取的错误。
因此,在嵌入式驱动中,往往会要求对结构体加入强制对齐的属性,以避免上述的问题。
5、代码优化
嵌入式系统对程序的质量要求更高,需要注意对代码进行优化,尽可能地提高代码的效率,减少代码的大小。
在嵌入式系统中,优化往往要结合机器特性来进行,给出一个优化的例子。
if(Length <= 8 && Length > 0)
{
*(((PUCHAR)pDest)++) = *(((PUCHAR)pSrc)++);
if(–Length == 0) return;
*(((PUCHAR)pDest)++) = *(((PUCHAR)pSrc)++);
if(–Length == 0) return;
*(((PUCHAR)pDest)++) = *(((PUCHAR)pSrc)++);
if(–Length == 0) return;
*(((PUCHAR)pDest)++) = *(((PUCHAR)pSrc)++);
if(–Length == 0) return;
*(((PUCHAR)pDest)++) = *(((PUCHAR)pSrc)++);
if(–Length == 0) return;
*(((PUCHAR)pDest)++) = *(((PUCHAR)pSrc)++);
if(–Length == 0) return;
*(((PUCHAR)pDest)++) = *(((PUCHAR)pSrc)++);
if(–Length == 0) return;
*(((PUCHAR)pDest)++) = *(((PUCHAR)pSrc)++);
if(–Length == 0) return;
}
else
memcpy(pDest, pSrc, Length);
上面的代码在嵌入式平台中对memcpy函数进行了重写。
我们知道,linux内核中使用汇编代码对memcpy函数进行实现。由于CPU对于不是字节对齐数据的存储速度会变慢,因此memcpy的代码算法会考虑先对数据进行4字节为单位的移动,当数据少于4字节的情况下,再进行2字节的移动,最后进行1字节的移动,这样显然可以提高函数的处理效率。
考虑移动长度很短,如在小于8字节的情况下,上面的做法是否总是必要。分成4、2、1字节的移动,需要对长度Length进行除法/移位运算,以得到4、2、1字节移动的次数,而对于整体移动长度较短的情况,这些除法/移位运算的开销就不能不计了。例子中给出的代码正是考虑到这个问题,对memcpy函数进行了重写,在长度小于8字节的情况下,总是采取1字节移动的过程。
还有很多代码优化的例子,如switch-case语句的写法、条件和循环的处理方法等等,建议平时多阅读相关代码进行积累,而这里我是没有办法更没有能力对代码优化问题进行完全详尽的阐述。