基于嵌入式L inux设备驱动程序的开发
时间:2007-04-23 来源:marsky
随着Internet的普及与发展,进入了后PC时代,嵌入式系统的应用进入了一个全新的时期。多年以来嵌入式设备中没有操作系统,但随着硬件的发展,嵌入式系统变得越来越复杂,有些功能必须由操作系统提供,这样就出现了嵌入式操作系统。
目前主要的商业嵌入式操作系统有Vxworks、Windows CE、pSoS,但其价格昂贵、封闭的源代码以及产品版税等限制了其普及和广泛应用。嵌入式Linux以其可应用于多种硬件平台、内核高效稳定、源码开放、软件丰富、网络通信和文件管理机制完善等优良特性而正在被广泛采用。
嵌入式Linux系统的软件开发涉及的内容很多。如网络、实时多任务、GU I系统、文件系统等,本文基于嵌入式L inux操作系统下设备程序的开发,阐述相关技术及设计方法。
1 嵌入式Linux操作系统
基于开放源代码的特性, L inux操作系统已经日益成为一个成熟的操作系统,获得了广泛的使用与认可,现在L inux广泛应用于各类计算,其中包括微型L inux腕表、手持设备、因特网装置、瘦客户机、防火墙等。虽然大多数Linux系统运行于PC平台上,但L inux的这些特点决定它同样适合于嵌入式系统应用。
嵌入式系统所具有的特殊要求包括:嵌入式系统有不同程度实时性要求,嵌入式在体积、功耗等方面受具体工作环境和开发生产成本的限制。嵌入式系统的操作系统应根据这些环境有很好的可移植性、可配置性和可剪裁性,把Linux用于嵌入式环境就必须修改L inux满足嵌入式的要求。修改Linux使其嵌入式化,主要集中在两方面: 一是体积,二是实时性。针对这两方面,国外一些公司和研究机构开发各具特色的嵌入式L inux版本。如:
RTL inux、KURT _ Linux、uClinux、Xlinux 和Embedix等。
本文选用的嵌入式L inux为uClinux. uClinux是专为无MMU的微处理器打造的嵌入式Linux操作系统, 由Linux2. 0 /2. 4 内核派生而来。现在uClinux越来越受到业界的青睐,被移植到更多的无MMU芯片上[ 1 ] 。
2 设备驱动程序
设备驱动程序是内核的一部分,是操作系统内核和机器硬件之间的接口,它由一组函数和一些私有数据组成,是连接应用程序与具体硬件的桥梁。Linux的一个基本特点是它对硬件设备的管理抽象化,系统中的每一个设备都用一个特殊的文件来表示。所有的硬件设备都像普通的文件一样看待,使用与操作系统相同的标准系统来进行打开、读写和关闭。
2. 1 设备与驱动程序的分类
uClinux是由标准Linux派生而来,它们的设备驱动开发基本相似,但标准Linux支持模块化,设备驱动都写成模块的形式,而且要求可以在不同的体系结构上安装。uClinux是可以支持模块功能的,在编译内核的时候可以选择支持或不支持模块功能,但嵌入式系统是针对具体的应用,而且嵌入式系统中内存空间资源比较紧张,所以在编译内核时,一般选择不支持模块功能,这样uClinux的驱动程序都是直接编译到内核中的。
在Linux操作系统下有3类主要的设备文件系统:块设备、字符设备和网络设备。其中字符设备没有缓冲区,数据的处理是以字节为单位按顺序进行的,它不支持随机读写,也不允许查找操作。系统的串口以及终端,是常见的字符设备,嵌入式系统中简单的按键、触摸屏、手写板也属于字符设备。块设备是指那些在输入输出时数据处理以块为单位的设备,采用了缓冲技术,支持数据的随机读写,块设备可以通过它们的设备做特殊文件访问,但是更常见的是通过文件系统访问。只有块设备上可以安装文件系统,Linux的文件系统只有建立在块设备上。典型的块设备有硬盘、CD2ROM等。而网络设备不同于字符设备和块设备,它面向的上一层不是文件系统而是网络协议层,是通过BSD套接口访问数据。
设备之间的分类不是绝对的,有些设备可以做字符设备,也可以做块设备,如磁盘。并不是每个驱动程序都控制一台物理设备,通过设备驱动程序接口可以创建一些伪设备,它们可以完成某些特殊的功能。只要把需要加入内核的功能做成伪设备驱动程序,就可以将其加入内核,而不需要将内核重新定做。
与设备相对应的是三类设备驱动程序,字符设备驱动程序、块设备驱动程序、网络设备驱动程序,系统调用是内核和应用程序之间的接口,而设备驱动程序是内核和机器硬件之间的接口,设备驱动程序可以分成以下三个主要组成部分。 (1)自动配置和初始化子程序
负责检测所需驱动的硬件设备是否存在以及是否能正常工作。如果设备正常,则对设备及其驱动程序所需的相关软件状态进行初始化,这部分驱动程序仅在初始化时被调用一次。
(2)服务I/O请求的子程序
是驱动程序的上半部分,调用这部分是系统调用的结果。系统认为这部分程序在执行时即可进行调用的进程,由用户态变成了内核态,并具有进行此系统调用的用户程序的运行环境。
(3)中断服务程序
又称驱动程序的下半部分,设备在I/O请求结束或其他状态改变时产生中断。中断可以产生在任何一个进程运行时,因此中断服务程序被调用时不能依赖于任何进程状态,因而也就不能调用任何与进程运行环境有关的函数。因为设备驱动程序一般支持同一类型的若干设备,所以调用中断服务子程序时都带有一个或多个参数,以唯一标识请求服务的设备[ 3 ] 。
2. 2 设备驱动程序的file_opera tion s结构
应用程序只有通过对设备文件的open、release、read、write、ioctl等才能访问硬件设备。在uClinux源代码uClinux - dist/ linux/ include / linux/ fs. h中定义了uClinux驱动程序中必须使用的file_operations结构,每个设备驱动都实现这个接口所定义的部分或全部函数。随着内核的不断升级, file_operations结构也越来越大,不同的版本的内核会稍有不同。
file_operations定义如下:
struct file_operations{
int( * lseek) ( struct inode * , struct file * , off_t , int) ;
int( * read) ( struct inode * , struct file * , char * , int) ;
int( * write) ( struct inode * , struct file * , const char * , int) ;
int( * readdir) ( struct inode * , struct file * , void * , dilldir) ;
int( * select) ( struct inode * , struct file * , int, select_table * ) ;
int( * ioctl) ( struct inode * , struct file * , unsigned int, unsigned long) ;
int( * mmap) ( struct inode * , struct file * , struct vm_area_struct * ) ;
int( * open) ( struct inode * , struct file * ) ;
int( * release) ( struct inode * , struct file * ) ;
int( * fsync) ( struct inode * , struct file * ) ;
int( * fasync) ( struct inode * , struct file * , int) ;
int( * check_media_change) ( kdev_t dev) ;
int( * revalidate) ( kdev_t dev) ; };
在这些函数指针中, open、release用于设备的打开和关闭,是每个驱动程序都要实现的函数,其它函数根据实际设备的作用不同而实现相应的函数。这些操作函数将被注册到内核。当应用程序对该设备相应的设备文件进行操作时,内核会找到相应的操作函数并进行调用〔4〕。
2. 3 设备驱动程序的编写
分析驱动程序的file_operations结构,用户就可以编写相应的外部设备驱动程序,其中的主要步骤根据具体的设备驱动程序的需要,完成file _opera2tions中的每个函数的编写。已完成了设备驱动程序编写的主要工作后必须定义一个初始化函数。
初始化函数主要的工作是: 对硬件寄存器进行设置、初始化设备相关的参数、注册设备、注册设备使用的中断和其它的一些初始化工作〔5〕。
2. 4 设备驱动程序的初始化
设备驱动程序所提供的入口点,在设备驱动程序初始化时向系统进行登记,以使系统在适当的时候调用。Linux系统中通常调用register_chrdev向系统注册一个字符型设备。register_chrdev定义为:
int reister_chrdev ( unsigned intmajor, const char * name, struct file_operations * fops) ;
当设备驱动模块从Linux内核中卸载时,对应的主设备号必须被释放。在模块卸载调用cleanup _module ( )函数时,应该调用下列的函数卸载设备驱动:
int unregister_ chrdev ( unsigned int major, const char * name) ;
初始化部分一般还负责给设备驱动程序申请系统资源,包括内存、中断、时钟、I/O端口等,这些资源也可以在open子程序或其它地方申请,这些资源不用时应该释放,以利于资源的共享〔6〕。
如果设备需要IRQ 支持,则要注册中断,注册中断的函数:
int request_ irq ( unsigned int irq, void ( * handler) ( int, void * , struct pt_regs 3 ) , unsigned longflags, const char * device, void * dev_id ) ;
3 设备驱动程序的编写实例
本实例的嵌入式芯片采用三星的S3C4510B芯片, S3C4510B 是一类性能价格比很高的16 /32 位RISC微控制器。内含一个由ARM 公司设计的ARM7TDMI处理器核。
字符设备的例子
static int demo_open ( struct inode * inode, struct file * file) {
if ( demo_flag = = 1) { /* 检查驱动忙否* /
return $ 1;
}
/* 可以初始化一些内部数据结构* /
printk (KERN_CR IT“DEMO: demo device open \n) ;
MOD_ INC_USE_COUNT; /* 模块使用次数加1 */
demo_flag = 1;
return 0;
}
static int demo_release ( struct inode * inode, struct file * file) {
if ( demo_flag = = 0) {
return 0;
}
p rintk (KERN_CR IT “DEMO: demo device close \n”) ;
MOD_DEC_USE_COUNT;
demo_flag = 0;
return 0;
}
static ssize_t demo_read ( struct file * file, char * buf, size_t count, loff_t * offset) {
/* 检查是否已有线程在读数据,返回error */
// DEMO_RD_LOCK;
printk(KERN_CRIT “DEMO: demo is reading, demo_param = %d \n”, demo_param) ;
//DEMO_RD_UNLOCK;
/* 通常返回成功读到的数据*/
return 0;
}
static int demo_ioctl( struct inode * inode, struct file * file, unsigned int cmd, unsigned long arg) {
if ( cmd = =COMMAND1) {
printk (KERN_CR IT “DEMO: set command COMMAND1 \n”) ;
return 0;
}
if ( cmd = =COMMAND2) {
printk (KERN_CR IT“DEMO: set command COMMAND2 \n”) ;
return 0;
}
printk (KERN_CR IT“DEMO: set commandWRONG \n”) ;
return 0;
}
struct file_operations demo_fops = {
open: demo_open,
read: demo_read,
release: demo_release,
ioctl: demo_ioctl,
};
static void demo_cleanup ( void) {
/* 确保要清掉的模块是已初始化的* /
if ( demo_initialized = = 1) {
/* 禁止中断 */
/* 释放该模块的中断服务程序 */
unregister_chrdev(DEMO_MAJOR, “demo_drv”) ;
demo_initialized = = 0;
printk (KERN_CR IT “DEMO: demo device is cleanup / n”) ;
}
return;
}
/* 驱动程序初始化函数 */
static int demo_init ( void) {
int i ;
/* 确定模块以前未初始化 */
if ( demo_initialized == 1)
return 0;
/* 分配并初始化所有数据结构为缺省状态 */
i = register_chrdev(DEMO_MAJOR, “demo_drv”, &demo_fop s) ; if ( i < 0) {
printk ( KERN_CRIT “DEMO: i = % d \n”, i )
return EIO;
}
p rintk ( KERN_CR IT “demo_drv registered successfully: ) = \n) ;
/* 请求中断 */
demo_initialized = 1;
return 0;
}
4 结束语
本文基于嵌入式L inux下的设备驱动程序的开发,并例举一个字符设备驱动程序的实例,简述了驱动程序的开发过程,熟悉了基本的流程,然后就可以在其上开发各种各样的设备驱动程序了。
目前主要的商业嵌入式操作系统有Vxworks、Windows CE、pSoS,但其价格昂贵、封闭的源代码以及产品版税等限制了其普及和广泛应用。嵌入式Linux以其可应用于多种硬件平台、内核高效稳定、源码开放、软件丰富、网络通信和文件管理机制完善等优良特性而正在被广泛采用。
嵌入式Linux系统的软件开发涉及的内容很多。如网络、实时多任务、GU I系统、文件系统等,本文基于嵌入式L inux操作系统下设备程序的开发,阐述相关技术及设计方法。
1 嵌入式Linux操作系统
基于开放源代码的特性, L inux操作系统已经日益成为一个成熟的操作系统,获得了广泛的使用与认可,现在L inux广泛应用于各类计算,其中包括微型L inux腕表、手持设备、因特网装置、瘦客户机、防火墙等。虽然大多数Linux系统运行于PC平台上,但L inux的这些特点决定它同样适合于嵌入式系统应用。
嵌入式系统所具有的特殊要求包括:嵌入式系统有不同程度实时性要求,嵌入式在体积、功耗等方面受具体工作环境和开发生产成本的限制。嵌入式系统的操作系统应根据这些环境有很好的可移植性、可配置性和可剪裁性,把Linux用于嵌入式环境就必须修改L inux满足嵌入式的要求。修改Linux使其嵌入式化,主要集中在两方面: 一是体积,二是实时性。针对这两方面,国外一些公司和研究机构开发各具特色的嵌入式L inux版本。如:
RTL inux、KURT _ Linux、uClinux、Xlinux 和Embedix等。
本文选用的嵌入式L inux为uClinux. uClinux是专为无MMU的微处理器打造的嵌入式Linux操作系统, 由Linux2. 0 /2. 4 内核派生而来。现在uClinux越来越受到业界的青睐,被移植到更多的无MMU芯片上[ 1 ] 。
2 设备驱动程序
设备驱动程序是内核的一部分,是操作系统内核和机器硬件之间的接口,它由一组函数和一些私有数据组成,是连接应用程序与具体硬件的桥梁。Linux的一个基本特点是它对硬件设备的管理抽象化,系统中的每一个设备都用一个特殊的文件来表示。所有的硬件设备都像普通的文件一样看待,使用与操作系统相同的标准系统来进行打开、读写和关闭。
2. 1 设备与驱动程序的分类
uClinux是由标准Linux派生而来,它们的设备驱动开发基本相似,但标准Linux支持模块化,设备驱动都写成模块的形式,而且要求可以在不同的体系结构上安装。uClinux是可以支持模块功能的,在编译内核的时候可以选择支持或不支持模块功能,但嵌入式系统是针对具体的应用,而且嵌入式系统中内存空间资源比较紧张,所以在编译内核时,一般选择不支持模块功能,这样uClinux的驱动程序都是直接编译到内核中的。
在Linux操作系统下有3类主要的设备文件系统:块设备、字符设备和网络设备。其中字符设备没有缓冲区,数据的处理是以字节为单位按顺序进行的,它不支持随机读写,也不允许查找操作。系统的串口以及终端,是常见的字符设备,嵌入式系统中简单的按键、触摸屏、手写板也属于字符设备。块设备是指那些在输入输出时数据处理以块为单位的设备,采用了缓冲技术,支持数据的随机读写,块设备可以通过它们的设备做特殊文件访问,但是更常见的是通过文件系统访问。只有块设备上可以安装文件系统,Linux的文件系统只有建立在块设备上。典型的块设备有硬盘、CD2ROM等。而网络设备不同于字符设备和块设备,它面向的上一层不是文件系统而是网络协议层,是通过BSD套接口访问数据。
设备之间的分类不是绝对的,有些设备可以做字符设备,也可以做块设备,如磁盘。并不是每个驱动程序都控制一台物理设备,通过设备驱动程序接口可以创建一些伪设备,它们可以完成某些特殊的功能。只要把需要加入内核的功能做成伪设备驱动程序,就可以将其加入内核,而不需要将内核重新定做。
与设备相对应的是三类设备驱动程序,字符设备驱动程序、块设备驱动程序、网络设备驱动程序,系统调用是内核和应用程序之间的接口,而设备驱动程序是内核和机器硬件之间的接口,设备驱动程序可以分成以下三个主要组成部分。 (1)自动配置和初始化子程序
负责检测所需驱动的硬件设备是否存在以及是否能正常工作。如果设备正常,则对设备及其驱动程序所需的相关软件状态进行初始化,这部分驱动程序仅在初始化时被调用一次。
(2)服务I/O请求的子程序
是驱动程序的上半部分,调用这部分是系统调用的结果。系统认为这部分程序在执行时即可进行调用的进程,由用户态变成了内核态,并具有进行此系统调用的用户程序的运行环境。
(3)中断服务程序
又称驱动程序的下半部分,设备在I/O请求结束或其他状态改变时产生中断。中断可以产生在任何一个进程运行时,因此中断服务程序被调用时不能依赖于任何进程状态,因而也就不能调用任何与进程运行环境有关的函数。因为设备驱动程序一般支持同一类型的若干设备,所以调用中断服务子程序时都带有一个或多个参数,以唯一标识请求服务的设备[ 3 ] 。
2. 2 设备驱动程序的file_opera tion s结构
应用程序只有通过对设备文件的open、release、read、write、ioctl等才能访问硬件设备。在uClinux源代码uClinux - dist/ linux/ include / linux/ fs. h中定义了uClinux驱动程序中必须使用的file_operations结构,每个设备驱动都实现这个接口所定义的部分或全部函数。随着内核的不断升级, file_operations结构也越来越大,不同的版本的内核会稍有不同。
file_operations定义如下:
struct file_operations{
int( * lseek) ( struct inode * , struct file * , off_t , int) ;
int( * read) ( struct inode * , struct file * , char * , int) ;
int( * write) ( struct inode * , struct file * , const char * , int) ;
int( * readdir) ( struct inode * , struct file * , void * , dilldir) ;
int( * select) ( struct inode * , struct file * , int, select_table * ) ;
int( * ioctl) ( struct inode * , struct file * , unsigned int, unsigned long) ;
int( * mmap) ( struct inode * , struct file * , struct vm_area_struct * ) ;
int( * open) ( struct inode * , struct file * ) ;
int( * release) ( struct inode * , struct file * ) ;
int( * fsync) ( struct inode * , struct file * ) ;
int( * fasync) ( struct inode * , struct file * , int) ;
int( * check_media_change) ( kdev_t dev) ;
int( * revalidate) ( kdev_t dev) ; };
在这些函数指针中, open、release用于设备的打开和关闭,是每个驱动程序都要实现的函数,其它函数根据实际设备的作用不同而实现相应的函数。这些操作函数将被注册到内核。当应用程序对该设备相应的设备文件进行操作时,内核会找到相应的操作函数并进行调用〔4〕。
2. 3 设备驱动程序的编写
分析驱动程序的file_operations结构,用户就可以编写相应的外部设备驱动程序,其中的主要步骤根据具体的设备驱动程序的需要,完成file _opera2tions中的每个函数的编写。已完成了设备驱动程序编写的主要工作后必须定义一个初始化函数。
初始化函数主要的工作是: 对硬件寄存器进行设置、初始化设备相关的参数、注册设备、注册设备使用的中断和其它的一些初始化工作〔5〕。
2. 4 设备驱动程序的初始化
设备驱动程序所提供的入口点,在设备驱动程序初始化时向系统进行登记,以使系统在适当的时候调用。Linux系统中通常调用register_chrdev向系统注册一个字符型设备。register_chrdev定义为:
int reister_chrdev ( unsigned intmajor, const char * name, struct file_operations * fops) ;
当设备驱动模块从Linux内核中卸载时,对应的主设备号必须被释放。在模块卸载调用cleanup _module ( )函数时,应该调用下列的函数卸载设备驱动:
int unregister_ chrdev ( unsigned int major, const char * name) ;
初始化部分一般还负责给设备驱动程序申请系统资源,包括内存、中断、时钟、I/O端口等,这些资源也可以在open子程序或其它地方申请,这些资源不用时应该释放,以利于资源的共享〔6〕。
如果设备需要IRQ 支持,则要注册中断,注册中断的函数:
int request_ irq ( unsigned int irq, void ( * handler) ( int, void * , struct pt_regs 3 ) , unsigned longflags, const char * device, void * dev_id ) ;
3 设备驱动程序的编写实例
本实例的嵌入式芯片采用三星的S3C4510B芯片, S3C4510B 是一类性能价格比很高的16 /32 位RISC微控制器。内含一个由ARM 公司设计的ARM7TDMI处理器核。
字符设备的例子
static int demo_open ( struct inode * inode, struct file * file) {
if ( demo_flag = = 1) { /* 检查驱动忙否* /
return $ 1;
}
/* 可以初始化一些内部数据结构* /
printk (KERN_CR IT“DEMO: demo device open \n) ;
MOD_ INC_USE_COUNT; /* 模块使用次数加1 */
demo_flag = 1;
return 0;
}
static int demo_release ( struct inode * inode, struct file * file) {
if ( demo_flag = = 0) {
return 0;
}
p rintk (KERN_CR IT “DEMO: demo device close \n”) ;
MOD_DEC_USE_COUNT;
demo_flag = 0;
return 0;
}
static ssize_t demo_read ( struct file * file, char * buf, size_t count, loff_t * offset) {
/* 检查是否已有线程在读数据,返回error */
// DEMO_RD_LOCK;
printk(KERN_CRIT “DEMO: demo is reading, demo_param = %d \n”, demo_param) ;
//DEMO_RD_UNLOCK;
/* 通常返回成功读到的数据*/
return 0;
}
static int demo_ioctl( struct inode * inode, struct file * file, unsigned int cmd, unsigned long arg) {
if ( cmd = =COMMAND1) {
printk (KERN_CR IT “DEMO: set command COMMAND1 \n”) ;
return 0;
}
if ( cmd = =COMMAND2) {
printk (KERN_CR IT“DEMO: set command COMMAND2 \n”) ;
return 0;
}
printk (KERN_CR IT“DEMO: set commandWRONG \n”) ;
return 0;
}
struct file_operations demo_fops = {
open: demo_open,
read: demo_read,
release: demo_release,
ioctl: demo_ioctl,
};
static void demo_cleanup ( void) {
/* 确保要清掉的模块是已初始化的* /
if ( demo_initialized = = 1) {
/* 禁止中断 */
/* 释放该模块的中断服务程序 */
unregister_chrdev(DEMO_MAJOR, “demo_drv”) ;
demo_initialized = = 0;
printk (KERN_CR IT “DEMO: demo device is cleanup / n”) ;
}
return;
}
/* 驱动程序初始化函数 */
static int demo_init ( void) {
int i ;
/* 确定模块以前未初始化 */
if ( demo_initialized == 1)
return 0;
/* 分配并初始化所有数据结构为缺省状态 */
i = register_chrdev(DEMO_MAJOR, “demo_drv”, &demo_fop s) ; if ( i < 0) {
printk ( KERN_CRIT “DEMO: i = % d \n”, i )
return EIO;
}
p rintk ( KERN_CR IT “demo_drv registered successfully: ) = \n) ;
/* 请求中断 */
demo_initialized = 1;
return 0;
}
4 结束语
本文基于嵌入式L inux下的设备驱动程序的开发,并例举一个字符设备驱动程序的实例,简述了驱动程序的开发过程,熟悉了基本的流程,然后就可以在其上开发各种各样的设备驱动程序了。
相关阅读 更多 +