随笔

记录,分享,然后享受生活。

0%

自下而上的了解Android

前言

开始时间(2021年9月11日9点50分 )
结束时间(2021年9月19日16点40分)
注意空间要留够,最好留500G空间用来存放AOSP的源代码。(笔者在第一次实验时只留了300G,结果做到最后发现实验空间不够了,临时切换到deepin系统使用gparted工具扩容到了600G以上)

该记录是为了自下而上的了解安卓程序运行的整个流程,参考的文档大部分都放在了本记录的参考内容之中,由于网络上的教程比较分散且部分内容和使用的源码版本不一致,故在此整理,以便日后复习使用。

环境准备(基于ubuntu16.04)

下载Android源代码

使用清华源下载源码

下载源码时可能需要把https换成http

1
repo init -u http://aosp.tuna.tsinghua.edu.cn/platform/manifest -b android-7.0.0_r28

另外如果碰到python环境有问题,可以重新安装python环境,之后就能执行repo脚本了

Python安装报错:”ModuleNotFoundError:No module named _ctypes“ 的解决方案

若担心网络不好中途断开链接,可以用一个shell脚本执行repo.sh

1
2
3
4
5
6
7
8
9
10
#!/bin/bash

repo sync -j8

while [ $? = 1 ]
do
echo "======sync failed ,re-sync again======"
sleep 3
repo sync -j8
done

编译Android源代码

安装openjdk 1.8

Ubuntu 16.04安装Java JDK

1
2
sudo apt-get update
sudo apt-get install openjdk-8-jdk

安装依赖库

1
2
3
4
5
6
7
sudo apt-get install libx11-dev:i386 libreadline6-dev:i386 libgl1-mesa-dev
g++-multilib git flex bison gperf build-essential libncurses5-dev:i386
tofrodos python-markdown libxml2-utils xsltproc zlib1g-dev:i386 dpkg-dev
libsdl1.2-dev libesd0-dev git-core gnupg flex bison gperf build-essential
zip curl zlib1g-dev gcc-multilib g++-multilib libc6-dev-i386
lib32ncurses5-dev x11proto-core-dev libx11-dev lib32z-dev
ccache libgl1-mesa-dev libxml2-utils xsltproc unzip m4

编译AOSP项目的源代码

由于最后编写应用程序验证驱动时会碰到 selinux 的问题,在查阅了相关资料后修改 system/core/init/init.cpp 的一行代码,关闭安全选项 SELINUX_ENFORCING

1
2
3
4
5
6
7
8
9
static selinux_enforcing_status selinux_status_from_cmdline() {
//selinux_enforcing_status status = SELINUX_ENFORCING;
selinux_enforcing_status status = SELINUX_PERMISSIVE;

import_kernel_cmdline(false, [&](const std::string& key, const std::string& value, bool in_qemu) {
if (key == "androidboot.selinux" && value == "permissive") {
status = SELINUX_PERMISSIVE;
}
});

开始编译源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 进入AOSP项目根目录
cd android

#设置环境变量
source build/envsetup.sh

#选择平台 选择2 2.aosp_arm64-eng
lunch 2

# 执行编译
make -j8

# 编译SDK
make sdk -j8

# 执行android启动sdk manager下载项目相关工具
android

下载好后通过 android list targets 命令来查看当前系统中可以创建哪些平台的虚拟设备,在我的系统下,这条命令的执行结果如下:

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
Available Android targets:
----------
id: 1 or "android-24"
Name: Android 7.0
Type: Platform
API level: 24
Revision: 2
Skins: HVGA, QVGA, WQVGA400, WQVGA432, WSVGA, WVGA800 (default), WVGA854, WXGA720, WXGA800, WXGA800-7in
Tag/ABIs : default/armeabi-v7a
----------
id: 2 or "Google Inc.:Google APIs:24"
Name: Google APIs
Type: Add-On
Vendor: Google Inc.
Revision: 1
Description: Android + Google APIs
Based on Android 7.0 (API level 24)
Libraries:
* com.android.future.usb.accessory (usb.jar)
API for USB Accessories
* com.google.android.media.effects (effects.jar)
Collection of video effects
* com.google.android.maps (maps.jar)
API for Google Maps
Skins: HVGA, QVGA, WQVGA400, WQVGA432, WSVGA, WVGA800 (default), WVGA854, WXGA720, WXGA800, WXGA800-7in
Tag/ABIs : no ABIs.

接下来创建设备并运行模拟器

1
2
3
4
5
#创建一个Android 7.0的虚拟设备。创建的命令如下:
android create avd -n helloandroid -t 1

# 通过 emulator -help 查看说明,然后运行安卓模拟器
emulator -show-kernel -avd helloandroid -partition-size 4096 -memory 2048 -cache-size 2048

之后运行安卓模拟器需要先设置好环境变量和平台

1
2
3
4
5
6
#设置环境变量
source build/envsetup.sh
#选择平台 与之前编译时的选择要相同
lunch 2
# 运行安卓模拟器
emulator -show-kernel -avd helloandroid -partition-size 4096 -memory 2048 -cache-size 2048

下载、编译与安装android内核源代码(linux kernel)

下载 Android kernel 代码

先设置代理,之后才能下载内核源码

1
2
3
4
5
6
7
8
9
10
11
#设置代理
git config --global http.proxy IPaddress:port
git config --global https.proxy IPaddress:port

#内核的下载花了点时间,执行命令后,可以去做其他事了
mkdir kernel && cd kernel
git clone https://android.googlesource.com/kernel/goldfish

#取消代理
git config --global --unset http.proxy
git config --global --unset https.proxy

下载好后进行内核版本选择,这里可以打开模拟器去看看预编译好的内核版本,然后再进行选择,笔者这里的安卓模拟器使用的Android版本是7.0内核版本显示3.10

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 进入goldfish版本内核
cd goldfish

# 查看有哪些分支
git branch -a

# 选择与预编译一致的linux内核版本的分支
git checkout remotes/origin/android-goldfish-3.10
git branch arm-3.10
git checkout arm-3.10

#设置环境变量
source build/envsetup.sh
#选择平台 与之前编译源码时的选择要相同
lunch 2

# 使用安卓项目中的编译脚本来编译内核
../../prebuilts/qemu-kernel/build-kernel.sh

或者导入环境变量再编译(后面编译驱动的时候就用这种方式)

1
2
3
4
5
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-android-
#如果无法使用make goldfish 可以直接cp arch/arm/configs/某个匹配的配置文件 .config
make goldfish
make -j8

运行安卓模拟器(使用编译好的内核)

1
2
3
4
5
6
#设置环境变量
source build/envsetup.sh
#选择平台
lunch 2
# 运行安卓模拟器,指定编译好的文件
emulator -show-kernel -avd helloandroid -kernel ../kernel/goldfish/arch/arm64/boot/Image -system ./out/target/product/generic_arm64/system.img -data ./out/target/product/generic_arm64/userdata.img -ramdisk ./out/target/product/generic_arm64/ramdisk.img

编写Linux内核驱动程序

新编译的linux内核是3.10版本,代码变动可以在drivers目录中找例子

编写代码

1
2
3
4
#从 Android kernel 根目录进入drivers,并创建相关文件夹和文件
cd drivers
mkdir hello && cd hello
touch hello.h hello.c Makefile Kconfig

各代码文件内容如下:

hello.h

hello.hview raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#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 cdev cdev*/
struct hello_android_dev
{
int val;
struct semaphore sem;
struct cdev dev;
};

#endif

hello.c

hello.cview raw
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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>

#include <linux/cdev.h>
#include <linux/semaphore.h>

//drivers/hello/hello.c:303:5: error: implicit declaration of function 'kmalloc' [-Werror=implicit-function-declaration]
#include <linux/slab.h>
#include <linux/seq_file.h>

#include "hello.h"

//主设备和从设备号变量
static int hello_major = 0;
static int hello_minor = 0;

//设备类别和设备变量
static struct class* hello_class = NULL;
static struct hello_android_dev* hello_dev = NULL;

//访问设置属性的方法
static ssize_t hello_val_show(struct device* dev, struct device_attribute* attr, char* buf);
static ssize_t hello_val_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t count);

//定义设备属性
static DEVICE_ATTR(val, S_IRUGO | S_IWUSR, hello_val_show, hello_val_store);

//传统设备文件操作方法
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,
};

//打开设备方法
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;
}

//设备文件释放时调用,空实现
static int hello_release(struct inode* inode, struct file* filp)
{
return 0;
}

//读取设备的寄存器val的值
static ssize_t hello_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos)
{
printk("hello_read()\n");

ssize_t err = 0;
struct hello_android_dev* dev = filp->private_data;

//同步访问
if(down_interruptible(&(dev->sem)))
{
return -ERESTARTSYS;
}

if(count < sizeof(dev->val))
{
goto out;
}

//将寄存器val的值拷贝到用户提供的缓冲区
if(copy_to_user(buf, &(dev->val), sizeof(dev->val)))
{
err = -EFAULT;
goto out;
}

err = sizeof(dev->val);

out:
up(&(dev->sem));
return err;
}

//写设备寄存器的值val
static ssize_t hello_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos)
{
struct hello_android_dev* dev = filp->private_data;
ssize_t err = 0;

printk("hello_write()\n");

//同步访问
if(down_interruptible(&(dev->sem))) {
return -ERESTARTSYS;
}

if(count != sizeof(dev->val)) {
goto out;
}

//将用户提供的缓冲区的值写到设备寄存器去
if(copy_from_user(&(dev->val), buf, count)) {
err = -EFAULT;
goto out;
}
err = sizeof(dev->val);
out:
up(&(dev->sem));
return err;
}

//读取寄存器val的值到缓冲区的buf中,内部使用
static ssize_t __hello_get_val(struct hello_android_dev* dev, char* buf)
{
printk("__hello_get_val()\n");

int val = 0;

//同步访问
struct semaphore *p_sem = &(dev->sem);
if(down_interruptible(p_sem))
{
printk("sem is used,pls wait to free.\n");
return -ERESTARTSYS;
}

val = dev->val;
up(p_sem);

printk("get val: %d\n",val);

return snprintf(buf, PAGE_SIZE, "%d\n", val);
}

//把缓冲区buf的值写到设备寄存器val中去,内部使用
static ssize_t __hello_set_val(struct hello_android_dev* dev, const char* buf, size_t count)
{
printk("__hello_set_val()\n");

int val = 0;

//将字符串转换成数字
val = simple_strtol(buf, NULL, 10);

printk("set val: %d\n",val);

//同步访问
if(down_interruptible(&(dev->sem)))
{
return -ERESTARTSYS;
}

dev->val = val;
up(&(dev->sem));

return count;
}

//读取设备属性
static ssize_t hello_val_show(struct device* dev, struct device_attribute* attr, char* buf)
{
printk("hello_val_show()\n");

struct hello_android_dev* hdev = (struct hello_android_dev*)dev_get_drvdata(dev);

return __hello_get_val(hdev, buf);
}

//写设备属性
static ssize_t hello_val_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t count)
{
printk("hello_val_store()\n");

struct hello_android_dev* hdev = (struct hello_android_dev*)dev_get_drvdata(dev);

return __hello_set_val(hdev, buf, count);
}

static int hello_proc_show(struct seq_file *m, void *v)
{
seq_printf(m, "%d\n", hello_dev->val);

return 0;
}


static int hello_proc_open(struct inode *inode, struct file *filp)
{
return single_open(filp, hello_proc_show, NULL);
}

//读取设备寄存器val的值,保存在page缓冲区中
static ssize_t hello_proc_read(char* page, char** start, off_t off, int count, int* eof, void* data)
{
printk("hello_proc_read()\n");

if(off > 0)
{
*eof = 1;
return 0;
}
return __hello_get_val(hello_dev, page);
}

//把缓冲区的值buff保存到设备寄存器val中去
static ssize_t hello_proc_write(struct file* filp, const char __user *buff, unsigned long len, void* data)
{
printk("hello_proc_write()\n");

int err = 0;
char* page = NULL;

if(len > PAGE_SIZE) {
printk(KERN_ALERT"The buff is too large: %lu./n", len);
return -EFAULT;
}

page = (char*)__get_free_page(GFP_KERNEL);
if(!page) {
printk(KERN_ALERT"Failed to alloc page./n");
return -ENOMEM;
}

//先把用户提供的缓冲区值拷贝到内核缓冲区中去
if(copy_from_user(page, buff, len)) {
printk(KERN_ALERT"Failed to copy buff from user./n");
err = -EFAULT;
goto out;
}
err = __hello_set_val(hello_dev, page, len);

out:
free_page((unsigned long)page);
return err;
}

//删除/proc/hello文件
static void hello_remove_proc(void)
{
remove_proc_entry(HELLO_DEVICE_PROC_NAME, NULL);
}

static const struct file_operations hello_proc_fops = {
.owner = THIS_MODULE,
.open = hello_proc_open,
.write = hello_proc_write,
.read = seq_read,
.llseek = seq_lseek,
.release= single_release,
};

//创建/proc/hello文件
static void hello_create_proc(void)
{
// struct proc_dir_entry* entry;
//
// entry = create_proc_entry(HELLO_DEVICE_PROC_NAME, 0, NULL);
// if(entry)
// {
// //entry->owner = THIS_MODULE; entry has no "owner" member
// entry->read_proc = hello_proc_read;
// entry->write_proc = hello_proc_write;
// }
proc_create(HELLO_DEVICE_PROC_NAME, 0, NULL, &hello_proc_fops);
}


//初始化设备
static int __hello_setup_dev(struct hello_android_dev* dev)
{
int err;
dev_t devno = MKDEV(hello_major, hello_minor);

printk("__hello_setup_dev()\n");

memset(dev, 0, sizeof(struct hello_android_dev));

cdev_init(&(dev->dev), &hello_fops);
dev->dev.owner = THIS_MODULE;
dev->dev.ops = &hello_fops;

//注册字符设备
err = cdev_add(&(dev->dev),devno, 1);
if(err)
{
return err;
}

//初始化信号量和寄存器val的值
//init_MUTEX(&(dev->sem)); 2.6.25及以后的linux内核版本废除了init_MUTEX函数
sema_init(&(dev->sem),1);
dev->val = 0;
return 0;
}

//模块加载方法
static int __init hello_init(void)
{
int err = -1;
dev_t dev = 0;
struct device* temp = NULL;

printk(KERN_ALERT"Initializing hello device./n");
/*动态分配主设备和从设备号*/
err = alloc_chrdev_region(&dev, 0, 1, HELLO_DEVICE_NODE_NAME);
if(err < 0) {
printk(KERN_ALERT"Failed to alloc char dev region./n");
goto fail;
}

hello_major = MAJOR(dev);
hello_minor = MINOR(dev);

//分配hello设备结构体变量
hello_dev = kmalloc(sizeof(struct hello_android_dev), GFP_KERNEL);
if(!hello_dev) {
err = -ENOMEM;
printk(KERN_ALERT"Failed to alloc hello_dev./n");
goto unregister;
}

//初始化设备
err = __hello_setup_dev(hello_dev);
if(err) {
printk(KERN_ALERT"Failed to setup dev: %d./n", err);
goto cleanup;
}

//在/sys/class/目录下创建设备类别目录hello
hello_class = class_create(THIS_MODULE, HELLO_DEVICE_CLASS_NAME);
if(IS_ERR(hello_class)) {
err = PTR_ERR(hello_class);
printk(KERN_ALERT"Failed to create hello class./n");
goto destroy_cdev;
}

//在/dev目录和/sys/class/hello目录下分别创建设备文件hello
temp = device_create(hello_class, NULL, dev, "%s", HELLO_DEVICE_FILE_NAME);
if(IS_ERR(temp)) {
err = PTR_ERR(temp);
printk(KERN_ALERT"Failed to create hello device.");
goto destroy_class;
}

//在/sys/class/hello/hello目录下创建属性文件val
err = device_create_file(temp, &dev_attr_val);
if(err < 0) {
printk(KERN_ALERT"Failed to create attribute val.");
goto destroy_device;
}

dev_set_drvdata(temp, hello_dev);

//创建/proc/hello文件
hello_create_proc();
printk(KERN_ALERT"Succedded to initialize hello device./n");
return 0;

destroy_device:
device_destroy(hello_class, dev);
destroy_class:
class_destroy(hello_class);

destroy_cdev:
cdev_del(&(hello_dev->dev));
cleanup:
kfree(hello_dev);

unregister:
unregister_chrdev_region(MKDEV(hello_major, hello_minor), 1);

fail:
return err;
}

//模块卸载方法
static void __exit hello_exit(void)
{
dev_t devno = MKDEV(hello_major, hello_minor);

printk(KERN_ALERT"Destroy hello device./n");

//删除/proc/hello文件
hello_remove_proc();

//销毁设备类别和设备
if(hello_class) {
device_destroy(hello_class, MKDEV(hello_major, hello_minor));
class_destroy(hello_class);
}

//删除字符设备和释放设备内存
if(hello_dev) {
cdev_del(&(hello_dev->dev));
kfree(hello_dev);
}

//释放设备号
unregister_chrdev_region(devno, 1);
}

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("First Android Driver");

module_init(hello_init);
module_exit(hello_exit);

makefile

Makefileview raw
1
obj-$(CONFIG_HELLO)    += hello.o

Kconfig

Kconfigview raw
1
2
3
4
5
config HELLO
tristate "Frist Android Driver"
default y
help
This is the first android driver.

接着在 kernel/drivers/Makefile 中添加如下代码:

1
obj-$(CONFIG_HELLO) += hello/

继续在 kernel/drivers/Kconfig 中添加如下代码:

1
source "drivers/hello/Kconfig"

其中配置Kconfig是为了在menuconfig中生成菜单项,如果不想在配置菜单中显示,可以直接将 $(CONFIG_HELLO) 替换成 y 将Kconfig文件删除即可,也不必在上层目录的Kconfig中添加代码。

重新编译内核

1
2
3
4
5
6
#在kernel/goldfish 目录执行以下命令进行内核编译
cp arch/arm/configs/ranchu_defconfig .config
#打开内核编译配置选项 确认 Device Drivers --> [*]Frist Android Driver 是开启的
make menuconfig
#执行编译
make -j8

使用编译好的内核启动模拟器,再用 adb shell 进入安卓模拟器的终端界面,运行以下命令进行验证

1
2
# 运行安卓模拟器,指定编译好的文件
emulator -show-kernel -avd helloandroid -kernel ../kernel/goldfish/arch/arm64/boot/Image -system ./out/target/product/generic_arm64/system.img -data ./out/target/product/generic_arm64/userdata.img -ramdisk ./out/target/product/generic_arm64/ramdisk.img

这个时候也可以直接在执行模拟器命令的终端上按回车键即可输入命令进行驱动的验证。

下面是使用 adb shell 的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
nicho@PC:~/work/android$ adb shell
generic_arm64:/ # cd dev
generic_arm64:/dev # ls hello
hello
generic_arm64:/dev # cd /proc
generic_arm64:/proc # ls hello
hello
generic_arm64:/proc # cat hello
0
generic_arm64:/proc # echo 9 > hello
generic_arm64:/proc # cat hello
9
generic_arm64:/proc # cd /sys/class/hello/hello/
generic_arm64:/sys/class/hello/hello # cat val
9
generic_arm64:/sys/class/hello/hello # echo 0 > val
generic_arm64:/sys/class/hello/hello # cat val
0

编写C可执行程序测试Linux内核驱动程序

编写代码

1
2
3
4
#从 AOSP 根目录进入external,并创建相关文件夹和文件
cd external
mkdir hello && cd hello
touch hello.c Android.mk

各文件代码内容如下:

hello.c

hello.cview raw
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
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#define DEVICE_NAME "/dev/hello"

int main(int argc, char** argv)
{
int fd = -1;
int val = 0;

fd = open(DEVICE_NAME, O_RDWR);
if(fd == -1)
{
printf("Failed to open device %s.\n", DEVICE_NAME);
return -1;
}

printf("read original value:\n");
read(fd, &val, sizeof(val));
printf("%d.\n\n", val);

val = 5;
printf("Write value %d to %s.\n\n", val, DEVICE_NAME);
write(fd, &val, sizeof(val));

printf("Read the value again:\n");
read(fd, &val, sizeof(val));
printf("%d.\n\n",val);

close(fd);
return 0;
}

Android.mk

Android.mkview raw
1
2
3
4
5
6
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE := hello
LOCAL_SRC_FILES := $(call all-subdir-c-files)
include $(BUILD_EXECUTABLE)

编译hello程序

1
2
3
4
#在AOSP根目录执行以下命令进行编译
mmm ./external/hello
#重新生成system.bin文件
make snod

最后重新启动模拟器即可直接在 adb shell 中执行 hello 命令验证

1
2
3
4
5
6
7
8
9
nicho@PC:~/work/android$ adb shell
generic_arm64:/ # hello
read original value:
0.

Write value 5 to /dev/hello.

Read the value again:
5.

增加硬件抽象层(HAL)模块访问Linux内核驱动程序

android/hardware/libhardware/include/hardware 目录中创建 hello.h 文件。

内容如下

hello.hview raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#ifndef Android_HELLO_INTERFACE_H
#define ANDROID_HELLO_INTERFACE_H
#include <hardware/hardware.h>

__BEGIN_DECLS

#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;
int (*set_val)(struct hello_device_t* dev, int val);
int (*get_val)(struct hello_device_t* dev, int* val);
};

__END_DECLS


#endif

这里按照Android硬件抽象层规范的要求,分别定义模块ID、模块结构体以及硬件接口结构体。在硬件接口结构体中,fd表示设备文件描述符,对应我们将要处理的设备文件”/dev/hello”,set_val和get_val为该HAL对上提供的函数接口。

android/hardware/libhardware/modules/hello 目录中创建 hello.cAndroid.mk 文件。

hello 目录需要手动创建

各文件内容如下

hello.cview raw
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
#include <hardware/hardware.h>
#include <hardware/hello.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <cutils/log.h>
#include <cutils/atomic.h>
#include <android/log.h>

#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,"hello_stub",__VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,"hello_stub",__VA_ARGS__)

#define LOG_TAG "HelloStub"

#define DEVICE_NAME "/dev/hello"
#define MODULE_NAME "hello"
#define MODULE_AUTHOR "y041039@gmail.com"

/*设备打开和关闭接口*/
static int hello_device_open(const struct hw_module_t* module, const char* name, struct hw_device_t** device);
static int hello_device_close(struct hw_device_t* device);

/*设备访问接口*/
static int hello_set_val(struct hello_device_t* dev, int val);
static int hello_get_val(struct hello_device_t* dev, int* val);

/*模块方法表*/
static struct hw_module_methods_t hello_module_methods = {
open: hello_device_open
};

/*模块实例变量*/
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,
}
};
/* 这里,实例变量名必须为HAL_MODULE_INFO_SYM,tag 也必须为HARDWARE_MODULE_TAG,
这是Android 硬件抽象层规范规定的 */


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_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 目录,里面有一个名为
uevent.rc 文件,往里面添加一行:
/dev/hello 0666 root root

*/


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.mkview raw
1
2
3
4
5
6
7
8
9
10
11
12
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_LDLIBS := -llog

LOCAL_MODULE_TAGS := optional
LOCAL_PRELINK_MODULE := false
LOCAL_MODULE_RELATIVE_PATH := hw
LOCAL_SHARED_LIBRARIES := liblog
LOCAL_SRC_FILES := hello.c
LOCAL_MODULE := hello.default
include $(BUILD_SHARED_LIBRARY)

最后记得在 android/system/core/rootdir/uevent.rc 文件中添加一行,使应用程序有权限访问设备节点

1
/dev/hello 0666 root root

开始编译并重新打包系统镜像

1
2
3
#回到源码根目录执行
mmm hardware/libhardware/moudles/hello
make snod

可以在 out/target/product/generic_arm64/system/lib/hw/ 目录中看到有 hello.default.so 生成。这时的 system.img 就包含我们定义的硬件抽象层模块 hello.default 了。虽然我们在Android系统为我们自己的硬件增加了一个硬件抽象层模块,但是现在Java应用程序还不能访问到我们的硬件。还必须编写JNI方法和在Android的Application Frameworks层增加API接口,才能让上层Application访问到硬件。

编写JNI 方法在应用程序框架层提供Java 接口访问硬件

进入 android/frameworks/base/services/core/jni 目录创建 com_android_server_HelloService.cpp 文件

在com_android_server_HelloService.cpp文件中,实现JNI方法。注意文件的命令方法,com_android_server前缀表示的是包名,表示硬件服务HelloService是放在frameworks/base/services/java目录下的com/android/server目录的,即存在一个命令为com.android.server.HelloService的类。这里,我们暂时略去HelloService类的描述,在下一篇文章中,我们将回到HelloService类来。简单地说,HelloService是一个提供Java接口的硬件访问服务类。内容如下

com_android_server_HelloService.cppview raw
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
#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>

#include <android/log.h>

#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,"hello_stub",__VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,"hello_stub",__VA_ARGS__)

//#define LOG_TAG "HelloService"

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));
}

};

修改同目录的 onload.cpp 文件

onload.cppview raw
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
#include "JNIHelp.h"
#include "jni.h"
#include "utils/Log.h"
#include "utils/misc.h"

namespace android {
int register_android_server_HelloService(JNIEnv* env); //新增加的一行
int register_android_server_ActivityManagerService(JNIEnv* env);
int register_android_server_AlarmManagerService(JNIEnv* env);
int register_android_server_AssetAtlasService(JNIEnv* env);
int register_android_server_BatteryStatsService(JNIEnv* env);
int register_android_server_ConsumerIrService(JNIEnv *env);
int register_android_server_InputApplicationHandle(JNIEnv* env);
int register_android_server_InputWindowHandle(JNIEnv* env);
int register_android_server_InputManager(JNIEnv* env);
int register_android_server_LightsService(JNIEnv* env);
int register_android_server_PowerManagerService(JNIEnv* env);
int register_android_server_SerialService(JNIEnv* env);
int register_android_server_SystemServer(JNIEnv* env);
int register_android_server_UsbDeviceManager(JNIEnv* env);
int register_android_server_UsbMidiDevice(JNIEnv* env);
int register_android_server_UsbHostManager(JNIEnv* env);
int register_android_server_vr_VrManagerService(JNIEnv* env);
int register_android_server_VibratorService(JNIEnv* env);
int register_android_server_location_GnssLocationProvider(JNIEnv* env);
int register_android_server_location_FlpHardwareProvider(JNIEnv* env);
int register_android_server_connectivity_Vpn(JNIEnv* env);
int register_android_server_hdmi_HdmiCecController(JNIEnv* env);
int register_android_server_tv_TvUinputBridge(JNIEnv* env);
int register_android_server_tv_TvInputHal(JNIEnv* env);
int register_android_server_PersistentDataBlockService(JNIEnv* env);
int register_android_server_Watchdog(JNIEnv* env);
int register_android_server_HardwarePropertiesManagerService(JNIEnv* env);
};

using namespace android;

extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
{
JNIEnv* env = NULL;
jint result = -1;

if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
ALOGE("GetEnv failed!");
return result;
}
ALOG_ASSERT(env, "Could not retrieve the env!");

register_android_server_HelloService(env); //新增加的一行
register_android_server_ActivityManagerService(env);
register_android_server_PowerManagerService(env);
register_android_server_SerialService(env);
register_android_server_InputApplicationHandle(env);
register_android_server_InputWindowHandle(env);

修改同目录下的 Android.mk 文件

Android.mkview raw
1
2
3
4
5
6
7
8
9
10
11
12
13
ifneq ($(ENABLE_CPUSETS),)
ifneq ($(ENABLE_SCHED_BOOST),)
LOCAL_CFLAGS += -DUSE_SCHED_BOOST
endif
endif

LOCAL_SRC_FILES += \
$(LOCAL_REL_DIR)/com_android_server_HelloService.cpp \ #新添加的一行
$(LOCAL_REL_DIR)/com_android_server_AlarmManagerService.cpp \
$(LOCAL_REL_DIR)/com_android_server_am_BatteryStatsService.cpp \
$(LOCAL_REL_DIR)/com_android_server_am_ActivityManagerService.cpp \
$(LOCAL_REL_DIR)/com_android_server_AssetAtlasService.cpp \
$(LOCAL_REL_DIR)/com_android_server_connectivity_Vpn.cpp \

开始编译并重新打包系统镜像

1
2
3
#回到源码根目录执行
mmm frameworks/base/services/core/jni
make snod

这样,重新打包的system.img镜像文件就包含刚才编写的JNI方法,也就是可以通过Android系统的Application Frameworks层提供的硬件服务HelloService来调用这些JNI方法,进而调用低层的硬件抽象层接口去访问硬件。

在应用程序框架层增加硬件服务接口

首先定义通信接口

1
2
nicho@PC:~/work/android$ cd frameworks/base/core/java/android/os/
nicho@PC:~/work/android/frameworks/base/core/java/android/os$ vi IHelloService.aidl
IHelloService.aidlview raw
1
2
3
4
5
6
7
package android.os;

interface IHelloService
{
void setVal(int val);
int getVal();
}

修改 frameworks/base目录,打开Android.mk文件,修改LOCAL_SRC_FILES变量的值,增加IHelloService.aidl源文件

Android.mkview raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
## READ ME: ########################################################
##
## When updating this list of aidl files, consider if that aidl is
## part of the SDK API. If it is, also add it to the list below that
## is preprocessed and distributed with the SDK. This list should
## not contain any aidl files for parcelables, but the one below should
## if you intend for 3rd parties to be able to send those objects
## across process boundaries.
##
## READ ME: ########################################################
LOCAL_SRC_FILES += \
core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl \
core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl \
core/java/android/accounts/IAccountManager.aidl \
Android.mkview raw
1
2
3
core/java/android/os/IUserManager.aidl \
core/java/android/os/IVibratorService.aidl \
core/java/android/os/IHelloService.aidl \ # 添加的一行

编译IHelloService.aidl接口

1
nicho@PC:~/work/android$ mmm frameworks/base/

这样,就会根据IHelloService.aidl生成相应的IHelloService.Stub接口。

接下来在 frameworks/base/services/java/com/android/server 目录中添加 HelloService.java 文件

HelloService.javaview raw
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
package com.android.server;

import android.content.Context;
import android.os.IHelloService;
import android.util.Slog;

public class HelloService extends IHelloService.Stub {
private static final String TAG = "HelloService";

HelloService()
{
System.out.println("frameworks/base/services/java/com/android/server/HelloService.java HelloService()");
init_native();
}

public void setVal(int val)
{
System.out.println("frameworks/base/services/java/com/android/server/HelloService.java setVal()");
setVal_native(val);
}

public int getVal()
{
System.out.println("frameworks/base/services/java/com/android/server/HelloService.java getVal()");
return getVal_native();
}

private static native boolean init_native();
private static native void setVal_native(int val);
private static native int getVal_native();

};

修改同目录的 SystemServer.java 文件

SystemServer.javaview raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// initialization.
mActivityManagerService.systemReady(new Runnable() {
@Override
public void run() {
/////////////////添加的内容
try {
Slog.i(TAG, "Hello Service");
ServiceManager.addService("hello", new HelloService());
} catch (Throwable e) {
Slog.i(TAG, "Failure starting Hello Service", e);
}
/////////////////添加的内容到此为止

Slog.i(TAG, "Making services ready");
mSystemServiceManager.startBootPhase(
SystemService.PHASE_ACTIVITY_MANAGER_READY);

开始编译并重新打包系统镜像

1
2
3
#回到源码根目录执行
mmm frameworks/base/services
make snod

这样,重新打包后的system.img系统镜像文件就在Application Frameworks层中包含了自定义的硬件服务HelloService,并且会在系统启动的时候,自动加载HelloService。这时,应用程序就可以通过Java接口来访问Hello硬件服务。

内置Java应用程序测试Application Frameworks层的硬件服务

原始教程是通过Android Studio创建工程项目的,这里笔者图了个方便直接在命令行中创建了。下面我们就可以开始创建我们第一个HelloAndroid工程了。在~/workandroid/external下建立helloandroid目录,进入helloandroid目录,执行下面命令(target 参数接的数字要与AOSP的版本对应):

1
2
3
cd ~/work/android/external
mkdir helloandroid && cd helloandroid
android create project --name helloandroid --activity HelloAndroid --path ./ --package com.examples.helloandroid --target 1

要修改的文件

1
2
3
4
helloandroid/src/com/examples/helloandroid/HelloAndroid.java 
helloandroid/res/layout/main.xml
helloandroid/AndroidManifest.xml
helloandroid/Android.mk

文件内容如下

HelloAndroid.javaview raw
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
package com.examples.helloandroid;

import com.examples.helloandroid.R;
import android.app.Activity;
import android.os.ServiceManager;
import android.os.Bundle;
import android.os.IHelloService;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;

public class HelloAndroid extends Activity implements OnClickListener {
private final static String LOG_TAG = "shy.luo.renju.Hello";

private IHelloService helloService = null;

private EditText valueText = null;
private Button readButton = null;
private Button writeButton = null;
private Button clearButton = null;

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
System.out.println("Hello Started");
helloService = IHelloService.Stub.asInterface(
ServiceManager.getService("hello"));

valueText = (EditText)findViewById(R.id.edit_value);
readButton = (Button)findViewById(R.id.button_read);
writeButton = (Button)findViewById(R.id.button_write);
clearButton = (Button)findViewById(R.id.button_clear);

readButton.setOnClickListener(this);
writeButton.setOnClickListener(this);
clearButton.setOnClickListener(this);

Log.i(LOG_TAG, "Hello Activity Created");
}

@Override
public void onClick(View v) {
if(v.equals(readButton)) {
try {
int val = helloService.getVal();
String text = String.valueOf(val);
valueText.setText(text);
} catch (RemoteException e) {
Log.e(LOG_TAG, "Remote Exception while reading value from device.");
}
}
else if(v.equals(writeButton)) {
try {
String text = valueText.getText().toString();
int val = Integer.parseInt(text);
helloService.setVal(val);
} catch (RemoteException e) {
Log.e(LOG_TAG, "Remote Exception while writing value to device.");
}
}
else if(v.equals(clearButton)) {
String text = "";
valueText.setText(text);
}
}
}
main.xmlview raw
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
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/value">
</TextView>
<EditText
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/edit_value"
android:hint="@string/hint">
</EditText>
</LinearLayout>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center">
<Button
android:id="@+id/button_read"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/read">
</Button>
<Button
android:id="@+id/button_write"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/write">
</Button>
<Button
android:id="@+id/button_clear"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/clear">
</Button>
</LinearLayout>
</LinearLayout>
AndroidManifest.xmlview raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.examples.helloandroid"
android:versionCode="1"
android:versionName="1.0">
<application android:label="@string/app_name" android:icon="@drawable/ic_launcher">
<activity android:name="HelloAndroid"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Android.mkview raw
1
2
3
4
5
6
7
8
# This Android.mk is empty, and >> does not build subdirectories <<.
# Individual packages in experimental/ must be built directly with "mmm".
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)

编译并运行模拟器进行验证

1
2
3
4
5
#回到源码根目录执行
nicho@PC:~/work/android$ mmm packages/experimental/helloandroid
make snod
# 运行安卓模拟器,指定编译好的文件
emulator -show-kernel -avd helloandroid -kernel ../kernel/goldfish/arch/arm64/boot/Image -system ./out/target/product/generic_arm64/system.img -data ./out/target/product/generic_arm64/userdata.img -ramdisk ./out/target/product/generic_arm64/ramdisk.img

至此,也就完整地学习了在Android的Linux内核空间添加硬件驱动程序、在Android的硬件抽象层添加硬件接口、在Android的Application Frameworks层提供硬件服务以及在Android的应用层调用硬件服务的整个过程。


参考内容

Android硬件抽象层(HAL)概要介绍和学习计划

(三)在Ubuntu上为Android增加硬件抽象层(HAL)模块访问Linux内核驱动程序 原始链接已经打不开了,重新找了一个

1.下载源码

在Ubuntu16.04上下载并编译Android源代码

国内不翻墙下载Android 源代码

Ubuntu下载编译android7.0源码教程从安装到编译

repo forall -c 用法

2.配置环境

下载jdk1.7

配置jdk的环境变量

用update-alternatives管理java版本

ubuntu安装和卸载jdk8

ubuntu16 安装openjdk java1.7

最终是使用open jdk8 进行编译

重新添加了 libwxgtk2.8-dev 的ppa

安装其他依赖

4.编译AOSP项目

Android6.0源码编译

使用openjdk1.8 编译android7 到37%出现严重错误

1
2
3
4
5
6
[ 34% 16828/48845] Ensure Jack server is installed and started
FAILED: /bin/bash -c "(prebuilts/sdk/tools/jack-admin install-server prebuilts/sdk/tools/jack-launcher.jar prebuilts/sdk/tools/jack-server-4.8.ALPHA.jar 2>&1 || (exit 0) ) && (JACK_SERVER_VM_ARGUMENTS=\"-Dfile.encoding=UTF-8 -XX:+TieredCompilation\" prebuilts/sdk/tools/jack-admin start-server 2>&1 || exit 0 ) && (prebuilts/sdk/tools/jack-admin update server prebuilts/sdk/tools/jack-server-4.8.ALPHA.jar 4.8.ALPHA 2>&1 || exit 0 ) && (prebuilts/sdk/tools/jack-admin update jack prebuilts/sdk/tools/jacks/jack-2.28.RELEASE.jar 2.28.RELEASE || exit 47; prebuilts/sdk/tools/jack-admin update jack prebuilts/sdk/tools/jacks/jack-3.36.CANDIDATE.jar 3.36.CANDIDATE || exit 47; prebuilts/sdk/tools/jack-admin update jack prebuilts/sdk/tools/jacks/jack-4.7.BETA.jar 4.7.BETA || exit 47 )"
Jack server already installed in "/home/nicho/.jack-server"
Communication error with Jack server (35), try 'jack-diagnose' or see Jack server log
SSL error when connecting to the Jack server. Try 'jack-diagnose'
SSL error when connecting to the Jack server. Try 'jack-diagnose'
1
2
3
4
5
6
7
[ 37% 18114/48845] target thumb C: lib...rnal/icu/icu4c/source/i18n/decNumber.c
ninja: build stopped: subcommand failed.
build/core/ninja.mk:148: recipe for target 'ninja_wrapper' failed
make: *** [ninja_wrapper] Error 1

#### make failed to build some targets (01:42:20 (hh:mm:ss)) ####

编译报错:build/core/ninja.mk:148: recipe for target ‘ninja_wrapper’ failed

AOSP中make clean与make clobber的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
make clean

它会删除本次设置所生成的所有的output与中间文件。
等价于指令

rm -rf $OUT

1

这里的$OUT指的是out/target/product/[product_name]

make clobber

它会删除所有设置所生成的所有的output与中间文件。
等价于指令

rm -rf out/

1

可以看到,make clobber的严格在于它把整个out目录都删除了。

5.下载编译安装 Android kernel

Building Kernels Manually-需要通过外网访问这个文档比较全,在构建安卓系统的过程中可以全程使用

Android源代码Linux Kernel下载及编译

编译可用的Android模拟器ranchu内核

6.编写Linux 3.4.67的字符驱动

Learn How to Write a Driver for Linux 3.x With The Linux Driver Template

A Linux Driver Template (LDT) has been published to help new Linux kernel developers writing hardware device drivers.

Constantine Shulyupin posted the Linux Driver Template (LDT) on the Linux mailing list in order to merge it into the mainline Linux kernel. The code can be used as as a starting point for new drivers, and shows how to use several Linux facilities such as module, platform driver, file operations (read/write, mmap, ioctl, blocking and nonblocking mode, polling), kfifo, completion, interrupt, tasklet, work, kthread, timer, simple misc device,
multiple char devices, Device Model, configfs, UART, hardware loopback, software loopback and ftracer.

This sample has been added to other device drivers samples in eLinux.org. And if you want to learn further there’s always the Linux driver bible: “Linux Device Drivers, Third Edition” which can be downloaded for free as PDF, although it’s for 2.6.10 kernel and many parts may not be up-to date.

以上内容摘自: https://www.cnx-software.com/2012/11/14/learn-how-to-write-a-driver-for-linux-3-x-with-the-linux-driver-template/

Android 驱动之旅: 第一章 在Android 内核源代码工程中编写硬件驱动程序

Android 驱动之旅: 第二章 — 在Android 系统中增加C 可执行程序来访问硬件驱动程序

Android 驱动之旅: 第三章 硬件抽象层(HAL)增加接口模块访问硬件驱动程序

LOCAL_MODULE_PATH与LOCAL_MODULE_RELATIVE_PATH区别

Android7.0 APP调用驱动流程 JNI层开发流程

在Ubuntu为Android硬件抽象层(HAL)模块编写JNI方法提供Java访问硬件服务接口

Android HAL(硬件抽象层)介绍以及调用

ubuntu下使用命令行创建一个android项目

使用命令行方式开发Android应用

Android9.0 mm编译失败:ninja: error: ‘xxx’, needed by ‘xxx’, missing and no known rule to make it

Android架构实例分析之编写hello驱动的系统硬件服务

Android7关闭selinux(设置为Permissive模式)

忽然意识到,驱动的编写模板应该在内核目录drivers里头去找。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
cd ~/work/android && source build/envsetup.sh && lunch 2

mmm packages/experimental/helloandroid/

make snod

emulator -show-kernel -avd helloandroid -kernel ../kernel/goldfish/arch/arm64/boot/Image -system ./out/target/product/generic_arm64/system.img -data ./out/target/product/generic_arm64/userdata.img -ramdisk ./out/target/product/generic_arm64/ramdisk.img


01-01 08:03:04.915 898 898 E ServiceManager: add_service('hello',6d) uid=1000 - PERMISSION DENIED


09-19 11:05:07.863 1866 1866 E AndroidRuntime: java.lang.NullPointerException: Attempt to invoke interface method 'int android.os.IHelloService.getVal()' on a null object reference

ServiceManager add_service SELinux Permission Denied

Android O 添加系统服务错误 add_service uid=1000 - PERMISSION DENIED

1
2
3
4
5
6
7
8
 Android source code compile error: “Try increasing heap size with java option '-Xmx<size>'”

export JACK_SERVER_VM_ARGUMENTS="-Dfile.encoding=UTF-8 -XX:+TieredCompilation -Xmx4g"

./prebuilts/sdk/tools/jack-admin kill-server

./prebuilts/sdk/tools/jack-admin start-server

repo介绍

repo 撤销本地代码

使用repo丢弃本地的改动

repo如何取消本地改动(How to discard changes using repo)

驱动的编写模板应该在内核目录drivers里头去找例子

1
2
3
cd ~/work/android && source build/envsetup.sh && lunch 2
emulator -show-kernel -avd helloandroid -kernel ../kernel/goldfish/arch/arm64/boot/Image -system ./out/target/product/generic_arm64/system.img -data ./out/target/product/generic_arm64/userdata.img -ramdisk ./out/target/product/generic_arm64/ramdisk.img

总结

9月15日下午编译SDK之后,安卓模拟器就总是卡在开机动画界面,9月18日早上打算重新走一遍教程,捋一捋问题出在了哪里。9月19日终于把整个流程都走完了,然后重头梳理教程,理清思路。

最初下载好源码后是使用了 lunch 2 去编译 arm64 架构,且能正常运行,但当走到 adb devices 这一步的时候发现 device 总是 offline ,所以后来又回过头去使用 lunch 1 编译了 arm 架构,这一次非常顺利,并且也能通过 adb shell 进入终端,不过到了最后一个步骤需要编写 apk 进行验证的时候需要编译 SDK ,结果编译完 SDK 之后忽然发现所有的模拟器(源码预编译好的与 SDK 中新编译出来的)都卡在启动界面无法进入,在网上参考了许多资料都没效果,后来实在想不出办法了,就 make clean 了以下,又重新使用 lunch 2 去编译 arm64 架构的,这一次全部都通了,然后在命令行启动 android 开启 SDK manager 下载与安卓源码匹配的工具与镜像。通过在命令行下创建安卓项目工程,编写好相关代码文件,编译生成安卓系统预置应用程序 helloandroid ,启动模拟器验证结果,这时候碰到了 selinux 的问题,在查阅了相关资料后修改 system/core/init/init.cpp 的一行代码,关闭安全选项,重新执行 make -j8 进行编译,开启模拟器验证成功,自此自下而上的了解安卓系统算是有了一点眉目。

在此非常感谢同学的帮助,也感谢所有在互联网上无私分享知识的网友们,没有你们的付出,我很难收获到这些知识,更不会有这篇记录的产生,谢谢你们。