android内核字符驱动设备实战之设备驱动程序篇

一. 进入到kernel/goldfish/drivers目录,新建testdev目录

~/Android$ cd kernel/goldfish/drivers

~/Android/kernel/goldfish/drivers$ mkdir testdev

二. 在hello目录中增加testdev.h文件:

//条件指示符#ifndef...#endif 最主要目的是防止头文件的重复包含和编译
#ifndef _TESTDEV_ANDROID_H_
#define _TESTDEV_ANDROID_H_
#include <linux/cdev.h>
#define TEST_DEVICE_NODE_NAME "testdev"
#define TEST_DEVICE_FILE_NAME "testdev"
#define TEST_DEVICE_CLASS_NAME "testdev"
//虚拟的硬件设备的,字符设备结构体
struct test_android_dev
{
int val; //设备要操作的成员变量
struct cdev dev; //内嵌标准的字符设备结构体,自定义字符设备驱动必须包含该结构
};
#endif

三.在testdev目录中增加testdev.c文件

//需要包含的头文件
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include "testdev.h"
//定义主设备号和从设备号
static int testdev_major = 0;
static int testdev_minor = 0;
//定义设备结构体变量
static struct test_android_dev* test_dev = NULL;
static struct class* testdev_class = NULL;
//定义标准的设备文件操作方法 ,返回值和参数必须按这个标准定义
static int testdev_open(struct inode* inode, struct file* filp);
static int testdev_release(struct inode* inode, struct file* filp);
static ssize_t testdev_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos);
static ssize_t testdev_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos);
//设备文件操作结构
static struct file_operations testdev_fops = {
.owner = THIS_MODULE,
.open = testdev_open,
.release = testdev_release,
.read = testdev_read,
.write = testdev_write,
};
/*-----------start---设备文件操作函数的实现------------*/
//打开设备
static int testdev_open(struct inode* inode, struct file* filp)
{
struct test_android_dev* dev;
//根据该设备的信息节点inode,获取设备结构体的指针
dev = container_of(inode->i_cdev, struct test_android_dev, dev);
//将自定义设备结构体保存在文件指针的私有数据域中,以便访问设备时拿来用
// 因为设备读写操作函数只有文件参数file,而没有信息节点参数
filp->private_data = dev;
return 0;
}

//释放设备,空实现,真实设备需要的话,可以在此做最后的清理工作
static int testdev_release(struct inode* inode, struct file* filp)
{
return 0;
}
//设备读取操作,返回值为读取的大小,如果为0则读取失败
static ssize_t testdev_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos)
{
ssize_t ret = 0;
//获取设备结构体指针
struct test_android_dev* dev = filp->private_data;
//如果用户空间的BUFF小于设备变量的值 ,则退出
if(count < sizeof(dev->val))
{
goto out;
}
//将内核空间设备变量的值拷贝到用户空间的BUF
if(copy_to_user(buf, &(dev->val), sizeof(dev->val)))
{
ret = -EFAULT;
goto out;
}
ret = sizeof(dev->val);
out:
return ret;
}

//设备写入操作,把用户空间的值写入到设备
static ssize_t testdev_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos)
{
ssize_t ret = 0;
//获取设备结构体指针
struct test_android_dev* dev = filp->private_data;
//如果用户空间BUF的大小同设备的不同,则退出
if(count != sizeof(dev->val))
{
goto out;
}
//将用户空间的数据拷贝到设备中去
if(copy_from_user(&(dev->val), buf, count))
{
ret = -EFAULT;
goto out;
}
ret = sizeof(dev->val);
out:
return ret;
}
/*------------end---设备文件操作函数的实现------------*/

//设备初始化注册函数,有模块加载入口函数调用
static int __testdev_setup_dev(struct test_android_dev* dev)
{
int err;
//根据设备的主设备号和从设备号生成设备编号
dev_t devno = MKDEV(testdev_major, testdev_minor);
memset(dev, 0, sizeof(struct test_android_dev));
//设备初始化,向系统注册该设备的操作函数表
cdev_init(&(dev->dev), &testdev_fops);
dev->dev.owner = THIS_MODULE;
dev->dev.ops = &testdev_fops;
dev->val = 0;
//添加设备到系统,激活设备
err = cdev_add(&(dev->dev),devno, 1);
if(err)
{
return err;
}
return 0;
}

//模块加载入口函数
static int __init testdev_init(void)
{
int err = -1;
dev_t dev = 0;
printk(KERN_ALERT"Initializing testdev device.\n");
//动态分配主设备和从设备号
err = alloc_chrdev_region(&dev, 0, 1, TEST_DEVICE_NODE_NAME);
if(err < 0)
{
printk(KERN_ALERT"Failed to alloc char dev region.\n");
goto fail;
}
//根据设备编号获取主设备号和从设备号
testdev_major = MAJOR(dev);
testdev_minor = MINOR(dev);
//给设备结构体分配空间
test_dev = kmalloc(sizeof(struct test_android_dev), GFP_KERNEL);
if(!test_dev)
{
err = -ENOMEM;
printk(KERN_ALERT"Failed to alloc test_dev.\n");
goto unregister;
}
/*初始化设备*/
err = __testdev_setup_dev(test_dev);
if(err) {
printk(KERN_ALERT"Failed to setup dev: %d.\n", err);
goto cleanup;
}
//*****模拟器测试很关键----注册一个类,使mdev可以在"/dev/"目录下 面建立设备节点
testdev_class = class_create(THIS_MODULE, TEST_DEVICE_FILE_NAME);
//*****模拟器测试很关键--创建一个设备节点,节点名为TEST_DEVICE_FILE_NAME
device_create(testdev_class, NULL, dev, "%s", TEST_DEVICE_FILE_NAME);
printk(KERN_ALERT"Succedded to initialize testdev device.\n");
return 0;
cleanup:
kfree(test_dev);
unregister:
unregister_chrdev_region(MKDEV(testdev_major, testdev_minor), 1);
fail:
return err;
}

//模块卸载函数
static void __exit testdev_exit(void)
{
dev_t devno = MKDEV(testdev_major, testdev_minor);
printk(KERN_ALERT"Destroy testdev device.\n");
if(test_dev)
{ //注销设备
cdev_del(&(test_dev->dev));
//释放设备内存
kfree(test_dev);
}
//释放设备号
unregister_chrdev_region(devno, 1);
}

//模块必须通过MODULE_LICENSE宏声明此模块的许可证,否则在加载此模块时,会收到内核被污染的警告
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Test Android Driver");

//声明模块的初始化入口函数和退出函数
module_init(testdev_init);
module_exit(testdev_exit);

四.在testdev录中新增Kconfig和Makefile两个文件

内核模块或驱动编译时,在模块目录内必须有这两个文件。

分布到各目录的Kconfig构成了一个分布式的内核配置数据库,每个Kconfig分别描述了所属目录源文档相关的内核配置菜单。

Kconfig文件的内容

config TESTDEV#配置的菜单项为TESTDEV

tristate "Test Android Driver"#tristate表示支持以模块、内建和不编译三种编译方法。后面是模块的提示字符 default n#默认值:y-内建 m-模块,n-不编译;此处表示:在编译配置时,如果用户没有选择,则先择不编译 help#help相当于注释一样,在给编辑Kconfig文件的人看的,这样可以保持其可读性,下面为注释的内容 This is thetest android driver. 注:模块的编译类型,有编译、还是不编译、以及编译成什么类型。类型可以是: bool、tristate、string、hex和int。

bool类型的只能选中或不选中,选中为y,不选中为n.

tristate类型的菜单项为值可为三种值,多了编译成内核模块的选项。其值可为y,n,m.

string类型表示需要用户输入一串字符串。

hex类型则需要用户输入一个16进制数。

int类型表示用户输入一个整型.

Makefile文件的内容

obj-$(CONFIG_TESTDEV) +=testdev.o 编译目标定义,定义hello.o这个目标是要编译进内核,还是作为模块编译: obj-y +=testdev.o //编译进内核 obj-m+= testdev.o //作为模块编译 obj-n += testdev.o //不编译 取值有$(CONFIG_TESTDEV)决定,而它的只在内核模块编译配置时,有用户设置

五. 修改arch/arm/Kconfig和drivers/kconfig两个文件

在menu "Device Drivers"和endmenu之间添加一行:

source "drivers/testdev/Kconfig" #菜单连接项,执行时需要连接该目录下的Kconfig文件 六.修改drivers/Makefile文件 添加如下内容: obj-$(CONFIG_TESTDEV) += testdev/ 表示testdev目录是否编译,当CONFIG_TESTDEV为y或者m的时候,会去找testdev目录下的Makefile文件 七. 配置编译选项 /Android/kernel/goldfish$make menuconfig 找到"Device Drivers" => "Test Android Drivers"选项,设置为y. 配置编译选项的工作流程: 1、执行编译选项配置命令:make menuconfig 2、从主配置菜单arch/arm/Kconfig和drivers/kconfig,读取主配置菜单。 3、在主配置菜单中,有一个名为“Device Drivers”的菜单,其菜单中有一个 菜单连接项source "drivers/testdev/Kconfig" 4、根据菜单连接项的指示,把testdev这个目录里的Kconfig文件连接进来。 5、根据testdev目录里Kconfig的内容,使在配置编译选项的配置界面中, 设备驱动菜单"Device Drivers"下,包含"Test Android Drivers"这个菜单项, 在此选择y,表示把testdev驱动编译进内核。 6、在配置完成后,goldfish目录下生成一个.config文件,这是个隐藏文件, 这个文件记录着各个选项的配置及值。供Makefile文件使用.

八. 编译:

/Android/kernel/goldfish$make 编译成功后,就可以在testdev目录下看到testdev.o文件了,这时候编译出来的zImage已经包含了testdev驱动。 编译的工作流程: 1、执行编译命令:make 2、读取内核配置文档".config",可知CONFIG_TESTDEV值为y 3、执行根目录的Makefile文件-->调用执行drivers/Makefile->其内容 有:obj-$(CONFIG_TESTDEV) += testdev/这一项。 4、由于CONFIG_TESTDEV值为y,则编译testdev目录下内容, 调用testdev目录下编译文件Makefile 5、obj-$(CONFIG_TESTDEV) +=testdev.o:由于CONFIG_TESTDEV值为y, 则把目标文件testdev.o编译进内核。 九. 运行新编译的内核文件,验证testdev驱动程序是否已经正常安装: /Android$emulator -kernel ./kernel/goldfish/arch/arm/boot/zImage & /Android$ adb shell 进入到dev目录,可以看到testdev设备文件: root@android:/ # cd dev root@android:/dev # ls

更多相关文章

  1. Android学习之文件存储
  2. Android InputStreamReader 解析gbk、gb2312编码的xml文件 编码
  3. Google 发布 Android @ Home,让你用 Android 设备控制家电
  4. android实现文件下载的几种方式
  5. android 动画模块分析
  6. Android Studio 3.0以后打包修改文件名方法
  7. Android 下载文件至SD卡,并用progressBar显示下载进度
  8. Android 文件IO总结
  9. Android--取出SDcard卡上指定后缀名的文件

随机推荐

  1. android 连接CMWAP
  2. android导出通讯录,通话记录,短信
  3. android rating bar style
  4. Android开发学习笔记:我的第一个Android程
  5. android 关于再按一次退出程序效果
  6. 【Android】Http请求
  7. Android 超级水平仪 金属红色仪表风格发
  8. Android中的GridView图片异步加载
  9. js判断移动端系统
  10. android真实项目教程(一)——App应用框架