uclinux内核与 驱动开发 -...
TRANSCRIPT
uClinux内核简介• uCLinux 是Linux 2.0核心的分支,是针对没有MMU管理单元的微控制器
• 继承了Linux的大多数特性• 多数的Linux下应用程序和驱动程序都可以在uClinux下运行
• 内核精简• Kernel < 512KB
• Kernel+root < 900KB
• 并入linux 2.6内核中
uClinux与linux的区别(1)内存管理
• uClinux运行于没有MMU的处理器上
• 使用平坦式(flat)内存管理模式,虚拟内存到物理内存是一对一的映射关系
• 对于应用程序,使用固定的栈空间。加载应用程序的时候需要重新定位
• 可用内存空间的大小受到物理内存的限制
uClinux与linux的区别(2)
fork与vfork
• uClinux 的多进程管理通过vfork 来实现,uClinux只能支持vfork
• vfork使父进程锁定直到子进程exec() 或exit()
注:uClinux的应用程序的多线程可以依赖于标准C库来实现(uClibc)
uClinux与uCOS-II(1)• uClinux源于linux,是一个很完整的系统,包括:
• 多任务调度
• 内存管理
• 文件系统(及接口)
• 设备驱动程序
• 完整的TCP/IP的支持
• 源码开放,支持广泛(GUI、FS、驱动程序等等)
uClinux与uCOS-II(2)uCOS-II
• 源码开放,内核简单,易于学习和移植
• 占先式内核,实时性好
• 只有多任务调度的简单内核
• 内存管理过于简单,几乎没有动态内存管理功能
• 文件系统和图形界面需要外挂
• 对于设备驱动程序没有专门统一的接口
uClinux的实时性问题• uClinux内核不关心实时性问题
• 可以和RTLinux配合来实现实时
• RTLinux处理实时任务,非实时任务由linux完成
• RTLinux是为linux提供实时性的方法,同样也适用于uClinux。通过RTLinux的patch,可以满足uClinux对实时性的需求
uClinux的移植版本• Motorola DragonBall、68K
• Motorola Coldfire
• ARM7TDMI、ARM9TDMI(ARM940T)
• Altera Nios
• …
www.uClinux.org
uClinux 的内核组成• 初始化程序段(init) 32k 左右
• 数据段(data) 50~100k 左右
• 未初始化数据段(bss) 100k~150k 左右
• 代码段(text) 300k 左右
• (init、data、bss 和text 的地址是由编译链接时的定位文件vmlinux.lds 决定的)
• 文件系统(romfs) > 80k 左右
移植uClinux的主要工作• 选择处理器对应的交叉编译器
• 选择并修改Bootloader
• 修改链接文件(vmlinux-armv.lds.in),定位各个数据段
• 定义系统定时器、控制台(Console)
• 编写中断的控制函数
• 定义根文件系统
• 编写其他系统设备驱动…
编译器的选择
uClinux内核在ARM上的编译
• 交叉编译器
• arm-elf-gcc、arm-linux-gcc
• 标准C库——应用程序
• uC-libc、uClibc
(uC)linux的bootloader• 系统配置、中断接管、引导
• 装载内核、根文件系统、参数传递、内核调试、内核和根文件系统的下载等等
• 常见的uClinux(Linux)的Bootloader:• Redboot
• Blob
• Vivi
• Uboot
• armBoot…
uClinux发行版的目录结构uClinux-distuClinux-dist
/NetSilicon/NetSilicon/Samsung/Samsung
/Arcturus/Arcturus
Romfs的相关工具
根文件系统
新版的标准C库 uClibc
应用程序
各制造商的配置文件
for NET+40for S3C4510
for uCdimm and uCsimm
configconfigDocumentationDocumentation
vendorsvendors
liblib
linux-2.0.xlinux-2.0.x
linux-2.4.xlinux-2.4.x
toolstools
uClibcuClibc
useruser
romfsromfs
uClinux-2.4内核uClinux-2.0内核
旧版的标准C库uC-libc配置脚本
相关的文档
uClinux的内核目录结构
Linux2.4.xLinux2.4.x
/arch/arch
/drivers/drivers
/fs/fs
/include/include
/init/init
/ipc/ipc
/kernel/kernel
/lib/lib
/mm/mm
/mmnommu/mmnommu
/net/net
/scripts/scripts
/armnommu/armnommu /i196/i196 /m68knommu/m68knommu
/boot/boot /mach-s3c44b0/mach-s3c44b0 /kernel/kernel /lib/lib /mm/mm
/asm-armnommu/asm-armnommu /linux/linux /net/net
/arch-netarm/arch-netarm /proc-netarm/proc-netarm
读懂uClinux内核源码• (uC)linux内核庞大,结构复杂
• 对uClinux内核的统计:接近1万个文件,4百万行代码
• 内核编程习惯(技巧)不同于应用程序
(uC)linux内核的C代码• Linux内核的主体使用GNU C,在ANSI C上进行了扩充
• Linux内核必须由gcc编译编译
• gcc和linux内核版本并行发展,对于版本的依赖性强
• 内核代码中使用的一些编程技巧,在通常的应用程序中很少遇到
GNU C的扩充举例• 从C++中吸收了inline和const关键字
• ANSI C代码与GNU C中的保留关键字冲突的问题可以通过双下划线(_ _)解决
• 例如:inline 等价于 __inline__、asm等价于__asm__
• 结构体(struct)的初始化
结构体初始化
struct sample {
int member_int;
char *member_str;
void (*member_fun)(void);
};
ANSI C中的实现struct sample inst_c={
100, //member_int
NULL, //*member_str;
myfunc //void (*member_fun)(void);
};
C99中的实现
struct sample inst_c99 = {
.member_int = 100,
.member_fun = myfun,
};
GCC中的实现struct sample inst_gcc = {
member_fun: myfun,
member_int: 100,
};
与C99中的用法类似,不必关心struct定义的中的实际的顺序和其他未定义的数据,在复杂的结构体初始化的时候很有优势。
宏定义的灵活使用(1)• 虽然GCC中定义了inline关键字,但是,宏操作(#define)仍然在系统中大量使用
• 举例:
#define DUMP_WRITE(addr,nr) do\
{ memcpy(bufp,addr,nr); bufp += nr; } while(0)
应用DUMP_WRITE,就像使用C的函数一样:
if(addr)
DUMP_WRITE(addr, nr);
else…
但是,如果如通过下的定义,都不能满足上述的情况
定义1:
#define DUMP_WRITE(addr,nr) memcpy(bufp,addr,nr);\bufp += nr ;
定义2:
#define DUMP_WRITE(addr,nr) {memcpy(bufp,addr,nr);\bufp += nr;}
宏定义的灵活使用(2)#define OFFSETOF(strct, elem) \
((long)&(((struct strct *)0)->elem))
• 1、((struct strct *)0) 结构体strct的指针
• 2、&((struct strct *)0)->elem)成员的地址,也就是相对于0的偏移
• 3、结果:OFFSETOF(strct,elem)返回的是,结构体strct中成员elem的偏移量
C语言中goto的使用• 在应用程序的C编程中,为了保证程序的模块化,建议不使用goto
• 内核代码需要兼顾到效率,所以,大量使用goto
• 整个内核的比例大概是每260行一个goto语句——速度优先
• 短距离的goto——在函数中
(uC)linux的驱动程序开发
(uC)linux的驱动程序• Linux下对外设的访问只能通过驱动程序• Linux对于驱动程序有统一的接口,以文件的形式定义系统的驱动程序:• Open、Release、read、write、ioctl…
• 驱动程序是内核的一部分,可以使用中断、DMA等操作
• 驱动程序需要在用户态和内核态之间传递数据• uClinux下可以在应用层直接访问外设,操作寄存器口,但是无法处理中断——不推荐使用
• uClinux不支持模块加载
内核功能的划分
• 进程管理(进程之间的通讯与同步)
• 内存管理(malloc/free)
• 文件系统
• 设备控制
• 网络功能(网络通讯协议等)
Linux下设备和模块的分类按照上述系统内核的功能,Linux中把系统
的设备定义成如下三类:
• 字符设备
• 块设备
• 网络设备
Linux下的设备• Linux的设备以文件的形式存在于/dev目录下
• 设备文件是特殊文件,使用ls /dev -l命令可以看到:
crw------- 1 root root 10, 7 Aug 31 2002 amigamouse1
crw------- 1 root root 10, 134 Aug 31 2002 apm_bios
brw-rw---- 1 root disk 29, 0 Aug 31 2002 aztcd
主设备号和次设备号
• 主设备号标识设备对应的驱动程序
• 一个驱动程序可以控制若干个设备,次设备号提供了一种区分它们的方法
• 系统增加一个驱动程序就要赋予它一个主设备号。这一赋值过程在驱动程序的初始化过程中
int register_chrdev(unsigned int major, const char*name,struct file_operations *fops);
创建设备节点
• 设备已经注册到内核表中,对于设备的访问通过设备文件(设备文件与设备驱动程序的主设备号匹配),内核会调用驱动程序中的正确函数
• 给程序一个它们可以请求设备驱动程序的名字。这个名字必须插入到/dev目录中,并与驱动程序的主设备号和次设备号相连
• 使用mknod在文件系统上创建一个设备节点
mknod /dev/mydevice c 254 0
动态分配设备号
• 在Documentation/device.txt文件中可以找到已经静态分配给大部分设备的列表
• 由于许多数字已经分配了,为新设备选择一个唯一的号码是很困难的
• 如果调用register_chrdev时的major为零,函数就会选择一个空闲号码并做为返回值返回
动态分配的问题
动态分配的主设备号不能保证总是一样的,无法事先创建设备节点
• 可以从/proc/devices读取
cat /proc/devices
• 利用脚本动态创建设备文件节点
设备管理的问题
如今,Linux 支持很多不同种类的硬件。这意味着/dev中都有数百个特殊文件来表示所有这些设备。而且,这些特殊文件中大多数甚至不会映射到系统中存在的设备上
使用devfs• 在Linux 2.4的内核里引入了devfs来解决linux下设备文件管理的问题
• 在驱动程序中通过devfs_register()函数创建设备文件系统的节点
• 系统启动的时候mount设备文件系统
• 所有需要的设备节点都由内核自动管理。/dev目录下只有挂载的设备
uClinux设备驱动程序剖析• 驱动程序的移植
• RTL8019AS——NE2000兼容的网卡驱动程序
• Nand Flash上的MTD设备驱动与文件系统
• 编写驱动程序
• CAN总线设备驱动程序
UP-NETARM3000上的RTL8019• 利用16位外部总线模式连接S3C44B0和RTL8019AS
• 使用外部中断1,高电平有效
• 没有配置EEPROM,使用跳线模式。通过IO口上电配置,通过驱动程序配置MAC地址等信息
原理框图
阅读Linux源码的工具• 在windows平台下
• SourceInsight(读C代码)
• UltraEdit(读汇编)
RTL8019网卡驱动程序的移植• Ne2000网卡的驱动就是drivers/net/ne.c,和它相关的还有8390.h和8390.c
• __init关键字:• 将该函数置于内核的特定区域
• 在内核完成自身初始化之后,就试图释放这个特定区域
• 在Ne.c中函数ne_probe是网卡的检测函数
• 在ne_probe1函数里的检测并初始化网卡
• 网卡的检测
• 内核调试信息输出(printk)
• 网卡复位
• 初始化配置(MAC地址等信息)
• dev->irq < 2 的问题
• 注册中断
• 网卡的检测是./drivers/net/Space.c的ethif_probe函数中实现的
• 定义struct devprobe arm_probes
•NS8390_init函数,配置网卡
•Ne2000兼容网卡的寄存器定义
• drivers/net/ne.c中的定义
• drivers/net/8390.h中的EI_SHIFT宏
这时候,系统启动的时候就会报告找到网卡。注意:
• 中断模式——高电平触发
• 关闭网卡空间的缓冲区
ARMv3与ARMv4• 在drivers/net/ne.c 中,ne_block_output函数里,针对16位总线有outsw函数的调用
• outsw函数定义在arch/armnommu/lib/ 包含io-writesw-armv3.S和io-writesw-armv4.S两个汇编文件 ,应该使用ARMv4的汇编
• 在arch/armnommu/Config.in中,定义CONFIG_ARCH_S3C44B0的时候,应该定义CONFIG_CPU_32v4
ARM家族的更新
网卡驱动程序移植需要注意的问题
• 确定网卡的基地址、中断无误
• 注意网卡的数据总线宽度,地址是否连续,如果不连续,如何映射
• 注意网卡中断的模式和对应的外部中断是不是一致
• 对于IO和RAM统一编址的处理器,注意缓冲区范围的设置
• 注意ARMv3和ARMv4等一些和处理器结构相关的底层函数库带来的问题
• 用抓包软件可以帮助分析定位问题所在
Nand Flash上的MTD设备驱动
• MTD(memory technology device)是用于访问memory设备(ROM、flash)的Linux子系统
• 在硬件和上层之间提供了一个抽象的接口 ,MTD把文件系统和Flash设备相隔离
MTD与文件系统
• Flash硬件驱动层• 在init时驱动Flash硬件,NAND型Flash的驱动程序则位于
/drivers/mtd/nand子目录下
• MTD原始设备有两部分组成• MTD原始设备的通用代码,
• 分是各个特定的Flash的数据,例如分区。
• mtd_info、mtd_table(mtdcore.c)、mtd_part(mtd_part.c)
• MTD设备层• linux系统定义出MTD的块设备(主设备号31)和字符设备(设备
号90)。设备节点:通过mknod在/dev子目录下建立MTD字符设备节点(主设备号为90)和MTD块设备节点(主设备号为31),通过访问此设备节点即可访问MTD字符设备和块设备。
• 根文件系统• 在Bootloader中将文件系统映像烧录到flash的某一个分区中,在
启动的时候,将该分区作为根文件系统挂载。
• 文件系统:• 内核启动后,mount
NAND和NOR——性能比较NOR和NAND是现在市场上两种主要的非易失
闪存技术
• NOR的读速度比NAND稍快一些
• NAND的写入速度比NOR快很多
• NAND的擦除速度远比NOR的快
• 大多数写入操作需要先进行擦除操作
• NAND的擦除单元更小,相应的擦除电路更少
接口差别
• NOR flash带有SRAM接口,线性寻址,可以很容易地存取其内部的每一个字节
• NAND flash使用复用接口和控制IO多次寻址存取数据
• NAND读和写操作采用512字节的块,这一点有点像硬盘管理此类操作易于取代硬盘等类似的块设备
容量和成本
• NAND flash生产过程更为简单,成本低
• 常见的NOR flash为128KB~16MB,而NANDflash通常有8~128MB
• NOR主要应用在代码存储介质中,NAND适合于数据存储
• NAND在CompactFlash、Secure Digital、PC Cards和MMC存储卡市场上所占份额最大
可靠性和耐用性
• 在NAND中每块的最大擦写次数是100万次,而NOR的擦写次数是10万次
• 位交换的问题NAND flash中更突出,需要ECC纠错
• NAND flash中坏块随机分布,需要通过软件标定——产品量产的问题
MTD设备驱动程序的移植• 建立在Nand Flash上
• MTD的补丁
• http://www.linux-mtd.infradead.org/
• MTD设备工具在uClinux发行版的应用程序中——eraseall等
参考:The Linux MTD, JFFS HOWTO
Nand Flash连接原理
D[15..0]
CLE16
ALE17
WE18
WP19
I/O0 29I/O1 30I/O2 31I/O3 32
VSS13 VCC 12
I/O4 41I/O5 42I/O6 43I/O7 44
VCC 37
R/B7
RE8
CE9
VSS36
SE6
U204K29F2808U
nSMCWEnSMCOE
R21710K
R21610K
D0D1D2D3D4D5D6D7
VDD33
VDD33
C205
104
C204
104
GPC10GPC11
GPE0
GPC15
移植代码
• 建议先给内核打补丁
• 有的linux内核带的MTD驱动程序有严重的问题
• nand flash相关代码在/drivers/mtd/nand/目录下
• 添加自己的驱动程序,可以从/drivers/mtd/nand/spia.c派生(arm_nand.c)
• 修改drivers/mtd/nand/config.in配置菜单
• 修改/drivers/mtd/nand/Makefile添加obj-$(CONFIG_MTD_NAND_ARM) +=arm_nand.o
解读arm_nand.c• 模块入口:armnand_init函数
• 初始化Nand flash所用端口
• 新版本的MTD设备驱动程序的改进
• module_init宏定义了linux加载的模块——启动的时候加载或者通过模块加载
• nand_scan确定设备及其类型,挂载相应的驱动程序
• add_mtd_partitions函数注册MTD分区
测试MTD设备(1)
• 正确加载了设备,使用命令cat /proc/mtd,可以显示MTD设备信息:dev: size erasesize name
mtd0: 00e00000 00004000 "Nand flash partition“
• 创建节点(如果不是用devfs)
• 在host linux上的romfs/dev目录创建@mtd0,c,90,0、@mtdblock0,b,31,0文件
• 用mkfs.jffs2(或者mkfs.yaffs)生成文件系统映象(比如jffs2.img)
测试MTD设备(2)• 目标板启动以后
cp jffs2.img /dev/mtd0
注意:这里使用的/dev/mtd0是字符设备
• mount –t jffs2 /dev/mtdblock0 /var/jffs2
• 需要在编译内核的时候包含jffs2文件系统
• 使用块设备
• 在文件系统上进行其他测试:
• [/var/jffs2]cp /root/hello .
嵌入式linux下常见的文件系统
• RomFS:只读文件系统,可以放在ROM空间,也可以在系统的RAM中,嵌入式linux中常用来作根文件系统
• RamFS:利用VFS自身结构而形成的内存文件系统,使用系统的RAM空间
• JFFS/JFFS2:为Flash设计的日志文件系统
• Yaffs:专门为Nand Flash设计
• proc:为内核和内核模块将信息发送给进程提供一种机制,可以查看系统模块装载的信息
• devFS:设备文件系统
Linux上的Ext2fs
• 支持 4 TB 存储、文件名称最长1012 字符
• 可选择逻辑块
• 快速符号链接
• Ext2不适合flash设备
• 是为象 IDE 设备那样的块设备设计的,逻辑块大小必须是 512 byte、1 KB、2KB等
• 没有提供对基于扇区的擦除/写操作的良好管理• 如果在一个扇区中擦除单个字节,必须将整个扇区复制到
RAM,然后擦除,再重写入
• 在出现电源故障时,Ext2fs 是不能防止崩溃的
• 文件系统不支持损耗平衡,缩短了flash的寿命
jffs/jffs2文件系统的优缺点• 日志文件系统
• 提供了更好的崩溃、掉电安全保护
• jffs2支持对flash的均匀磨损
• 在扇区级别上执行闪存擦除/写/读操作要比 Ext2文件系统好
• 文件系统接近满时,JFFS2 会大大放慢运行速度——垃圾收集
Nand上yaffs文件系统的优势• 专门为Nand flash设计的日志文件系统
• jffs/jffs2不适合大容量的Nand flash
• jffs的日志通过jffs_node建立在RAM中,占用RAM空间:对于128MB的Nand大概需要4MB的空间来维护节点
• 启动的时候需要扫描日志节点,不适合大容量的Nand flash
• FAT系统没有日志
编译yaffs文件系统
• mtd的最新补丁升级?
• 接口更新,适合与yaffs
• 与原有的mtd驱动程序不兼容,需要重写
• 如果使用旧mtd驱动需要定义Makefile中
MTD_OLD = -DCONFIG_YAFFS_USE_OLD_MTD
• 参考文档:yaffs-rootfs-howto
• 最新版的yaffs网站:http://www.aleph1.co.uk/armlinux/projects/yaffs
使用yaffs文件系统• 通过cat /proc/yaffs命令可以看到yaffs系统的相关信息
• mount -t yaffs /dev/mtdblock/0 /mnt/yaffs
CAN总线设备驱动程序• 编写自己的驱动程序——字符设备
• 驱动程序与应用程序之间的交互
硬件连接
TXCAN 1RXCAN 2
CLKOUT 3
TX0RTS4TX1RTS5TX2RTS6
OSC2 7OSC1 8
VSS9
RX1BUF10RX0BUF11 INT 12
SCK13SI14SO15
CS16RESET17
VD
D18
U502
MCP2510SO
nRESET
SIOTXDSIORXD
SIOCLK
VDD33
EXINT6
TXD1
GND 2
VCC 3
RXD4Vref5 CANL 6CANH 7Rs8
U503
TJA1050
SR140021K
VCC
SR14003
120CANHCANLEXIO2
D12CLKOUT
CAN总线驱动程序的结构
添加驱动程序
• 在./driver/char/Config.in中添加配置菜单
• 创建自己的驱动程序
./driver/char/s3c44b0-mcp2510
• 修改./driver/char/Makefile
• 定义驱动程序的加载入口
• module_init(s3c44b0_mcp2510_init);
• module_exit(s3c44b0_mcp2510_exit);
• 在s3c44b0_mcp2510_init中实现硬件的初始化:设置中断模式、初始化芯片等
• 注册字符设备register_chrdev,定义file_operations结构
• 申请中断request_irq
• 允许启动devfs,创建devfs节点• devfs_mk_dir
• devfs_register
初始化配置过程
设备的读写与等待
• copy_from_user和copy_to_user
• 对非堵塞标志O_NONBLOCK的支持
• 进程的同步:interruptible_sleep_on
• signal_pending(current)
• current是一个宏,指向代表当前正在执行的进程的struct task_struct结构
• 中断服务程序
• wake_up_interruptible
应用程序和设备文件的对话
• read和write功能有限
• 其他的控制需要使用ioctl
• 定义:int (*ioctl) (struct inode *, structfile *, unsigned int, unsigned long);
• ioctrl的分配,参考Documentation/ioctl-number.txt
• CAN总线驱动程序中的ioctl定义s3c44b0_mcp2510_ioctl
应用程序接口
exp-CAN的例子:
• 使用open以O_RDWR方式打开CAN设备
• ioctl的使用
• pthread_create创建接收线程
谢谢!