第六章 Linux系统在ARM平台的移植 1、 使某一个平台的代码运行在其它平台上的过程就叫做移植。 2、 Linux内核结构: * /arch包含了所有硬件结构特定的内核代码。 Linux系统能支持如此多平台的部分原因是因为内河把原程序代码清晰的划分为体系结构无关部分和体系结构相关部分。对于任何平台,都必须包含以下几个目录: * boot:包括启动内核所使用的部分或全部平台特有代码。 * kernel:存放支持体系结构特有的(如信号处理和SMP)特征的实现。 * lib:存放高速体系结构特有的(如strlen和memcpy)同用函数的实现。 * mm:存放体系结构特有的内存管理程序的实现。 * math-emu:模拟FPU的代码。对于ARM处理器来说,此目录用mach-xxx代替。 显然,移植工作的重点就是移植arch目录下的文件。 * /drivers包含了内核中所有的设备驱动程序。 * /fs包含了所有的文件系统的代码。 * /include包含了建立内核代码时所需的大部分库文件,这个模块利用其他模块重建内核。该目录也包含了不同平台需要的库文件。比如,asm-arm是arm平台需要的库文件。 * /init包含了内核的初始化代码,内核从此处工作。 不是系统的引导代码,由main.c和version.c两个文件。这是研究核心如何工作的好起点。 * /ipc包含了进程间通信代码。 * /kernel包含了主内核代码。 * /mm包含了所有内存管理代码。 * /net包含了和网络相关的代码。 3、 在移植过程中,定时器、中断、CACHE管理、MMU等和硬件密切相关的地方都是要相关平台的底层代码支持的,要特别注意。 4、 编译内核: 1) 配置内核 make ARCH=arm CROSS_COMPILE=arm-Linux- menuconfig 2) 创建内核依赖关系 make dep 3) 创建内核镜像文件 make zImage 4) 创建内核模块 make modules make modules_install 对每一个配置来说,内核生成以后包括4个文件:没有压缩的内核镜像(zImage或bzImage),压缩的内核镜像(vmlinux),内核符号映射文件(System.map)以及配置文件(config)。 第七章 Linux设备驱动程序开发 1、 设备驱动的任务包括: 1) 自动配置和初始化子程序。这部分程序仅在初始化的时候被调用一次。 2) 服务于I/O请求的子程序。这部分是系统调用的结果。在执行这部分程序的时候,系统仍认为和进行调用的进程属于同一个进程,只是由用户态变成了核心态,并具有进行此系统调用的用户程序的运行环境,所以可以在其中调用sleep()等与进程运行环境有关的函数。 2、 设备类型分类: 1) 字符设备(char device)。字符设备是Linux最简单的设备,可以向文件一样访问。 初始化字符设备时,它的设备驱动程序向Linux登记,并在字符设备向量表中增加一个device_struct数据结构条目,这个设备的主设备标识符用作这个向量表的索引。一个设备的主设备标识符是固定的。chrdevs向量表中的每一个条目,一个device_struct数据结构,包括两个元素:一个登记的设备驱动程序的名称的指针和一个指向一组文件操作的指针。参见include/linux/major.h。 2) 块设备(block device)。是文件系统的物质基础,它也支持像文件一样被访问。 Linux用blkdevs向量表维护已经登记的块设备文件。它像chrdevs向量表一样,使用设备的主设备号作为索引。它的条目也是device_struct数据结构。与字符设备不同的是,块设备分为SCSI类和IDE类。类向Linux内核登记并向河心提供文件操作。一种块设备类的设备驱动程序向这种类提供和类相关的接口。参见fs/devices.c。 每一个块设备驱动程序必须提供普通的文件操作接口和对于buffer cache的接口。每一个块设备驱动程序填充blk_dev向量表中的blk_dev_struct数据结构。这个向量表的索引还是设备的主设备号。这个blk_dev_struct数据结构包括一个请求例程的地址和一个指针,指向一个request数据结构的列表,每一个都表达buffer cache向设备读/写一块数据的一个请求。参见drivers/block/ll_rw_blk.c和include/linux/blkdev.h。 当buffer_cache从一个已登记的设备读/写一块数据,或者希望读写一块数据到其他位置时,他救灾blk_dev_struct中增加一个request数据结构。每个request数据结构都有一个指向一个或多个buffer_head数据结构的指针,每一个都是读/写一块数据的请求。如果buffer_head数据结构被锁定(buffer_cache),可能会有一个进程在等待这个缓冲区的阻塞进成完成。每一个request数据结构都是从all_request表中分配的。如果request增加到空的request列表,就调用驱动程序的request函数处理这个request队列,否则驱动程序只是简单的处理request队列中的每一个请求。 块设备驱动程序和字符设备驱动程序的主要区别是:在对字符设备发出读写请求时,实际的硬件I/O一般紧接着就发生了,块设备则不然,它利用一块系统内存作为缓冲区,当用户进程对设备请求能满足用户的要求时,就返回请求的数据,如果不能就调用请求函数来进行实际的I/O操作。块设备是主要针对磁盘等慢速设备的,以免耗费过多地CPU时间来等待。 3) 网络设备。 3、 设备驱动中关键数据结构: 1) file_operations:内核内部通过file结构识别设备,通过file_operations数据结构提供文件系统的入口点函数。file_operations定义在中的函数指针表。这个结构的每一个成员的名字都对应着一个系统调用。从某种意义上说,写驱动程序的任务之一就是完成file_operations中的函数指针。如果在2.4版本内核下开发的驱动很可能在2.6版本中无法使用,需要进行移植。 2) inode:文件系统处理的文件所需要的信息在inode(索引节点)中。 3) file:主要用于与文件系统对应的设备驱动程序使用。 第八章 网络设备驱动程序开发 1、 网络设备使用网络接口管理表dev_base。dev_base是一个指向device结构的指针,因为网络设备是通过device数据结构来表示的。 2、 网络驱动程序必须解决的两个问题: 1) 不是所有建立在Linux内核的网络设备驱动程序都会有控制的设备。 2) 系统中的以太网设备总是叫做/dev/eth0到/dev/eth7,而不管底层的设备驱动程序是什么。当驱动程序找到它的以太网设备时,它就填充它现在拥有的ethn的device数据结构。这时网络驱动程序也要初始化它控制的物理硬件,并找出它使用的IRQ、DMA等。一旦所有的8个标准的/dev/ethn都分配了,就不会再探测更多的以太网设备。 3、 两个重要的数据结构: 1) device:这个数据结构是在系统中每一个设备的代表,它提供了多个设备方法,供操作系统或协议层使用。这其中就包括设备初始化时调用的init函数、打开和关闭设备的open和stop函数、处理数据包发送的hard_start_xmit函数等。 2) sk_buff:Linux网络各层之间的数据传送都是通过sk_buff(套接字缓冲区)完成的。每个sk_buff包括一些控制方法和一块数据缓冲区,这个区域存放了网络传输的数据包。控制方法按功能分为两种类型,一种是控制整个buffer链的方法,另一种是控制数据缓冲区的方法。sk_buff组成双向链表的形式,对链表的操作主要是删除链表头的元素和添加到链表尾。sk_buff数据结构再include/linux/skbuff.h文件中定义。 4、 内核的驱动程序接口: 1) 打开函数net_open:执行"ifconfig eth0 up"命令时网络层调用它,把设备连到线路上并启用来接受/发送数据。open函数在网络设备驱动程序里是网络设备被激活的时候被调用的,即设备状态由down至up。所以实际上很多在初始化的工作可以放到这里来做。 2) 关闭函数net_close:执行"ifconfig eth0 down"时使网卡进入一个清醒的状态。如果硬件许可那么它会释放中断和DMA通道,并完全关闭以节约能源。 3) 探测函数probe/probe1:在启动时调用以检测网卡存在与否。如果可以通过读取内存等非强制手段进行检查最好,也可以从I/O端口读取。在探测函数的最后它会填充device结构各域。 4) 发送函数:它与dev->hard_start_xmit()连接,在内核想通过设备传送数据时调用它。如果发送成功,hard_start_xmit()方法里释放sk_buff,返回0,否则返回1。如果dev->tbusy置为非0,则系统认为硬件忙,要等到dev->tbusy置0后才会再次发送;也可以不置dev->tbusy为非0,这样系统会不断尝试重发。tbusy的置0任务一般由中断完成,然后用mark_bh()调用通知系统可以再次发送。 5) 接收函数:它把数据从硬件移出,放在sk_buff结构中,执行netif_rx(sk_buff)告诉内核数据所在位置。真正的处理是在中断返回之后,这样可以减少中断时间。在协议层,接收数据包的流程控制分两个层次:首先,netif_rx()函数限制了从物理层到协议层的数据帧的数量;然后,每一个套接字都有一个队列,限制从协议层到套接字层的数据帧的数量。 6) 中断处理函数:需要了解相关的中断状态位以进行相应的操作。 7) 其他函数。 第九章 USB驱动程序开发 1、 OHCI简介: OHCI规范定义了两个主机控制器(HC)与主机控制器驱动(HDC)的通信通道(Communication Channel)。第一个为主机控制器操作寄存器,第二个为主机控制器通信域(Host Controller Communications Area, HCCA)。 OHCI规范支持USB四种数据通信方式,并根据数据传输特定,将中断数据传输和等时数据传输归为同一类周期性数据传输方式。在HCCA中定义了4个链表。其中除完成数据链表外,其他的周期性数据链表、控制传输数据链表和批量传输数据链表都是二维链表。每个ED(Endpoint Descriptor)描述USB设备的一个端点的所有的数据传输,所有的ED被连接在一起,而TD(Transfer Descriptor)描述的才是最终要在USB总线上传输的数据包。属于同一个USB设备的端点的TD被连接在一起,并挂在相应的ED上。 主机控制器硬件通过寄存器访问该链表来得到相关的USB传输数据包,并将其发送到USB总线上。主机控制器驱动程序则根据实际的数据传输需要,将要发送的数据包添加到相应的链表上。 OHCI定义了两类TD:通用TD(General TD)和等时TD(Isochronous TD)。通用TD被用来支持USB中断、批量和控制三类传输方式,而等时TD被用来支持USB等时数据传输。用一个单独类型的TD来实现USB等时数据传输的目的是为了方便实现DMA数据传输功能。 2、 Linux下USB系统文件节点:同其他外设一样,上层应用软件对连接在系统地USB设备访问是通过文件系统的形式进行的。每个连接到系统总线上的USB设备可以同时对应有一个或者多个驱动程序。即一个USB设备可以在Linux系统上形成一个或多个设备节点,以供应用程序使用。在Linux系统上,每个设备节点都有其相关的主设备号和次设备号。 3、 USB主机驱动结构:Linux USB主机驱动由三部分组成: 1) USB主机控制器驱动(HCD):是USB主机驱动程序中直接与硬件交互的软件模块,其主要功能有:主机控制器硬件初始化;为USBD层提供相应的接口函数;提供根HUB(ROOT HUB)设备配置、控制功能;完成4种类型的数据传输等。 2) USB驱动(USBD):是整个USB主机驱动的核心,其主要实现的功能有:USB总线管理、USB总线设备、USB总线带宽管理、USB的4种类型数据传输、USB HUB驱动、为USB设备类驱动提供相关接口、提供应用程序访问的USB系统的文件接口等。 3) USB设备类驱动:是最终与应用程序交互的软件模块,其主要实现的功能有:访问特定的USB设备、为应用程序提供访问接口等。 应用程序首先通过文件系统(POSIX)接口来访问相应的USB设备类驱动程序和USBD;USB设备类驱动程序则通过USBD提供的相关接口将数据请求包传递给USBD;USBD通过HCD提供的接口,进一步将数据包传递给HCD;HCD最终将数据发送到USB总线上。Linux定义了通用的数据结构URB用来在USB设备类驱动和USBD,USBD和HCD间进行数据传输。统一的URB(Universal Request Block)结构为usb主机驱动程序的开发带来了很大方便。 4、 USB时序: 1) 数据传输时序:在USB总线上,所有的数据传输都是由USB HOST发起的。每个USB设备通过地址过滤出自己要接受的数据包,并根据数据包请求的类型与USB HOST进行数据传输。由于数据传输的时序和总线带宽问题,当应用程序通过设备类提供一个URB时,该数据包并不能立即被送到USB总线上,而只能在USB总线上有足够带宽的情况下,该数据请求才会被传输。因而,HCD层为不同类型的数据传输维护了相应的数据链,当数据链上的数据包传输结束后,HCD通过调用与该数据包相关联的回调函数来通知设备类驱动程序。 2) 设备连接:当一个USB设备连接到USB总线上时,USB HUB驱动程序首先通过中断数据传输获得设备连接信息,然后通过调用USB内核模块所提供的相关函数来完成对USB设备的配置工作。同时USBD不但要为新设备分配在USBD层所需要的资源,同时也要为USB设备分配在HCD层所需要的资源。接着,USBD通过调用所有USB设备类驱动程序提供的Probe函数来查找适合该USB设备的驱动程序。 3) 设备断开:当一个USB设备断开时,USB HUB驱动程序首先通过中断数据传输获得设备断开信息,然后通过调用USB内核模块所提供的相关函数来释放USBD层和HCD层为该设备分配资源。同时通过调用该设备的相关驱动程序提供的Disconnect函数来通知设备驱动程序该设备已经断开。 第十章 图形用户接口 1、 嵌入式系统中几种流行的GUI: 1) MicroWindows; 2) MiniGUI:是我国为数不多的在国际比较知名的自由软件之一。几乎所有的MiniGUI代码都采用C语言开发,提供了完备的多窗口机制和消息传递机制以及众多空间和其他GUI元素。其引人注目的特性和技术创新主要有: * 是一个轻量级的图形系统; * 完善的对中日韩文字、输入法的多字体和多字符集支持 * 提供图形抽象层(GAL)以及输入抽象层(IAL)以适应嵌入式系统各种显示和输入设备 * 提供MiniGUI-Threads、MiniGUI-Lite、MiniGUI-standone三种不同架构的版本以满足不同的嵌入式系统 * 提供了丰富的应用软件,其商业版本提供了手机、PDA类产品、媒体及机顶盒类产品以及工业控制方面的诸多程序; 3) Qt/Embedded:是挪威Trolltech软件公司的产品,Linux桌面系统的KDE就是基于Qt库(不是QtE)开发的。使用QtE苦,开发者可以: * 当移植QtE程序到不同平台时,只需要重新编译代码,而不需要对代码进行修改; * 随意设置程序界面的外观; * 方便地为程序连接数据库; * 使程序本地化; * 将程序与Java集成 同Qt一样,QtE也是用C++写的,虽然这样会增加系统资源消耗,但是却为开发者提供了清晰的程序框架,使开发者能够迅速上手,并且可以方便的编写自定义的用户界面程序。 2、 MiniGUI编程: 它采用类似WINDOWS SDK的窗口和事件驱动机制,MiniGUI的存储空间占用情况如下: 项目 容量 备注 Linux内核 300~500KB 由系统决定 MiniGUI支持库 500~700KB 由编译选项确定 MiniGUI字体、位图等资源 400KB 由应用程序确定,可缩小到200KB以内 GB2312输入法码表 200KB 不是必需的,由应用程序确定 应用程序 1~2MB 由应用程序决定 MiniGUI层次: FrameWork, MMI, Key Apps MiniGUI ANSIC Library Portable Layer Devices Linux/uCLinux, eCos, uC/OS-II, VxWorks, Psos ix86, ARM, MIPS, PowerPC, M68K MiniGUI是一个典型的消息驱动的GUI系统,每一个MiniGUI程序都从MiniGUIMain函数开始。应用程序先以CreatMainWindow函数创建一个主窗口,由于窗口创建的时候默认是不可见的,所以我们通过ShowWindow函数显现该窗口,最后通过GetMessage(&Msg, hMainWnd)进入消息循环。 3、 Qt/Embedded编程: Qt是以工具开发包的形式提供给开发者的,这些工具开发包包括了图形设计器、Makefile制作工具、字体国际化工具和Qr的C++类库等。如果不考虑X窗口系统的需要,基于QrE的应用程序可以直接对缓冲帧进行写操作。QtE提供了大约200个可配置的特征,由此再Intel X86平台上库的大小范围为700KB~5MB。大部分客户选择的配置使得库的大小为1.5~4MB。Qt/Embedded与Qt/X11的Linux版本架构比较如下: 应用源代码 Qt API Qt/Embedded Qt/X11 Qt/XLib X Window sever 帧缓冲 Linux内核 信号与插槽机制提供了对象间的通信机制。Qt的窗口在事件发生后会激发信号,程序员通过建立一个函数(称作插槽)然后调用connect()函数把这个插槽和一个信号连接起来,这样就完成了一个事件和响应代码的连接。信号与插槽机制不需要类之间互相知道细节,这样就可以相对容易地开发出代码可高度重用的类。信号与插槽机制是类型安全的,它以警告的方式报告类型错误,而不会使系统产生崩溃。 Qt拥有丰富的满足不同需求的窗体(如按钮、滚动条等)。窗体是QWidget类或它子类的实例,客户自己的窗体类需要从QWidget的子类继承。一个窗体可以包含任意数量的子窗体,子窗体可以显示在父窗体的客户区,一个没有父窗体的窗体称为顶级窗体(一个"窗口"),一个窗体通常有一个边框和标题栏作为装饰。在父窗体无效、隐藏或被删除后,它的子窗体都会进行同样的操作。 第十一章系统设计开发 1、 按照嵌入式系统的工程设计方法,嵌入式系统的设计可以分成7个阶段:产品定义、硬件与软件划分、迭代与实现、详细的硬件与软件设计、硬件与软件集成、接受测试和维护与升级。前三个阶段确定要解决的问题及需要完成的目标,也常被称为"需求阶段";第四阶段主要解决如何在给定的约束条件下完成用户的需求;后三个阶段主要解决如何在所选择的硬件和软件基础上进行整个软/硬件系统的协调实现。 2、 硬件设计主要有以下五个关键步骤: 1) 功能定义; 2) 原理图设计:应尽量做到标准化、通用化、模块化、可扩展化; 3) PCB设计:是硬件设计的难点; 4) 硬件调试; 5) 产品化调整:一个系统的设计样机到产品化是个漫长的过程,单从硬件角度讲,至少要满足功能性、稳定性、可靠性、工艺性等最低要求。 3、 原理图设计中的几点建议: 1) 开关电源和线性电源的优缺点: 优点 缺点 开关电源 * 输入电压范围宽 * 效率高 * 输出功率大 * 应用比较灵活 * 电路相对复杂,外围器件较多 * 对电容电感的要求很高,布线也很讲究 * 开关频率会给系统带来干扰 * 纹波比较难控制 线性电源 * 电路简单,外围器件很少 * 输出精度高,有很好的负载曲线 * 工作在低频状态,不会给系统带来麻烦 * 输入范围比较受限制 * 效率低,这是由于线性电源自身的损耗造成的 * 输出功率相对较小 2) NAND和NOR Flash的速度差异: * NOR得读速度比NAND稍快一些; * NAND的写入速度比NOR快很多; * NAND的4ms擦除速度远比NOR的5s快; * 大多数写入操作需要先进行擦除操作; * NAND的擦除单元更小,相应的擦除电路更少。 4、 PCB设计要点: 1) 元件封装的准备: * 尽量调用标准封装库中的文件; * 严密按照所选器件的数据手册上的规范制作封装,不能忽略累计误差; * 注意二极管、三极管等极性元件及一些非对称元件的引脚定义不能搞错。 2) 合理布局: * 尽量依照参考板的模式进行布局; * 模块化布局; * 要求模拟电路与数字电路分开; * 输入模块与输出模块隔离; * 去隅电容尽量靠近元件的电源/地; * 电源等发热元件要考虑散热,主发热元件靠近出风口,大体积元件的放置避开风路; * 元件分布均匀,避免电流过于密集; * 板上的跳线或按键考虑易操作性; * 元件的排列尽量整齐美观; * 考虑机械尺寸,不要超过结构所允许的范围。 3) PCB分层: * 如果有参考板,按照参考板进行分层; * 多层板安排:顶层和底层为元件面,第二层为地平面,倒数第二层为电源层; * 在不影响性能的情况下,减少PCB层数,降低成本。 4) 电源考虑: * 系统电源入口做高频和低频滤波处理; * 功率较高的器件配备大容量电容去除低频干扰; * 每个器件配备0.1Uf电容过滤高频干扰; * 高频器件电源管脚和电容之间串联磁珠达到更好的效果; * 去耦电容的引线不能过长,特别是高频旁路电容不能带引线。 5) 时钟考虑: * 时钟电路尽量靠近芯片; * 晶体下方不要走线; * 晶体外科接地,增加抗电磁干扰能力; * 频率大于20MHz的时钟信号有地线护送; * 时钟线宽大于10mil; * 时钟输出端串联22~220Ω的阻尼电阻。 6) 高速信号: * 采用手工布线; * 高速总线走线尽量等长,并且在靠近数据输出端串联22~300Ω的阻尼电阻; * 高速信号远离时钟芯片和晶体; * 高速信号远离外部输入输出端口,或地线隔离。 7) 差分信号: * 差分信号要平行等长; * 信号之间不能走其他信号线; * 信号要求在同一层上。 8) 走线规范: * 不同层的信号垂直走线; * 地线和电源层不要走线,否则要保证平面的完整性; * 导线宽度不要突变; * 导线变向时导角要大于90度; * 定位孔周围0.5mm范围不要走线。 5、 硬件调试: * 优先调试电源:保证系统可靠地供电; * 分模块调试:可以分清模块间的问题,不至于混淆; * 结合软件调试:对于复杂的接口,单纯硬件角度不易调试,结合软件从不同的角度测试,能起到更好的效果。 * 正确、合理的使用示波器,提高工作效率; * 对比调试:有条件用评估板或功能相似的电路板作参考,比较差异并找出问题所在; * 系统时钟受干扰或晶体震荡不正常导致系统工作故障; * 复位不可靠,造成个单元未进入预期状态而出现问题; * 因焊接问题引发的各种问题,如方向焊错、虚焊、错焊等; * 因时序不匹配引发的通信故障,如时钟信号通过逻辑器件后产生延时,与读写信号时序搭配不上导致读写错误。 6、 嵌入式文件系统: 目前支持闪存的文件系统技术有以下几种: * JFFS2和Yaffs。这些文件系统可以使用在没有初始化的NAND Flash和有CFI接口的NOR Flash中。JFFS2的特点包括: * 支持数据压缩; * 提供了"写平衡"支持; * 支持多种结点类型; * 提高了对闪存的利用率,降低了闪存的消耗。 JFFS2中最重要的数据结构是jffs2_sb_info,这个数据结构用来管理所有的结点链表和闪存块。它在/src/include/Linux/jffs2_fs_sb.h中定义。 Yaffs/Yaffs2(Yet Another Flash File System)和JFFS相比,它减少了一些功能,因此速度更快、占用内存更少。 * TrueFFS。该文件系统相当于Linux中的MTD层,必须配合其他文件系统。 * FTL/NTFL。它是一种中间层解决方案的统称,为上层文件系统提供接口。 * RAMFS、CRAMFS和ROMFS。这些文件系统用于早期的小容量闪存设备,系统功能比较简单,仅提供基本接口,只属于只读的闪存文件系统。适合存储空间小的系统。 7、 MTD简介: 无论JFFS2还是Yaffs,都需要MTD(Memory Technology Devices,内存技术设备)的支持。MTD是对Flash操作的接口,提供了一系列的标准函数,将硬件驱动设计和系统程序设计分开,硬件驱动人员不用了解存储设备的组织方法,只需提供标准的函数调用,如读、写等。 一个MTD原始设备可以通过mtd_part分割成数个MTD原始设备注册进mtd_table,mtd_table中的每个MTD原始设备都可以被注册成一个MTD设备,