1. 程式人生 > >Android應用層到Framework到HAL再到驅動層的整個流程分析

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

本篇參考老羅的例項進行總結。老羅寫六篇,層層巢狀,他告訴了我們流程,但沒有說程式設計思想,所以,即使知道怎麼做也很快會忘調,因此打算總結下每層之間是怎麼呼叫的,以加深印象。不對細節進行探討。細節可以參見老羅的blog:http://blog.csdn.net/luoshengyang/article/details/6567257

老羅的分析是從驅動到應用層的,但我想從app開發者的角度去反思這個流程,我反過來說吧。

Tips:老羅這個例子,太多hello相關的函式和類了,要區分的話,目錄是個好東西!要注意當前說的層在哪個目錄!我會把它加粗。

Tips2:封裝是理清各層關係的關鍵,除了驅動,上面的app/framework(JNI)/HAL層主要工作都是封裝。

應用層->Framwork層->HAL層

問題一.作為app開發者,如果我想呼叫硬體驅動的一個功能,我要怎麼做?

1.先按常規辦法,做好UI介面。可以IDE中除錯好。

2.在事件觸發函式裡,呼叫SystemService,獲取底層的服務,並把它轉化為aidl介面

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執行目標功能。
  public void onClick(View v) { 
    .....
    helloService.setVal(val);
    .....
  }

問題二.如果要在SDK原始碼裡測試,有什麼要注意?——Android.mk

1.在SDK裡,aidl檔案,會產生在out/target/common/obj目錄下,自己去搜吧(參照http://blog.csdn.net/xzongyuan/article/details/38119551)

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介面定義檔案:

      [email protected]:~/Android$ cd frameworks/base/core/java/android/os

      [email protected]:~/Android/frameworks/base/core/java/android/os$ vi IHelloService.aidl

IHelloService.aidl定義了IHelloService介面:

    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原始檔

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子介面,該子介面提供了一個函式,
public static com.styleflying.AIDL.forActivity asInterface(android.os.IBinder obj)  
{  
   if ((obj==null)) {  
   return null;  
}  

這個函式就是前面提到的供Activity用的asInterface了。

activity裡的使用方法如下,把具體的服務調出來了:

   helloService = IHelloService.Stub.asInterface( ServiceManager.getService("hello")); 

3.這個IHelloService物件應該放在哪裡?

定一個類,繼承它,並封裝它的函式,最後把它註冊到ServiceManager就行了

進入到frameworks/base/services/java/com/android/server目錄,新增HelloService.java檔案:

<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();  
};  
要注意,service是怎麼呼叫jni的,是通過後面三句宣告:private static native xxx();
修改同目錄的SystemServer.java檔案,在ServerThread::run函式中增加載入HelloService的程式碼:
 @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這個自定義標頭檔案中定義了:

#define HELLO_HARDWARE_MODULE_ID "hello" 
3).有了module,就呼叫這個module的open方法來獲得hw_device_t關鍵類!這個是在HAL層定義的“硬體介面結構體”)。用處後面會提到。為了使得JNI層的設計更加模組化,為該呼叫動作做一個封裝。因此有:
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介面呼叫

       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檔案

     int register_android_server_HelloService(JNIEnv *env) {  
        return jniRegisterNativeMethods(env, "com/android/server/HelloService", method_table, NELEM(method_table));  
        }  
在onload.cpp裡
      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檔案:

包含如下標頭檔案:

    #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方法:

    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檔案裡的函式就是呼叫了它的函式,並沒有作硬體操作,如下面函式:

 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。如下:

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裡面還有一個通用結構體就行了。另外它還有一個作用,下一小節會提到。

#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規範要求。

/*模組方法表*/  
static struct hw_module_methods_t hello_module_methods = {  
    open: hello_device_open  
};  
/**
 * 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結構體。如下:

    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>)。

    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能夠保證我們的模組總能被硬象抽象層載入到。

[email protected]:~/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目錄:

 [email protected]:~/Android$ cd kernel/common/drivers

 [email protected]:~/Android/kernel/common/drivers$ mkdir hello

在hello目錄中增加hello.h檔案:
    #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。

/*傳統的裝置檔案操作方法*/  
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裝置裝置操作方法裡,又做了什麼事?

/*開啟裝置方法*/  
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的結構體。它已經規定了函式的輸入引數型別,即我們要增加新的操作時候,要參考該結構體來編寫。

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