目的
似乎作为软件开发人员来说,一旦涉及硬件,不管是从心里还是从实际操作上都总有一层厚厚的隔阂,但是要认识Android清晰层次化的认识Android系统,也得咬牙去接触,我终究在等待这么一天:其实它没有想象的那么难,它层次相当分明,有助于们整体上去认识Android系统,更便于我们对源代码的分析。
探究的位置:
学Android 的同学都看过下面一个图:
在上图中,Application层就是一个个应用程序。Framework提供一个java的运行环境以及对功能实现的封装,Runtime/ART是一个java虚拟机,通过它编译java。从Libraries那些名字也可以看出来,这里有很多高端大气库,它是功能实现区,多媒体编解码,浏览器渲染,数据库实现,and so on。Kernel部分负责交互硬件。
再看看站在 HAL的角度 重视这个架构:
内核空间中可以有特权操作硬件设备,而用户空间不行,用户空间包含了上层的一些应用层的功能模块,以及面向用户的Application。这样的分层结构实际上在保护移动设备厂商。
Hardware Abstract Layer (HAl) 硬件抽象层运行在用户空间中。
开发Android硬件驱动程序
实现内核驱动模块
目录结构
- ~/Android/kernel/goldfish-android-goldfish-3.4
- drivers
- freg
- freg.h
- freg.c
- Kconfig
- Makefile
关于命名 freg:
按照老罗的最高指示:我开发的是一个虚拟的字符硬件设备驱动程序,手里没有任何单片机或者寄存器,当然是开发虚拟的了…实体的玩意儿我也搞不了呀,这个字符硬件虚拟设备只有一个大小为4个字节的可读可写寄存器,叫做fake register,驱动程序命令为freg。
- freg
- drivers
在其目录下创建上述四个文件:1
2coffee@ubuntu:~/Android/kernel/goldfish-android-goldfish-3.4/drivers/freg$ ls
freg.c freg.h Kconfig Makefile
freg.h
1 |
|
freg.c 为驱动程序的实现文件,该程序主要实现了 向用户空间提供了三个接口来访问该虚拟设备中的寄存器val
- 接口1 proc文件系统接口
- 接口2 设备文件系统接口
- 接口3 devfs文件系统接口
freg.c
1 |
|
Kconfig
在编译freg之前,我们需要通过 make menuconfig 命令来设置这些选项,指定驱动程序freg的编译方式。
下面的配置文件有三种方式来编译:
- 建立在内核中
- 编译成内核模块
- 不编译到内核中
1 | config FREG |
Makefile
1 | obj-$(CONFIG_FREG) += freg.o |
修改Kconfig文件
我们需要修改内核的根Kconfig文件,让直行make menuconfig 的时候,让编译系统找到驱动程序freg的Kconfig文件
一般来说,各个CPU体系架构目录下的Kconifg文件都会通过source “drivers/Kconfig” 命令将drivers 目录下的Kconfig文件包含进去。
打开drivers/Kcofig文件 添加 source “drivers/freg/Kconfig”
修改内核Makefile文件
不仅是Kconfig需要让编译器找到,新增的驱动的Makefile也要让编译器找到,所以需要修改内核的Makefile文件。
drivers/Makefile下添加1
obj-$(CONFIG_FREG) += freg/
编译内核驱动程序模块
设置编译选项 make menuconfig
执行 make menuconfig 命令来配置编译方式:
Fake Register Driver 选项
- ❤ 号 表示建立在内核中
- M 表示 编译成内核模块
exit后 执行 make
error11
2In function 'freg_create_proc':
error: 'struct proc_dir_entry' has no member named 'owner'
原因:
由错误信息可以看出struct proc_dir_entry结构体中没有找到owner的成员。
看到引用的proc_fs.h头文件,发现里面的struct proc_dir_entry结构体中,是有owner成员的,Linux API 里说在2.6版本,struct proc_dir_entry:owner就已经被移除了
我也尝试注释掉owner使用的代码,跟踪到 freg.c 的create文件:
error2 :1
implicit declaration of function 'init_MUTEX' [-Werror=implicit-function-declaration]
原因:
在新版本的linux内核中,init_mutex已经被废除了,新版本使用sema_init函数。查了一下早期版本的定义:
新内核中的定义: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/*
* linux/include/asm-arm/semaphore.h
*/
struct semaphore {
atomic_t count;
int sleepers;
wait_queue_head_t wait;
};
{
.count = ATOMIC_INIT(cnt),
.wait = __WAIT_QUEUE_HEAD_INITIALIZER((name).wait),
}
struct semaphore name = __SEMAPHORE_INIT(name,count)
static inline void sema_init(struct semaphore *sem, int val)
{
atomic_set(&sem->count, val);
sem->sleepers = 0;
init_waitqueue_head(&sem->wait);
}
static inline void init_MUTEX(struct semaphore *sem)
{
sema_init(sem, 1);
}
.....
....
老内核中的定义1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16static inline void sema_init(struct semaphore *sem, int val)
{
static struct lock_class_key __key;
*sem = (struct semaphore) __SEMAPHORE_INITIALIZER(*sem, val);
lockdep_init_map(&sem->lock.dep_map, "semaphore->lock", &__key, 0);
}
extern void down(struct semaphore *sem);
extern int __must_check down_interruptible(struct semaphore *sem);
extern int __must_check down_killable(struct semaphore *sem);
extern int __must_check down_trylock(struct semaphore *sem);
extern int __must_check down_timeout(struct semaphore *sem, long jiffies);
extern void up(struct semaphore *sem);
解决办法:绕过函数 init_MUTEX ,在freg.c中直接调用:sema_init(),不过要添加一个整形参数11
2
3
4
5
6semaphore.h:
static inline void init_MUTEX(struct semaphore *sem)
{
sema_init(sem, 1);
}
freg.c 修改 init_MUTEX()调用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19//初始化设备
static int __freg_setup_dev(struct fake_reg_dev* dev) {
int err;
dev_t devno = MKDEV(freg_major, freg_minor);
memset(dev, 0, sizeof(struct fake_reg_dev));
cdev_init(&(dev->dev), &freg_fops);
dev->dev.owner = THIS_MODULE;
dev->dev.ops = &freg_fops;
err = cdev_add(&(dev->dev),devno, 1);
if(err) {
return err;
}
sema_init(&(dev->sem),1); //modify init_MUTEX()
dev->val = 0;
return 0;
}
编译成功:
内核镜像文件zImage 保存在arch/arm/boot目录下,可以用它来启动Android模拟器
验证内核驱动程序模块
分别验证 proc文件系统接口, devfs 文件系统, dev文件系统来访问虚拟寄存器
首先打开Android模拟器:
1 | emulator -kernel -3.4 arm zImage & goldfish-android-goldfish |
proc文件系统接口访问虚拟寄存器验证
1 | cd dev //验证驱动程序freg是否成功注册虚拟硬件设备到设备文件系统中 |
devfs 文件系统接口访问虚拟寄存器验证
1 | root@generic://sys/class/freg/freg # cat val |
开发C程序来验证dev文件系统访问虚拟寄存器的正确性
通过c程序来对寄存器进行读写操作:
编写源文件:
- Android/external
- freg
- freg.c
- Android.mk
- freg
freg.c:
1 |
|
freg.c 源文件的编译脚本文件:Android.mk
1
2
3
4
5
6LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE := freg
LOCAL_SRC_FILES := $(call all-subdir-c-files)
include $(BUILD_EXECUTABLE)
对于C程序来说,include后面的参数为BUILD_EXECUTABLE 可执行应用模块程序。编译结果在:
out/target/product/generic/system/bin中
编译:
1
2
3source ./build/envsetup.sh
mmm ./external/freg/
make snod
进入Android系统执行1
2
3
4emulator -kerner /Kernel/goldfish-android-goldfish-3.4/arch/arm/boot/zImage &
adb shell
cd system/bin/
./freg
当我们看到了输出为5 ,说明C程序可以执行,虚拟寄存器正常使用。
开发Android硬件抽象层模块
开发Android硬件访问服务
开发Android应用程序来使用硬件访问服务
声明: 相关内容及源代码参考老罗的《Android系统源代码情景分析》感谢老罗!