[置顶] 开发第一个Android设备驱动程序
编写第一个Android驱动程序
什么是驱动程序?有些权威人士说的很好,认为驱动程序即是使对设备的操作更为方便、更为高效、更加有组织,比较接近人类思维方式而已。所以文件操作只是对设备操作的组织和抽象,而设备操作就是对文件操作的最终实现。当然,下面是参考相关资料和自己的理解整理的文档,拿来与大家分享,如有错误的分析,请不吝赐教!!
我们都知道,Android系统内核是基于Linux内核的,所以对于编写Android系统的驱动与编写Linux系统的驱动是一样的实现方式。对于Linux驱动的实现,可以参考相关的书籍,我这里只是简单说明设备驱动在Linux中的不同实现方式,如下图所示:
上面为Linux中的设备驱动分层实现逻辑图,便于理解设备驱动的不同实现方式,从上面我们可以知道由实现应用程序的进程通过“设备文件号”来与打开的文件结构相联系,同时每个文件结构则代表着对应一个已打开设备文件的操作上下文。借助这个操作的上下文,进程使用各个文件的线性逻辑空间来进行文件操作。
对于设备文件,它的逻辑空间与设备的逻辑空间一般是相同的,所以在文件系统层就不需要映射了,只需要从“设备逻辑空间”到“物理设备空间”的一层映射即可;而对于普通文件或称之为“磁盘文件”而言,则需要先按照文件系统的结构和规则映射到”设备逻辑空间“的线性逻辑空间。而后,再由”设备逻辑空间“映射到”设备物理空间“了。下面是一个类似于我们都熟悉的程序”HelloWorld”。在这里,这个HelloWorld只是一个实现和验证在Android系统中实现驱动开发的流程,并没有任何实际的意义,具体看下面的例子。
例子:字符驱动程序(最简单的驱动程序,类似于我们都熟悉的第一个HelloWorld程序,它很好地印证了Linux驱动的开发过程)。
这是个虚拟的4字节寄存器(对于寄存器相关可以参考相关书籍),我们要实现的功能是想这个寄存器中写入一个值,然后再从这个寄存器中取出刚刚写入的值,如果相同就说明功能实现正常了。
具体看下面的代码:
头文件部分:helloworld.h
#ifndef_FAKE_REG_H_
#define_FAKE_REG_H_
//导入相关字符设备头文件
#include<linux/cdev.h>
#include<linux/semaphore.h>
//自定义目标设备描述变量
#defineHELLOWORLD_DEVICE_NODE_NAME"helloworld"
#defineHELLOWORLD_DEVICE_FILE_NAME"helloworld"
#defineHELLOWORLD_DEVICE_PROC_NAME"helloworld"
#defineHELLOWORLD_DEVICE_CLASS_NAME"helloworld"
//自定义设备结构体对象
structhelloworld_reg_dev{
intval;
structsemaphoresem;
structcdevdev;
};
#endif
执行文件部分:helloworld.c
//引用相关必须头文件
#include<linux/init.h>
#include<linux/module.h>
#include<linux/types.h>
#include<linux/fs.h>
#include<linux/proc_fs.h>
#include<linux/device.h>
#include<asm/uaccess.h>
//自定义头文件
#include"helloworld.h"
//主次设备号,通过设备文件名和主设备号可以唯一确定一设备
//次设备号表示同类设备的编号
staticinthelloworld_major=0;
staticinthelloworld_minor=0;
//定义设备类别和设备变量
staticstructclass*helloworld_class=NULL;
staticstructfake_reg_dev*helloworld_dev=NULL;
//定义传统的设备文件操作方法
staticinthelloworld_open(structinode*inode,structfile*file);
staticinthelloworld_release(structinode*inode,structfile*file);
staticssize_thelloworld_read(structfile*filechar__user*buf,size_tcount,loff_t*f_pos);
staticssize_thelloworld_write(structfile*file,constchar__user*buf,size_tcount,loff_t*f_pos);
//传统的设备文件操作方法列表,后面利用这个方法表来实现对设别的操作
staticstructfile_operationshelloworld_fops={
.owner=THIS_MODULE,
.open=helloworld_open,
.release=helloworld_release,
.read=helloworld_read,
.write=helloworld_write,
};
//devfs文件系统---特殊的树状结构文件系统,优化了传统的文件系统
//下面为该系统的设属性操作方法
staticssize_thelloworld_val_show(structdevice*dev,structdevice_attribute*attr,char*buf);
staticssize_thelloworld_val_store(structdevice*dev,structdevice_attribute*attr,constchar*buf,size_tcount);
//devfs系统的设备属性
staticDEVICE_ATTR(val,S_IRUGO|S_IWUSR,helloworld_val_show,helloworld_val_store);
//下面为相关定义方法及系统调用方法的具体实现
staticinthelloworld_open(structinode*inode,structfile*file){
structfake_helloworld_dev*dev;
dev=container_of(inode->i_cdev,structfake_helloworld_dev,dev);
file->private_data=dev;//私有文件指针域
return0;
}
staticssize_thelloworld_read(structfile*file,char__user*buf,size_tcount,loff_t*f_pos){
ssize_terr=0;
structfake_helloworld_dev*dev=file->private_data;
if(down_interruptible(&(dev->sem))){
return-ERESTARTSYS;
}
if(count<sizeof(dev->val)){
gotoout;
}
if(copy_to_user(buf,&(dev->val),sizeof(dev->val))){
err=-EFAULT;
gotoout;
}
err=sizeof(dev->val);
out:
up(&(dev->sem));//同步信号量访问
returnerr;
}
staticssize_thelloworld_write(structfile*file,constchar__user*buf,size_tcount,loff_t*f_pos){
structfake_helloworld_dev*dev=filp->private_data;
ssize_terr=0;
if(down_interruptible(&(dev->sem))){
return-ERESTARTSYS;
}
if(count!=sizeof(dev->val)){
gotoout;
}
if(copy_from_user(&(dev->val),buf,count)){
err=-EFAULT;
gotoout;
}
err=sizeof(dev->val);
out:
up(&(dev->sem));
returnerr;
}
staticinthelloworld_release(structinode*inode,structfile*file){
return0;
}
//devfs文件系统获取和修改寄存器中val的方法
staticssize_t__helloworld_get_val(structfake_helloworld_dev*dev,char*buf){
intval=0;
if(down_interruptible(&(dev->sem))){
return-ERESTARTSYS;
}
val=dev->val;
up(&(dev->sem));
returnsnprintf(buf,PAGE_SIZE,"%d\n",val);
}
staticssize_t__helloworld_set_val(structhelloworld_reg_dev*dev,constchar*buf,size_tcount){
intval=0;
val=simple_strtol(buf,NULL,10);
if(down_interruptible(&(dev->sem))){
return-ERESTARTSYS;
}
dev->val=val;
up(&(dev->sem));
returncount;
}
staticssize_thelloworld_val_show(structdevice*dev,structdevice_attribute*attr,char*buf){
structfake_helloworld_dev*hdev=(structfake_helloworld_dev*)dev_get_drvdata(dev);
return__helloworld_get_val(hdev,buf);
}
staticssize_thelloworld_val_store(structdevice*dev,structdevice_attribute*attr,constchar*buf,size_tcount){
structfake_helloworld_dev*hdev=(structfake_helloworld_dev*)dev_get_drvdata(dev);
return__helloworld_set_val(hdev,buf,count);
}
//实现proc文件系统的接口方法
staticssize_thelloworld_proc_read(char*page,char**start,off_toff,intcount,int*eof,void*data){
if(off>0){
*eof=1;
return0;
}
return__helloworld_get_val(helloworld_dev,page);
}
staticssize_thelloworld_proc_write(structfile*file,constchar__user*buff,unsignedlonglen,void*data){
interr=0;
char*page=NULL;
if(len>PAGE_SIZE){
printk(KERN_ALERT"Thebuffistoolarge:%lu.\n",len);
return-EFAULT;
}
page=(char*)__get_free_page(GFP_KERNEL);
if(!page){
printk(KERN_ALERT"Failedtoallocpage.\n");
return-ENOMEM;
}
if(copy_from_user(page,buff,len)){
printk(KERN_ALERT"Failedtocopybufffromuser.\n");
err=-EFAULT;
gotoout;
}
err=__helloworld_set_val(helloworld_dev,page,len);
out:
free_page((unsignedlong)page);
returnerr;
}
staticvoidhelloworld_create_proc(void){
structproc_dir_entry*entry;
entry=create_proc_entry(HELLOWORLD_DEVICE_PROC_NAME,0,NULL);
if(entry){
entry->owner=THIS_MODULE;
entry->read_proc=helloworld_proc_read;
entry->write_proc=helloworld_proc_write;
}
}
staticvoidhelloworld_remove_proc(void){
remove_proc_entry(HELLOWORLD_DEVICE_PROC_NAME,NULL);
}
staticint__helloworld_setup_dev(structfake_helloworld_dev*dev){
interr;
dev_tdevno=MKDEV(helloworld_major,helloworld_minor);
memset(dev,0,sizeof(structfake_helloworld_dev));
cdev_init(&(dev->dev),&helloworld_fops);
dev->dev.owner=THIS_MODULE;
dev->dev.ops=&helloworld_fops;
err=cdev_add(&(dev->dev),devno,1);
if(err){
returnerr;
}
init_MUTEX(&(dev->sem));
dev->val=0;
return0;
}
staticint__inithelloworld_init(void){
interr=-1;
dev_tdev=0;
structdevice*temp=NULL;
printk(KERN_ALERT"Initializinghelloworlddevice.\n");
err=alloc_chrdev_region(&dev,0,1,HELLOWORLD_DEVICE_NODE_NAME);
if(err<0){
printk(KERN_ALERT"Failedtoallocchardevregion.\n");
gotofail;
}
helloworld_major=MAJOR(dev);
helloworld_minor=MINOR(dev);
helloworld_dev=kmalloc(sizeof(structfake_helloworld_dev),GFP_KERNEL);
if(!helloworld_dev){
err=-ENOMEM;
printk(KERN_ALERT"Failedtoallochelloworlddevice.\n");
gotounregister;
}
err=__helloworld_setup_dev(helloworld_dev);
if(err){
printk(KERN_ALERT"Failedtosetuphelloworlddevice:%d.\n",err);
gotocleanup;
}
helloworld_class=class_create(THIS_MODULE,HELLOWORLD_DEVICE_CLASS_NAME);
if(IS_ERR(helloworld_class)){
err=PTR_ERR(helloworld_class);
printk(KERN_ALERT"Failedtocreatehelloworlddeviceclass.\n");
gotodestroy_cdev;
}
temp=device_create(helloworld_class,NULL,dev,"%s",HELLOWORLD_DEVICE_FILE_NAME);
if(IS_ERR(temp)){
err=PTR_ERR(temp);
printk(KERN_ALERT"Failedtocreatehelloworlddevice.\n");
gotodestroy_class;
}
err=device_create_file(temp,&dev_attr_val);
if(err<0){
printk(KERN_ALERT"Failedtocreateattributevalofhelloworlddevice.\n");
gotodestroy_device;
}
dev_set_drvdata(temp,helloworld_dev);
helloworld_create_proc();
printk(KERN_ALERT"Succeddedtoinitializehelloworlddevice.\n");
return0;
destroy_device:
device_destroy(helloworld_class,dev);
destroy_class:
class_destroy(helloworld_class);
destroy_cdev:
cdev_del(&(helloworld_dev->dev));
cleanup:
kfree(helloworld_dev);
unregister:
unregister_chrdev_region(MKDEV(helloworld_major,helloworld_minor),1);
fail:
returnerr;
}
staticvoid__exithelloworld_exit(void){
dev_tdevno=MKDEV(helloworld_major,helloworld_minor);
printk(KERN_ALERT"Destroyhelloworlddevice.\n");
helloworld_remove_proc();
if(helloworld_class){
device_destroy(helloworld_class,MKDEV(helloworld_major,helloworld_minor));
class_destroy(helloworld_class);
}
if(helloworld_dev){
cdev_del(&(helloworld_dev->dev));
kfree(helloworld_dev);
}
unregister_chrdev_region(devno,1);
}
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("FakeRegisterDriver");
module_init(helloworld_init);
module_exit(helloworld_exit);
驱动程序编译方式配置文件:Kconfig
configHELLOWORLD
tristate"HelloWorldRegisterDriver"
defaultn
Thisisthehelloworlddriverforandroid.
驱动程序编译脚本文件:Makefile
obj-$(CONFIG_HELLOWORLD)+=helloworld.o
,在上面的资源文件准备好之后,我们还需要修改内核默认的Kconfig和Makefile文件,目的是让系统在编译的时候能搞编译和找到我们自己编写的驱动文件。修改的内容如下:
Kconfig文件--->位于arch/arm/下,修改内容:打开文件在menu“Devicedrivers”和endmenu之间添加内容为source“drivers/helloworld/Kconfig”保存退出。
Makefile文件--->位于drivers/Makefile,修改内容为:打开文件添加内容为:obi-$(CONFIG_HELLOWORLD)+=helloworld/即可。
最后,我们就可以执行makemenuconfig来配置编译方式了。我们可以选择的编译方式有编译到系统内核中或以模块的方式编译。我这里选择将其潜入到内核编译。
配置完成之后,执行make命令来编译所编写的驱动了。若出现如下提示信息,则表示编译成功了。
提示信息:Kernelarch/arm/boot/zImageisready
验证内核驱动程序:
启动虚拟器
--->emulator-kernelkernel/goldfish/arch/arm/boot/zImage&
进入内核
--->adbshell
--->cddev
--->lshelloworld显示helloworld说明找到了编写的helloworld驱动程序了。
继续验证:
--->cdproc
--->cathelloworld默认寄存器里内容为0
--->echo1>helloworld写入值1到寄存器中
--->cathelloworld显示为1表示验证通过
继续验证:
--->cd/sys/class/helloworld/helloworld/
--->catval显示为1标志正确
--->echo0>val
--->catval显示为0验证正确通过,恭喜,第一个驱动编写成功!
注意:如果选择以模块方式编译的话,则必须先在第一个界面中选择“Enableloadablemodulesupport”选项,按”y”键设为true,即表示内核支持动态加载模块。
本人刚创建了一个QQ群,目的是共同研究学习Android,期待兴趣相投的同学加入,申请时,请输入加入理由!!
群号:179914858
更多相关文章
- Android(安卓)Rom分区 与 SD卡读写
- 解决Android(安卓)Studio 新建导入项目时死掉
- 9、Libgdx的输入处理
- android扫描sdcard中的音视频及图片等文件
- Android的log机制小结
- 如何在Android(安卓)Studio中使用Gradle发布项目至Jcenter仓库
- (六)android recovery 升级UI显示之资源文件
- android批量安装APK
- 【cocos2dx-3.0beta-制作flappybird】就要结束了吗—Android交叉