0%

1. clion vscode 阅读Android native源码

1.1. 为native源码编译生成cmake文件

1
2
export SOONG_GEN_CMAKEFILES=1
export SOONG_GEN_CMAKEFILES_DEBUG=1

将上面两句加入到~/.bashrc下, 以后新工程就不用重复export了

源码source lunch后, 直接make随便一个module即可.
cmake文件会生成在源码根目录的out/development/ide/clion下

1.2. 使用clion导入native工程

可以直接看单module,
file->new cmake project from sources

但多个module 需要手动写cmake 引进子的module, 重复工作量大, 而且soong生成的cmake 同一module包含多个子目标

1
2
drwxrwxr-x  2 mi mi 4096 3月  13 20:51 librecovery-arm64-android
drwxrwxr-x 2 mi mi 4096 3月 13 20:51 librecovery-arm-android

手动添加工作量比较大, 而且是重复工作

阅读全文 »

1. Ext3文件系统读写过程分析

1.1. Ext3文件读写流程概述

Ext3文件系统在进行读写操作的时候,首先需要open相应的文件,然后再进行读写操作。在open操作时,Linux kernel会创建一个file对象描述这个文件。File对象和文件的dentry和inode对象建立联系,并且将ext3的文件操作方法、映射处理方法(address space)注册到file对象中。

Ext3文件读写过程会涉及到VFS层的page cache,并且通常的读写操作都会使用到这层page cache,目的是提高磁盘的IO性能。在Linux中后台会运行writeback线程定时同步pagecache和设备之间的数据。Page cache的方式虽然能够提高IO性能,但是也对数据的安全性带来了潜在影响。

本文的目的是分析ext3文件系统读写流程中的关键函数,对于page cache原理以及writeback机制将在后继文章中做深入分析。

1.2. 关键数据结构

File数据结构是Linux用来描述文件的关键数据结构,该对象在一个文件被进程打开的时候被创建。当一个文件被关闭的时候,file对象也会被立即销毁。file数据结构不会被作为元数据信息持久化保存至设备。该数据结构定义如下:

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
struct file {  
/*
* fu_list becomes invalid after file_free is called and queued via
* fu_rcuhead for RCU freeing
*/
union {
struct list_head fu_list;
struct rcu_head fu_rcuhead;
} f_u;
struct path f_path; /* 文件路径,包含文件dentry目录项和vfsmount信息 */
#define f_dentry f_path.dentry
#define f_vfsmnt f_path.mnt
const struct file_operations *f_op; /* 文件操作函数集 */

/*
* Protects f_ep_links, f_flags, f_pos vs i_size in lseek SEEK_CUR.
* Must not be taken from IRQ context.
*/
spinlock_t f_lock;
#ifdef CONFIG_SMP
int f_sb_list_cpu;
#endif
atomic_long_t f_count;
unsigned int f_flags;
fmode_t f_mode; /* 文件操作模式 */
loff_t f_pos;
struct fown_struct f_owner;
const struct cred *f_cred;
struct file_ra_state f_ra;

u64 f_version;
#ifdef CONFIG_SECURITY
void *f_security;
#endif
/* needed for tty driver, and maybe others */
void *private_data;

#ifdef CONFIG_EPOLL
/* Used by fs/eventpoll.c to link all the hooks to this file */
struct list_head f_ep_links;
struct list_head f_tfile_llink;
#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping; /* address space映射信息,指向inode中的i_mapping */
#ifdef CONFIG_DEBUG_WRITECOUNT
unsigned long f_mnt_write_state;
#endif
};

每个文件在内存中都会对应一个inode对象。在设备上也会保存每个文件的inode元数据信息,通过inode元数据信息可以找到该文件所占用的所有文件数据块(block)。VFS定义了一个通用的inode数据结构,同时ext3定义了ext3_inode元数据结构。在创建内存inode对象时,需要采用ext3_inode元数据信息初始化inode对象。Inode数据结构定义如下:

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
 struct inode {  
umode_t i_mode;
unsigned short i_opflags;
uid_t i_uid;
gid_t i_gid;
unsigned int i_flags;

#ifdef CONFIG_FS_POSIX_ACL
struct posix_acl *i_acl;
struct posix_acl *i_default_acl;
#endif

const struct inode_operations *i_op; /* inode操作函数集 */
struct super_block *i_sb; /* 指向superblock */
struct address_space *i_mapping; /* 指向当前使用的页缓存的映射信息 */

#ifdef CONFIG_SECURITY
void *i_security;
#endif

/* Stat data, not accessed from path walking */
unsigned long i_ino;
/*
* Filesystems may only read i_nlink directly. They shall use the
* following functions for modification:
*
* (set|clear|inc|drop)_nlink
* inode_(inc|dec)_link_count
*/
union {
const unsigned int i_nlink;
unsigned int __i_nlink;
};
dev_t i_rdev; /* 设备号,major&minor */
struct timespec i_atime;
struct timespec i_mtime;
struct timespec i_ctime;
spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */
unsigned short i_bytes;
blkcnt_t i_blocks; /* 文件块数量 */
loff_t i_size;

#ifdef __NEED_I_SIZE_ORDERED
seqcount_t i_size_seqcount;
#endif

/* Misc */
unsigned long i_state;
struct mutex i_mutex;

unsigned long dirtied_when; /* jiffies of first dirtying */

struct hlist_node i_hash; /* 连接到inode Hash Table中 */
struct list_head i_wb_list; /* backing dev IO list */
struct list_head i_lru; /* inode LRU list */
struct list_head i_sb_list;
union {
struct list_head i_dentry;
struct rcu_head i_rcu;
};
atomic_t i_count;
unsigned int i_blkbits; /* 块大小,通常磁盘块大小为512字节,因此i_blkbits为9 */
u64 i_version;
atomic_t i_dio_count;
atomic_t i_writecount;
const struct file_operations *i_fop; /* former ->i_op->default_file_ops,文件操作函数集 */
struct file_lock *i_flock;
struct address_space i_data; /* 页高速缓存映射信息 */
#ifdef CONFIG_QUOTA
struct dquot *i_dquot[MAXQUOTAS];
#endif
struct list_head i_devices;
union {
struct pipe_inode_info *i_pipe; /* 管道设备 */
struct block_device *i_bdev; /* block device块设备 */
struct cdev *i_cdev; /* 字符设备 */
};

__u32 i_generation;

#ifdef CONFIG_FSNOTIFY
__u32 i_fsnotify_mask; /* all events this inode cares about */
struct hlist_head i_fsnotify_marks;
#endif

#ifdef CONFIG_IMA
atomic_t i_readcount; /* struct files open RO */
#endif
void *i_private; /* fs or device private pointer */
};
阅读全文 »

1. fingerprint编译过程

1
2
3
4
5
6
7
8
9
10
PRODUCT_BRAND := SPRD
PRODUCT_DEVICE := --TARGET_DEVICE
PRODUCT_NAME := --TARGET_PRODUCT
# build_id.mk
BUILD_ID :=
TARGET_BUILD_VARIANT:userdebug:user
BUILD_VERSION_TAGS:test-keys release-keys
BF_BUILD_NUMBER := $(USER)$$($(DATE_FROM_FILE) +%m%d%H%M)

BUILD_FINGERPRINT := $(PRODUCT_BRAND)/$(TARGET_PRODUCT)/$(TARGET_DEVICE):$(PLATFORM_VERSION)/$(BUILD_ID)/$(BF_BUILD_NUMBER):$(TARGET_BUILD_VARIANT)/$(BUILD_VERSION_TAGS)

SPRD/sp9832e_1h10_oversea/sp9832e_1h10:9/PPR1.180610.021/liguang.zhang12111501:userdebug/test-keys

fingerprint的变动是跟着BF_BUILD_NUMBER字段变更的, 而BF_BUILD_NUMBER字段只在make时更新, 即执行一次make更新一次.
整编时, 一次编译出所有的img, img中所有的fingerprint都是一样的.

1
2
3
4
5
6
7
8
9
10
11
12
@startuml
start
#HotPink:make;
->system.img\nvendor.img\nproduct.img\nrecovery.img\n;
note right:ro.build.fingerprint\nro.product.build.fingerprint\nro.vendor.build.fingerprint
#pink:pac;
:make productimage;
->product.img;
note right:ro.product.build.fingerprint
:replace pac;
end
@enduml

2. fingerprint用途

2.1. ota升级

差分升级时会校验fingerprint. 升级包中的fingerprint来自ota素材包(target_file)中的system分区的build.prop文件.
src recovery分区的fingerprint来自prop.default
即进到recovery模式下getprop ro.build.fingerprint 与基础版本和目标版本的ro.build.fingerprint值进行比对, 有一个相等则符合要求.

  • prop.default结构:
    ro.build.fingerprint -< 来自system编译过程
    ro.product.build.fingerprint -< 来自product编译过程

如果只make productimage, 则recovery分区的prop.default不会更新.

阅读全文 »

Incremental File System

Overview

Incremental FS is special-purpose Linux virtual file system that allows
execution of a program while its binary and resource files are still being
lazily downloaded over the network, USB etc. It is focused on incremental
delivery for a small number (under 100) of big files (more than 10 megabytes).
Incremental FS doesn?t allow direct writes into files and, once loaded, file
content never changes. Incremental FS doesn?t use a block device, instead it
saves data into a backing file located on a regular file-system.

But why?

To allow running big Android apps before their binaries and resources are
fully downloaded to an Android device. If an app reads something not loaded yet,
it needs to wait for the data block to be fetched, but in most cases hot blocks
can be loaded in advance.

Workflow

A userspace process, called a data loader, mounts an instance of incremental-fs
giving it a file descriptor on an underlying file system (like ext4 or f2fs).
Incremental-fs reads content (if any) of this backing file and interprets it as
a file system image with files, directories and data blocks. At this point
the data loader can declare new files to be shown by incremental-fs.

A process is started from a binary located on incremental-fs.
All reads are served directly from the backing file
without roundtrips into userspace. If the process accesses a data block that was
not originally present in the backing file, the read operation waits.

Meanwhile the data loader can feed new data blocks to incremental-fs by calling
write() on a special .cmd pseudo-file. The data loader can request information
about pending reads by calling poll() and read() on the .cmd pseudo-file.
This mechanism allows the data loader to serve most urgently needed data first.
Once a data block is given to incremental-fs, it saves it to the backing file
and unblocks all the reads waiting for this block.

Eventually all data for all files is uploaded by the data loader, and saved by
incremental-fs into the backing file. At that moment the data loader is not
needed any longer. The backing file will play the role of a complete
filesystem image for all future runs of the program.

阅读全文 »

1. 设计原则

1.1. 通用原则

1.1.1. KISS原则(keep it sample and stupid)

  • 懒人原则
  • 注重简约的原则, 逻辑处理上应该用简单直接的设计.
  • 圈复杂度越小越好, if for case while的数量.

如非必要, 请远离各种设计模式

1.2. 核心原则

1.2.1. 单一职责原则(SRP)

做一个专一的人

不能存在多于一个导致类变更的原因, 一个类之负责一项职责.

迭代器的实现, 集合只负责存储数据, 迭代器完成遍历数据的功能

阅读全文 »

主要调研下Settings对应的通用搜索框架的实现.

1. 自定义fragment支持search扩展

需要继承com.android.settings.search.Indexable接口, 定义SearchIndexProvider的实例, 并需要将自己加到SearchIndexableResources的sProviders中

1.1. 接口定义

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
// 嵌套接口的用法
public interface Indexable {

interface SearchIndexProvider {
/**
* Return a list of references for indexing.
*
* See {@link android.provider.SearchIndexableResource}
*
*
* @param context the context.
* @param enabled hint telling if the data needs to be considered into the search results
* or not.
* @return a list of {@link android.provider.SearchIndexableResource} references.
* Can be null.
*/
List<SearchIndexableResource> getXmlResourcesToIndex(Context context, boolean enabled);

/**
* Return a list of raw data for indexing. See {@link SearchIndexableRaw}
*
* @param context the context.
* @param enabled hint telling if the data needs to be considered into the search results
* or not.
* @return a list of {@link SearchIndexableRaw} references. Can be null.
*/
List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled);

/**
* Return a list of data keys that cannot be indexed. See {@link SearchIndexableRaw}
*
* @param context the context.
* @return a list of {@link SearchIndexableRaw} references. Can be null.
*/
List<String> getNonIndexableKeys(Context context);

/**
* @param context
* @return a list of {@link AbstractPreferenceController} for ResultPayload data during
* Indexing.
*/
List<AbstractPreferenceController> getPreferenceControllers(Context context);
}
}

1.2. 接口实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 定义SearchIndexProvider的实例
public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider() {
@Override
public List<SearchIndexableResource> getXmlResourcesToIndex(
Context context, boolean enabled) {
final SearchIndexableResource sir = new SearchIndexableResource(context);
sir.xmlResId = R.xml.tether_prefs;
return Arrays.asList(sir);
}
};
public class BaseSearchIndexProvider implements Indexable.SearchIndexProvider
// 加到sResMap中, 后面遍历时用打
addIndex(TetherSettings.class, NO_DATA_RES_ID, 0);

定义了通用类, BaseSearchIndexProvider, 实现接口的通用方法, 如果有具体的定制需求, 需要自定义BaseSearchIndexProvider的子类覆盖对应方法.

1.3. 结构图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@startuml
interface Indexable {
interface SearchIndexProvider
}
interface SearchIndexProvider {
getXmlResourcesToIndex(...)
getRawDataToIndex()
getNonIndexableKeys()
}
Indexable *-- SearchIndexProvider
class BaseSearchIndexProvider implements SearchIndexProvider {
getXmlResourcesToIndex(...)
getRawDataToIndex()
getNonIndexableKeys()
}
class DemoFragment implements Indexable {
SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER
}

DemoFragment o-- BaseSearchIndexProvider
@enduml
阅读全文 »

文件加密FBE&DirectBoot模式介绍

1. File based Encryption

Android 7.0及以上版本提供基于文件的加密方式(FBE). 这种方式允许使用不同的密钥对不同的文件进行加密, 并且可以独立进行解密.
Android 7.0为基于文件的加密引入了新的功能, 命名为Direct Boot. 它允许加密设备直接启动至锁定屏幕的状态. 在以前的版本中, 使用全局磁盘加密(FDE)的设备,
用户需要在任何数据能够被访问前提供认证凭证(图形/密码/指纹), 来预防设备可能被进行的任何基本操作.
如: 闹钟无法执行, 辅助功能服务无法使用, 电话无法接受通话请求, 除了最基本的紧急通话操作.
使用基于文件加密的设备, 应用可以了解当前加密的情况, 同时被允许执行限定范围内的操作. 这些操作能够在用户提供认证凭证前执行, 同时依然能够保护私有用户信息.
在使用FBE的设备上, 设备的每个用户对于设备应用均拥有两个存储空间:

  • 凭证加密存储区间(Credential Encrypted (CE)), 这是缺省的存储空间, 并且只有当用户解锁设备后才可用.
  • 设备加密存储空间(Device Encrypted (DE)), 该存储空间在Direct Boot模式和用户解锁设备后均可用.

1.1. FDE Vs FBE:

  • FBE: FILE BASED ENCRYPTION:
    • 每一个用户一个CE key,CE key的加密信息存储在data分区的相应文件夹中。
    • 每个用户对应一个DE key,除此之外,还有一个全局的DE key用来为与用户非强关联的文件夹设置加密。
    • 设备未解锁之前,可以使用CE区域以外的文件夹。
    • 加密针对文件夹级别,加密机制利用了Ext4 文件系统的加密特性。
    • 这种区分能够使工作模型更为安全, 因为它允许在同一时间能够保护多个用户,并不是基于一个启动时密码。
  • FDE(FULL DISK ENCTYPTION)
    • 只有一个key,key的加密信息存储在磁盘尾部区域。
    • 设备未解锁之前,data目录是以tmpfs格式挂载的data分区处于完全不可使用状态。
    • 加密针对的是分区级别,加密机制利用了dm-crypt加密特性。

1.2. Dependencies

设备必须满足以下的要求来安全使用AOSP的FBE实现:

  • Kernel支持ext4加密(相关的kernel config为: EXT4_FS_ENCRYPTION)

  • Keymaster支持1.0/2.0的HAL版本. 0.3版本的keymaster hal并不提供相关需要的属性支持, 而且不能保证加密密钥的安全保护机制.

  • Keymaster/Keystore 和 GateKeeper必须在可信任执行环境(TEE)中实现, 用以提供对DE密钥的保护, 该情况下, 未认证的系统OS(自行输入到设备上的OS)将不能简单的访问DE密钥.

  • kernel中提供的加密性能必须达到使用AES-XTS时最低为50MB/s来保证良好的用户体验.

  • Verified Boot机制下 Keymaster root of trust 必须绑定至keymaster的初始化过程中. 这将保证设备加密证书不会被未验证的操作系统访问到

  • bootloader passes the following information to the TEE after boot/recovery partition verification and

    TEE initialization to bind the Keymaster root of trust:

    • the public key that was used to sign the boot partition

1.3. 加密规则 Encryption policy

阅读全文 »

1. 低存储方案机制部分

1.1. 系统通知提示

获得data分区可用存储空间:

1
Environment.getDataDirectory().getUsableSpace()

1.1.1. 第一档

阈值为100M, 100 *1024 *1024

弹出系统通知

  • 通知标题

    Insufficient internal storage

    内部存储空间不足

  • 通知内容:

    Insufficient internal storage space, some system functions may not work properly, please clean up storage space immediately.

    内部存储空间不足,部分系统功能可能无法正常使用,请立即清理存储空间。

点击通知, 进入存储空间清理界面

1.1.2. 第二档

阅读全文 »

1. 并发编程笔记

1.1. 容器加锁注意事项

  • 加锁可以防止容器的迭代溢出.
  • 迭代期间对容器进行加锁会降低程序的可伸缩性. 降低程序性能及cpu使用率.甚至可能造成死锁.取决于容器的规模.
  • 替代方法是克隆容器.(克隆过程中仍然需要加锁,会有额外的性能开销)
  • 使用容器的一些方法时,会隐藏使用迭代器.

1.2. 并发容器

Queue BlockingQueue ConcurrentHashMap ConcurrentMap CopyOnWriteArrayList

PriorityBlockingQueue SynchronousQueue(put take一直阻塞)

1.3. 模式

生产者消费者模式 (如BlockingQueue 所有消费者有一个共享的工作队列)

工作密取(对应双端队列 如BlockingDeque, 每个消费者都有各自的工作队列,如果一个消费者完成了自己双端队列上的全部工作,它可以从其它消费者双端队列上秘密的获取工作,适合执行某个工作时发现有更多的工作.)

1.4. 闭锁

阅读全文 »

1. 本地调试使用C++相关

strstr strcmp strcpy的头文件在<string.h>中
std::string的头文件在

1
2
3
#include <stdio.h>
#include <string>
#include <string.h>

编译时需要使用如下命令:

1
g++ -lstdc++ <abc.cpp>

选择c++11编译的命令为:

1
g++ -g -Wall -std=c++11 <abc.cpp>
阅读全文 »