Archive

Archive for the ‘Embedded Linux Systems’ Category

(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语句的写法、条件和循环的处理方法等等,建议平时多阅读相关代码进行积累,而这里我是没有办法更没有能力对代码优化问题进行完全详尽的阐述。

(2006年04月5日 星期三 19:13)有关内核与驱动调试

这里要介绍的是在没有任何调试工具下的一些调试方法与技巧。

1、oops/Kernel Panic的调试

      Linux下的oops大多数是由指针错误造成,其结果会产生一个oops消息。在没有任何调试/解析工具的情况下,oops消息会呈现为一堆看似无意义的指针地址。

      在这些地址中,对调试有帮助的主要是:

      epc,程序寄存器的值

      lr,连接寄存器的值。一般在RISC体系的CPU中会有这个寄存器,用来保存函数的返回地址,而CISC体系CPU对应的这个值是保存在堆栈中的

      Call trace,记录oops发生前内核的函数调用路径

      通过上面的解释可以看出,oops指针地址包含了对程序调试非常有意义的地址信息,如果能解析这些地址信息,就可以定位oops发生的位置。下面将会讨论一下如何对oops消息中的指针地址进行解析。

      oops消息中的地址一般对应于程序的两个地方:内核代码段和内核模块(一般为设备驱动)代码段。对于内核代码段地址的解析,可以对内核的未压缩映象文件vmlinux进行objdump,通过搜索相应的dump文件即可。

      对于内核模块代码段地址的解析,则需要一些技巧。由于内核模块编译时都采用的是-r选项,相应objdump得到的地址会是以0×00起始的相对地址,因此不能通过简单的搜索匹配进行解析。但是如果我们能得到相应模块insmod装载的绝对地址,然后通过简单运算,就可以进行正确解析了。为了得到模块加载地址,需要进入内核sys_init_module函数,打印mod->init的值。

2、内核路径的trace

      在调试中,往往需要对内核路径的调用非常清楚,要明了内核在什么时候会发生调度,什么时候可以被中断,进程上下文何时会被切换,哪些资源需要互斥等等。通过实践经验,较深的内核oops问题,往往会隐藏于在中断频繁,资源互斥较多的case下。

3、printk与printf

      在没有调试工具的情况下,往往会使用printk以及printf对程序进行调试,问题是在任何情况下,我们都要相信printf和printk的输出吗?

      答案是否定的。由于printf和printk在内核中,是通过软中断实现的,而我们都知道软中断在内核路径中是很难保证其运行的时间的。特别是在硬中断的情况下,硬中断可以中断软中断,这样会导致printf和printk的输出信息的顺序被打断,甚至乱序,而如果我们总是认为其输出是正确的,显然会给我们的调试带来困惑。

4、关于硬中断

      上面谈到了硬中断的话题,突然想起关于硬中断的一个问题:写一个中断处理函数要注意哪些问题?

       1)中断处理函数不能传参,也没有返回值

       2)中断处理函数中不要使用printk,由于printk是软中断实现,在中断处理函数中加入printk对于调试没有什么太多的意义

       3)中断处理函数中不要进行调度,或者调用任何会引起调度的函数,否则会使系统死锁

       4)为了提高系统性能,开关中断的时机,以及选择哪些过程使用底半非常的重要

(2006年05月19日 星期五 16:17)回顾一下

      某老同志整天嚷着要我做讲座,然后我就做了。终于在西元2006年2月16日下午,我开始了系列讲座的第一讲,题目是"Building Embedded Linux Systems"。

      为了准备这次的讲座,我整理了自己此前所有Presentation的PPT,本来是想从中找到一两张直接可用的Slide,却意外地发现了件巧合的事情:2004年,在2月16日这天,也是下午,我做过一次讲座,题目竟然同样是"Building Embedded Linux Systems"。

      到现在为止,我的讲座已经进行了三讲,回顾一下。

      第一讲–“Introduction & Basic Concepts”,时间是2月16日下午。自己懂一点点嵌入式CPU,体系结构以及实时操作系统的概念,这些就组成了讲座的基本内容。

      讲座中我卖了个噱头,介绍了两本书作为Reference,《花间词》和Thomas L Friedman的The World is Flat。前者纯粹是我胡扯搞笑,书是王敏师姐留下的,温庭筠、韦庄的风雅与我无关;后者倒是本好书,IT、通信在北美是有文化历史的,技术发展本身也有流派和继承的原因,Thomas L Friedman为了说明世界的平坦,而讲了许多与技术文化相关的故事,读起来还是很有意思和有益的。

      第二讲–“Development  Environment”,时间是3月21日的下午。具体忘了为什么拖了这么久才有第二讲,反正是连续跳票引起了N多人的不满!这一讲的重点是如何Build自己的cross-platform development toolchain,为了说明清楚还扯了点编译原理的背景,并提供了我之前做的素材tgz包和笔记。

      惯性地又介绍了本书,Dr. Brooks的The Mythical Man-Month。作者是IBM的前辈,书是那种Bible级别的经典,遗憾是30年前书就完稿,因此虽然经过了多次修改和再出版,还是会觉得其中介绍的内容有点陈旧。还好不管怎样,经典就是经典,人月、狼人和银弹的比喻很形象,所谓外科手术与贵族专制的说法可以统一实践中的疑虑,按部就班的开发方法倒是很适合学校项目的环境。

      第三讲–“Setting Up the Bootloader”,时间是4月6日,前三讲中唯一能被戏称为可以算上技术层面的一讲。对待技术,我不敢忽悠,讲了很多代码,目的是给一个这个层次程序写法的一个感观的印象,同时也拿kernel的一些代码进行了比较说明。

      下一讲?计划中,很快了……

Follow

Get every new post delivered to your Inbox.