大学毕业,初入公司,招进去的是android驱动开发工程师的岗位,那时候刚进去,首先学到的就是如何搭建kernel、android的编译环境,然后就是了解如何刷设备以及一些最基本的工具。如adb、fastboot、grep、minicom、kermit、svn、git、eclispe、ndk等相关的知识,记得那时候很挫,过去很多东西都不懂。到了那,一周,都是熟悉使用ubuntu,然后了解刷机的流程,了解uboot、kernel、ramdisk、recovery、system的作用以及相关的框架,印象最深的是,就搞定刷机这个问题,都折腾了很久,原因之前的文章也说了,usb id 没有配好,因为android设备在开机状态和fastboot的模式下,usb id是不一样的。在开机状态下,可以通过adb shell 进入android系统,但一切换fastboot模式,就发现无法找到设备。
当初,学习驱动开发的第一步,就是点亮一个LED灯,当然是基于android系统的,不是裸版上操作。正所谓初生牛犊不怕虎,先把百度,网上多的是例子,很高兴,马上copy一份代码,修改修改,试一试,编译通过,然后按着说明步骤,一步一步操作,发现insmod led.ko的时候,加载不成功,没办法,继续百度,搞了半天,没找到问题所在,然后尝试静态的编进去,别说,成功加载了,在/dev下找到自己的驱动,灯也亮了。当时,觉得完成任务,也没有多考虑什么,就向师父说,搞定了。就这样,一步一步的学下去,平台设备驱动模型,帧缓冲设备,输入子系统,中断,并态竞争,并开始慢慢解bug,调模块,UHF,nfc,rfid,电池,3G,音频,扫描头,wifi等一些,也许由于时间较紧,或者更可能也是因为得过且过,觉得在这家公司也能生存下去,对一些细节、原理性的东西并未深究。比如,内核层的数组越界表现为设备整个重启,JNI层数组越界,可能从andorid重启,app出现问题,表现应用挂掉。现在,事情不太多,想着把以前学的东西,从新梳理一下,并且深入的跟一下,毕竟不能浮于表面,应该多学习学习。但因中途电脑出现故障,所存资料全报废了,只能挑一些当时印象比较深刻的问题,重新学习一下。这次主要讲一下字符设备。
1. 什么是字符设备?
字符设备是 3 大类设备(字符设备、块设备和网络设备)中较简单的一类设备,提供连续的数据流,应用程序可以顺序读取,通常不支持随机存取。相反,此类设备支持按字节/字符来读写数据。
其驱动程序中完成的主要工作是初始化、添加和删除 cdev 结构体,申请和释放设备号,以及填充file_operations 结构体中的操作函数,实现file_operations 结构体中的read()、write()和ioctl()等函数是驱动设计的主体工作。
2. 字符设备的框架模型:
(备注:此图片来源于:http://my.oschina.net/u/1169027/blog/191538)
3. 字符设备的重要的数据结构
3.1 一个简单的字符设备的例子:
- #include <linux/module.h>
- #include <linux/kernel.h>
- #include <linux/init.h>
- #include <linux/input.h>
- #include <linux/platform_device.h>
- #include <linux/miscdevice.h>
- static int first_drv_open(struct inode *inode, struct file *file)
- {
- printk("first_drv_open\n");
- return 0;
- }
- static int first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
- {
- printk("first_drv_write\n");
- return 0;
- }
- /*3. 这个结构是字符设备驱动程序的核心
- * 当应用程序操作设备文件时所调用的open、read、write等函数,
- * 最终会调用这个结构中指定的对应函数
- static struct file_operations first_drv_fops = {
- .owner = THIS_MODULE,
- .open = first_drv_open,
- .write = first_drv_write,
- };
- static struct class *firstdrv_class;
- static struct device *firstdrv_class_dev;
- int major;
- // 执行insmod命令时就会调用这个函数
- static int first_drv_init(void)
- {
- major = register_chrdev(0, "first_drv", &first_drv_fops);
- firstdrv_class = class_create(THIS_MODULE, "firstdrv");
- firstdrv_class_dev = device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "myhello");
- printk("add ko,/dev/myhello \n");
- return 0;
- }
- /*
- * 执行rmmod命令时就会调用这个函数 ,注销函数,主要是释放你在注册是申请的资源,与你注册顺序相反,先注册的后释放。
- */
- static void first_drv_exit(void)
- {
- unregister_chrdev(major, "first_drv");
- device_unregister(firstdrv_class_dev);
- class_destroy(firstdrv_class);
- printk("del ko, \n");
- }
- //1. 我们一般从入口函数看起,先找到该字符设备的入口函数,
- module_init(first_drv_init);
- module_exit(first_drv_exit);
- MODULE_LICENSE("GPL");
3.2. 主要看入口函数:
在入口函数中,有3个主要的函数:几个重要的结构体:firstdrv_class,firstdrv_class_dev,cdev
先看一下 字符设备注册函数:
static inline int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
参数说明:
major:cdev的主设备号,此为0;
name:cdev的名称,此为 first_drv;
file_operation: cdev的文件操作接口,非常主要,一般为open、close、read、write、ioctl等,此只有open、write。
看下此函数如何调下去的:
__register_chrdev(major, 0, 256, name, fops); 这里可以看出,它把major的主设备号下的256个次设备号都归为此字符设备
下面三个函数,是注册字符设备的3步:
cd = __register_chrdev_region(major, baseminor, count, name);
cdev = cdev_alloc();
err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);
此中重要的结构体:
struct cdev *cdev;
struct cdev {
struct kobject kobj;//内嵌的kobject对象
struct module *owner;所属模块,通常为THIS_MODULE
const struct file_operations *ops;//文件操作结构体
struct list_head list;
dev_t dev; //设备号
unsigned int count;
};
然后看device_create,这是创建一个类,然后在类下创建一个设备,这个其实就是帮你在proc/ 和 dev/下创建设备节点,赋予相应的属性,
我们跟下代码,看看是如何调用的:
__class_create(struct module *owner, const char *name,struct lock_class_key *key)
__class_register(cls, key);
error = kset_register(&cp->subsys);
kobject_uevent(&k->kobj, KOBJ_ADD);
kobject_uevent_env(kobj, action, NULL);
/* environment buffer *//* 分配保存环境变量的内存 */
env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL);
/* complete object path */
devpath = kobject_get_path(kobj, GFP_KERNEL);
/* default keys */ /*设置环境变量 */
retval = add_uevent_var(env, "ACTION=%s", action_string);
retval = add_uevent_var(env, "DEVPATH=%s", devpath);
retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem);
/* 调用应用程序: 比如mdev */
/* 启动脚本 echo /sbin/mdev > /proc/sys/kernel/
* 设置了uevent_helper为“/sbin/mdev“
*/
argv [0] = uevent_helper;
argv [1] = (char *)subsystem;
argv [2] = NULL;
retval = add_uevent_var(env, "HOME=/");
retva=add_uevent_var(env,"PATH=/sbin:/bin:/usr/sbin:/usr/bin");
retval = call_usermodehelper(argv[0], argv,env->envp, UMH_WAIT_EXEC);
再看一下设备创建:
device_create()
device_destroy(struct class *cls, dev_t devt);
dev = class_find_device(class, NULL, &devt, __match_devt);
编译的Makefile 文件:
# 下面这个很重要,指向你的内核路径,在编译完成后,会出现Module.symvers
#在内核源码树根目录中,其中的Module.symvers文件就包含了内核所有的导出符号以及所有编译后模块的导出符号。
#在编译内核时,根目录下会生成Module.symvers文件,它包含了内核以及编译后的模块导出的所有符号。对于每一个符号,相应的CRC校验值也被保存,
#当内核编译选项CONFIG_MODVERSIONS关闭时,所有的CRC值都为0x00000000。
#Module.symvers文件主要有以下用途:
#1.列出vmlinux和所有模块的导出函数
#2.列出所有符号的CRC校验值
#若不指向,则insmod 模块时,会不成功。
- KERN_DIR = /home/yl/workplace/svn4.0/urovo-sq39/samsung_android_kernel_3.0/
- all:
- make -C $(KERN_DIR) M=`pwd` modules
- clean:
- make -C $(KERN_DIR) M=`pwd` modules clean
- rm -rf modules.order
- obj-m += first_drv.o
编译:
只需要配置好内核的交叉编译环境即可,
编译: make
清除: make clean
4、 字符设备驱动的加载、卸载、测试
编译:
- yl@yl-Lenovo:~/workplace/svn4.0/urovo-sq39/study$ make -j4
- make -C /home/yl/workplace/svn4.0/urovo-sq39/samsung_android_kernel_3.0/ M=`pwd` modules
- make[1]: Entering directory `/home/yl/workplace/svn4.0/urovo-sq39/samsung_android_kernel_3.0'
- make[1]: warning: jobserver unavailable: using -j1. Add `+' to parent make rule.
- CC [M] /home/yl/workplace/svn4.0/urovo-sq39/study/first_drv.o
- Building modules, stage 2.
- MODPOST 1 modules
- CC /home/yl/workplace/svn4.0/urovo-sq39/study/first_drv.mod.o
- LD [M] /home/yl/workplace/svn4.0/urovo-sq39/study/first_drv.ko
- make[1]: Leaving directory `/home/yl/workplace/svn4.0/urovo-sq39/samsung_android_kernel_3.0'
推送到设备的目录下,并加载设备:
获得root权限
- yl@yl-Lenovo:~/workplace/svn4.0/urovo-sq39/study$ adb root
- adbd is already running as root
加载驱动,并查看:
- yl@yl-Lenovo:~/workplace/svn4.0/urovo-sq39/study$ adb push first_drv.ko system/
- 1288 KB/s (59300 bytes in 0.044s)
- yl@yl-Lenovo:~/workplace/svn4.0/urovo-sq39/study$ adb shell
- root@android:/ # insmod system/first_drv.ko
- root@android:/ # ls dev/myhello -l
- crw------- root root 248, 0 2015-08-04 15:31 myhello
查看kernel日志,adb shell cat proc/kmsg ,会发现加载驱动时,打印的log。
这一部分,主要涉及到一些基本的命令,如adb push ,adb pull,adb root,
或 lsmod 查看系统加载的动态模块
insmod 加载模块
rmmod 删除模块
至于如何编译android文件系统下的字符设备测试程序,字符设备的高阶写法,如具体去操作某一个设备(led、按键),加入中断、并发、定时器等,留在下一编讲解,不过,字符设备的框架基本这样:
1. 分配
2. 设置
3. 注册
4. 硬件相关的代码
初入android驱动开发之字符设备(一)的更多相关文章
-
初入android驱动开发之字符设备(四-中断)
上一篇讲到android驱动开发中,应用是怎样去操作底层硬件的整个流程,实现了按键控制led的亮灭.当然,这是一个非常easy的实例,只是略微演变一下,就能够得到广泛的应用. 如开发扫描头,应用透过监 ...
-
驱动开发--【字符设备、块设备简介】【sky原创】
驱动开发 字符设备,块设备,网络设备 字符设备 以字节流的方式访问, 不能随机访问 有例外,显卡.EEPROM可以随机访问 EEPROM可以擦写1亿次,是一种字符设备,可以随机访问 读写是 ...
-
Linux驱动开发2——字符设备驱动
1.申请设备号 #include <linux/fs.h> int register_chrdev_region(dev_t first, unsigned int count, char ...
-
Linux驱动开发之字符设备模板
/***************************** ** 驱动程序模板* 版本:V1* 使用方法(末行模式下):* :%s/xxx/"你的驱动名称"/g********* ...
-
Linux驱动开发之字符设备驱动模型之file_operations
90%的驱动模型都是按照下图开发的 下面来说下设备描述结构是什么东西 打开Linux-2.6.32.2的Source Insight 工程,搜索cdev 比如一个应用程序需要调用read和write这 ...
-
Android驱动开发5-8章读书笔记
Android驱动开发读书笔记 第五章 S5PV210是一款32位处理器,具有 ...
-
Android驱动开发前的准备
最近看了一些Android驱动开发前需要知道的资料,收获很多,接下来就谈谈我自己的一些心得体会. Android在近几年时间发展迅速,已经成为智能手机操作系统的老大.不过,因为Android原生的代码 ...
-
LCD驱动分析(一)字符设备驱动框架分析
参考:S3C2440 LCD驱动(FrameBuffer)实例开发<一> S3C2440 LCD驱动(FrameBuffer)实例开发<二> LCD驱动也是字符设备驱动,也 ...
-
Android安卓书籍推荐《Android驱动开发与移植实战详解》下载
百度云下载地址:点我 Android凭借其开源性.优异的用户体验和极为方便的开发方式,赢得了广大用户和开发者的青睐,目前已经发展成为市场占有率很高的智能手机操作系统. <Android驱动开发与 ...
随机推荐
-
JSON入门教程
尽管有许多宣传关于 XML 如何拥有跨平台,跨语言的优势,然而,除非应用于 Web Services,否则,在普通的 Web 应用中,开发者经常为 XML 的解析伤透了脑筋,无论是服务器端生成或处理 ...
-
[goa]golang微服务框架学习(三)-- 使用swagger-ui展示API
既然goa框架自动生成啦swagger-json文件,那么如何用swagger-ui展示出来呢? 这里分三步: 1.下载swagger-ui的web代码 2.添加swagger.json 和 swag ...
-
[leetcode]_Merge Sorted Array
题目:合并两个有序数组A , B,将合并后的数组存到A中.假设A的空间足够装下A和B所有的元素. 思路:这道题考虑如果正向扫描两个数组,则每插入一个元素,则需移动A后的所有元素.换个角度想,既然元素个 ...
-
java 抽象类和接口
接口和内部类为我们提供了一种将接口与实现分离的更加结构化的方法. 抽象类与接口是java语言中对抽象概念进行定义的两种机制,正是由于他们的存在才赋予java强大的面向对象的能力.他们两者之间对抽象概念 ...
-
Spark Core源代码分析: Spark任务模型
概述 一个Spark的Job分为多个stage,最后一个stage会包含一个或多个ResultTask,前面的stages会包含一个或多个ShuffleMapTasks. ResultTask运行并将 ...
-
《Language Implementation Patterns》之 构建语法树
如果要解释执行或转换一段语言,那么就无法在识别语法规则的同时达到目标,只有那些简单的,比如将wiki markup转换成html的功能,可以通过一遍解析来完成,这种应用叫做 syntax-direct ...
-
.net 枚举类型转换
数字转化名称 Enum.GetName(typeof(枚举), 数字); 名称转化数字 (int)枚举
-
前后端分离springmvc和RESTful理解
1. 理解MVC MVC是一种经典的设计模式,全名为Model-View-Controller,即模型-视图-控制器. 其中,模型是用于封装数据的载体,例如,在Java中一般通过一个简单的POJO(P ...
-
iOS 新浪微博-4.0 OAuth授权
申请开发者 想要拉到到新浪微博的数据,首先让自己成为开发者.申请成为开发者账号很简单,只要有新浪微博的账号即可. 申请地址:http://open.weibo.com/ 在开发的过程中,我们需要拿到几 ...
-
034 Spark Sql的入门介绍
一:进程介绍 1.use sql 2.shark 3.spark sql 4.终止shark 5.进程线 二:spark sql细节介绍 1.hive 与sparkSql比较(以后具体学习) 2.使用 ...