- 论坛徽章:
- 0
|
Linux操作系统运行在两种模式下,一种是用户模式,另一种是内核模式。在编写驱动程序时,必须决定哪些功能将用模块来实现,哪些功能的实现要留在用户空间。一般原则是只要是能够在用户空间编程实现的,就不要放到内核里面去实现。虽然有时写一个用户空间设备驱动程序是对内核扩充的明智选择,但是用户空间驱动程序毕竟做不了太多的事情。当在用户空间无法实现设备驱动,或者强调驱动程序的实时性与稳定性时,就必须编写内核级的设备驱动程序。
Linux内核空间设备驱动程序的开发
Developing Linux kernel space device driver
郑伟 王钦若 吴乃优
Zheng,Wei Wang,Qinruo Wu,Naiyou
摘 要 本文详细介绍了Linux平台下内核空间设备驱动程序的开发。在比较proc和dev两种文件系统的基础上,分别以PCI设备和USB设备的驱动程序开发为实例来介绍利用两种文件系统开发字符设备驱动程序的方法。
关 键 词 Linux; 设备驱动; 内核空间; PCI; USB
Abstract: This thesis introduces how to develop kernel level device driver on Linux platform in detail. On the basis of comparing proc file system with dev file system, we choose PCI device and USB device as instances to introduce the method of writing device driver for char devices by using these two file systems.
Key words: Linux; device driver; kernel space; PCI; USB
Linux操作系统运行在两种模式下,一种是用户模式,另一种是内核模式。在编写驱动程序时,必须决定哪些功能将用模块来实现,哪些功能的实现要留在用户空间。一般原则是只要是能够在用户空间编程实现的,就不要放到内核里面去实现。虽然有时写一个用户空间设备驱动程序是对内核扩充的明智选择,但是用户空间驱动程序毕竟做不了太多的事情。当在用户空间无法实现设备驱动,或者强调驱动程序的实时性与稳定性时,就必须编写内核级的设备驱动程序。
1 概述
1.1 设备分类
Linux系统支持三类硬件设备:字符设备、块设备和网络设备。字符设备是那些无需缓冲直接读写的设备,它们以字节为单位进行读写,正因为它们可以读写,所以可以被看作文件。块设备则仅能以块为单位读写,典型的块大小为512或1024字节,它的存取是通过buffer cache来进行,并且可以进行随机访问,即不管块位于设备中何处都可以对其进行读写。虽然大多数设备驱动程序都要通过文件系统进行访问,网络设备是一个例外,它们在Linux中没有与之对应的文件数据项,虽然也用设备相关文件来表示,但只有到Linux寻找和初始化网络设备时才建立这种文件。
事实上,大多数时候需要进行驱动开发的设备都可以看作是字符设备,而且编写三种设备的驱动程序只存在着很小的差别,这里将以PCI(外部设备互连)设备和USB(通用串行总线)设备作为开发实例,详细介绍使用proc和dev两种不同文件系统开发字符设备的驱动程序的方法。
1.2 设备驱动
设备驱动的一个基本特征是设备处理的抽象概念。所有硬件设备都被看成文件,可以通过与操作普通文件相同的标准系统调用来打开、关闭、读取和写入设备,系统中每一设备都用一种特殊的设备相关文件来表示。
Linux下开发内核设备驱动程序有两种方式,一种是在配置和编译内核时将驱动程序编译进内核;另一种方式是编译成可动态加载的模块。将所有的东西都编译进内核无疑会使内核变得庞大而导致实时性与稳定性的降低,而根据需要动态地加载模块则保持了操作系统内核的紧凑性,所以大多数时候我们推荐使用模块机制。
1.3 proc 和dev文件系统比较
Linux的proc文件系统是进程文件系统和内核文件系统组成的复合体,是将内核数据对象化为文件形式进行存取的一种内存文件系统,提供了一种监控内核的用户接口。proc文件系统是一个虚拟文件系统,它通过文件系统接口实现,用于输出系统运行状态,以文件系统的形式为操作系统本身和应用进程之间的通信提供了一个界面,使应用程序能够安全、方便地获得系统当前的运行状况和内核的内部数据信息,并且可以修改某些系统的配置信息。
Linux将设备看成特殊的文件,设备文件不使用文件系统中的任何数据空间,它仅仅作为设备驱动的访问入口点,用户利用该入口点就可以象操作普通文件一样来操作设备。dev目录是用来存放设备文件的,每个设备都会有对应的设备文件存在,对设备文件的动作就是对设备的存取。
下面以PCI设备为例介绍proc文件系统驱动程序开发方法,以USB设备为例介绍dev文件系统驱动程序开发的方法。
2 proc文件系统驱动程序开发
proc文件系统工作起来和一个真正的文件系统非常相似,它也需要挂载,也要用标准的文件操作命令来读写。从proc文件系统的某个文件里读出来的数据是由模块或内核即时生成的,可以通过它们了解到系统运行时的统计资料和其他相关信息,而可写数据项可以用来改变驱动程序的配置和行为。因此,为设备在proc文件系统里添加一个条目将使我们能够直接从运行着的模块那里检索信息,不再需要编写程序来发出ioctl命令。
2.1 PCI设备模块初始化
PCI是当前最常用的外设总线之一,也是Linux内核支持最优秀的总线。Linux的PCI内核代码为PCI驱动程序的开发提供了宝贵的参考,下述代码实现了整个驱动模块的框架的构建,完成PCI设备的识别、资源申请以及资源释放等功能:
#define MODULE_VERSION "1.0"
#define MODULE_NAME "PCIDEV"
#define DRV_NAME "pcidev"
MODULE_LICENSE("GPL");
static struct pci_device_id drv_pci_tbl[] __devinitdata = {
{idVendor,idProduct, …}
}; // 该结构用于识别PCI设备
MODULE_DEVICE_TABLE (pci, drv_pci_tbl);
static int __devinit drv_init_pci (struct pci_dev *pdev, const struct pci_device_id *ent){
… // 为PCI设备申请资源
init_procfs (); // 初始化proc
return 0;
}
static struct proc_dir_entry *feeddir;
static int init_procfs (void){
int ret = 0;
/* create directory */
feeddir = proc_mkdir ("pcidev", NULL);
if (feeddir == NULL){
return -ENOMEM;
}
/* create file in the directory */
ptpmove_file=create_proc_entry(“ptpmove”, 0644, feeddir);
if(ptpmove==NULL){
return -ENOMEM;
}
}
static void __devexit drv_remove_pci (struct pci_dev *pdev){
… //释放PCI申请得到的资源
}
static struct pci_driver drv_pci_driver = {
name:DRV_NAME,
id_table:drv_pci_tbl,
probe:drv_init_pci,
remove:__devexit_p (drv_remove_pci),
}; // pci_driver数据结构请见
static int __init drv_init (void){
return pci_module_init (&drv_pci_driver);
}
static void __exit drv_cleanup (void){
pci_unregister_driver (&drv_pci_driver);
remove_proc_entry(“pcidev”, NULL);
printk(KERN_INFO “%s removed.n”, MODULE_NAME, MODULE_VERSION);
}
module_init (drv_init);
module_exit (drv_cleanup);
此时,我们完成了PCI设备驱动模块基本框架程序的编写,编译模块并用insmod加载,可以发现在/proc下出现pcidev目录,并为PCI设备申请了资源;用rmmod卸载模块,可以正确的删除pcidev目录,并释放PCI申请得到的资源。
2.2 内核空间与用户空间通信
proc并不从内核空间直接读写信息,而是利用回调函数机制访问文件来达到目的。当特定文件被读写时,相应的回调函数就会被调用。建立proc文件系统后,可以使用下述代码来实现回调函数,完成PCI设备与用户空间的通信:
ptpmove_file=create_proc_entry(“ptpmove”,0644,feeddir);
if(ptpmove==NULL){
return -ENOMEM;
}
ptpmove_file->read_proc=proc_read_ptpmove;
ptpmove_file->write_proc=proc_write_ptpmove;
具体实现代码为:
static int proc_read_ptpmove(struct file *file, const char *buffer, unsigned long count, void *data){
char mybuf[50];
if(copy_to_user(mybuf, buffer, count*sizeof(long))){
return –EFAULT;
}
… // 操作PCI设备I/O端口
}
static int proc_read_ptpmove(struct file *file, const char *buffer, unsigned long count, void *data){
char mybuf[50];
if(copy_from_user(mybuf, buffer,count*sizeof(long))){
return –EFAULT;
}
… // 操作PCI设备I/O端口
}
相应地,在卸载模块时,必须删除ptpmove文件:remove_proc_entry(“ptpmove”, NULL);
上述代码来自于我们开发的一种PCI运动控制卡的驱动代码,此时,在/proc/pcidev目录下建立了文件ptpmove,它用来实现电机的点对点运动,在用户空间读写该文件,并通过copy_from_user()<span style="black; FONT-FAMILY: 宋体; mso-bidi-font-size: 9.0pt; mso
本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u/2943/showart_9413.html |
|