Android最强保活黑科技的最强技术实现,2024年最新阿里资深Android开发带你搞懂Framework

简介: Android最强保活黑科技的最强技术实现,2024年最新阿里资深Android开发带你搞懂Framework

[图片上传中…(image-237bb2-1586154434812-4)]

咳咳。不禁想到ioctl的方式我也可以尝试着实现一下。ioctl是一个linux标准方法,那么我们就直奔主题看看,binder是什么,ioctl怎么跟binder driver通信。

Binder介绍

Binder是Android系统提供的一种IPC机制。每个Android的进程,都可以有一块用户空间和内核空间。用户空间在不同进程间不能共享,内核空间可以共享。Binder就是一个利用可以共享的内核空间,完成高性能的进程间通信的方案。

Binder通信采用C/S架构,从组件视角来说,包含Client、Server、ServiceManager以及binder驱动,其中ServiceManager用于管理系统中的各种服务。如图:

可以看到,注册服务、获取服务、使用服务,都是需要经过binder通信的。

  • Server通过注册服务的Binder通信把自己托管到ServiceManager
  • Client端可以通过ServiceManager获取到Server
  • Client端获取到Server后就可以使用Server的接口了

Binder通信的代表类是BpBinder(客户端)和BBinder(服务端)。

ps:有关binder的详细知识,大家可以查看Gityuan大佬的Binder系列文章。

ioctl函数

ioctl(input/output control)是一个专用于设备输入输出操作的系统调用,它诞生在这样一个背景下:

操作一个设备的IO的传统做法,是在设备驱动程序中实现write的时候检查一下是否有特殊约定的数据流通过,如果有的话,后面就跟着控制命令(socket编程中常常这样做)。但是这样做的话,会导致代码分工不明,程序结构混乱。所以就有了ioctl函数,专门向驱动层发送或接收指令。

Linux操作系统分为了两层,用户层和内核层。我们的普通应用程序处于用户层,系统底层程序,比如网络栈、设备驱动程序,处于内核层。为了保证安全,操作系统要阻止用户态的程序直接访问内核资源。一个Ioctl接口是一个独立的系统调用,通过它用户空间可以跟设备驱动沟通了。函数原型:

int ioctl(int fd, int request, …);

作用:通过IOCTL函数实现指令的传递

  • fd 是用户程序打开设备时使用open函数返回的文件描述符
  • request是用户程序对设备的控制命令
  • 后面的省略号是一些补充参数,和cmd的意义相关

应用程序在调用ioctl进行设备控制时,最后会调用到设备注册struct file_operations结构体对象时的unlocked_ioctl或者compat_ioctl两个钩子上,例如Binder驱动的这两个钩子是挂到了binder_ioctl方法上:

static const struct file_operations binder_fops = {
.owner = THIS_MODULE,
.poll = binder_poll,
.unlocked_ioctl = binder_ioctl,
.compat_ioctl = binder_ioctl,
.mmap = binder_mmap,
.open = binder_open,
.flush = binder_flush,
.release = binder_release,
};
它的实现如下:
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
/根据不同的命令,调用不同的处理函数进行处理/
switch (cmd) {
case BINDER_WRITE_READ:
/读写命令,数据传输,binder IPC通信的核心逻辑/
ret = binder_ioctl_write_read(filp, cmd, arg, thread);
break;
case BINDER_SET_MAX_THREADS:
/设置最大线程数,直接将值设置到proc结构的max_threads域中。/
break;
case BINDER_SET_CONTEXT_MGR:
/设置Context manager,即将自己设置为ServiceManager,详见3.3/
break;
case BINDER_THREAD_EXIT:
/binder线程退出命令,释放相关资源/
break;
case BINDER_VERSION: {
/获取binder驱动版本号,在kernel4.4版本中,32位该值为7,64位版本该值为8/
break;
}
return ret;
}

具体内核层的实现,我们就不关心了。到这里我们了解到,Binder在Android系统中会有一个设备节点,调用ioctl控制这个节点时,实际上会调用到内核态的binder_ioctl方法。

为了利用ioctl启动Android Service,必然是需要用ioctl向binder驱动写数据,而这个控制命令就是BINDER_WRITE_READ。binder驱动层的一些细节我们在这里就不关心了。那么在什么地方会用ioctl 向binder写数据呢?

IPCThreadState.talkWithDriver

阅读Gityuan的Binder系列6—获取服务(getService)一节,在binder模块下IPCThreadState.cpp中有这样的实现(源码目录:

frameworks/native/libs/binder/IPCThreadState.cpp):
status_t IPCThreadState::talkWithDriver(bool doReceive) {
binder_write_read bwr;
bwr.write_buffer = (uintptr_t)mOut.data();
status_t err;
do {
//通过ioctl不停的读写操作,跟Binder Driver进行通信
if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
err = NO_ERROR;
} while (err == -EINTR); //当被中断,则继续执行
return err;
}

可以看到ioctl跟binder driver交互很简单,一个参数是mProcess->mDriverFD,一个参数是BINDER_WRITE_READ,另一个参数是binder_write_read结构体,很幸运的是,NDK中提供了linux/android/binder.h这个头文件,里面就有binder_write_read这个结构体,以及BINDER_WRITE_READ常量的定义。

[惊不惊喜意不意外]

#include
struct binder_write_read {
binder_size_t write_size;
binder_size_t write_consumed;
binder_uintptr_t write_buffer;
binder_size_t read_size;
binder_size_t read_consumed;
binder_uintptr_t read_buffer;
};
#define BINDER_WRITE_READ _IOWR(‘b’, 1, struct binder_write_read)

这意味着,这些结构体和宏定义很可能是版本兼容的。

那我们只需要到时候把数据揌到binder_write_read结构体里面,就可以进行ioctl系统调用了!


/dev/binder
再来看看mProcess->mDriverFD是什么东西。mProcess也就是ProcessState.cpp(源码目录:frameworks/native/libs/binder/ProcessState.cpp):
ProcessState::ProcessState(const char *driver)mDriverName(String8(driver))
, mDriverFD(open_driver(driver))
, …
{}
从ProcessState的构造函数中得知,mDriverFD由open_driver方法初始化。
static int open_driver(const char *driver) {
int fd = open(driver, O_RDWR | O_CLOEXEC);
if (fd >= 0) {
int vers = 0;
status_t result = ioctl(fd, BINDER_VERSION, &vers);
}
return fd;
}
ProcessState在哪里实例化呢?
sp ProcessState::self() {
if (gProcess != nullptr) {
return gProcess;
}
gProcess = new ProcessState(kDefaultDriver);
return gProcess;
}

可以看到,ProcessState的gProcess是一个全局单例对象,这意味着,在当前进程中,open_driver只会执行一次,得到的 mDriverFD 会一直被使用。

const char* kDefaultDriver = “/dev/binder”;

而open函数操作的这个设备节点就是/dev/binder。

纳尼?在应用层直接操作设备节点?Gityuan大佬不会骗我吧?一般来说,Android系统在集成SELinux的安全机制之后,普通应用甚至是系统应用,都不能直接操作一些设备节点,除非有SELinux规则,给应用所属的域或者角色赋予了那样的权限。

看看文件权限:

➜ ~ adb shell

chiron:/ $ ls -l /dev/binder

crw-rw-rw- 1 root root 10, 49 1972-07-03 18:46 /dev/binder

可以看到,/dev/binder设备对所有用户可读可写。

再看看,SELinux权限:

chiron:/ $ ls -Z /dev/binder

u:object_r:binder_device:s0 /dev/binder

查看源码中对binder_device角色的SELinux规则描述:

allow domain binder_device:chr_file rw_file_perms;

也就是所有domain对binder的字符设备有读写权限,而普通应用属于domain。

既然这样,肝它!

写个Demo试一下

验证一下上面的想法,看看ioctl给binder driver发数据好不好使。

1、打开设备

int fd = open(“/dev/binder”, O_RDWR | O_CLOEXEC);
if (fd < 0) {
LOGE(“Opening ‘%s’ failed: %s\n”, “/dev/binder”, strerror(errno));
} else {
LOGD(“Opening ‘%s’ success %d: %s\n”, “/dev/binder”, fd, strerror(errno));
}
2、ioctl
Parcel *parcel = new Parcel;
parcel->writeString16(String16(“test”));
binder_write_read bwr;
bwr.write_size = parcel->dataSize();
bwr.write_buffer = (binder_uintptr_t) parcel->data();
int ret = ioctl(fd, BINDER_WRITE_READ, bwr);
LOGD(“ioctl result is %d: %s\n”, ret, strerror(errno));
3、查看日志
D/KeepAlive: Opening ‘/dev/binder’ success, fd is 35
D/KeepAlive: ioctl result is -1: Invalid argument

打开设备节点成功了,耶✌️!但是ioctl失败了🤔,失败原因是Invalid argument,也就是说可以通信,但是Parcel数据有问题。来看看数据应该是什么样的。

binder_write_read结构体数据封装

IPCThreadState.talkWithDriver方法中,bwr.write_buffer指针指向了mOut.data(),显然mOut是一个Parcel对象。
binder_write_read bwr;
bwr.write_buffer = (uintptr_t)mOut.data();
再来看看什么时候会向mOut中写数据:
status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags,
int32_t handle, uint32_t code, const Parcel& data, status_t* statusBuffer)
{
binder_transaction_data tr;
tr.data.ptr.buffer = data.ipcData();
mOut.writeInt32(cmd);
mOut.write(&tr, sizeof(tr));
return NO_ERROR;
}
writeTransactionData方法中,会往mOut中写入一个binder_transaction_data结构体数据,binder_transaction_data结构体中又包含了作为参数传进来的data Parcel对象。
writeTransactionData方法会被transact方法调用:
status_t IPCThreadState::transact(int32_t handle, uint32_t code, const Parcel& data,
Parcel* reply, uint32_t flags) {
status_t err = data.errorCheck(); // 数据错误检查
flags |= TF_ACCEPT_FDS;
if (err == NO_ERROR) {
// 传输数据
err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);
}
// 默认情况下,都是采用非oneway的方式, 也就是需要等待服务端的返回结果
if ((flags & TF_ONE_WAY) == 0) {
if (reply) {
//等待回应事件
err = waitForResponse(reply);
}else {
Parcel fakeReply;
err = waitForResponse(&fakeReply);
}
} else {
err = waitForResponse(NULL, NULL);
}
return err;
}

IPCThreadState是跟binder driver真正进行交互的类。每个线程都有一个IPCThreadState,每个IPCThreadState中都有一个mIn、一个mOut。成员变量mProcess保存了ProcessState变量(每个进程只有一个)。

接着看一下一次Binder调用的时序图:

Binder介绍一节中说过,BpBinder是Binder Client,上层想进行进程间Binder通信时,会调用到BpBinder的transact方法,进而调用到IPCThreadState的transact方法。来看看BpBinder的transact方法的定义:

status_t BpBinder::transact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) {
if (mAlive) {
status_t status = IPCThreadState::self()->transact(mHandle, code, data, reply, flags);
if (status == DEAD_OBJECT) mAlive = 0;
return status;
}
return DEAD_OBJECT;
}

BpBinder::transact方法的code/data/reply/flags这几个参数都是调用的地方传过来的,现在唯一不知道的就是mHandle是什么东西。mHandle是BpBinder(也就是Binder Client)的一个int类型的局部变量(句柄),只要拿到了这个handle就相当于拿到了BpBinder。

ioctl启动Service分几步?

下面是在依赖libbinder.so时,启动Service的步骤:

// 获取ServiceManager
sp sm = defaultServiceManager();
// 获取ActivityManager binder
sp binder = sm->getService(String16(“activity”));
// 传输parcel
int result = binder.get()->transact(code, parcel, NULL, 0);

1、获取到IServiceManager Binder Client;

2、从ServiceManager中获取到ActivityManager Binder Client;

3、调用ActivityManager binder的transact方法传输Service的Parcel数据。

通过ioctl启动Service也应该是类似的步骤:

1、获取到ServiceManager的mHandle句柄;

2、进行binder调用获取到ActivityManager的mHandle句柄;

3、进行binder调用传输启动Service的指令数据。

这里有几个问题:

1、不依赖libbinder.so时,ndk中没有Parcel类的定义,parcel数据哪里来,怎么封装?

2、如何获取到BpBinder的mHandle句柄?

如何封装Parcel数据

Parcel类是Binder进程间通信的一个基础的、必不可少的数据结构,往Parcel中写入的数据实际上是写入到了一块内部分配的内存上,最后把这个内存地址封装到binder_write_read结构体中。Parcel作为一个基础的数据结构,和Binder相关类是可以解耦的,可以直接拿过来使用,我们可以根据需要对有耦合性的一些方法进行裁剪。

c++ Parcel类路径:frameworks/native/libs/binder/Parcel.cpp

jni Parcel类路径:frameworks/base/core/jni/android_os_Parcel.cpp

如何获取到BpBinder的mHandle句柄

具体流程参考Binder系列4—获取ServiceManager

1、获取ServiceManager的mHandle句柄

defaultServiceManager()方法用来获取gDefaultServiceManager对象,gDefaultServiceManager是ServiceManager的单例。

sp defaultServiceManager() {
if (gDefaultServiceManager != NULL) return gDefaultServiceManager;
while (gDefaultServiceManager == NULL) {
gDefaultServiceManager = interface_cast(
ProcessState::self()->getContextObject(NULL));
}
}
return gDefaultServiceManager;
}
getContextObject方法用来获取BpServiceManager对象(BpBinder),查看其定义:
sp ProcessState::getContextObject(const sp& /caller/) {
sp context = getStrongProxyForHandle(0);
return context;
}

可以发现,getStrongProxyForHandle是一个根据handle获取IBinder对象的方法,而这里handle的值为0,可以得知,ServiceManager的mHandle恒为0

2、获取ActivityManager的mHandle句柄

获取ActivityManager的c++方法是:

sp binder = serviceManager->getService(String16(“activity”));
BpServiceManager.getService:
virtual sp getService(const String16& name) const {
sp svc = checkService(name);
if (svc != NULL) return svc;
return NULL;
}
BpServiceManager.checkService:
virtual sp checkService( const String16& name) const {
Parcel data, reply;
//写入RPC头
data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
//写入服务名
data.writeString16(name);
remote()->transact(CHECK_SERVICE_TRANSACTION, data, &reply);
return reply.readStrongBinder();
}

可以看到,CHECK_SERVICE_TRANSACTION这个binder调用是有返回值的,返回值会写到reply中,通过reply.readStrongBinder()方法,即可从reply这个Parcel对象中读取到ActivityManager的IBinder。每个Binder对象必须要有它自己的mHandle句柄,不然,transact操作是没办法进行的。所以,很有可能,Binder的mHandle的值是写到reply这个Parcel里面的。

看看reply.readStrongBinder()方法搞了什么鬼:

sp Parcel::readStrongBinder() const {
sp val;
readNullableStrongBinder(&val);
return val;
}
status_t Parcel::readNullableStrongBinder(sp* val) const {
return unflattenBinder(val);
}


相关文章
|
2天前
|
Android开发 开发者 UED
探索安卓应用开发中的UI设计趋势
随着移动应用市场的不断发展和用户需求的变化,安卓应用的UI设计趋势也在不断演进。本文将深入探讨当前安卓应用开发中的UI设计趋势,包括暗黑模式、原生化设计、动效设计等方面的发展趋势,为开发者提供参考和启发。
|
2天前
|
机器学习/深度学习 传感器 vr&ar
探索安卓与iOS平台下的虚拟现实技术发展
随着移动设备的普及和技术的不断进步,安卓和iOS平台上的虚拟现实(VR)技术发展迅速。本文将探讨安卓与iOS平台下虚拟现实技术的最新进展,包括技术特点、应用场景以及未来发展趋势。
6 0
|
2天前
|
安全 网络安全 量子技术
网络安全与信息安全:漏洞、加密技术与安全意识的探索安卓应用开发中的内存管理策略
【5月更文挑战第31天】随着互联网的普及,网络安全问题日益严重。本文将深入探讨网络安全漏洞、加密技术以及安全意识等方面的问题,以期提高公众对网络安全的认识和防范能力。
|
2天前
|
机器学习/深度学习 Linux 5G
揭秘安卓系统背后的技术奇迹
【5月更文挑战第31天】本文将深入探讨安卓系统背后的技术原理,从其发展历程、核心架构到最新的技术创新,全面解析安卓系统的技术魅力。通过对比分析,我们将揭示安卓系统在移动设备领域的独特优势和未来发展趋势。
|
2天前
|
机器学习/深度学习 算法 Android开发
安卓应用开发:打造高效通知管理系统
【5月更文挑战第31天】在移动应用的海洋中,用户经常被各种推送通知所困扰。一个精心设计的通知管理系统对于提升用户体验至关重要。本文将探讨如何在安卓平台上开发一个高效的通知管理系统,包括系统设计原则、实现机制以及性能优化策略,旨在为开发者提供一套可行的解决方案,以减少用户干扰并提高应用的用户留存率。
|
3天前
|
物联网 区块链 Android开发
构建高效Android应用:Kotlin与Jetpack的实践之路未来技术的融合潮流:区块链、物联网与虚拟现实的交汇点
【5月更文挑战第30天】 在移动开发领域,效率和性能始终是开发者追求的核心。随着技术的不断进步,Kotlin语言以其简洁性和现代化特性成为Android开发的新宠。与此同时,Jetpack组件为应用开发提供了一套经过实践检验的库、工具和指南,旨在简化复杂任务并帮助提高应用质量。本文将深入探索如何通过Kotlin结合Jetpack组件来构建一个既高效又稳定的Android应用,并分享在此过程中的最佳实践和常见陷阱。
|
3天前
|
缓存 监控 Android开发
Android 开发中的内存优化策略
【5月更文挑战第30天】在移动应用的开发过程中,性能和用户体验始终是核心关注点。对于基于Android平台的应用程序,有效的内存管理是确保流畅运行和优异性能的关键因素之一。本文将深入探讨Android开发中常见的内存问题,并提出一系列实用的内存优化策略。我们将从内存泄漏的识别与防止开始,到合理使用内存缓存技巧,以及高效的数据结构选择等方面进行详细阐述。通过这些策略的实施,开发者可以显著减少应用的内存占用,提升应用的稳定性和响应速度,进而改善最终用户的体验。
|
3天前
|
搜索推荐 Android开发 开发者
安卓应用开发:打造高效通知管理系统
【5月更文挑战第30天】在移动应用的世界中,通知管理是提升用户体验的关键。本文将深入探讨如何在安卓平台上实现一个高效、用户友好的通知管理系统。我们将分析安卓通知渠道的优势,探索定制化通知的设计理念,并提供实用的编程技巧以优化通知的显示和用户交互。通过实例和代码片段,读者将学习如何有效地利用安卓系统服务来创建静默但强大的后台通知逻辑,从而在维持用户参与度的同时避免打扰用户。
|
4天前
|
编解码 测试技术 Android开发
安卓应用开发:打造高效用户界面的五大关键策略
【5月更文挑战第29天】 在竞争激烈的移动应用市场中,一个直观高效的用户界面(UI)对于吸引和保持用户至关重要。针对安卓平台,本文将探讨五种提升应用UI效率与用户满意度的关键策略。这些策略不仅基于最新的设计趋势,还结合了性能优化的实践技巧,旨在为开发者提供实用指南,帮助他们创建流畅、响应迅速且美观的应用体验。
|
4天前
|
安全 网络安全 Android开发
安卓应用开发:打造流畅的用户体验网络安全与信息安全:防御前线的技术与意识
【5月更文挑战第29天】在移动应用市场中,用户对流畅体验的要求日益提高。针对安卓平台,开发者需深入理解系统原理、内存管理及性能优化策略。本文旨在探讨安卓应用开发中的关键技术和工具,包括布局优化、内存泄漏防治以及使用Kotlin语言的优势,以期为读者提供实用的性能提升指导和实践建议。