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接口定义文件:

      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接口:

    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能够保证我们的模块总能被硬象抽象层加载到。

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文件:

    #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


  • 10
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
7 Android的Audio系统 7.1 Audio系统的综述 Audio系统架构和代码路径 7.2 meida库中Audio相关接口 Audio系统的本地核心接口,类的次结构 7.3 Audio系统和上接口 Audio系统的JAVA次的接口 7.4 Audio硬件抽象 Audio系统的移植,Audio硬件抽象的实现方法 ALSA Audio HAL实现 8 Android的Video 输入输出系统 8.1 Video输入输出系统的综述 视频输入输出系统架构和代码路径 8.2 Overlay系统 视频输出系统的结构 8.3 Overlay的硬件抽象 视频输出系统的移植,抽象实现 8.4 Camera系统与上接口 Camera系统的次结构 8.5 Camera的硬件抽象 Camera系统的移植 Camera和Overlay的结合与数据传送 9 Android的多媒体系统 9.1 多媒体系统的结构 应用到底次结构 9.2 media核心库的结构 多媒体本地代码的结构 9.3 多媒体部分的上代码 照相机,媒体播放,媒体录制的框架结构 9.4 PackageVideo的架构 OpenMAX编译码插件的集成 9.5 stagefright的架构与实现(Éclair新增) 10 Android的电话系统 10.1 电话系统结构 Android电话系统的次结构 10.2 电话系统组成部分 Android电话系统的本地和JAVA程序运行流程 10.3 电话系统移植 为特定Modem移植的方法 11 Android的连接部分 11.1 WIFI无线局域网的部分 11.2 蓝牙部分 11.3 GPS部分 12 Android的传感器系统 12.1 Android传感器系统概述 12.2 Android传感器系统的次结构 12.3 Android传感器系统的硬件抽象和移植 重力加速度等传感器的移植 12.4 Android传感器系统的使用 1.HAL 技术详解 1.1 HAL 架構解析 1.2 Service与Manager的意义与用途 1.3 libhardware 与 HAL API 1.4 Stub & Module 的观念 1.5 专题讨论:定义并撰写第一个HAL Stub 2.HAL Development 2.1 HAL Stub Analysis and Design (OOAD) 2.2 HAL Stub Class 2.3 HAL Stub Interface 2.4 专题讨论:开发 LED 的 HAL 模组 3.Android API Design 3.1 Abstract Class and Interface in Android 3.2 The Reuse of Framework Design 3.3 OOAD of new APIs 3.4 Implementaion of new APIs 3.5 Singleton Pattern to Android APIs 3.6 Factory Method Pattern to Android APIs 4.Extend Android API 4.1 如何加入 API 至 Android Framework 4.2 如何编译并制作独立 JAR 档 4.3 上机实验:开发LedService API 与制作mokoid.jar 程式库 5.JNI & Runtime Library 5.1 什么是 JNI 5.2 如何撰写 JNI & Native Method 5.3 如何制作 Android Runtime Library 5.4 专题讨论:如何开发与制作Runtime Library 6.5 专题讨论:SensorManager与SensorService实例 7.SystemService 与 HAL 整合7.1 IPC、 Remote method call与Binder观念说明 7.2 AIDL 介绍与IInterface设计观念解析 7.3 Activity & ApplicationContext 7.4 ServiceManager 7.5 专题讨论:LedService设计与ILedService探讨 8.Manager API (Refinement-架构优化) 8.1 SensorManager与 SensorService实例探讨 8.2 Remote Object观念与IBinder介绍 8.3 如何以Proxy Object整合Android Service 8.4 Long operations 的解析与实作细节 8.5 RemoteException 的解析与实作细节 8.6 Handler 与 Message 的解析与实作细节 8.7 Error Handling 9.专题探讨 9.1 整合驱动程式至Android 框架 9.2 撰写Anroid应用程式以控制LED 9.3 标准的Android HAL 架构 9.4 HAL Stub OOAD & Implementation
在内容上,《Android系统源代码情景分析(含CD光盘1张)》结合使用情景,全面、深入、细致地分析Android系统的源代码,涉及到Linux内核、硬件抽象HAL)、运行时库(Runtime)、应用程序框架(Application Framework)以及应用程序(Application)。   在组织上,《Android系统源代码情景分析(含CD光盘1张)》将上述内容划分为初识Android系统、Android专用驱动系统和Android应用程序框架三大篇。初识Android系统篇介绍了参考书籍、基础知识以及实验环境搭建;Android专用驱动系统篇介绍了Logger日志驱动程序、Binder进程间通信驱动程序以及Ashmem匿名共享内存驱动程序;Android应用程序框架篇从组件、进程、消息以及安装四个维度对Android应用程序的框架进行了深入的剖析。   通过上述内容及其组织,本书能使读者既能从整体上把握Android系统的次结构,又能从细节上掌握每一个次的要点。 第1篇 初识Android系统 第1章 准备知识 第2章 硬件抽象 第3章 智能指针 第2篇 Android专用驱动系统 第4章 Logger日志系统 第5章 Binder进程间通信系统 第6章 Ashmem匿名共享内存系统 第7章 Activity组件的启动过程 第8章 Service组件的启动过程 第9章 Android系统广播机制 第10章 Content Provider组件的实现原理 第11章 Zygote和System进程的启动过程 第12章 Android应用程序进程的启动过程 第13章 Android应用程序的消息处理机制 第14章 Android应用程序的键盘消息处理机制 第15章 Android应用程序线程的消息循环模型 第16章 Android应用程序的安装和显示过程

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值