Android應用層到Framework到HAL再到驅動層的整個流程分析

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

相關文章
相關標籤/搜索