快捷搜索:  汽车  科技

android文件属性(深入理解Android系统资源异常之文件描述符异常篇)

android文件属性(深入理解Android系统资源异常之文件描述符异常篇)linux的设计哲学是一切兼文件。这句话的意思是,所有的系统资源都可以通过文件IO的方式进行访问。这就凸显了作为索引的文件描述符的重要性。对于linux内核,所有打开的文件都是通过文件描述符引用,文件描述符实现为一个非负整数。3)android fdsan机制设计思路与实现4)android fdtrack机制设计思路与实现文件描述符,即file descriptor,缩写为fd。

一、引言

本文的目标是帮助大家深入理解Android系统资源异常之文件描述符异常,对于文件描述符异常的通用检测机制,当前包括fdtrack和fdsan两种机制展开剖析。

通过阅读本篇文章,期望读者可以了解到:

1)什么是文件描述符

2)linux kernel中如何使用文件描述符,来管理进程打开文件资源

3)android fdsan机制设计思路与实现

4)android fdtrack机制设计思路与实现

二、背景知识1. 什么是文件描述符

文件描述符,即file descriptor,缩写为fd。

对于linux内核,所有打开的文件都是通过文件描述符引用,文件描述符实现为一个非负整数。

linux的设计哲学是一切兼文件。这句话的意思是,所有的系统资源都可以通过文件IO的方式进行访问。这就凸显了作为索引的文件描述符的重要性。

2. 获取fd的时机

当打开一个现有的文件,或创建一个新文件时,内核会向进程返回一个文件描述符。

当读、写一个文件时,使用open/create返回的文件描述符来标识该文件,将其作为参数传递给read或write。

3. fd取值范围的限制

文件描述符取值范围在[0~OPEN_MAX - 1],在早期的操作系统实现中OPEN_MAX取值很小,但对于现代的操作系统实现,文件描述符的变化范围几乎是无限制的,只受到系统的硬件配置、整型的字长以及系统管理员配置的软、硬限制的约束。

POSIX语义中,0、1、2这三个文件描述符被标准赋予特殊含义,分别指代标准输入STDIN_FILENO、标准输出STDOUT_FILENO、标准错误STDERR_FILENO。

系统中对每个进程可以打开最大fd数量的限制,可以通过命令ulimit -n查询。

也可以加上-S标识软限制,-H标识硬限制。

我这里分别查询了手机系统和服务器系统上的数值,手机系统上为 32768;服务器系统上为 20480000。

那么,手机上的这个fd最大限制数值是怎么来的呢?

标准linux实现中,在头文件include/uapi/linux/fs.h中有宏定义,标识了系统默认的软、硬限制。

#define INR_OPEN_CUR 1024 /* Initial setting for nfile rlimits */ #define INR_OPEN_MAX 4096 /* Hard limit for nfile rlimits */ android系统在core/rootdir/init.rc文件中,on early-init时候进行了重新设定。 # Allow up to 32K FDs per process setrlimit nofile 32768 32768 4. 内核如何使用fd管理进程打开的文件

我们知道进程是操作系统资源管理的基本单元。

linux内核中使用struct task_struct来描述进程。

(1) struct task_struct

task_struct结构体中有一个字段files,对应的struct files_struct结构体用于管理进程打开的文件资源。

下面列出了task_struct中与文件资源管理相关的核心字段。

struct task_struct { /* Open file information: */ struct files_struct *files; };

可以看到files字段是一个指针,指向了一个struct files_struct的结构体。

(2) struct files_struct

files_struct结构体用于管理进程打开的所有文件资源。采用数组的方式来管理进程打开的文件,fd(非负整数)就作为数组的索引。

android文件属性(深入理解Android系统资源异常之文件描述符异常篇)(1)

可以看到数组分布在两个地方:

1)struct file __rcu * fd_array[NR_OPEN_DEFAULT],是直接包含在files_struct结构体的静态数组部分,在64位系统上,NR_OPEN_DEFAULT对应为64;

2)struct fdtab,是一个动态数组结构,数组没有直接包含在files_struct结构体中,是根据需要动态分配的。

这种静态分配加动态扩展的方式,是软件设计中的常用技巧,是对性能与资源的tradeoff。

对于大部分只打开少量文件的进程来说,静态数组就可以满足需求。对于少部分需要打开更多文件的进程,当打开文件数量超过了静态数组的阈值后,会动态分配fdtab数组来进行扩展。

(3) struct fdtab

fdtab结构体是封装动态扩展fd数组的,其中关键字段为fd和max_fds。

android文件属性(深入理解Android系统资源异常之文件描述符异常篇)(2)

fd字段是一个二级指针,指向一个数组(动态分配),数组元素类型为struct file *,与上面的静态数组fd_array一样。max_fds字段表明这个动态数组的大小。

再次重复强调一下,文件描述符fd,就是一个数组索引,用于索引进程(files_struct结构体中)打开的文件。

(4) struct file

fd本质是进程中的一个数组索引,索引的数组元素类型为struct file *,而struct file才是表示文件信息的正主。

下面列出了struct file结构体中,需要重点关注的几个字段。

struct file { struct path f_path; struct inode *f_inode; /* cached value */ const struct file_operations *f_op; loff_t f_pos; } __randomize_layout __attribute__((aligned(4))); /* lest something weird decides that 2 is OK */

f_path用来表示文件名;

f_inode用来关联文件系统信息,这里的inode是vfs的inode类型,是基于具体的文件系统之上一层通用抽象;

f_pos表示当前文件的偏移,在进行实际IO的时候非常重要。f_pos会在open的时候设置成默认值,seek的时候修改为指定值。

(5) 文件描述符与文件关系

需要注意的是,struct files_struct结构体归属于某个进程,所以fd是进程内部的资源,用于管理本进程内打开的文件。

struct file结构体是系统级别,不归属于单个进程。多个进程可以打开同一个文件,使用自身的fd资源索引该文件。

下面引用自参考资料[2]中的一张图片来直观说明这种关系:

android文件属性(深入理解Android系统资源异常之文件描述符异常篇)(3)

三、fdsan机制介绍1. fdsan简介

(1) fdsan是什么

file descriptor sanitizer的缩写,是Android在Q版本中引入的一种文件描述符异常检测机制。

(2) fdsan可以干什么

可以检测fd ownership mis-handling这种类型的错误。

(3) fdsan怎么用

Android在Q版本引入的针对fd ownership mid-handling的异常检测机制。代码固化在bionic的libc库。在通过linker加载libc库时,fdsan相关初始化代码会自动导入。

所有包含了libc库的共享库以及可执行程序,已经包含了fdsan的基础设施,只要在代码中使用fdsan提供的API来检查文件打开与关闭操作即可:

1)android_fdsan_exchange_owner_tag,在打开文件后,紧接着调用该API设置owner tag

2)android_fdsan_close_with_tag,在关闭文件前,调用该API进行fdsanitizer检测

需要注意的是,fdsan的基础设施固化在libc库中,所以没有包含libc库的共享库或者可执行程序,无法使用该检测机制提供的能力。

当前AOSP代码中共享库与可执行程序已经包含了对fdsan的使用。

使用范例可以参考AOSP代码中的libziparchive实现。

在构造函数中,增加android_fdsan_exchange_owner_tag的调用。

android文件属性(深入理解Android系统资源异常之文件描述符异常篇)(4)

在析构函数中增加android_fdsan_close_with_tag的调用。

android文件属性(深入理解Android系统资源异常之文件描述符异常篇)(5)

其中通过GetOwnerTag,我们可以看到对于owner tag的构造。

android文件属性(深入理解Android系统资源异常之文件描述符异常篇)(6)

这样所有使用的libziparchive的代码就包含了对于ANDROID_FDSAN_OWNER_TYPE_ZIPARCHIVE 类型fd的sanitizer检测。

2. 涉及代码路径

bionic/docs/fdsan.md bionic/libc/include/android/fdsan.h bionic/libc/private/bionic_fdsan.h bionic/libc/bionic/fdsan.cpp bionic/tests/fdsan_test.cpp

其中fdsan.md,是fdsan的manual文件;

fdsan.h,是fdsan方案的公共头文件,包含了API接口原型声明以及tag类型的枚举定义;

bionic_fdsan.h,是fdsan方案内部私有头文件,定义了进程内部存储fd关联tag信息的数据结构,按照静态数组(128)加动态扩展数组方式来实现存储结构;

fdsan.cpp,fdsan方案的实现文件;

fdsan_test.cpp,fdsan方案测试代码,针对各种类型的fd ownership mis-handling的模拟故障测试代码。

3. 设计思路解读

fdsan的设计思路浅显易懂:

1)打开已有文件或创建一个新文件的时候,在得到返回fd后,设置一个关联的tag,来标记fd的属主信息;

关闭文件前,检测fd关联的tag,判断是否符合预期(属主信息一致),符合就继续走正常文件关闭流程;如果不符合就是检测到异常,根据设置,调用对应的异常处理。

4. 实现代码解读

android文件属性(深入理解Android系统资源异常之文件描述符异常篇)(7)

通过代码注释,可以看到如果没有主动设置tag,则文件打开相关操作,默认tag为0,关闭的时候也按照对应的默认tag(0)来做匹配检测。

(1) 关联tag格式定义

fdsan内部实现,使用struct FdEntry来表示与fd关联的tag,可以看到实际上就是一个u64原子类型的整数。

struct FdEntry {

_Atomic(uint64_t) close_tag = 0;

};

还有一个关键的枚举android_fdsan_owner_type给出了tag格式的解读。

定义在bionic/libc/include/android/fdsan.h头文件中。

android文件属性(深入理解Android系统资源异常之文件描述符异常篇)(8)

可以看到将64bit的tag数据拆分为两个部分:最高字节用于标识type类型,剩下字节用于标识实际的owner tag。

从注释可以看到,android当前预定义了12种type,这12种之外的其他java对象,以及native指针type域都会对应到255。

对于通用java对象,type域定义为255,并使用对象的hashcode作为tag域的值;

对于native指针,整个close_tag取值为48bit虚拟地址的符号扩展,type域的值正好也是255,并且可以使用bit49~56的值来区分是native指针还是通用java对象类型。

这个可以从内部函数android_fdsan_get_tag_type的实现中,得到很好的解读。

android文件属性(深入理解Android系统资源异常之文件描述符异常篇)(9)

(2) 实现内部存储tag数据结构定义

定义在bionic/libc/private/bionic_fdsan.h头文件中。

tag存储实体struct FdEntry定义:

android文件属性(深入理解Android系统资源异常之文件描述符异常篇)(10)

动态扩展数组结构struct FdTableOverflow定义:

android文件属性(深入理解Android系统资源异常之文件描述符异常篇)(11)

FdTable数据结构模板FdTableImpl定义:

android文件属性(深入理解Android系统资源异常之文件描述符异常篇)(12)

包含如下重要成员:

error_level,用于控制检测到异常后的处理行为,放到该结构体里,可以实现每进程细粒度控制;

entries数组,静态数组,大小由模板特化的时候参数传入,对于大多数进程来说,需要打开文件数量有限,静态数组就可以满足存储需求;

overflow,动态数组,当进程打开文件数量超出静态数组阈值时候,动态分配;

at,成员函数,用于根据fd做索引,返回对应的tag值;

模板特化类型FdTable定义,指定静态数组大小为128。

using FdTable = FdTableImpl<128>;

全局变量fd_table定义在头文件bionic/libc/private/bionic_globals.h中

android文件属性(深入理解Android系统资源异常之文件描述符异常篇)(13)

通过注释,可以知道libc_shared_globals结构体会在动态链接libc共享库时候得到构造,其中成员fd_table也就会被构造。

(3) 初始化

fdsan模块的初始化函数__libc_init_fdsan,完成fdsan特性属性debug.fdsan设置到内部数据fd_table中。

android文件属性(深入理解Android系统资源异常之文件描述符异常篇)(14)

默认值为ANDROID_FDSAN_ERROR_LEVEL_FATAL,如果系统属性"debug.fdsan"有设置有效值,则使用读取的有效值对fd_talbe变量进行设定,否则使用默认值设定。

fdsan特性属性名kFdsanPropertyName 定义如下 static constexpr const char* kFdsanPropertyName = "debug.fdsan"; fdsan特性属性"debug.fdsan"可能取值定义如下:

android文件属性(深入理解Android系统资源异常之文件描述符异常篇)(15)

__libc_init_fdsan会在libc的初始化流程中被调用到。调用点位于bionic/libc/bionic/libc_init_common.cpp文件中的__libc_init_common

android文件属性(深入理解Android系统资源异常之文件描述符异常篇)(16)

从__libc_init_common的调用点,我们可以明白fdsan初始化生效逻辑。

bionic/libc/bionic/libc_init_dynamic.cpp文件中的__libc_preinit_impl;

bionic/libc/bionic/libc_init_static.cpp文件中的__real_libc_init。

可见不管是静态链接libc还是动态链接libc,只要是链接了libc库的进程,都会保证fdsan的初始化流程的执行。

下面从fdsan对外暴露的三个API来剖析fdsan的内部实现。

(4) android_fdsan_create_owner_tag

android文件属性(深入理解Android系统资源异常之文件描述符异常篇)(17)

通过传入的type和tag字段,拼接成一个有效的close_tag值。

然后调用android_fdsan_exchange_owner_tag进行ownership的设定。

(5) android_fdsan_exchange_owner_tag

android文件属性(深入理解Android系统资源异常之文件描述符异常篇)(18)

入参说明:

fd,fd句柄,作为FdEntry的索引

expected_tag,期望的ownership tag值

new_tag,设置新的ownership tag值

通过fd索引找到对应的FdEntry,判断tag值是否与expected_tag一致,一致说明ownership符合预期,可以使用new_tag值重新设定对应的FdEntry。

比较与设置操作通过原子操作atomic_compare_exchange_strong完成,可以保证是线程安全的。

如果不符合ownership预期,则说明检测到了异常,根据expected_tag和FdEntry tag关系调用fdsan_error进行错误处理。

(6) android_fdsan_close_with_tag

android文件属性(深入理解Android系统资源异常之文件描述符异常篇)(19)

入参说明:

fd,待关闭的fd句柄

expected_tag,期望的ownership tag,如果与fd对应的FdEntry匹配,则执行正常关闭操作,否则,说明检测到异常,进行错误处理。

其中FDTRACK_CLOSE在fdtrack章节4.4.1进行介绍。

tag匹配检查操作也是通过原子操作atomic_compare_exchange_strong完成,保证线程安全。

如果close_tag和expected_tag相等,符合预期,可以继续调用__close执行关闭操作;

否则检测到异常,根据close_tag和expected_tag的关系,调用fdsan_error进行错误处理。错误处理相关代码足够清晰,就不用赘述了。

如果ownership匹配,但是调用__close返回失败,再进行判断,是否发生了double close。

(7) fdsan_error

android文件属性(深入理解Android系统资源异常之文件描述符异常篇)(20)

根据设定的error_level ,进行异常处理。

如果是ANDROID_FDSAN_ERROR_LEVEL_DISABLED,do nothing;

如果是ANDROID_FDSAN_ERROR_LEVEL_WARN_ONCE,打印一次告警信息,然后重新设定error_level为ANDROID_FDSAN_ERROR_LEVEL_DISABLED

如果是ANDROID_FDSAN_ERROR_LEVEL_WARN_ALWAYS,总是打印告警信息;

如果是ANDROID_FDSAN_ERROR_LEVEL_FATAL,直接发送abort信号自杀。

四、fdtrack机制介绍1. fdtrack简介

(1) fdtrack是什么

fdtrack是android在R版本开始引入,为进程fd资源泄露问题,提供一套统一的检测机制。

(2) fdtrack可以干什么

fdtrack使能后,可以检测进程fd资源泄露问题,检测到fd资源泄露后,可以打印出fd分配路径的调用栈,辅助问题的定位。

(3) fdtrack怎么用

fdtrack的实现方式中,固化了一部分桩代码到libc中,主要检测代码则实现在一个共享库libfdtrack中。要使用fdtrack功能的进程,需要动态的加载libfdtrack库来使能fdtrack功能。

android中给出了libfdtrack如何使用的示例代码:

在system_server进程中使能fdtrack检测功能,基本思路是,通过进程内已分配的fd资源的绝对数量来决定何时启用fdtrack功能,以及判定何时发生了fdleak。

创建一个monitor线程,周期性的检测进程fd资源是否超过了预定的阈值,当超过第一个检测trigger阈值时,主动加载libfdtrack库,使能fdtrack功能;当继续超过第二个泄露阈值时,会发送信号BIONIC_SIGNAL_FDTRACK,调用到libfdtrack库中信号处理函数进行异常处理。

下面让我们一下看一看,system_server使用fdtrack的具体示例代码吧。

在system_server主线程run函数中,调用spawnFdLeakCheckThread创建一个moniter线程,默认只在debug版本中打开。

if (Build.IS_DEBUGGABLE) { spawnFdLeakCheckThread(); }

创建monitor线程的实现,android R版本中是通过JNI接口调用native实现,android S版本中是直接实现在java端了,我们看一下S版本中的实现。

android文件属性(深入理解Android系统资源异常之文件描述符异常篇)(21)

两个阈值可以通过属性重新设定,检测trigger阈值默认为1024,泄露阈值默认为2048,检测周期默认值设置为120秒。

当fd首次超过检测阈值时,动态加载libfdtrack共享库,使能fdtrack检测;

当fd超过泄露阈值时,调用fdtrackAbort进行异常处理。

其中有个小优化项:每个检测周期,如果fd超过检测阈值时,先尝试主动GC进行一次清理,看是否改善,如果清理完成后,fd还是超过检测阈值,就会走上面描述的逻辑。

2. 涉及代码路径

android/bionic/libc/platform/bionic/fdtrack.h android/bionic/libc/private/bionic_fdtrack.h android/bionic/libfdtrack/fdtrack.cpp android/bionic/libc/bionic/fdtrack.cpp

其中fdtrack.h,是fdtrack方案的公共头文件,包含了API接口原型声明,fdtrack_event的定义以及fdtrack_event_type的枚举定义;

bionic_fdtrack.h,是fdtrack方案内部私有头文件,定义了fdtrack在libc里内部嵌入的包装宏定义FDTRACK_CREATE_NAME、FDTRACK_CLOSE;

libc/bionic/fdtrack.cpp,是fdtrack方案固化在libc部分的实现代码。包括初始化部分,以及钩子函数设置,以及线程内部fdtack使能设置函数;

libfdtrack/fdtrack.cpp,是fdtrack方案libfdtrack的实现部分。

3. 设计思路解读

fdtrack的设计思路也比较直观明了:

通过预先在libc代码中埋伏好钩子函数(所有文件打开相关的API接口已经预先埋好桩),通过FDTRACK_CREATE_NAME包装宏实现埋桩。

埋桩点是否生效,是通过钩子函数__android_fdtrack_hook是否有效来控制,而钩子函数又是通过libfdtrack共享库的加载来动态赋有效值的。

这就非常好的实现了动态控制。需要使用fdtrack功能的时候,动态加载libfdtrack库,设置有效钩子函数,激活埋桩点代码。否则埋桩点代码执行空语句,不增加运行负载。

加载libfdtrack共享库后,以后的每次文件打开操作,都会调用到fdtrack内部实现代码,进行调用栈记录;每次文件关闭操作,也会调用到fdtrack内部实现代码,进行调用栈移除操作;

最后如果发生了fd泄露,只需要打印出内部记录的调用栈信息,即可辅助fd泄露问题的分析定位。

4. 实现代码解读

(1) 预埋桩代码解读

钩子函数__android_fdtrack_hook定义在libc/bionic/fdtrack.cpp中。

_Atomic(android_fdtrack_hook_t) __android_fdtrack_hook;

是一个指向android_fdtrack_hook_t类型的原子类型变量,默认初始值为空指针,只有当libfdtrack加载后,才会被赋予有效值fd_hook。

文件打开与关闭API包装宏,功能与用法通过注释可以看到。

android文件属性(深入理解Android系统资源异常之文件描述符异常篇)(22)

包装宏FDTRACK_CREATE_NAME可以自己指定name参数,包装参数到fdtrack_event,调用__android_fdtrack_hook处理fdtrack_event。

android文件属性(深入理解Android系统资源异常之文件描述符异常篇)(23)

包装宏FDTRACK_CREATE直接使用包装宏的调用函数名作为name参数。

只有加载了libfdtrack共享库,才会将__android_fdtrack_hook设置为有效值,满足非空条件,执行hook函数,从而进入到fdtrack内部实现。

android文件属性(深入理解Android系统资源异常之文件描述符异常篇)(24)

包装宏FDTRACK_CLOSE,仅包装参数到fdtrack_event,调用__android_fdtrack_hook处理fdtrack_event,完成fdtrack调用栈信息记录的闭环,不真实执行close操作。

FDTRACK_CREATE_NAME埋桩调用点,通过搜索代码,可以看到预先埋到了各个file descriptor creation API接口函数中

android文件属性(深入理解Android系统资源异常之文件描述符异常篇)(25)

FDTRACK_CLOSE,在3.4.6节介绍fdsan的时候有提到过,埋桩到android_fdsan_close_with_tag函数中,在各个链接了libc的进程中执行file close时,保证都会调用到android_fdsan_close_with_tag。

预埋桩代码的初始化部分__libc_init_fdtrack同3.4.3节介绍的__libc_init_fdsan,不再赘述。可以保证不论是动态加载libc库,还是静态加载libc库,都会调用到fdtrack静态预埋装部分代码。

android文件属性(深入理解Android系统资源异常之文件描述符异常篇)(26)

_libc_init_fdtrack在调用进程内部注册信号BIONIC_SIGNAL_FDTRACK,处理函数为空函数,只实现预先占位功能。后面讲到ctor初始化时,可以看到动态加载fdtrack库时,ctor初始化时,会重新注册信号BIONIC_SIGNAL_FDTRACK处理函数。

(2) fdtrack内部实现数据结构定义

首先介绍的是android_fdtrack_event结构体,封装了发送到fd_hook的数据包

android文件属性(深入理解Android系统资源异常之文件描述符异常篇)(27)

其中有重要的成员type,用来在fd_hook中区分是create还是close事件。

android文件属性(深入理解Android系统资源异常之文件描述符异常篇)(28)

fdtrack内部使用全局数组stack_traces记录调用栈信息,该静态数组大小为4096。

android文件属性(深入理解Android系统资源异常之文件描述符异常篇)(29)

(3) 动态加载初始化与析构去初始化

libfdtrack库动态加载时,会调用到初始化函数ctor,这是通过属性__attribute__((constructor))设定的。

android文件属性(深入理解Android系统资源异常之文件描述符异常篇)(30)

该函数会完成4件事情

1)初始化调用栈记录器stack_traces的backtrace成员,分配数组大小为kStackDepth;

2)注册信号BIONIC_SIGNAL_FDTRACK的处理函数fdtrack_dump/fdtrack_dump_fatal;

3)设置__android_fdtrack_hook为fd_hook;

4)设置fdtrack使能标志。

对应的去初始化dtor,主要做了一件事情,就是将__android_fdtrack_hook设定为nullptr。

android文件属性(深入理解Android系统资源异常之文件描述符异常篇)(31)

(4) fd_hook实现

android文件属性(深入理解Android系统资源异常之文件描述符异常篇)(32)

根据event的类型进行相应处理:

如果是create,以fd为索引,获取对应的FdEntry,并记录分配路径的调用栈;

如果是close,以fd为索引,获取对应的FdEntry,清除调用栈记录;

其中GetFdEntry,以fd为索引,返回指向对应的FdEntry元素的指针。

android文件属性(深入理解Android系统资源异常之文件描述符异常篇)(33)

(5) fdtrack_dump实现

从前面ctor的分析可知,注册的BIONIC_SIGNAL_FDTRACK信号处理函数中,会根据发送的信号附加的siginfo信息,区分处理方式,是fatal模式还是非fatal模式。

其中使用sigqueue方式发送BIONIC_SIGNAL_FDTRACK信号,且携带了正确的siginfo时,会走到fdtrack_dump_fatal里。

android文件属性(深入理解Android系统资源异常之文件描述符异常篇)(34)

可以看到两个不同的接口函数,调用到同一个实现函数fdtrack_dump_impl里,通过参数区分。

fdtrack_dump_impl的核心实现,包含以下几个部分:

1)内部定义一个128个元素的静态StackInfo数组,来对全局stack_traces中记录的调用栈,进行相同调用栈合并计数(通过hash判断是否相同调用栈),并输出最多次数的分配调用栈信息。

android文件属性(深入理解Android系统资源异常之文件描述符异常篇)(35)

2)调用辅助函数fdtrack_iterate,对记录的调用栈信息进行合并与排序处理。

android文件属性(深入理解Android系统资源异常之文件描述符异常篇)(36)

3)如果是fatal类型的,另起一个线程主动abort,通过注释,可以看到是为了避免ART dump runtime stack

android文件属性(深入理解Android系统资源异常之文件描述符异常篇)(37)

fdtrack_iterate,实现了遍历全局调用栈数组stack_traces,对其中每一项有效调用栈记录backtrace,提取出函数name与函数内offset信息,然后调用callback接口进行计算处理。

完成相同调用栈计数合并,以及找到最大计数调用栈。

android文件属性(深入理解Android系统资源异常之文件描述符异常篇)(38)

我们注意到通过在入口处调用android_fdtrack_set_enabled(false),出口处调用android_fdtrack_set_enabled(prev),来实现fdtrack_iterate函数的线程安全处理。

相同调用栈判断,是通过计算调用栈中所有函数name与函数内offset的hash方式来得出的。

android文件属性(深入理解Android系统资源异常之文件描述符异常篇)(39)

五、我们可以从中学到什么

android中fdtrack的实现方式非常值得我们学习。

通过静态埋桩把基础代码固化到libc中,所有链接libc的程序,都会得到静态埋桩。然后通过动态加载库来设置前面静态埋桩的钩子函数为有效函数,从而达到动态使能某个特性的目的。

这种静态埋桩,外加动态库动态使能的设计方法值的我们学习。

六、进一步思考

1)GKI后,定制化feature如何实现?

android最近几个版本,都在强力推行GKI,完全采用GKI后,vendor和OEM厂家的kernel定制化代码实现方式只能有两种选择:

对于必须要实现在内核态的代码,选择ko化,需要严格遵守KMI约束;

对于可以上移到用户空间实现的代码,上移到bionic中实现。

而对于后一种方式,fdtrack方案的设计方法非常值得我们学习借鉴。

2)fdtrack还有没有其他的使用方式?

AOSP中针对fdtrack方案,给出的使用方式示例代码,为system_server创建一个monitor线程周期检测。

那么还有没有其他的使用方式呢?

在这里给大家抛出一个问题,大家可以结合自己的实际使用场景,思考一下。

参考资料:

1.《Unix环境高级编程》

2.《存储基础——文件描述符fd究竟是什么》https://www.qiyacloud.cn/2021/04/2021-04-07/

3.android S版本AOSP源代码 https://android.googlesource.com/platform/bionic/ /refs/heads/master

猜您喜欢: