字符设备驱动是Linux内核中的一种设备驱动类型,主要用于管理字符设备的输入输出操作。如终端设备、串口、打印机、键盘等都是字符设备。与块设备驱动不同的是,字符设备驱动不须要缓存数据,因而特别适宜这些不须要频繁的数据访问的设备。
通常而言,一个完整的字符设备驱动包含以下几个部份:设备初始化、设备注册、文件操作函数定义、中断处理函数定义、设备卸载等。
一、设备初始化
设备初始化是字符设备驱动的第一步,其主要任务是在内核中分配设备号并将字符设备驱动注册到内核中。设备号是一个32位的整数,拿来惟一标示Linux系统中的所有设备linux常用命令,不同的设备应使用不同的设备号。系统中的前256个设备号被预留给Linux内核使用串口驱动调试 linux,因而对于第三方的字符设备来说,应当选择一个未被预留的设备号。
为了分配设备号,我们须要定义一个structcdev对象,并在其中填充设备号、文件操作函数表等相关信息。诸如:
structcdevmydev;
staticstructfile_operationsmydev_fops={
.owner=THIS_MODULE,
.read=mydev_read,
.write=mydev_write,
.open=mydev_open,
.release=mydev_release,
};
staticint__initmydev_init(void)
intret;
dev_tdevno;
//分配设备号
if(alloc_chrdev_region(&devno,0,1,"mydev")<0){
printk(KERN_ERR"alloc_chrdev_regionfailedn");
return-1;
//初始化cdev对象
cdev_init(&mydev,&mydev_fops);
mydev.owner=THIS_MODULE;
//注册字符设备驱动到内核
ret=cdev_add(&mydev,devno,1);
if(ret<0){
printk(KERN_ERR"cdev_addfailedn");
unregister_chrdev_region(devno,1);
return-1;
printk(KERN_INFO"mydevdriverinitializedn");
return0;
里面的代码中,我们首先定义了一个structcdev对象mydev,并将其与文件操作函数表mydev_fops相关联。之后通过alloc_chrdev_region函数分配一个设备号,并将其保存在devno中。接着我们调用cdev_init函数对mydev进行初始化,并将其owner成员指向当前模块。
最后,我们使用cdev_add函数将mydev注册到内核中,并检测其返回值,假如返回值大于0则表示注册失败。当设备注册成功后,我们可以使用cat/proc/devices命令来查看系统中所有的字符设备,其中包括我们刚才注册的设备。
二、设备注册
设备注册是指将字符设备驱动注册到系统中的设备插口列表中。在注册设备之前串口驱动调试 linux,我们须要定义一个structclass对象,并在其中填充设备类名等相关信息,比如:
structclass*mydev_class;
staticint__initmydev_init(void)
...
//创建设备类
mydev_class=class_create(THIS_MODULE,"mydev_class");
if(IS_ERR(mydev_class)){
printk(KERN_ERR"class_createfailedn");
cdev_del(&mydev);
unregister_chrdev_region(devno,1);
return-1;
//创建设备文件
device_create(mydev_class,NULL,devno,NULL,"mydev%d",0);
printk(KERN_INFO"mydevdriverinitializedn");
return0;
里面的代码中,在成功分配设备号并将mydev注册到内核中以后,我们通过class_create函数创建了一个设备类mydev_class。设备类用于将同一类型的设备分组,比如所有的并口设备可以置于一个设备类中。
之后我们使用device_create函数创建了设备文件,将设备号devno与设备类mydev_class进行关联,为设备文件命名为mydev%d,其中%d表示设备文件的编号,这儿我们只创建了一个设备文件,因而参数为0。
在设备文件创建成功后,我们可以在/dev目录下见到mydev0文件。
三、文件操作函数
文件操作函数是字符设备驱动中最为重要的部份,它定义了对设备的读写操作、设备打开和关掉以及设备的控制操作。在字符设备驱动中,每位文件操作函数都对应着一个文件操作符,因而任何对该设备的操作就会触发相应的文件操作函数。
字符设备驱动中文件操作函数的定义如下:
structfile_operations{
structmodule*owner;
loff_t(*llseek)(structfile*,loff_t,int);
ssize_t(*read)(structfile*,char__user*,size_t,loff_t*);
ssize_t(*write)(structfile*,constchar__user*,size_t,loff_t*);
int(*open)(structinode*,structfile*);
int(*release)(structinode*,structfile*);
int(*flush)(structfile*,fl_owner_tid);
int(*fsync)(structfile*,intdatasync);
};
其中,llseek函数用于控制读写表针的位置,read函数用于从设备中读数据,write函数用于向设备中写数据,open和release函数用于打开和关掉设备。
这儿我们给出一个简单的示例,实现从设备中读取10个字节的数据。
staticssize_tmydev_read(structfile*filp,char__user*buf,size_tcount,loff_t*f_pos)
charkbuf[10]="abcdefghi";
intret=0;
if(*f_pos>9)
return0;
if(count+*f_pos>10)
count=10-*f_pos;
if(copy_to_user(buf,kbuf+*f_pos,count))
return-EFAULT;
*f_pos+=count;
ret=count;
returnret;
里面的代码中,我们定义了一个kbuf链表来储存数据。之后我们按照当前读取的位置f_pos,估算出须要读取的字节数count,并使用copy_to_user函数将数据复制到用户空间。
最后我们更新读取的位置f_pos,并返回读取的字节数。
四、中断处理函数
在字符设备驱动中,有些硬件设备会形成中断,这时侯须要定义相应的中断处理函数来处理中断风波。Linux中的中断机制十分强悍linux教程,可以处理各种类型的中断风波,包括定时器中断、硬件中断、软中断等。
在字符设备驱动中,我们须要定义一个中断处理函数,并将其注册到相应的中断号上。诸如:
staticirqreturn_tmydev_interrupt(intirq,void*dev_id)
printk(KERN_INFO"mydev_interruptn");
returnIRQ_HANDLED;
staticint__initmydev_init(void)
...
//注册中断
if(request_irq(IRQ_NUMBER,mydev_interrupt,IRQF_SHARED,"mydev",&mydev)){
printk(KERN_ERR"request_irqfailedn");
device_destroy(mydev_class,devno);
class_destroy(mydev_class);
cdev_del(&mydev);
unregister_chrdev_region(devno,1);
return-1;
printk(KERN_INFO"mydevdriverinitializedn");
return0;
里面的代码中,我们定义了一个mydev_interrupt函数来处理中断风波。在设备初始化中,我们通过request_irq函数将该中断处理函数注册到中断号IRQ_NUMBER上,并将mydev作为传递给中断处理函数的参数。其中,IRQF_SHARED表示该中断可以被多个驱动程序共享。
在步入中断处理函数后,我们可以对设备进行相应的操作,比如读取早已接收到的数据、清除中断标志等。最后,我们须要使用IRQ_HANDLED返回一个中断风波早已被成功处理的标志。
五、设备卸载
设备卸载须要释放所有早已分配的资源,包括设备号、设备文件、设备类、中断等。一个完整的设备卸载函数应当包含以下几个步骤:
staticvoid__exitmydev_exit(void)
//释放中断
free_irq(IRQ_NUMBER,&mydev);
//销毁设备文件和设备类
device_destroy(mydev_class,devno);
class_destroy(mydev_class);
//从内核中注销字符设备驱动
cdev_del(&mydev);
unregister_chrdev_region(devno,1);
printk(KERN_INFO"mydevdriverexitedn");
module_exit(mydev_exit);
里面的代码中,我们依次使用free_irq、device_destroy、class_destroy、cdev_del和unregister_chrdev_region函数来释放各类资源。最后我们使用module_exit宏定义来注册设备卸载函数mydev_exit。当该模块被卸载时,mydev_exit函数将被手动调用。
总结
本文详尽介绍了字符设备驱动框架的实现,包括设备初始化、设备注册、文件操作函数定义、中断处理函数定义和设备卸载等。字符设备驱动是Linux内核中十分重要的一部份,通过学习字符设备驱动的实现,可以帮助我们更好地理解Linux内核中设备驱动的工作原理。