论文部分内容阅读
摘 要:主要阐述了嵌入式Linux设备驱动程序的概念,归纳嵌入式Linux设备驱动程序的共性,探讨嵌入式Linux设备驱动程序具体开发流程以及驱动程序的关键代码,总结嵌入式 Linux设备驱动程序开发的主导思想。
关键词:嵌入式系统;Linux;设备驱动程序;内核
Design and Implementation of Embedded Linux Device Drivers
QU Xiao-ping,LIU Tao
(Information Science and Technology College, Jiujiang University, Jiangxi Jiujiang 332005)
Key words: Embedded System;Linux;device driver;kernel
嵌入式系统被广泛运用到消费、汽车、电子、微控制、无线通信、数码产品、网络设备、安全系统等领域。越来越多的公司、研究单位、大专院校、以及个人开始进行嵌入式系统的研究,嵌入式系统设计将是未来相当长一段时间内研究的热点。
1 Linux设备驱动程序概述
嵌入式Linux以其可应用于多种硬件平台、内核高效稳定、源码开放、软件丰富、网络通信和文件管理机制完善等优良特性,成为嵌入式系统领域中的一个研究热点。嵌入式Linux系统中,内核提供保护机制,用户空间的进程一般不能直接访问硬件。进行嵌入式系统的开发,很大的工作量是为各种设备编写驱动程序,除非系统不使用操作系统。Linux设备驱动程序在Linux内核源代码中占有很大比例,从2.0、2.2到2.4版本的内核,源代码的长度日益增加,其实主要是设备驱动程序在增加。
设备驱动程序在Linux内核中占有极其重要的位置,它是内核用于完成对物理设备的控制操作的功能模块。除了CPU、内存以及其他很少的几个部分之外,所有的设备控制操作都必须由与被控设备相关的代码,也就是驱动程序来完成。内核必须包括与系统中的每个外部设备对应的驱动程序。否则设备就无法在Linux下正常工作。这就是驱动程序开发成为Linux内核开发的主要工作的原因。从内核源码的代码分布可以看出,设备驱动源码至少占据了一半的内核源码量,更能说明设备驱动程序对操作系统的意义和价值。
2 嵌入式Linux设备驱动程序开发
2.1设备驱动程序工作原理
在Linux操作系统下有3类主要的设备文件类型:块设备、字符设备和网络设备。这种分类方法可以将控制不同输入/输出设备的驱动程序与其它操作系统软件分离开来。字符设备与块设备的主要区别是:在对字符设备发出读/写请求时,实际的硬件I/O一般紧接着发生。块设备则不然,它利用一块系统内存作缓冲区,若用户进程对设备的请求能满足用户的要求,就返回请求的数据;否则,就调用请求函数来进行实际的I/O操作。网络设备可以通过BSD套接口访问数据。所有嵌入式Linux设备驱动程序都有一些共性,是编写所有类型的驱动程序都通用的,操作系统提供给驱动程序的支持也大致相同。这些特性包括:(1)读/写 几乎所有设备都有输入和输出。每个驱动程序都要负责本设备的读/写操作,操作系统的其它部分不需要知道对设备的具体读/写操作是怎样进行的,这些都由驱动程序屏蔽掉了。操作系统定义好一些读/写接口,由驱动程序完成具体的功能。在驱动程序初始化时,需要把具有这种接口的读/写函数注册到操作系统。(2)中断 中断在现代计算机结构中有重要的地位,操作系统必须提供驱动程序响应中断的能力。一般是把一个中断处理程序注册到系统中,操作系统在硬件中断发生后,调用驱动程序的处理程序。Linux支持中断的共享,即多个设备共享一个中断。(3)时钟 在实现驱动程序时,很多地方会用到时钟,例如某些协议里的超时处理,没有中断机制的硬件的轮询等。操作系统应为驱动程序提供定时机制,一般是在预定的时间过了以后,回调注册的时钟函数。
嵌入式Linux系统驱动程序开发与普通Linux没有太多区别。嵌入式设备由于硬件种类非常丰富,在缺省的内核发布版中不可能包括所有驱动程序。可以在硬件生产厂家或者Intemet上寻找驱动程序,如果找不到,可以根据一个相近硬件的驱动程序来改写。实现一个嵌入式Linux设备驱动的大致流程如下:(1)定义主、次设备号,也可以动态获取;(2)实现驱动初始化和清除函数,如果驱动程序采用模块方式,则要实现模块初始化和清除函数;(3)设计所要实现的文件操作,定义file_operations结构;(4)实现所需的文件操作调用,如read、write等;(5)实现中断服务函数,并用request irq向内核注册;(6)将驱动编译到内核或编译成模块,用insmod命令加载;(7)生成设备节点文件。
与普通文件相比,设备文件的操作要复杂得多,不可能简单地通过read、write和llseek等来实现。所有其它类型的操作都可以通过VFS的ioctl调用来执行,为此,只需要在驱动程序中实现 ioctl函数,并在其中添加相应的case即可。通过cmd区分操作,通过arg传递参数和结果。
2.2设备驱动程序的开发流程
2.2.1设备驱动接口
Struct file_operation IOdriver_fops=
{read:1Odriver_read,
write:IOdriver_write,
};
字符设备驱动接口中关键的file_operations结构:
的定义如下:
struct file_operations{
struct module *owner;
loft_t(*llseek)(struct file*,loff_t,int);
ssize_t(*read)(struct file*,char*,size_t,loft_t);
ssize_t(*write)(struct file*,const char*,size_t,1off_t*);
int(readdir struct inode*,struct file*,void*,filldir_t);
int(*select)(struct inode*,struct file*,int,select table*);
int(*ioct1)(struct inode*,struct file*,unsigned int,unsigned int);
int(*mmap)(struct inode*,struct file*,struct vm_area_struct*);
int( *open)(struct inode*,struct file*);
void(*release)(struct inode*,struct file*);
int(*fsync)(struct inode*,struct file*);
};
2.2.2注册和注销模块
2.2.2.1设备注册模块
static int_nit IOdriver_init(void)
{ int ret=0:
ret=register_chrdv(MAJOR_NUM,"IOdriver",&IOdriv
if(ret)
{ printk(KERN_ALERT"IOdriver register failure!") }
else
{ printk(KERN_AL ERT"IOdriver register success!")}
return ret;
}
2.2.2.2设备注销模块
static int_exit IOdriver_exit(void)
{ int ret=0;
ret=unregister_chrdv(MAJOR_NUM,"IOdriver");
if(ret)
{ printk(KERN_AL ERT"IOdriver unregister failure!"); }
else
{ printk(KERN_AL ERT"IOdriver unregister success!''); }
return ret;
}
2.2.3基本入口点设备函数的具体实现
2.2.3.1设备读取模块
static ssize_t IOdriver_read(struct file*file,char*buf,size_t len,loft_t*off)
{ if(_ _copy_to_user(bur,&IOdriver_var,sizeof(int)))
{ retum _EFAULT; }
retum sizeof(int);
}
2.2.3.2设备写入模块
static ssize_t IOdriver_write(struct file*file,const char*buf,size_t len,loff_t*off)
{ if(_ _copy_from_user(&IOdriver_var,buf,sizeof(int)))
{ retum __EFAULT;}
retum sizeof(int);
}
2.2.4 module_init和 module_exit宏
module_init(IOdriver_init);
module_exit(IOdriver_exit);
3 设备驱动程序编译到内核的过程
把驱动程序编译进内核的步骤如下:
(1)把IOdriver.c复制到Linux-2.4.20-8/drivers/char下,并修改该目录下的config.in文件,config.in是每个模块的配置脚本,在这个文件当中定义配置那些模块、怎样配置等,因此当添加了IO的配置后,会在内核配置时出现这个模块的配置选项。
(2)修改当前目录下的 Makefile,在每个模块的Makefile中包括该模块所包含的子模块,在 char devices中,要包括IO设备,就要告诉Makefile编译IOdriver.c并包含编译出的IOdriver.c文件,这样最后内核做链接时,才会链接进这个模块。
(3)重新配置内核,选中IO模块执行命令make menuconfig,进入内核配置菜单。
(4)重新编译内核,并更新嵌入式目标系统的内核。
(5)创建设备文件,重新启动嵌入式目标系统的Linux,进入目录/proc,查看devices文件,在devices文件列出了当前系统所有的字符设备和块设备,包括设备号和设备名称,在 Character devices的最后移行的设备是:254 IOdriver,说明IOdriver设备已经正确地加载到了内核。
(6)添加设备文件节点,执行如下命令,添加设备文件节点,在此创建的设备名称用于在应用程序中访问对应的设备。如果为字符设备,设备号类型用c表示,块设备则用b表示;主设备号就是/proc/devices中的IOdriver的设备号,由于该类设备只有一个,因此次设备号为0,如果还有该类型的其他设备,则一次为1、2、3等。
5 结束语
设备驱动程序是操作系统内核和机器硬件之间的接口。内核利用驱动程序的接口完成对设备的初始化和释放,在系统内核和硬件之间传送数据,并时刻检测和处理设备出现的错误。它是操作系统最基本的组成部分,因此熟悉驱动的编写是很重要的。
参考文献:
[1]宋宝华.Linux设备驱动开发详解[M].北京:人民邮电出版社,2008,
[2]赵炯.Linux内核完全剖析--基于0.12内核[M].上海:机械工业出版社,2009,
[3]魏永明.Linux设备驱动程序[M].北京:中国电力出版社,2006,
[4]吴迪.嵌入式系统原理、设计与应用[M].北京:机械工业出版社,2004,
[5]罗苑棠.嵌入式Linux应用系统开发实例精讲[M].北京:电子工业出版社,2007.
关键词:嵌入式系统;Linux;设备驱动程序;内核
Design and Implementation of Embedded Linux Device Drivers
QU Xiao-ping,LIU Tao
(Information Science and Technology College, Jiujiang University, Jiangxi Jiujiang 332005)
Key words: Embedded System;Linux;device driver;kernel
嵌入式系统被广泛运用到消费、汽车、电子、微控制、无线通信、数码产品、网络设备、安全系统等领域。越来越多的公司、研究单位、大专院校、以及个人开始进行嵌入式系统的研究,嵌入式系统设计将是未来相当长一段时间内研究的热点。
1 Linux设备驱动程序概述
嵌入式Linux以其可应用于多种硬件平台、内核高效稳定、源码开放、软件丰富、网络通信和文件管理机制完善等优良特性,成为嵌入式系统领域中的一个研究热点。嵌入式Linux系统中,内核提供保护机制,用户空间的进程一般不能直接访问硬件。进行嵌入式系统的开发,很大的工作量是为各种设备编写驱动程序,除非系统不使用操作系统。Linux设备驱动程序在Linux内核源代码中占有很大比例,从2.0、2.2到2.4版本的内核,源代码的长度日益增加,其实主要是设备驱动程序在增加。
设备驱动程序在Linux内核中占有极其重要的位置,它是内核用于完成对物理设备的控制操作的功能模块。除了CPU、内存以及其他很少的几个部分之外,所有的设备控制操作都必须由与被控设备相关的代码,也就是驱动程序来完成。内核必须包括与系统中的每个外部设备对应的驱动程序。否则设备就无法在Linux下正常工作。这就是驱动程序开发成为Linux内核开发的主要工作的原因。从内核源码的代码分布可以看出,设备驱动源码至少占据了一半的内核源码量,更能说明设备驱动程序对操作系统的意义和价值。
2 嵌入式Linux设备驱动程序开发
2.1设备驱动程序工作原理
在Linux操作系统下有3类主要的设备文件类型:块设备、字符设备和网络设备。这种分类方法可以将控制不同输入/输出设备的驱动程序与其它操作系统软件分离开来。字符设备与块设备的主要区别是:在对字符设备发出读/写请求时,实际的硬件I/O一般紧接着发生。块设备则不然,它利用一块系统内存作缓冲区,若用户进程对设备的请求能满足用户的要求,就返回请求的数据;否则,就调用请求函数来进行实际的I/O操作。网络设备可以通过BSD套接口访问数据。所有嵌入式Linux设备驱动程序都有一些共性,是编写所有类型的驱动程序都通用的,操作系统提供给驱动程序的支持也大致相同。这些特性包括:(1)读/写 几乎所有设备都有输入和输出。每个驱动程序都要负责本设备的读/写操作,操作系统的其它部分不需要知道对设备的具体读/写操作是怎样进行的,这些都由驱动程序屏蔽掉了。操作系统定义好一些读/写接口,由驱动程序完成具体的功能。在驱动程序初始化时,需要把具有这种接口的读/写函数注册到操作系统。(2)中断 中断在现代计算机结构中有重要的地位,操作系统必须提供驱动程序响应中断的能力。一般是把一个中断处理程序注册到系统中,操作系统在硬件中断发生后,调用驱动程序的处理程序。Linux支持中断的共享,即多个设备共享一个中断。(3)时钟 在实现驱动程序时,很多地方会用到时钟,例如某些协议里的超时处理,没有中断机制的硬件的轮询等。操作系统应为驱动程序提供定时机制,一般是在预定的时间过了以后,回调注册的时钟函数。
嵌入式Linux系统驱动程序开发与普通Linux没有太多区别。嵌入式设备由于硬件种类非常丰富,在缺省的内核发布版中不可能包括所有驱动程序。可以在硬件生产厂家或者Intemet上寻找驱动程序,如果找不到,可以根据一个相近硬件的驱动程序来改写。实现一个嵌入式Linux设备驱动的大致流程如下:(1)定义主、次设备号,也可以动态获取;(2)实现驱动初始化和清除函数,如果驱动程序采用模块方式,则要实现模块初始化和清除函数;(3)设计所要实现的文件操作,定义file_operations结构;(4)实现所需的文件操作调用,如read、write等;(5)实现中断服务函数,并用request irq向内核注册;(6)将驱动编译到内核或编译成模块,用insmod命令加载;(7)生成设备节点文件。
与普通文件相比,设备文件的操作要复杂得多,不可能简单地通过read、write和llseek等来实现。所有其它类型的操作都可以通过VFS的ioctl调用来执行,为此,只需要在驱动程序中实现 ioctl函数,并在其中添加相应的case即可。通过cmd区分操作,通过arg传递参数和结果。
2.2设备驱动程序的开发流程
2.2.1设备驱动接口
Struct file_operation IOdriver_fops=
{read:1Odriver_read,
write:IOdriver_write,
};
字符设备驱动接口中关键的file_operations结构:
struct file_operations{
struct module *owner;
loft_t(*llseek)(struct file*,loff_t,int);
ssize_t(*read)(struct file*,char*,size_t,loft_t);
ssize_t(*write)(struct file*,const char*,size_t,1off_t*);
int(readdir struct inode*,struct file*,void*,filldir_t);
int(*select)(struct inode*,struct file*,int,select table*);
int(*ioct1)(struct inode*,struct file*,unsigned int,unsigned int);
int(*mmap)(struct inode*,struct file*,struct vm_area_struct*);
int( *open)(struct inode*,struct file*);
void(*release)(struct inode*,struct file*);
int(*fsync)(struct inode*,struct file*);
};
2.2.2注册和注销模块
2.2.2.1设备注册模块
static int_nit IOdriver_init(void)
{ int ret=0:
ret=register_chrdv(MAJOR_NUM,"IOdriver",&IOdriv
if(ret)
{ printk(KERN_ALERT"IOdriver register failure!") }
else
{ printk(KERN_AL ERT"IOdriver register success!")}
return ret;
}
2.2.2.2设备注销模块
static int_exit IOdriver_exit(void)
{ int ret=0;
ret=unregister_chrdv(MAJOR_NUM,"IOdriver");
if(ret)
{ printk(KERN_AL ERT"IOdriver unregister failure!"); }
else
{ printk(KERN_AL ERT"IOdriver unregister success!''); }
return ret;
}
2.2.3基本入口点设备函数的具体实现
2.2.3.1设备读取模块
static ssize_t IOdriver_read(struct file*file,char*buf,size_t len,loft_t*off)
{ if(_ _copy_to_user(bur,&IOdriver_var,sizeof(int)))
{ retum _EFAULT; }
retum sizeof(int);
}
2.2.3.2设备写入模块
static ssize_t IOdriver_write(struct file*file,const char*buf,size_t len,loff_t*off)
{ if(_ _copy_from_user(&IOdriver_var,buf,sizeof(int)))
{ retum __EFAULT;}
retum sizeof(int);
}
2.2.4 module_init和 module_exit宏
module_init(IOdriver_init);
module_exit(IOdriver_exit);
3 设备驱动程序编译到内核的过程
把驱动程序编译进内核的步骤如下:
(1)把IOdriver.c复制到Linux-2.4.20-8/drivers/char下,并修改该目录下的config.in文件,config.in是每个模块的配置脚本,在这个文件当中定义配置那些模块、怎样配置等,因此当添加了IO的配置后,会在内核配置时出现这个模块的配置选项。
(2)修改当前目录下的 Makefile,在每个模块的Makefile中包括该模块所包含的子模块,在 char devices中,要包括IO设备,就要告诉Makefile编译IOdriver.c并包含编译出的IOdriver.c文件,这样最后内核做链接时,才会链接进这个模块。
(3)重新配置内核,选中IO模块执行命令make menuconfig,进入内核配置菜单。
(4)重新编译内核,并更新嵌入式目标系统的内核。
(5)创建设备文件,重新启动嵌入式目标系统的Linux,进入目录/proc,查看devices文件,在devices文件列出了当前系统所有的字符设备和块设备,包括设备号和设备名称,在 Character devices的最后移行的设备是:254 IOdriver,说明IOdriver设备已经正确地加载到了内核。
(6)添加设备文件节点,执行如下命令,添加设备文件节点,在此创建的设备名称用于在应用程序中访问对应的设备。如果为字符设备,设备号类型用c表示,块设备则用b表示;主设备号就是/proc/devices中的IOdriver的设备号,由于该类设备只有一个,因此次设备号为0,如果还有该类型的其他设备,则一次为1、2、3等。
5 结束语
设备驱动程序是操作系统内核和机器硬件之间的接口。内核利用驱动程序的接口完成对设备的初始化和释放,在系统内核和硬件之间传送数据,并时刻检测和处理设备出现的错误。它是操作系统最基本的组成部分,因此熟悉驱动的编写是很重要的。
参考文献:
[1]宋宝华.Linux设备驱动开发详解[M].北京:人民邮电出版社,2008,
[2]赵炯.Linux内核完全剖析--基于0.12内核[M].上海:机械工业出版社,2009,
[3]魏永明.Linux设备驱动程序[M].北京:中国电力出版社,2006,
[4]吴迪.嵌入式系统原理、设计与应用[M].北京:机械工业出版社,2004,
[5]罗苑棠.嵌入式Linux应用系统开发实例精讲[M].北京:电子工业出版社,2007.