Ubuntu中为Android系统上编写Linux内核驱动程序实现方法
本文主要介绍在Ubuntu 上为Android系统编写Linux内核驱动程序, 这里对编写驱动程序做了详细的说明,对研究Android源码和HAL都有巨大的帮助,有需要的小伙伴可以参考下
在智能手机时代,每个品牌的手机都有自己的个性特点。正是依靠这种与众不同的个性来吸引用户,营造品牌凝聚力和用户忠城度,典型的代表非iphone莫属了。据统计,截止2011年5月,AppStore的应用软件数量达381062个,位居第一,而Android Market的应用软件数量达294738,紧随AppStore后面,并有望在8月份越过AppStore。随着Android系统逐步扩大市场占有率,终端设备的多样性亟需更多的移动开发人员的参与。据业内统计,Android研发人才缺口至少30万。目前,对Android人才需求一类是偏向硬件驱动的Android人才需求,一类是偏向软件应用的Android人才需求。总的来说,对有志于从事Android硬件驱动的开发工程师来说,现在是一个大展拳脚的机会。那么,就让我们一起来看看如何为Android系统编写内核驱动程序吧。
这里,我们不会为真实的硬件设备编写内核驱动程序。为了方便描述为Android系统编写内核驱动程序的过程,我们使用一个虚拟的硬件设备,这个设备只有一个4字节的寄存器,它可读可写。想起我们第一次学习程序语言时,都喜欢用“Hello, World”作为例子,这里,我们就把这个虚拟的设备命名为“hello”,而这个内核驱动程序也命名为hello驱动程序。其实,Android内核驱动程序和一般Linux内核驱动程序的编写方法是一样的,都是以Linux模块的形式实现的,具体可参考前面Android学习启动篇一文中提到的Linux Device Drivers一书。不过,这里我们还是从Android系统的角度来描述Android内核驱动程序的编写和编译过程。
一. 参照前面两篇文章Android源码 在Ubuntu上下载,编译和安装和在Android内核源码 在Ubuntu上下载,编译,安装(Linux Kernel)准备好Android内核驱动程序开发环境。
二. 进入到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文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
这个头文件定义了一些字符串常量宏,在后面我们要用到。此外,还定义了一个字符设备结构体hello_android_dev,这个就是我们虚拟的硬件设备了,val成员变量就代表设备里面的寄存器,它的类型为int,sem成员变量是一个信号量,是用同步访问寄存器val的,dev成员变量是一个内嵌的字符设备,这个Linux驱动程序自定义字符设备结构体的标准方法。
四.在hello目录中增加hello.c文件,这是驱动程序的实现部分。
驱动程序的功能主要是向上层提供访问设备的寄存器的值,包括读和写。这里,提供了三种访问设备寄存器的方法,一是通过proc文件系统来访问,二是通过传统的设备文件的方法来访问,三是通过devfs文件系统来访问。下面分段描述该驱动程序的实现。
首先是包含必要的头文件和定义三种访问设备的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
|
定义传统的设备文件访问方法,主要是定义hello_open、hello_release、hello_read和hello_write这四个打开、释放、读和写设备文件的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
|
定义通过devfs文件系统访问方法,这里把设备的寄存器val看成是设备的一个属性,通过读写这个属性来对设备进行访问,主要是实现hello_val_show和hello_val_store两个方法,同时定义了两个内部使用的访问val值的方法__hello_get_val和__hello_set_val:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
|
定义通过proc文件系统访问方法,主要实现了hello_proc_read和hello_proc_write两个方法,同时定义了在proc文件系统创建和删除文件的方法hello_create_proc和hello_remove_proc:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
|
最后,定义模块加载和卸载方法,这里只要是执行设备注册和初始化操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
|
五.在hello目录中新增Kconfig和Makefile两个文件,其中Kconfig是在编译前执行配置命令make menuconfig时用到的,而Makefile是执行编译命令make是用到的:
Kconfig文件的内容
config HELLO
tristate "First Android Driver"
default n
help
This is the first android driver.
Makefile文件的内容
obj-$(CONFIG_HELLO) += hello.o
在Kconfig文件中,tristate表示编译选项HELLO支持在编译内核时,hello模块支持以模块、内建和不编译三种编译方法,默认是不编译,因此,在编译内核前,我们还需要执行make menuconfig命令来配置编译选项,使得hello可以以模块或者内建的方法进行编译。
在Makefile文件中,根据选项HELLO的值,执行不同的编译方法。
六. 修改arch/arm/Kconfig和drivers/kconfig两个文件,在menu "Device Drivers"和endmenu之间添加一行:
source "drivers/hello/Kconfig"
这样,执行make menuconfig时,就可以配置hello模块的编译选项了。
七. 修改drivers/Makefile文件,添加一行:
obj-$(CONFIG_HELLO) += hello/
八. 配置编译选项:
USER-NAME@MACHINE-NAME:~/Android/kernel/common$ make menuconfig
找到"Device Drivers" => "First Android Drivers"选项,设置为y。
注意,如果内核不支持动态加载模块,这里不能选择m,虽然我们在Kconfig文件中配置了HELLO选项为tristate。要支持动态加载模块选项,必须要在配置菜单中选择Enable loadable module support选项;在支持动态卸载模块选项,必须要在Enable loadable module support菜单项中,选择Module unloading选项。
九. 编译:
USER-NAME@MACHINE-NAME:~/Android/kernel/common$ make
编译成功后,就可以在hello目录下看到hello.o文件了,这时候编译出来的zImage已经包含了hello驱动。
十. 参照在Ubuntu上下载、编译和安装Android最新内核源代码(Linux Kernel)一文所示,运行新编译的内核文件,验证hello驱动程序是否已经正常安装:
USER-NAME@MACHINE-NAME:~/Android$ emulator -kernel ./kernel/common/arch/arm/boot/zImage &
USER-NAME@MACHINE-NAME:~/Android$ adb shell
进入到dev目录,可以看到hello设备文件:
root@android:/ # cd dev
root@android:/dev # ls
进入到proc目录,可以看到hello文件:
root@android:/ # cd proc
root@android:/proc # ls
访问hello文件的值:
root@android:/proc # cat hello
0
root@android:/proc # echo '5' > hello
root@android:/proc # cat hello
5
进入到sys/class目录,可以看到hello目录:
root@android:/ # cd sys/class
root@android:/sys/class # ls
进入到hello目录,可以看到hello目录:
root@android:/sys/class # cd hello
root@android:/sys/class/hello # ls
进入到下一层hello目录,可以看到val文件:
root@android:/sys/class/hello # cd hello
root@android:/sys/class/hello/hello # ls
访问属性文件val的值:
root@android:/sys/class/hello/hello # cat val
5
root@android:/sys/class/hello/hello # echo '0' > val
root@android:/sys/class/hello/hello # cat val
0
至此,我们的hello内核驱动程序就完成了,并且验证一切正常。这里我们采用的是系统提供的方法和驱动程序进行交互,也就是通过proc文件系统和devfs文件系统的方法,下一篇文章中,我们将通过自己编译的C语言程序来访问/dev/hello文件来和hello驱动程序交互,敬请期待。