论文部分内容阅读
摘要:首先介绍了嵌入式Linux环境下设备驱动程序的开发流程,详细描述了Linux设备驱动程序的体系结构和Linux设备文件的概念。然后通过一个例子描述了如何设计和编写Linux设备驱动程序,并解释了其中的核心代码。最后构建一个Glade工程来调用这个设备驱动程序,完成测试工作。
关键词:嵌入式Linux;设备文件;设备驱动;Glade
0 引言
Linux是开放源代码的操作系统,由于其高效稳定、执行速度快、实现了真正的多任务、多用户环境、强大的网络功能、较好的可裁减性与移植性等特点,在嵌入式系统领域获得了飞速发展。针对ARM体系结构CPU开发的,具有MMU功能的嵌入式Linux操作系统无疑是ARM平台上操作系统的最佳选择。本文主要研究了在Linux下开发驱动程序并构建一个GUI程序来使用这个驱动程序的一般流程。
1 Linux设备驱动程序
Linux支持三类硬件设备:字符设备、块设备和网络设备。字符设备是指无须缓存直接按字节读写的设备。块设备以块为单位进行读写,能够进行随机访问。网络设备在Linux里有专门的处理,它没有被映射到文件系统的设备节点,对它的访问采用socket机制。字符设备与块设备的主要区别是:在对字符设备发出读/写请求时,实际的硬件I/O一般紧接着发生;块设备是利用一块系统内存作缓冲区来进行实际的I/O操作。
图1 设备驱动流程
在Linux中,几乎所有的内容都是文件,对设备驱动的访问也是以文件操作的方式实现。无论是字符设备还是块设备,用户对没备的操作都是通过虚拟文件系统(VFS)转化为设备驱动与硬件操作例程的交互(见图1)。即使是访问网络设备的socket接口,也是通过VFS实现的。Linux通过VFS为用户提供了一个统一的设备访问接口,使用户能够透明地访问设备驱动程序。所有的硬件设备都可以使用和操作系统调用接口来打开、关闭、读写和I/O控制,而驱动程序的主要任务就是实现这些系统调用函数。Linux系统中的所有硬件设备都使用一种特殊的设备文件来表示。每个设备文件都有两个设备号:一个是主设备号,它用来标识该设备的种类,也标识该设备使用的驱动程序;另一个是次设备号,用来标识使用同一设备驱动程序的不同硬件设备。
实现一个嵌入式Linux设备驱动的大致流程如下:
(1)定义主、次设备号;也可以动态获取。
(2)实现驱动初始化和清除函数,如果驱动程序采用模块方式,则要实现模块初始化和清除函数。
(3)设计所要实现的文件操作,定义file_operations结构。
(4)实现所需的文件操作调用,如read、write等。
(5)实现中断服务函数,并用request_irq向内核注册。中断并不是每个设备驱动所需要。
(6)将驱动编译到内核或编译成模块,用insmod命令加载。
(7)生成设备节点文件。
下面以一个简单的例子来说明设备驱动程序的结构,这个驱动程序用来控制目标板上的一组LED灯。led_fops结构体定义了该设备需要的操作接口。它的成员全部是函数指针,所以实质上就是函数跳转表。
struct file_operations led_fops={
open:led_open,
/*打开设备操作*/
read:led_read,
/*读设备操作*/
write:led_write,
/*写设备操作*/
ioctl:led_ioctl,
/*控制模块的设置*/
release:led_release /*释放设备操作*/
}:
ssize_t led_read(struct nIe*flip,char*Putbuf,size_t length,
loff__t*f_pos)
{unsigned short BottonStatus;
unsigned char Bottontmp=0;
int i;
BottonStatus=(KEY_CS&Oxff);/*获取当前8个按键的状态*/
for(i=0:i<8:++i)
{if(((BottonStatus>>i)&1)==0)
Bottontmp=i+1;
}
copy_to user(Putbuf,&Bottontmp,length);
/*将内核空间的数据复制到用户空间*/
return length;
}
ssize_t led_write(struct file *filp,const char *Getbuf,
size_t length,loff-t *f_pos)
{int num;
unsigned char UsrWantLed;
copy_from_user(&UsrWantLed,Getbuf,length);
/*将用户空间的写入到设备文件的内容传送到内核空间*/
num=((UsrWantLed)&0xff);
LED_CS=-(1<<(num-1));
/*通过对LED_CS地址赋值来控制LED的亮灭*/
return(0);
}
int led_ioctl(struct inode *inode,struct file*filp,
unsigned int cmd,unsigned long arg)
{switch(cmd)/*利用cmd和arg参数可以完成有些复杂的I/O
操作*/
{ case LED_SHOW:
{if(arg)
led_off_on();
break;
}
}return 0:
}
static int inn keypad_init(void)
{int result;
result=register_chrdev(Led_MAJOR.“led”,&led_fops);
/*这个函数是向内核注册设备,Led_MAJOR是驱动的主设备
号,led是驱动名,led_fops是驱动所执行的操作*/
printk(“%s%sinitialized.\n”,KEYPAD_NAME,KEYPAD_VERSION);
return 0;
}
static void_exit keypad_exit(void)
{unregister_chrdev(Led_MAJOR,“led”);/*向内核注销设备*/
led_off_on();
}
module_init(keypad_init);
module_exit(keypad_exit); module_init()和module_exit()这对宏是对程序模块的初始化和退出函数名称进行记录。它能显式地命名模块的注册和注销函数并保证内核中驱动名的惟一性。
2 嵌入式LInux的GUI
Tiny-X是由XFree86核心小组的成员Keith Packard一手设计的。它能够在配有IMB以下内存的系统上,建立起标准的X系统,是一个性能相当出色并且免费的嵌入式图形界面GUI。在嵌入式系统中,使用Tiny-X图形界面开发产品,上层的应用程序编写将会很方便。可以通过GTK的集成开发环境——Glade完成界面布局并生成原始代码(界面图如图2),再通过文本编辑工具添加事件响应程序。 在程序控制硬件设备之前要打开设备文件,所以要在执行gtk_main()前调用设备打开函数。这个函数主要用于打开设备文件并获得主设备号。关键代码如下: int fd_keypad;
static char*dev_keypad=“/dev/keypad”;
fd_keypad=open(dev_keypad,O_RDWR);
return fd_keypad;
图2 Glade工程界面
然后对每个button添加click响应事件。对buttonl的click响应事件编写控制函数如下:
write(fd_keypad,(const char*)&lednum,sizeof((const char)
lednum));
进入该工程目录,使用命令:#./autogen.sh配置相应的程序以及生成所需的Makefiles文件,然后修改Makefile文件,把自行编写的C文件添加到编译表中,主要是在参数Led_SOURCE和Led_OBJECTS中。然后执行下面的命令:
#exportCC=arm-linux-gcc
/*通过设置环境变量.指定编译工具为arm-linux-gcc*/
#./configure host=arm build=i686
target=armwith-gtk-exec-prefix=usr/Iocal/arm-linux
/*设置生成二进制文件的工作环境为arm,并指定Linux内核所
在的目录*/
#make
/*在/src编译生成二进制代码,下载到开发平台上即可运行*/
用户程序的开发调试主要有两种方式:一是在主机上编写用户程序,将其直接编译入内核,整体下载入目标板,再进行调试;二是在主机上通过交叉编译器编译用户程序,生成能在目标板上执行的二进制文件,通过串口或网络将用户程序下载到目标板上,进行调试。第一种方式由于每次更改程序都需要编译入内核、下载到目标板,显得灵活性较低,且十分繁琐。而第二种开发方式每次只用下载用户程序即可调试,非常方便,因此笔者在驱动程序开发中使用了第二种方式。
3 结束语
在Linux中,系统调用是操作系统内核和应用程序之间的接口,而设备驱动程序就是操作系统内核和机器硬件之间的接口。内核利用驱动程序的接口完成对设备的初始化和释放,在系统内核和硬件、设备文件和应用程序之间传送数据,并时刻检测和处理设备出现的错误。当操作系统对设备进行操作时,会调用驱动程序注册的file_operations结构中的函数指针,找到相应的功能函数。
注:本文中所涉及到的图表、注解、公式等内容请以PDF格式阅读原文。
关键词:嵌入式Linux;设备文件;设备驱动;Glade
0 引言
Linux是开放源代码的操作系统,由于其高效稳定、执行速度快、实现了真正的多任务、多用户环境、强大的网络功能、较好的可裁减性与移植性等特点,在嵌入式系统领域获得了飞速发展。针对ARM体系结构CPU开发的,具有MMU功能的嵌入式Linux操作系统无疑是ARM平台上操作系统的最佳选择。本文主要研究了在Linux下开发驱动程序并构建一个GUI程序来使用这个驱动程序的一般流程。
1 Linux设备驱动程序
Linux支持三类硬件设备:字符设备、块设备和网络设备。字符设备是指无须缓存直接按字节读写的设备。块设备以块为单位进行读写,能够进行随机访问。网络设备在Linux里有专门的处理,它没有被映射到文件系统的设备节点,对它的访问采用socket机制。字符设备与块设备的主要区别是:在对字符设备发出读/写请求时,实际的硬件I/O一般紧接着发生;块设备是利用一块系统内存作缓冲区来进行实际的I/O操作。
图1 设备驱动流程
在Linux中,几乎所有的内容都是文件,对设备驱动的访问也是以文件操作的方式实现。无论是字符设备还是块设备,用户对没备的操作都是通过虚拟文件系统(VFS)转化为设备驱动与硬件操作例程的交互(见图1)。即使是访问网络设备的socket接口,也是通过VFS实现的。Linux通过VFS为用户提供了一个统一的设备访问接口,使用户能够透明地访问设备驱动程序。所有的硬件设备都可以使用和操作系统调用接口来打开、关闭、读写和I/O控制,而驱动程序的主要任务就是实现这些系统调用函数。Linux系统中的所有硬件设备都使用一种特殊的设备文件来表示。每个设备文件都有两个设备号:一个是主设备号,它用来标识该设备的种类,也标识该设备使用的驱动程序;另一个是次设备号,用来标识使用同一设备驱动程序的不同硬件设备。
实现一个嵌入式Linux设备驱动的大致流程如下:
(1)定义主、次设备号;也可以动态获取。
(2)实现驱动初始化和清除函数,如果驱动程序采用模块方式,则要实现模块初始化和清除函数。
(3)设计所要实现的文件操作,定义file_operations结构。
(4)实现所需的文件操作调用,如read、write等。
(5)实现中断服务函数,并用request_irq向内核注册。中断并不是每个设备驱动所需要。
(6)将驱动编译到内核或编译成模块,用insmod命令加载。
(7)生成设备节点文件。
下面以一个简单的例子来说明设备驱动程序的结构,这个驱动程序用来控制目标板上的一组LED灯。led_fops结构体定义了该设备需要的操作接口。它的成员全部是函数指针,所以实质上就是函数跳转表。
struct file_operations led_fops={
open:led_open,
/*打开设备操作*/
read:led_read,
/*读设备操作*/
write:led_write,
/*写设备操作*/
ioctl:led_ioctl,
/*控制模块的设置*/
release:led_release /*释放设备操作*/
}:
ssize_t led_read(struct nIe*flip,char*Putbuf,size_t length,
loff__t*f_pos)
{unsigned short BottonStatus;
unsigned char Bottontmp=0;
int i;
BottonStatus=(KEY_CS&Oxff);/*获取当前8个按键的状态*/
for(i=0:i<8:++i)
{if(((BottonStatus>>i)&1)==0)
Bottontmp=i+1;
}
copy_to user(Putbuf,&Bottontmp,length);
/*将内核空间的数据复制到用户空间*/
return length;
}
ssize_t led_write(struct file *filp,const char *Getbuf,
size_t length,loff-t *f_pos)
{int num;
unsigned char UsrWantLed;
copy_from_user(&UsrWantLed,Getbuf,length);
/*将用户空间的写入到设备文件的内容传送到内核空间*/
num=((UsrWantLed)&0xff);
LED_CS=-(1<<(num-1));
/*通过对LED_CS地址赋值来控制LED的亮灭*/
return(0);
}
int led_ioctl(struct inode *inode,struct file*filp,
unsigned int cmd,unsigned long arg)
{switch(cmd)/*利用cmd和arg参数可以完成有些复杂的I/O
操作*/
{ case LED_SHOW:
{if(arg)
led_off_on();
break;
}
}return 0:
}
static int inn keypad_init(void)
{int result;
result=register_chrdev(Led_MAJOR.“led”,&led_fops);
/*这个函数是向内核注册设备,Led_MAJOR是驱动的主设备
号,led是驱动名,led_fops是驱动所执行的操作*/
printk(“%s%sinitialized.\n”,KEYPAD_NAME,KEYPAD_VERSION);
return 0;
}
static void_exit keypad_exit(void)
{unregister_chrdev(Led_MAJOR,“led”);/*向内核注销设备*/
led_off_on();
}
module_init(keypad_init);
module_exit(keypad_exit); module_init()和module_exit()这对宏是对程序模块的初始化和退出函数名称进行记录。它能显式地命名模块的注册和注销函数并保证内核中驱动名的惟一性。
2 嵌入式LInux的GUI
Tiny-X是由XFree86核心小组的成员Keith Packard一手设计的。它能够在配有IMB以下内存的系统上,建立起标准的X系统,是一个性能相当出色并且免费的嵌入式图形界面GUI。在嵌入式系统中,使用Tiny-X图形界面开发产品,上层的应用程序编写将会很方便。可以通过GTK的集成开发环境——Glade完成界面布局并生成原始代码(界面图如图2),再通过文本编辑工具添加事件响应程序。 在程序控制硬件设备之前要打开设备文件,所以要在执行gtk_main()前调用设备打开函数。这个函数主要用于打开设备文件并获得主设备号。关键代码如下: int fd_keypad;
static char*dev_keypad=“/dev/keypad”;
fd_keypad=open(dev_keypad,O_RDWR);
return fd_keypad;
图2 Glade工程界面
然后对每个button添加click响应事件。对buttonl的click响应事件编写控制函数如下:
write(fd_keypad,(const char*)&lednum,sizeof((const char)
lednum));
进入该工程目录,使用命令:#./autogen.sh配置相应的程序以及生成所需的Makefiles文件,然后修改Makefile文件,把自行编写的C文件添加到编译表中,主要是在参数Led_SOURCE和Led_OBJECTS中。然后执行下面的命令:
#exportCC=arm-linux-gcc
/*通过设置环境变量.指定编译工具为arm-linux-gcc*/
#./configure host=arm build=i686
target=armwith-gtk-exec-prefix=usr/Iocal/arm-linux
/*设置生成二进制文件的工作环境为arm,并指定Linux内核所
在的目录*/
#make
/*在/src编译生成二进制代码,下载到开发平台上即可运行*/
用户程序的开发调试主要有两种方式:一是在主机上编写用户程序,将其直接编译入内核,整体下载入目标板,再进行调试;二是在主机上通过交叉编译器编译用户程序,生成能在目标板上执行的二进制文件,通过串口或网络将用户程序下载到目标板上,进行调试。第一种方式由于每次更改程序都需要编译入内核、下载到目标板,显得灵活性较低,且十分繁琐。而第二种开发方式每次只用下载用户程序即可调试,非常方便,因此笔者在驱动程序开发中使用了第二种方式。
3 结束语
在Linux中,系统调用是操作系统内核和应用程序之间的接口,而设备驱动程序就是操作系统内核和机器硬件之间的接口。内核利用驱动程序的接口完成对设备的初始化和释放,在系统内核和硬件、设备文件和应用程序之间传送数据,并时刻检测和处理设备出现的错误。当操作系统对设备进行操作时,会调用驱动程序注册的file_operations结构中的函数指针,找到相应的功能函数。
注:本文中所涉及到的图表、注解、公式等内容请以PDF格式阅读原文。