老羅的分析是從驅動到應用層的,但我想從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 ---------------------