老罗的分析是从驱动到应用层的,但我想从app开发者的角度去反思这个流程,我反过来讲吧。javascript
Tips:老罗这个例子,太多hello相关的函数和类了,要区分的话,目录是个好东西!要注意当前说的层在哪一个目录!我会把它加粗。java
Tips2:封装是理清各层关系的关键,除了驱动,上面的app/framework(JNI)/HAL层主要工做都是封装。node
应用层->Framwork层->HAL层
问题一.做为app开发者,若是我想调用硬件驱动的一个功能,我要怎么作?
1.先按常规办法,作好UI界面。能够IDE中调试好。linux
2.在事件触发函数里,调用SystemService,获取底层的服务,并把它转化为aidl接口android
[javascript] view plain copy
import android.os.IHelloService;
public class Hello extends Activity implements OnClickListener {
private IHelloService helloService = null;
.....
public void onCreat(Bundle savedInstanceState){
.....
helloService = IHelloService.Stub.asInterface( ServiceManager.getService("hello"));
.....
}
}
3.在onClick函数里调用该接口,让service执行目标功能。
[cpp] view plain copy
public void onClick(View v) {
.....
helloService.setVal(val);
.....
} app
问题二.若是要在SDK源码里测试,有什么要注意?——Android.mk
1.在SDK里,aidl文件,会产生在out/target/common/obj目录下,本身去搜吧(参照http://blog.csdn.net/xzongyuan/article/details/38119551)ide
2.若是你在Eclipse上写aidl文件,会产生在apk源码目录的gen下。所以,若是要把源码复制到SDK,要把gen目录删掉,否则这个目录会生成aidl相关的java文件,会和第一步生成的产生冲突。模块化
3.在源码目录新增长Android.mk,这样SDK编译的时候,才会把该源码编译进去。例如:能够把本身的测试代码放到:/package/experimental/hello 下,并在该目录新增Android.mk,这点能够查看兄弟目录的文件结构。函数
Android.mk的文件内容以下:测试
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_PACKAGE_NAME := Hello
include $(BUILD_PACKAGE)
问题三.要怎样设计一个IHelloService供上层调用?
1.进入到frameworks/base/core/java/android/os目录,新增IHelloService.aidl接口定义文件:
USER-NAME@MACHINE-NAME:~/Android$ cd frameworks/base/core/java/android/os
USER-NAME@MACHINE-NAME:~/Android/frameworks/base/core/java/android/os$ vi IHelloService.aidl
IHelloService.aidl定义了IHelloService接口:
[cpp] view plain copy
package android.os;
interface IHelloService {
void setVal(int val);
int getVal();
}
2.为啥必须是os文件夹下呢?android下级目录还有不少其它目录,我猜想应该均可以放进去的。只须要你改下
/framework/base下的Android.mk,可见Android.mk在SDK里面是很重要的,它是个组织文件的Makefile。例子:
返回到frameworks/base目录,打开Android.mk文件,修改LOCAL_SRC_FILES变量的值,增长IHelloService.aidl源文件
[cpp] view plain copy
LOCAL_SRC_FILES += /
....................................................................
core/java/android/os/IVibratorService.aidl /
core/java/android/os/IHelloService.aidl /
core/java/android/service/urlrenderer/IUrlRendererService.aidl /
.....................................................................
编译后,会生成IHelloService.java(就是上面提到的/out/target/common下的目录),这个文件的IHelloService接口,会实现一个Stub子接口,该子接口提供了一个函数,
[cpp] view plain copy
public static com.styleflying.AIDL.forActivity asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
这个函数就是前面提到的供Activity用的asInterface了。
activity里的使用方法以下,把具体的服务调出来了:
[cpp] view plain copy
helloService = IHelloService.Stub.asInterface( ServiceManager.getService("hello"));
3.这个IHelloService对象应该放在哪里?
定一个类,继承它,并封装它的函数,最后把它注册到ServiceManager就好了
进入到frameworks/base/services/java/com/android/server目录,新增HelloService.java文件:
[cpp] view plain copy
<span><span class="keyword">package</span><span> com.android.server; </span></span><span><span class="keyword">
import</span><span> android.content.Context; </span></span><span><span></span></span><span><span class="keyword">
import</span><span> android.os.IHelloService; </span></span><span></span><span><span class="keyword">
import</span><span> android.util.Slog;
</span></span>public class HelloService extends IHelloService.Stub {
private static final String TAG = "HelloService";
/*封装IHelloService接口的函数*/
HelloService() {
init_native();
}
public void setVal(int val) {
setVal_native(val);
}
public int getVal() {
return getVal_native();
}
private static native boolean init_native();
private static native void setVal_native(int val);
private static native int getVal_native();
};
[cpp] view plain copy
要注意,service是怎么调用jni的,是经过后面三句声明:private static native xxx();
修改同目录的SystemServer.java文件,在ServerThread::run函数中增长加载HelloService的代码:
[cpp] view plain copy
@Override
public void run() {
....................................................................................
try {
Slog.i(TAG, "DiskStats Service");
ServiceManager.addService("diskstats", new DiskStatsService(context));
} catch (Throwable e) {
Slog.e(TAG, "Failure starting DiskStats Service", e);
}
try {
Slog.i(TAG, "Hello Service");
ServiceManager.addService("hello", new HelloService());
} catch (Throwable e) {
Slog.e(TAG, "Failure starting Hello Service", e);
}
......................................................................................
}
4.JNI怎么关联JAVA和C语言?和上面的对象有什么关系?
上面提到的都是Java文件,包括SystemServer也是!JAVA到驱动,确定有个转化,JNI就负责这个转化功能,那到底是怎么实现的?其实第3点提到的HelloService.java所封装的IHelloService.Stub接口的函数,就是JNI往上层提供的函数。
这时,再回想下第1点,它提供了setVal函数,可是封装函数时,变为了setval_native。哪里作了这个转化?在JNI类里定义的,JNI类里主要作了如下事情:
1).引入HAL层的头文件
2).经过hw_get_module函数(HAL层通用函数)获取HAL层中,定义好的对应ID为(HELLO_HARDWARE_MODULE_ID)的module。
在HAL层的hardware/hello.h这个自定义头文件中定义了:
[cpp] view plain copy
#define HELLO_HARDWARE_MODULE_ID "hello"
3).有了module,就调用这个module的open方法来得到hw_device_t(关键类!这个是在HAL层定义的“硬件接口结构体”)。用处后面会提到。为了使得JNI层的设计更加模块化,为该调用动做作一个封装。所以有:
[cpp] view plain copy
static inline int hello_device_open(const hw_module_t* module, struct hello_device_t** device) {
return module->methods->open(module, HELLO_HARDWARE_MODULE_ID, (struct hw_device_t**)device);
}
这个函数做用:传入第二步得到的HAL层的module,和一个空hw_device_t结构体。调用module的method(这个method在HAL层定义,详情请看后面介绍),这样,module调用open函数,打开了hw_device_t结构体变量hello_device。以后,setVal和getVal就利用这个hello_device实现HAL层读写操做了。可见,JNI层的关键是经过module的method来初始化hw_device_t,这是其它函数的根本。
4).最后,把jni文件(cpp文件)定义的函数,做封装,建立“方法表”,以供aidl对应的java文件调用。以老罗的例子来讲就是供IHelloService.java里的Stub接口调用
[cpp] view plain copy
static const JNINativeMethod method_table[] = {
{"init_native", "()Z", (void*)hello_init},
{"setVal_native", "(I)V", (void*)hello_setVal},
{"getVal_native", "()I", (void*)hello_getVal},
};
这时,咱们能看到setVal_native了,可见,java中的setVal_native对应jni文件的hello_setVal。仅仅是一个映射,没做什么特别高深的事。
5)注册上面的方法表。怎么注册?由于是在onload.cpp进行注册,而不是直接在该类文件下注册,因此得先把jniRegisterNativeMethods封装为register_android_server_HlloService。而后把该封装放到同目录下的onload.cpp文件
[cpp] view plain copy
int register_android_server_HelloService(JNIEnv *env) {
return jniRegisterNativeMethods(env, "com/android/server/HelloService", method_table, NELEM(method_table));
}
在onload.cpp里
[cpp] view plain copy
namespace android {
..............................................................................................
int register_android_server_HelloService(JNIEnv *env);
};
//在JNI_onLoad增长register_android_server_HelloService函数调用:
extern "C" jint JNI_onLoad(JavaVM* vm, void* reserved)
{
.................................................................................................
register_android_server_HelloService(env);
.................................................................................................
}
这样,在Android系统初始化时,就会自动加载该JNI方法调用表。
6.最后在Android.mk这个组织者文件下添加说明:
修改同目录下的Android.mk文件,在LOCAL_SRC_FILES变量中增长一行:
LOCAL_SRC_FILES:= \
com_android_server_AlarmManagerService.cpp \
com_android_server_BatteryService.cpp \
com_android_server_InputManager.cpp \
。。。。。。。。。。。。。。。。。。。。。。。。。。
com_android_server_HelloService.cpp /
onload.cpp
以下面的例子:
进入到frameworks/base/services/jni目录,新建com_android_server_HelloService.cpp文件:
包含以下头文件:
[cpp] view plain copy
#define LOG_TAG "HelloService"
#include "jni.h"
#include "JNIHelp.h"
#include "android_runtime/AndroidRuntime.h"
#include <utils/misc.h>
#include <utils/Log.h>
#include <hardware/hardware.h>
#include <hardware/hello.h>
#include <stdio.h>
注意:头文件引入了HAL层定义的头文件hello.h和hardware.h。还有个AndroidRuntime.h(还没研究过,估计是jni须要的吧)
接着定义hello_init、hello_getVal和hello_setVal三个JNI方法:
[cpp] view plain copy
namespace android
{
/*在硬件抽象层中定义的硬件访问结构体,参考<hardware/hello.h>*/
struct hello_device_t* hello_device = NULL;
/*经过硬件抽象层定义的硬件访问接口设置硬件寄存器val的值*/
static void hello_setVal(JNIEnv* env, jobject clazz, jint value) {
int val = value;
LOGI("Hello JNI: set value %d to device.", val);
if(!hello_device) {
LOGI("Hello JNI: device is not open.");
return;
}
hello_device->set_val(hello_device, val);
}
/*经过硬件抽象层定义的硬件访问接口读取硬件寄存器val的值*/
static jint hello_getVal(JNIEnv* env, jobject clazz) {
int val = 0;
if(!hello_device) {
LOGI("Hello JNI: device is not open.");
return val;
}
hello_device->get_val(hello_device, &val);
LOGI("Hello JNI: get value %d from device.", val);
return val;
}
/*经过硬件抽象层定义的硬件模块打开接口打开硬件设备*/
static inline int hello_device_open(const hw_module_t* module, struct hello_device_t** device) {
return module->methods->open(module, HELLO_HARDWARE_MODULE_ID, (struct hw_device_t**)device);
}
/*经过硬件模块ID来加载指定的硬件抽象层模块并打开硬件*/
static jboolean hello_init(JNIEnv* env, jclass clazz) {
hello_module_t* module;
LOGI("Hello JNI: initializing......");
if(hw_get_module(HELLO_HARDWARE_MODULE_ID, (const struct hw_module_t**)&module) == 0) {
LOGI("Hello JNI: hello Stub found.");
if(hello_device_open(&(module->common), &hello_device) == 0) {
LOGI("Hello JNI: hello device is open.");
return 0;
}
LOGE("Hello JNI: failed to open hello device.");
return -1;
}
LOGE("Hello JNI: failed to get hello stub module.");
return -1;
}
/*JNI方法表*/
static const JNINativeMethod method_table[] = {
{"init_native", "()Z", (void*)hello_init},
{"setVal_native", "(I)V", (void*)hello_setVal},
{"getVal_native", "()I", (void*)hello_getVal},
};
/*注册JNI方法*/
int register_android_server_HelloService(JNIEnv *env) {
return jniRegisterNativeMethods(env, "com/android/server/HelloService", method_table, NELEM(method_table));
}
};
小结
至此,就打通了app和HAL的通道了。
1.hw_device_t是关键,jni文件里的函数就是调用了它的函数,并无做硬件操做,以下面函数:
[cpp] view plain copy
static void hello_setVal(JNIEnv* env, jobject clazz, jint value) {
int val = value;
...............//判断hw_device_t变量是否为空
hello_device->set_val(hello_device, val);
}
2.JNI的注册也并不难理解,就是根据语法填写方法表method_table[],而后封装一个函数,并把这个函数声明在onload.cpp中。
3.另一个关键是自定义的Service类,这个类主要是用来实现IHelloService.aidl提供的接口;要注意的是,系统会自动把IHelloService.aidl编译为IHelloSerivce.java。这个文件提供了Stub这个代理接口,因此,Service类主要是实现该java的代理接口Stub。以下:
[cpp] view plain copy
public class HelloService extends IHelloService.Stub {
private static final String TAG = "HelloService";
/*封装IHelloService接口的函数*/
HelloService() {
init_native();
}
public void setVal(int val) {
setVal_native(val);
}
public int getVal() {
return getVal_native();
}
怎么实现的呢?就是把JNI对上层声明的xxx_native函数封装在接口里。可见,Service类也只是封装,没什么特别高深的。
4.最后就是activity怎么调用HAL层的函数的问题了:先经过Stub代理类得到Service后,而后经过Service封装好的函数(调用JNI函数),实现JAVA到C的过渡。不过这一步还没达到驱动,只是到了HAL层。HAL层实际上也只是封装驱动的操做,因此,下面要讨论,HAL层是怎么“封装”驱动的。
HAL层的实现
HAL头文件分析
进入到在hardware/libhardware/include/hardware目录,新建hello.h文件
下面的Module ID,就是上一节提到的,会被JNI文件调用,用来获取对应的module实例。还记得module实例用来干嘛吧,用来open一个hw_device_t变量。下面的module和device结构体都属于HAL层的,不要和驱动混淆了,它仅仅是起封装做用(一个中介而已)。
两个结构体都有一个成员变量:common,做者这样写有什么含义?它意思应该是该HAL模块和设备结构只是在通用结构体(hw_module_t和hw_device_t)上加了一些成员变量和方法,封装了一个通用的HAL接口(其它HAL对象也会这样作),该通用结构还有不少函数,能够定义成其它名,如module或device。只要你在JNI引用HAL函数的时候,知道你调用的hello_module_t和hello_device_t里面还有一个通用结构体就好了。另外它还有一个做用,下一小节会提到。
[cpp] view plain copy
#ifndef ANDROID_HELLO_INTERFACE_H
#define ANDROID_HELLO_INTERFACE_H
#include <hardware/hardware.h>
__BEGIN_DECLS
/*定义模块ID*/
#define HELLO_HARDWARE_MODULE_ID "hello"
/*硬件模块结构体*/
struct hello_module_t {
struct hw_module_t common;
};
/*硬件接口结构体*/
struct hello_device_t {
struct hw_device_t common;
int fd; //对应咱们将要处理的设备文件"/dev/hello"
int (*set_val)(struct hello_device_t* dev, int val);
int (*get_val)(struct hello_device_t* dev, int* val);
};
__END_DECLS
#endif
让咱们看看hw_module_t有什么做用?源码解释:
/**
* Every hardware module must have a data structure named HAL_MODULE_INFO_SYM
* and the fields of this data structure must begin with hw_module_t
* followed by module specific information.
*/
typedef struct hw_module_t {
}
HAL实现文件分析:
module必须经过下面的宏HAL_MODULE_INFO_SYM来初始化通用结构体,tag也是固定的宏,这是HAL规范要求。
[cpp] view plain copy
/*模块方法表*/
static struct hw_module_methods_t hello_module_methods = {
open: hello_device_open
};
[cpp] view plain copy
/**
* Name of the hal_module_info
*/
#define HAL_MODULE_INFO_SYM HMI
/*模块实例变量*/
struct hello_module_t HAL_MODULE_INFO_SYM = {
common: {
tag: HARDWARE_MODULE_TAG,
version_major: 1,
version_minor: 0,
id: HELLO_HARDWARE_MODULE_ID,
name: MODULE_NAME,
author: MODULE_AUTHOR,
methods: &hello_module_methods, //对上层提供了函数表,只提供了open,其它函数如close/getVal/setVal都在hello_device_t的成员变量中
}
};
初始化完后,就有了name/method/author等变量值了,如今module的任务已经完成,能够无论这个变量了。
而后就是实现hello_device_open,用来填充hello_module_t里的methods成员,将会被JNI层调用。这个函数主要是填充了hello_device_t里的成员变量,包括通用的hw_device_t结构体。以下:
[cpp] view plain copy
static int hello_device_open(const struct hw_module_t* module, const char* name, struct hw_device_t** device) {
struct hello_device_t* dev;dev = (struct hello_device_t*)malloc(sizeof(struct hello_device_t));
if(!dev) {
LOGE("Hello Stub: failed to alloc space");
return -EFAULT;
}
memset(dev, 0, sizeof(struct hello_device_t));
dev->common.tag = HARDWARE_DEVICE_TAG;
dev->common.version = 0;
dev->common.module = (hw_module_t*)module;
dev->common.close = hello_device_close;
dev->set_val = hello_set_val;dev->get_val = hello_get_val;
if((dev->fd = open(DEVICE_NAME, O_RDWR)) == -1) {
LOGE("Hello Stub: failed to open /dev/hello -- %s.", strerror(errno));free(dev);
return -EFAULT;
}
*device = &(dev->common);
LOGI("Hello Stub: open /dev/hello successfully.");
return 0;
}
最后那个*device=&(dev->common)有什么做用?device是传进来的参数,是hw_device_t指针的指针,这里把hello_devict_t 的common的指针传给它。回想JNI层作了什么?它经过这个hw_device_t指针,调用HAL的函数。且慢,hw_device_t是个成员变量,它怎么调用身为兄弟成员变量的函数? 有读者已经分析了,以下:
JNI层的com_android_server_HelloService.cpp中
/*经过硬件抽象层定义的硬件模块打开接口打开硬件设备*/
static inline int hello_device_open(const hw_module_t* module, struct hello_device_t** device) {
return module->methods->open(module, HELLO_HARDWARE_MODULE_ID, (struct hw_device_t**)device);
}
因而可知*device=&(dev->common)返回的是(struct hw_device_t*),而后又传给了(struct hello_device_t*)...,问题是何不支持返回*device =dev?
后来查看了一下hardware.h,获得如下原型:
typedef struct hw_module_methods_t {
/** Open a specific device */
int (*open)(const struct hw_module_t* module, const char* id,
struct hw_device_t** device);
} hw_module_methods_t;
原来Open函数的原型中,第三个参数的类型明确指出是: struct hw_device_t** device,而不能随即是咱们定义的(struct hello_device_t**)
由于hello_device_t的第一个成员变量是common,它的类型为hw_device_t,因此能够把一个hello_device_t指针强制转换为hw_device_t指针。这种用法在linux内核中很广泛。
DEVICE_NAME定义为"/dev/hello"。因为设备文件是在内核驱动里面经过device_create建立的,而device_create建立的设备文件默认只有root用户可读写,而hello_device_open通常是由上层APP来调用的,这些APP通常不具备root权限,这时候就致使打开设备文件失败:
Hello Stub: failed to open /dev/hello -- Permission denied.
解决办法是相似于Linux的udev规则,打开Android源代码工程目录下,进入到system/core/rootdir目录,里面有一个名为ueventd.rc文件,往里面添加一行:
/dev/hello 0666 root root
HAL小结
methods->open规定了第三个参数只能是通用结构体。但JNI操做的是自定义的hello_device_t结构体的内部函数,因此把common放在第一个成员变量的好处是,二者能够互相强制转化,既保证了methods->open的通用性,又保证了JNI能经过通用结构体hw_device_t得到自定义的hello_device_t,这样,JNI就能经过通用结构体的指针调用HAL函数了。这种办法很巧妙,要好好记住。
定义hello_device_close、hello_set_val和hello_get_val这三个函数:
前面的open函数里,已经把close函数指针赋给了“common通用结构体”(hw_device_t)和setVal/getVal函数指针赋给“自定义的变量”(hello_device_t)。下面是实现方法,你们关注下这些方法是怎么实现的。
close函数传给common变量,它主要是经过hello_device_t的文件描述符fd,来执行读写操做,调用了文件操做函数close/write/read(#include <fcntl.h>
)。
[cpp] view plain copy
static int hello_device_close(struct hw_device_t* device) {
struct hello_device_t* hello_device = (struct hello_device_t*)device;
if(hello_device) {
close(hello_device->fd);
free(hello_device);
}
return 0;
}
static int hello_set_val(struct hello_device_t* dev, int val) {
LOGI("Hello Stub: set value %d to device.", val);
write(dev->fd, &val, sizeof(val));
return 0;
}
static int hello_get_val(struct hello_device_t* dev, int* val) {
if(!val) {
LOGE("Hello Stub: error val pointer");
return -EFAULT;
}
read(dev->fd, val, sizeof(*val));
LOGI("Hello Stub: get value %d from device", *val);
return 0;
}
最后,还要修改Android.mk文件
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_PRELINK_MODULE := false
LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
LOCAL_SHARED_LIBRARIES := liblog
LOCAL_SRC_FILES := hello.c
LOCAL_MODULE := hello.default
include $(BUILD_SHARED_LIBRARY)
注意,LOCAL_MODULE的定义规则,hello后面跟有default,hello.default可以保证咱们的模块总能被硬象抽象层加载到。
USER-NAME@MACHINE-NAME:~/Android$ mmm hardware/libhardware/modules/hello
编译成功后,就能够在out/target/product/generic/system/lib/hw目录下看到hello.default.so文件了。
HAL层的文件,编译后,都会在out/target下生成so文件。
怎么调用HAL生成的.so文件?
这是我猜想的:
应该是在系统启动的时候,把全部的.so文件读进来,这样,系统里有了module实例,上层(JNI)经过hw_get_module函数找到对应的module,而后进行后续的操做:先open,获得一个hello_device_t,而后调用它的成员变量实现各类底层操做。
HAL层->驱动层
这两层之间的交互,主要是经过文件节点。底层的驱动,一般会在/dev , /sys/class , /proc下生成不一样驱动的文件节点。而后HAL经过上面一节提到的write/read/open/close函数对其进行操做。还有ioctl或者其它通信机制,以前有学过udev,它使用netlink机制,而不是文件节点。
驱动编写要点
进入到kernel/common/drivers目录,新建hello目录:
USER-NAME@MACHINE-NAME:~/Android$ cd kernel/common/drivers
USER-NAME@MACHINE-NAME:~/Android/kernel/common/drivers$ mkdir hello
在hello目录中增长hello.h文件:
[cpp] view plain copy
#ifndef _HELLO_ANDROID_H_
#define _HELLO_ANDROID_H_
#include <linux/cdev.h>
#include <linux/semaphore.h>
#define HELLO_DEVICE_NODE_NAME "hello"
#define HELLO_DEVICE_FILE_NAME "hello"
#define HELLO_DEVICE_PROC_NAME "hello"
#define HELLO_DEVICE_CLASS_NAME "hello"
struct hello_android_dev {
int val;
struct semaphore sem;
struct cdev dev;
};
#endif
为了让文件节点能被fctl.h的read/write/close/open操做,必须在驱动提供file_operations。忽然感受,从上层分析到底层,比较好理解这里为啥定义了个file_operations。
[cpp] view plain copy
/*传统的设备文件操做方法*/
static int hello_open(struct inode* inode, struct file* filp);
static int hello_release(struct inode* inode, struct file* filp);
static ssize_t hello_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos);
static ssize_t hello_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos);
/*设备文件操做方法表*/
static struct file_operations hello_fops = {
.owner = THIS_MODULE,
.open = hello_open,
.release = hello_release,
.read = hello_read,
.write = hello_write,
};
这个hello_fops使用传统的设备操做方法进行初始化,那什么是不传统的呢?就是使用了统一接口。上层只须要用标准的fctl.h函数就能够操做fd对应的文件节点了,而不须要知道这个module的read函数是命名为xx_read,仍是yy_read。
那传统的hello_open设备设备操做方法里,又作了什么事?
[cpp] view plain copy
/*打开设备方法*/
static int hello_open(struct inode* inode, struct file* filp) {
struct hello_android_dev* dev;
/*将自定义设备结构体保存在文件指针的私有数据域中,以便访问设备时拿来用*/
dev = container_of(inode->i_cdev, struct hello_android_dev, dev);
filp->private_data = dev;
return 0;
}
看来,只是经过已经初始化的inode->i_cdev变量(cdev类型),初始化一个hello_android_dev类型。这个转化是怎么实现的?container_of第三个参数dev是第二个参数的成员变量,而头文件把这个dev定义为cdev,只要第一个和第三个参数同类型,就能够基于已有的第一个参数初始化第二个参数。
那么第一个参数inode->i_cdev是在哪里初始化的呢?
module_init(hello_init)->hello_init()->hello_dev = kmalloc(sizeof(struct hello_android_dev), GFP_KERNEL)->__hello_setup_dev(hello_dev)
->cdev_init(&(dev->dev), &hello_fops)-> cdev_add(&(dev->dev),devno, 1)
这样,cdev在系统初始化时就init了,并注册到字符设备列表中。
注意:cdev_init(&(dev->dev), &hello_fops)把传统操做函数传给了字符设备。
到这里,我以为仍是没说清楚什么是传统函数,我查了下file_operation的结构体。它已经规定了函数的输入参数类型,即咱们要增长新的操做时候,要参考该结构体来编写。
[cpp] view plain copy
struct file_operations {
struct module *owner;
ssize_t(*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t(*write) (struct file *, const char __user *, size_t, loff_t *);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
.............................................
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*release) (struct inode *, struct file *);
};<span></span>
最后是建立文件节点
linux提供了易用的函数,没什么难度,就不详谈了。
/*在/sys/class/目录下建立设备类别目录hello*/
class_create();
/*在/dev/目录和/sys/class/hello目录下分别建立设备文件hello*/
device_create()
/*在/sys/class/hello/hello目录下建立属性文件val*/
device_create_file()
dev_set_drvdata(temp, hello_dev);
/*建立/proc/hello文件*/
hello_create_proc();
对应的有destroy函数,具体能够参见http://blog.csdn.net/luoshengyang/article/details/6568411 ---------------------