日期: 2021 年 4 月 21 日

Android Binder IPC分析

1.binder通信概述

binder通信是一种client-server的通信结构,
1.从表面上来看,是client通过获得一个server的代理接口,对server进行直接调用;
2.实际上,代理接口中定义的方法与server中定义的方法是一一对应的;
3.client调用某个代理接口中的方法时,代理接口的方法会将client传递的参数打包成为Parcel对象;
4.代理接口将该Parcel发送给内核中的binder driver.
5.server会读取binder driver中的请求数据,如果是发送给自己的,解包Parcel对象,处理并将结果返回;
6.整个的调用过程是一个同步过程,在server处理的时候,client会block住。

 

%title插图%num

2.service manager

Service Manager是一个linux级的进程,顾名思义,就是service的管理器。这里的service是什么概念呢?这里的service的概念和init过程中init.rc中的service是不同,init.rc中的service是都是linux进程,但是这里的service它并不一定是一个进程,也就是说可能一个或多个service属于同一个linux进程。在这篇文章中不加特殊说明均指android native端的service。

任何service在被使用之前,均要向SM(Service Manager)注册,同时客户端需要访问某个service时,应该首先向SM查询是否存在该服务。如果SM存在这个service,那么会将该service的handle返回给client,handle是每个service的唯一标识符。

SM的入口函数在service_manager.c中,下面是SM的代码部分
int main(int argc, char **argv)
{
struct binder_state *bs;
void *svcmgr = BINDER_SERVICE_MANAGER;

bs = binder_open(128*1024);

if (binder_become_context_manager(bs)) {
LOGE(“cannot become context manager (%s)/n”, strerror(errno));
return -1;
}

svcmgr_handle = svcmgr;
binder_loop(bs, svcmgr_handler);
return 0;
}

这个进程的主要工作如下:
1.初始化binder,打开/dev/binder设备;在内存中为binder映射128K字节空间;
2.指定SM对应的代理binder的handle为0,当client尝试与SM通信时,需要创建一个handle为0的代理binder,这里的代理binder其实就是*节中描述的那个代理接口;

3.通知binder driver(BD)使SM成为BD的context manager;
4.维护一个死循环,在这个死循环中,不停地去读内核中binder driver,查看是否有可读的内容;即是否有对service的操作要求, 如果有,则调用svcmgr_handler回调来处理请求的操作。

5.SM维护了一个svclist列表来存储service的信息。

%title插图%num

这里需要声明一下,当service在向SM注册时,该service就是一个client,而SM则作为了server。而某个进程需要与service通信时,此时这个进程为client,service才作为server。因此service不一定为server,有时它也是作为client存在的。

由于下面几节会介绍一些与binder通信相关的几个概念,所以将SM的功能介绍放在了后面的部分来讲。

应用和service之间的通信会涉及到2次binder通信。

1.应用向SM查询service是否存在,如果存在获得该service的代理binder,此为一次binder通信;
2.应用通过代理binder调用service的方法,此为第二次binder通信。

3.ProcessState

ProcessState是以单例模式设计的。每个进程在使用binder机制通信时,均需要维护一个ProcessState实例来描述当前进程在binder通信时的binder状态。
ProcessState有如下2个主要功能:
1.创建一个thread,该线程负责与内核中的binder模块进行通信,称该线程为Pool thread;
2.为指定的handle创建一个BpBinder对象,并管理该进程中所有的BpBinder对象。

3.1 Pool thread

在Binder IPC中,所有进程均会启动一个thread来负责与BD来直接通信,也就是不停的读写BD,这个线程的实现主体是一个IPCThreadState对象,下面会介绍这个类型。

下面是 Pool thread的启动方式:

ProcessState::self()->startThreadPool();

3.2 BpBinder获取

BpBinder主要功能是负责client向BD发送调用请求的数据。它是client端binder通信的核心对象,通过调用transact函数向BD发送调用请求的数据,它的构造函数如下:

BpBinder(int32_t handle);
通过BpBinder的构造函数发现,BpBinder会将当前通信中server的handle记录下来,当有数据发送时,会通知BD数据的发送目标。

ProcessState通过如下方式来获取BpBinder对象:

ProcessState::self()->getContextObject(handle);

在这个过程中,ProcessState会维护一个BpBinder的vector mHandleToObject,每当ProcessState创建一个BpBinder的实例时,回去查询mHandleToObject,如果对应的handle已经有binder指针,那么不再创建,否则创建binder并插入到mHandleToObject中。
ProcessState创建的BpBinder实例,一般情况下会作为参数构建一个client端的代理接口,这个代理接口的形式为BpINTERFACE,例如在与SM通信时,client会创建一个代理接口BpServiceManager.

4.IPCThreadState

IPCThreadState也是以单例模式设计的。由于每个进程只维护了一个ProcessState实例,同时ProcessState只启动一个Pool thread,也就是说每一个进程只会启动一个Pool thread,因此每个进程则只需要一个IPCThreadState即可。
Pool thread的实际内容则为:
IPCThreadState::self()->joinThreadPool();

 

ProcessState中有2个Parcel成员,mIn和mOut,Pool thread会不停的查询BD中是否有数据可读,如果有将其读出并保存到mIn,同时不停的检查mOut是否有数据需要向BD发送,如果有,则将其内容写入到BD中,总而言之,从BD中读出的数据保存到mIn,待写入到BD中的数据保存在了mOut中。

ProcessState中生成的BpBinder实例通过调用IPCThreadState的transact函数来向mOut中写入数据,这样的话这个binder IPC过程的client端的调用请求的发送过程就明了了。

 

IPCThreadState有两个重要的函数,talkWithDriver函数负责从BD读写数据,executeCommand函数负责解析并执行mIn中的数据。

%title插图%num

5.主要基类

5.1基类IInterface

为server端提供接口,它的子类声明了service能够实现的所有的方法;

5.2基类IBinder
BBinder与BpBinder均为IBinder的子类,因此可以看出IBinder定义了binder IPC的通信协议,BBinder与BpBinder在这个协议框架内进行的收和发操作,构建了基本的binder IPC机制。
5.3基类BpRefBase
client端在查询SM获得所需的的BpBinder后,BpRefBase负责管理当前获得的BpBinder实例。

 

 

6.两个接口类

6.1 BpINTERFACE

如果client想要使用binder IPC来通信,那么首先会从SM出查询并获得server端service的BpBinder,在client端,这个对象被认为是server端的远程代理。为了能够使client能够想本地调用一样调用一个远程server,server端需要向client提供一个接口,client在在这个接口的基础上创建一个BpINTERFACE,使用这个对象,client的应用能够想本地调用一样直接调用server端的方法。而不用去关心具体的binder IPC实现。
下面看一下BpINTERFACE的原型:
class BpINTERFACE : public BpInterface<IINTERFACE>

顺着继承关系再往上看
template<typename INTERFACE>
class BpInterface : public INTERFACE, public BpRefBase

BpINTERFACE分别继承自INTERFACE,和BpRefBase;
● BpINTERFACE既实现了service中各方法的本地操作,将每个方法的参数以Parcel的形式发送给BD。
例如BpServiceManager的
virtual status_t addService(const String16& name, const sp<IBinder>& service)
{
Parcel data, reply;
data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
data.writeString16(name);
data.writeStrongBinder(service);
status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply);
return err == NO_ERROR ? reply.readExceptionCode() : err;
}
● 同时又将BpBinder作为了自己的成员来管理,将BpBinder存储在mRemote中,BpServiceManager通过调用BpRefBase的remote()来获得BpBinder指针。

 

6.2 BnINTERFACE

在定义android native端的service时,每个service均继承自BnINTERFACE(INTERFACE为service name)。BnINTERFACE类型定义了一个onTransact函数,这个函数负责解包收到的Parcel并执行client端的请求的方法。

顺着BnINTERFACE的继承关系再往上看,
class BnINTERFACE: public BnInterface<IINTERFACE>

IINTERFACE为client端的代理接口BpINTERFACE和server端的BnINTERFACE的共同接口类,这个共同接口类的目的就是保证service方法在C-S两端的一致性。

再往上看
class BnInterface : public INTERFACE, public BBinder

同时我们发现了BBinder类型,这个类型又是干什么用的呢?既然每个service均可视为一个binder,那么真正的server端的binder的操作及状态的维护就是通过继承自BBinder来实现的。可见BBinder是service作为binder的本质所在。

那么BBinder与BpBinder的区别又是什么呢?

其实它们的区别很简单,BpBinder是client端创建的用于消息发送的代理,而BBinder是server端用于接收消息的通道。查看各自的代码就会发现,虽然两个类型均有transact的方法,但是两者的作用不同,BpBinder的transact方法是向IPCThreadState实例发送消息,通知其有消息要发送给BD;而BBinder则是当IPCThreadState实例收到BD消息时,通过BBinder的transact的方法将其传递给它的子类BnSERVICE的onTransact函数执行server端的操作。

 

7. Parcel

Parcel是binder IPC中的*基本的通信单元,它存储C-S间函数调用的参数.但是Parcel只能存储基本的数据类型,如果是复杂的数据类型的话,在存储时,需要将其拆分为基本的数据类型来存储。

简单的Parcel读写不再介绍,下面着重介绍一下2个函数

 

7.1 writeStrongBinder

当client需要将一个binder向server发送时,可以调用此函数。例如
virtual status_t addService(const String16& name, const sp<IBinder>& service)
{
Parcel data, reply;
data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
data.writeString16(name);
data.writeStrongBinder(service);
status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply);
return err == NO_ERROR ? reply.readExceptionCode() : err;
}

看一下writeStrongBinder的实体
status_t Parcel::writeStrongBinder(const sp<IBinder>& val)
{
return flatten_binder(ProcessState::self(), val, this);
}

接着往里看flatten_binder
status_t flatten_binder(const sp<ProcessState>& proc,
const sp<IBinder>& binder, Parcel* out)
{
flat_binder_object obj;

obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
if (binder != NULL) {
IBinder *local = binder->localBinder();
if (!local) {
BpBinder *proxy = binder->remoteBinder();
if (proxy == NULL) {
LOGE(“null proxy”);
}
const int32_t handle = proxy ? proxy->handle() : 0;
obj.type = BINDER_TYPE_HANDLE;
obj.handle = handle;
obj.cookie = NULL;
} else {
obj.type = BINDER_TYPE_BINDER;
obj.binder = local->getWeakRefs();
obj.cookie = local;
}
} else {
obj.type = BINDER_TYPE_BINDER;
obj.binder = NULL;
obj.cookie = NULL;
}

return finish_flatten_binder(binder, obj, out);
}

还是拿addService为例,它的参数为一个BnINTERFACE类型指针,BnINTERFACE又继承自BBinder,
BBinder* BBinder::localBinder()
{
return this;
}
所以写入到Parcel的binder类型为BINDER_TYPE_BINDER,同时你在阅读SM的代码时会发现如果SM收到的service的binder类型不为BINDER_TYPE_HANDLE时,SM将不会将此service添加到svclist,但是很显然每个service的添加都是成功的,addService在开始传递的binder类型为BINDER_TYPE_BINDER,SM收到的binder类型为BINDER_TYPE_HANDLE,那么这个过程当中究竟发生了什么?
为了搞明白这个问题,花费我很多的事件,*终发现了问题的所在,原来在BD中做了如下操作(drivers/staging/android/Binder.c):

static void binder_transaction(struct binder_proc *proc,
struct binder_thread *thread,
struct binder_transaction_data *tr, int reply)
{
……………………………………

if (fp->type == BINDER_TYPE_BINDER)
fp->type = BINDER_TYPE_HANDLE;
else
fp->type = BINDER_TYPE_WEAK_HANDLE;
fp->handle = ref->desc;
……………………………………
}

 

阅读完addService的代码,你会发现SM只是保存了service binder的handle和service的name,那么当client需要和某个service通信了,如何获得service的binder呢?看下一个函数

7.2 readStrongBinder

当server端收到client的调用请求之后,如果需要返回一个binder时,可以向BD发送这个binder,当IPCThreadState实例收到这个返回的Parcel时,client可以通过这个函数将这个被server返回的binder读出。

sp<IBinder> Parcel::readStrongBinder() const
{
sp<IBinder> val;
unflatten_binder(ProcessState::self(), *this, &val);
return val;
}

往里查看unflatten_binder

status_t unflatten_binder(const sp<ProcessState>& proc,
const Parcel& in, sp<IBinder>* out)
{
const flat_binder_object* flat = in.readObject(false);

if (flat) {
switch (flat->type) {
case BINDER_TYPE_BINDER:
*out = static_cast<IBinder*>(flat->cookie);
return finish_unflatten_binder(NULL, *flat, in);
case BINDER_TYPE_HANDLE:
*out = proc->getStrongProxyForHandle(flat->handle);
return finish_unflatten_binder(
static_cast<BpBinder*>(out->get()), *flat, in);
}
}
return BAD_TYPE;
}

发现如果server返回的binder类型为BINDER_TYPE_BINDER的话,也就是返回一个binder引用的话,直接获取这个binder;如果server返回的binder类型为BINDER_TYPE_HANDLE时,也就是server返回的仅仅是binder的handle,那么需要重新创建一个BpBinder返回给client。

有上面的代码可以看出,SM保存的service的binder仅仅是一个handle,而client则是通过向SM获得这个handle,从而重新构建代理binder与server通信。

这里顺带提一下一种特殊的情况,binder通信的双方即可作为client,也可以作为server.也就是说此时的binder通信是一个半双工的通信。那么在这种情况下,操作的过程会比单工的情况复杂,但是基本的原理是一样的,有兴趣可以分析一下MediaPlayer和MediaPlayerService的例子。

 

8. 经典桥段分析

main_ mediaserver.cpp
int main(int argc, char** argv)
{

//创建进程mediaserver的ProcessState实例
sp<ProcessState> proc(ProcessState::self());

//获得SM的BpServiceManager
sp<IServiceManager> sm = defaultServiceManager();
LOGI(“ServiceManager: %p”, sm.get());

//添加mediaserver中支持的service。
AudioFlinger::instantiate();
MediaPlayerService::instantiate();
CameraService::instantiate();
AudioPolicyService::instantiate();

//启动ProcessState的pool thread
ProcessState::self()->startThreadPool();

//这一步有重复之嫌,加不加无关紧要。
IPCThreadState::self()->joinThreadPool();
}

 

9. Java 层的binder机制

了解了native通信机制后,再去分析JAVA层的binder机制,就会很好理解了。它只是对native的binder做了一个封装。这一部分基本上没有太复杂的过程,这里不再赘述了。

Android Binder机制的详解

1.Binder机制简介
Android Binder是源于Palm的OpenBinder,它为android设备跨进程访问而设计。Binder机制从表现上看可以实现java 和native层的对象实例可以从一个进程访问到另一个进程,从而透明的实现跨进程的调用。调用者感觉不到自己的请求是在另一个进程中完成并返回结果的。在android系统中使用Binder机制*多的是系统服务,这些服务一般运行在特定的进程中。服务的使用者成为Client,Client一般是应用程序,是跨进程访问的请求发起者。运行行在特定进程中的服务被成为server,这些server用于接受client端发起的请求并处理具体相关业务并返回结果。

Android系统中的Binder机制涉及java和c++层两部分,使java和native层都可以实现Binder的client、server逻辑并通过系统的/dev/binder虚拟设备提供的跨进程能力发起请求和接受请求处理业务返回结果。其中java层的Binder是对C++层Binder机制的上层封装,通过JNI的方式调用到C++的实现逻辑中。

我将通过分别分析C++层的实现和java的封装两部分分析Binder机制。

2.C++层Binder的实现
我通过分析MediaServer的实现逻辑通过阅读源码了解一下Binder的实现逻辑.MediaServer执行程序的入口是main()方法:

…/frameworks/av/media/mediaserver/main_mediaserver.cpp
int main(int argc __unused, char** argv)
{
…………………

//获取一个ProcessState对象,同时通过pen_driver()打开/dev/binder的虚拟设备,通过mmap()为binder分配一块空间用于通信读写,通过
//iotrl()为binder指定线程数
sp<ProcessState> proc(ProcessState::self());
//获取一个IServerManager对象,用于服务注册
sp<IServiceManager> sm = defaultServiceManager();
ALOGI(“ServiceManager: %p”, sm.get());
//初始化音频系统AudioFlinger
AudioFlinger::instantiate();
//多媒体服务初始化
MediaPlayerService::instantiate();
ResourceManagerService::instantiate();
//cameraServer 相机服务
CameraService::instantiate();
//音频系统AudioPolicy服务
AudioPolicyService::instantiate();
SoundTriggerHwService::instantiate();
RadioService::instantiate();
registerExtensions();
//创建线程池?
ProcessState::self()->startThreadPool();
//加入线程池?
IPCThreadState::self()->joinThreadPool();

………………..
}

1.1ProcessState的作用
从main_mediaServer.cpp的main()方法中可以看到 sp proc(ProcessState::self())获取一个ProcessState对象,先看一下ProcessState::self()函数都做了什么:

sp<ProcessState> ProcessState::self()
{
Mutex::Autolock _l(gProcessMutex);
if (gProcess != NULL) {
return gProcess;
}
//创建一个新的ProcessState对象
gProcess = new ProcessState;
return gProcess;
}

实现很简单,self()方法就是一个简单的单例调用new 创建了一个ProcessState对象。那看一下ProcessState的构造方法都做了些什么:

…/frameworks/native/libs/binder/ProcessState.cpp

ProcessState::ProcessState()
: mDriverFD(open_driver()) //打开Binder
, mVMStart(MAP_FAILED) //映射内存的的起始地址
, mThreadCountLock(PTHREAD_MUTEX_INITIALIZER)
, mThreadCountDecrement(PTHREAD_COND_INITIALIZER)
, mExecutingThreadsCount(0)
, mMaxThreads(DEFAULT_MAX_BINDER_THREADS)
, mManagesContexts(false)
, mBinderContextCheckFunc(NULL)
, mBinderContextUserData(NULL)
, mThreadPoolStarted(false)
, mThreadPoolSeq(1)
{
if (mDriverFD >= 0) {
#if !defined(HAVE_WIN32_IPC)
//为Binder分配一块内存用于存取数据
// mmap the binder, providing a chunk of virtual address space to receive transactions.
mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);
if (mVMStart == MAP_FAILED) {
// *sigh*
ALOGE(“Using /dev/binder failed: unable to mmap transaction memory.\n”);
close(mDriverFD);
mDriverFD = -1;
}
#else
mDriverFD = -1;
#endif
}

LOG_ALWAYS_FATAL_IF(mDriverFD < 0, “Binder driver could not be opened. Terminating.”);
}

在ProcessState的构造函数中首先调用了open_driver()方法初始化了一个ProcessState的变量mDriverFD,这个open_driver()方法很重要,它打开/dev/binder这个虚拟设备为binder通信做准备。在打开binder设备后又调用系统函数mmap() 为打开的binder设备在内存中映射了一块内存空间并将内存地址返回给变量mVMStart。

那先看一下open_driver()是怎么样打开binder驱动设备的。

…/frameworks/native/libs/binder/ProcessState.cpp
static int open_driver()
{
//打开/dev/binder虚拟设备
int fd = open(“/dev/binder”, O_RDWR);

“““““““““““`
//通过ioctl()告诉驱动*多支持的线程数 15
size_t maxThreads = DEFAULT_MAX_BINDER_THREADS;
result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads);
return fd;
}

在open_driver()中直接调用了linux系统函数open()打开了/dev/binder设备并为启动的Binder虚拟设备通过ioctl()[系统函数]为binder 设备指定了*大线程数;

分析到这里基本上就是main_mediaserver–>main()函数中sp proc(ProcessState::self())这句代码做的事情。总结起来主要做了两件事情:

open_driver()打开/dev/binder虚拟设备驱动
为打开的Binder虚拟设备分配内存映射空间
那下边继续回到main_mediaserver的main()方法中继续分析,看在获取到ProcessState后的sp sm =defaultServiceManager()干了什么。

defaultServerManager
defaultServerManager()函数会返回一个实现了IServerManager接口的对象,通过这个对象我们可以和另外一个进程的ServerManager进行通信.先看一下defaultServerManager()方法都干了点什么:

…/frameworks/native/libs/binder/IServiceManager.cpp
sp<IServiceManager> defaultServiceManager()
{
if (gDefaultServiceManager != NULL) return gDefaultServiceManager;

{
AutoMutex _l(gDefaultServiceManagerLock);
while (gDefaultServiceManager == NULL) {
//gDefaultServiceManager对象创建的地方
gDefaultServiceManager = interface_cast<IServiceManager>(
ProcessState::self()->getContextObject(NULL));
if (gDefaultServiceManager == NULL)
sleep(1);
}
}

return gDefaultServiceManager;
}

这个方法在IServerManager.cpp中定义的,这个方法又是一个单例来获取一个IServerManger对象。内容很简单但是当我看到interface_cast(…)后我感觉一点都不好,但是它是很重要的不过暂时先不管它。先看看ProcessState::self()->getContextObject(NULL)方法都干了点什么:

…/frameworks/native/libs/binder/ProcessState.cpp

sp<IBinder> ProcessState::getContextObject(const sp<IBinder>& /*caller*/)
{
//好吧,这里直接调用了getStrongProxyForHande()方法。
//但是传入的参数 0很重要
return getStrongProxyForHandle(0);
}

sp<IBinder> ProcessState::getStrongProxyForHandle(int32_t handle)
{
sp<IBinder> result;

AutoMutex _l(mLock);
//资源查找,找不到会创建一个新的对象
handle_entry* e = lookupHandleLocked(handle);

if (e != NULL) {
IBinder* b = e->binder;
//新创建的binder 一定为空
if (b == NULL || !e->refs->attemptIncWeak(this)) {
if (handle == 0) {

························

Parcel data;
//这里是一次IPC的binder通信过程,暂时不分析
//我们现在分析的是getStrongProxyForHander()方法返回的是什么
status_t status = IPCThreadState::self()->transact(
0, IBinder::PING_TRANSACTION, data, NULL, 0);
if (status == DEAD_OBJECT)
return NULL;
}
//创建一个BpBinder对象
b = new BpBinder(handle);
e->binder = b; //填充
if (b) e->refs = b->getWeakRefs();
result = b;

···············
}

return result;
}

在getStrongProxyForHandle()函数中首先调用了lookupHandleLocked(0)方法获取一个handle_entry 类型的结构指针。lookupHandleLocked()就是在一个Vector中查找对象找不到就创建一个,不重要先忽略。然后这里创建了一个BpBinder对象,根据名字看应该是和Binder机制有关的我们先可以一下BpBinder的构造方法:

…/frameworks/native/libs/binder/BpBinder.cpp

//BpBinder构造函数
BpBinder::BpBinder(int32_t handle)
: mHandle(handle) //handle 为 0
, mAlive(1)
, mObitsSent(0)
, mObituaries(NULL)
{
“““““““`
//另外一个重要对象?
IPCThreadState::self()->incWeakHandle(handle);
}

在BpBinder的构造函数中出现了一个新对象IPCThreadState,对象名字竟然以IPC应该是和Binder的跨进程通信又关系的。

…/frameworks/native/libs/binder/IPCThreadState.cpp

IPCThreadState* IPCThreadState::self()
{
if (gHaveTLS) {
restart:
const pthread_key_t k = gTLS;
//TLS = thread location storage 线程本地空间 是线程独有空间
//getSpecific()和setSpecific()可以操作这部分空间
IPCThreadState* st = (IPCThreadState*)pthread_getspecific(k);
if (st) return st;
//返回一个创建的IPCThreadState对象
return new IPCThreadState;
}

if (gShutdown) return NULL;

““““
goto restart;
}

当我*次看到这个方法时还是有点震惊的,我虽然没有学习过C++但是大一那会儿学习过C语言。当时老师就告诉我们尽量少用goto这个逻辑跳转。这里竟然用了。

这个方法中IPCThreadState* st = (IPCThreadState*)pthread_getspecific(k)是从线程的TLS中获取一个IPCThreadState对象指针,*次调用市肯定是不存在这个对象的所以会new 一个IPCThreadState对象。

TLS是操作系统为每个线程都单独分配的一块内存空间,当前线程独占其他线程无法访问。所以在多线程下对它的访问的线程安全的。

既然这里调用了IPCThreadState的构造函数我们就看看它长什么样子:

…/frameworks/native/libs/binder/IPCThreadState.cpp

IPCThreadState::IPCThreadState()
: mProcess(ProcessState::self()), //获取上边我们创建的ProcessState对象
mMyThreadId(gettid()),
mStrictModePolicy(0),
mLastTransactionBinderFlags(0)
{
//在构造函数中将自己设置到TLS中
pthread_setspecific(gTLS, this);
clearCaller();
mIn.setDataCapacity(256);
mOut.setDataCapacity(256);
}

IPCThreadState的构造函数很简单就是创建自己后将自己设置到当前线程的TLS中,下次就可以直接从当前线程的TLS中获取了。

我们分析了这么多其实都是gDefaultServiceManager = interface_cast(ProcessState::self()->getContextObject(NULL))的getContextObject(null)方法开始分析。好吧,要不是不是回到看一下我都已经不知道自己现在在分析什么了。那现在我们知道了getContextObject()返回的是一个BpBinder对象。那我们再看看interface_cast()设个什么东西:

…/frameworks/native/include/binder/IInterface.h
template<typename INTERFACE>
inline sp<INTERFACE> interface_cast(const sp<IBinder>& obj)
{
return INTERFACE::asInterface(obj);
}

原来interface_cast()是一个定义在IInterface.h头文件中的模范方法,那我们现在根据我们上边的调用翻译一下这个模版方法现在的样子:

inline sp<IServiceManager> interface_cast(const sp<IBinder>& obj) {
return IServiceManager::asInterface(obj);
}

实话说分析到这里我有点懵了不知道怎么分析了. 既然模版方法中调用了IServiceManager::asInterface()方法我们就先看看IServiceManager接口的内容:

…/frameworks/native/include/binder/IServiceManager.h

class IServiceManager : public IInterface
{
public:
//这个鸿定义在IInterface.h中,他主要做了如下工作
//1.定义了一个描述字符串
//2.定义了一个asInterface函数
//3.定义了一个getInterfaceDescriptor函数用于返回上边定义的描述字符串
//4.定义了构造函数和析构函数
DECLARE_META_INTERFACE(ServiceManager);

virtual sp<IBinder> getService( const String16& name) const = 0;

virtual sp<IBinder> checkService( const String16& name) const = 0;

virtual status_t addService( const String16& name,
const sp<IBinder>& service,
bool allowIsolated = false) = 0;

virtual Vector<String16> listServices() = 0;

enum {
GET_SERVICE_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION,
CHECK_SERVICE_TRANSACTION,
ADD_SERVICE_TRANSACTION,
LIST_SERVICES_TRANSACTION,
};
};

我们都知道Android系统中的系统服务像AMS、WMS等都是有ServiceManager统一管理的。这里的IServiceManager.h就是定义了他的功能接口。在这个接口定义中我们看到一个宏DECLARE_META_INTERFACE(ServiceManager)的使用,那我们先分析一下这个宏定义是什么样子:

#define DECLARE_META_INTERFACE(INTERFACE) \
static const android::String16 descriptor; \
static android::sp<I##INTERFACE> asInterface( \
const android::sp<android::IBinder>& obj); \
virtual const android::String16& getInterfaceDescriptor() const; \
I##INTERFACE(); \
virtual ~I##INTERFACE();

同样我还是先翻译一下这个宏在当前使用场景的样子:

static const android::String16 descriptor;
static android::sp<IServiceManager> asInterface(const android::sp<IBinder>& obj);
virtual const android::String16 getInterfaceDescriptor() const;
IServiceManager();
virtual ~IServiceManager();

从对DECLARE_META_INTERFACE宏的翻译可以看出这是属性和方法的声明;

通过上边的对那个IServiceManager.h和对DECLARE_META_INTERFACE的翻译我们已经知道了IServiceManager接口定义的样子我们下面看一下他的具体实现,但是具体实现内容比较多我抽取其中重要部分分析一下:

…/frameworks/native/libs/binder/IServiceManager.cpp

…………省略………….

//IMPLEMENT_META_INTERFACE这个宏是定义在IInterface.h文件中的
//1.定义常量字符串android.os.IServiceManager
//2.实现getInterfaceDescriptor()函数
//3.实现asInterface()函数,并返回一个BpServiceManager对象
IMPLEMENT_META_INTERFACE(ServiceManager, “android.os.IServiceManager”);

status_t BnServiceManager::onTransact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
//printf(“ServiceManager received: “); data.print();
switch(code) {
………..继续省略…………..
}
}

在IServiceManager的实现中真正让我感兴趣的这个IMPLEMENT_META_INTERFACE 宏的使用。同样我们找找这个宏的定义并将这个宏翻译一下:

宏定义在IInterface.h文件中
#define IMPLEMENT_META_INTERFACE(INTERFACE, NAME) \
const android::String16 I##INTERFACE::descriptor(NAME); \
const android::String16& \
I##INTERFACE::getInterfaceDescriptor() const { \
return I##INTERFACE::descriptor; \
} \
android::sp<I##INTERFACE> I##INTERFACE::asInterface( \
const android::sp<android::IBinder>& obj) \
{ \
android::sp<I##INTERFACE> intr; \
if (obj != NULL) { \
intr = static_cast<I##INTERFACE*>( \
obj->queryLocalInterface( \
I##INTERFACE::descriptor).get()); \
if (intr == NULL) { \
intr = new Bp##INTERFACE(obj); \
} \
} \
return intr; \
} \
I##INTERFACE::I##INTERFACE() { } \
I##INTERFACE::~I##INTERFACE() { } \

感觉当前这个宏的使用场景翻译一下:

const android::String16 IServiceManager::descriptor(“android.os.IServiceManager”);
const android::String16& IServiceManager::getInterfaceDescriptor() const {
return IServiceManager::descriptor;
}
android::sp<IServiceManager> IServiceManager::asInterface(const android::sp<android::IBinder>& obj) {
android::sp<IBinder> intr;
if(obj != null) {
//这里也很重要
intr = static_cast<IServiceManager>(obj->queryLocalInterface(IServicManager:;descriptor).get());
if(intr == NULL) {
//重点看这里
intr = new BpServieceManager(obj);
}
}
return intr;
//构造函数我就不翻译了。这翻译真要命
}

现在我们已经翻译完了***IMPLEMENT_META_INTERFACE***宏的内容现在将翻译后内容替换到IServiceManager.cpp中看看

…/frameworks/native/libs/binder/IServiceManager.cpp

…………省略………….

//IMPLEMENT_META_INTERFACE这个宏是定义在IInterface.h文件中的
//1.定义常量字符串android.os.IServiceManager
//2.实现getInterfaceDescriptor()函数
//3.实现asInterface()函数,并返回一个BpServiceManager对象
<!–IMPLEMENT_META_INTERFACE(ServiceManager, “android.os.IServiceManager”);–>

const android::String16 IServiceManager::descriptor(“android.os.IServiceManager”);
const android::String16& IServiceManager::getInterfaceDescriptor() const {
return IServiceManager::descriptor;
}
android::sp<IServiceManager> IServiceManager::asInterface(const android::sp<android::IBinder>& obj) {
android::sp<IBinder> intr;
if(obj != null) {
//这里也很重要
intr = static_cast<IServiceManager>(obj->queryLocalInterface(IServicManager:;descriptor).get());
if(intr == NULL) {
//重点看这里
intr = new BpServieceManager(obj);
}
}
return intr;
//构造函数我就不翻译了。这翻译真要命

status_t BnServiceManager::onTransact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
//printf(“ServiceManager received: “); data.print();
switch(code) {
………..继续省略…………..
}
}

这就是翻译后的IServiceManager.cpp的样子我们重点看一下asInterface()方法, 这里这个方法竟然返回了一个BpServiceManager对象。还记得我们先变分析中哪里调用了这个asInterface()方法吗?没错就是我们在interface_cast()模版方法调用,现在我们知道在当前分析场景下interface_cast()返回的是一个***BpServiceManager***类型的对象。那么问题现在有来了,还记得interface_cast()方法是在哪里调用的吗?没错就是defaultServiceManager()方法中调用的。

现在终于将defaultServiceManager()函数分析完了,*终这个方法会返回一个BpServiceManager类型的对象(累死人了,分析了这么多终于知道了这个方法返回了什么。要疯了,特别想爆粗口)。那现在我是特别想知道BpServiceManager到底是个什么东西。但是现在不着急,defautServiceManager()方法引发这么多的联动分析我还是先回味一下defaultServiceManager()函数都干了些啥:

首先调用了ProcessState::getContextObject(null)函数,但是这个函数什么都没有干就直接调用了自己的getStrongProxyForHandle(0)函数。在getStrongProxyForHandle()函数中创建了一个BpBinder对象。
在BpBinder构造函数中又引出了一个重要的类IPCThreadState类。在这里调用了IPCThreadState::self();
在IPCThreadState::self()函数中首先从线程的TLS中获取IPCThreadState对象。到那时在首次点用时TLS中并未存储,所以会调用它的构造方法创建一个IPCThreadState对象并将创建的对象保存到TLS中。以后就可以直接从TLS中获取。
interface_cast()模版方法中调用了IServiceManager的asInterface()函数。通过我们对模版方法、DECLARE_META_INTERFACE和IMPLEMENT_META_INTERFACE宏的分析翻译知道*终asInterface()返回了一个BpServiceManager对象。
在整个defaultServiceManager()函数中涉及到BpBinder、ProcessState、IPCThreadState、BpServiceManager等新的类。我们有必要分析一下目前他们的关系如何。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8YrAU0q2-1593588562795)(https://thumbnail0.baidupcs.com/thumbnail/bb7c4d396ec292dc71350f5750f7c4d5?fid=2586003311-250528-448513258459521&time=1546952400&rt=sh&sign=FDTAER-DCb740ccc5511e5e8fedcff06b081203-HxxYlrpgwgJNMNKaDwPEh57%2BXOk%3D&expires=8h&chkv=0&chkbd=0&chkpc=&dp-logid=183401465543768111&dp-callid=0&size=c710_u400&quality=100&vuk=-&ft=video)]

现在我们可以根据类图关系再来分析一下他们。首先看一下BpServiceManager:

class BpServiceManager : public BpInterface<IServiceManager>
{
//impl参数是IBinder类型,实际上就是上边创建的BpBinder
public BpServiceManager(const sp<IBinder>& impl)
//调用了父类的构造方法,参数impl 是 BpBinder
: BpInterface<IServiceManager>(impl)
{
}
}

BpServiceManager继承自BpInterface。现在看一下BpInterface:

模版类
template<typename INTERFACE>
class BpInterface : public INTERFACE, public BpRefBase
{
public:
BpInterface(const sp<IBinder>& remote);

protected:
virtual IBinder* onAsBinder();
};

—————————-
模版方法
template<typename INTERFACE>
//remote参数是BpBinder
inline BpInterface<INTERFACE>::BpInterface(const sp<IBinder>& remote)
//调用父类的构造方法
: BpRefBase(remote)
{
}

继续看一下BpRefBase的实现:

//mRemote*终等于BpBinder(0)对象
BpRefBase::BpRefBase(const sp<IBinder>& o)
//重点在这里,Bpbinder本传递给了mRemote.Binder的通信需要通过mRemote来完成。
: mRemote(o.get()), mRefs(NULL), mState(0)
{
extendObjectLifetime(OBJECT_LIFETIME_WEAK);

if (mRemote) {
mRemote->incStrong(this); // Removed on first IncStrong().
mRefs = mRemote->createWeak(this); // Held for our entire lifetime.
}
}

现在我们知道BpServiceManager实现了IServiceManager接口同时持有了BpBinder,BpBinder可以用于进程的通信。此时BpServiceManager是ServiceManager客户端的代理对象,BpBinder是Binder通信机制的Client端代表。

2. MediaPlayerService addService()
我们继续回到MediaServer的main()函数中继续分析。看看MediaPlayerService::instantiate()实现:

void MediaPlayerService::instantiate() {
//通过上边的分析我们已经知道defaultServiceManager()返回的是一个BpServiceManager对象。在这里调用了他的addService()方法。
defaultServiceManager()->addService(
String16(“media.player”), new MediaPlayerService());
}

在instantiate()函数中调用了BpServiceManager的addService():

…/frameworks/native/libs/binder/IServiceManager.cpp的内嵌类BpServiceManager

virtual status_t addService(const String16& name, const sp<IBinder>& service,
bool allowIsolated)
{
Parcel data, reply;
//
data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
data.writeString16(name);
data.writeStrongBinder(service);
data.writeInt32(allowIsolated ? 1 : 0);
//remote()函数返回的是mRemote,就是BpRefBase中的mRemote,即BpBinder对象。这里调用了BpBinder的transact()方法。
status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply);
return err == NO_ERROR ? reply.readExceptionCode() : err;
}

从BpServiceManager的addService()方法可以看出*终将调用转移到了BpBinder中。由此我们知道BpBinder才是代表client进行跨进程通信的代表。下面我们继续分析一下BpBinder;

status_t BpBinder::transact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
// Once a binder has died, it will never come back to life.
if (mAlive) {
//BpBinder将通信委托给了IPCThreadState的transcat()方法
status_t status = IPCThreadState::self()->transact(
mHandle, code, data, reply, flags);
if (status == DEAD_OBJECT) mAlive = 0;
return status;
}

return DEAD_OBJECT;
}

看到这里还是有点震惊的,我本来认为BpBinder已经是实现通信的客户端的代表了。现在发现它竟然将通信委托给IPCThreadState来完成。原来BpBinder还是个壳子。继续看一下IPCThreadState::transact():

status_t IPCThreadState::transact(int32_t handle,
uint32_t code, const Parcel& data,
Parcel* reply, uint32_t flags)
{
status_t err = data.errorCheck();

““““““““““““““`

if (err == NO_ERROR) {
LOG_ONEWAY(“>>>> SEND from pid %d uid %d %s”, getpid(), getuid(),
(flags & TF_ONE_WAY) == 0 ? “READ REPLY” : “ONE WAY”);
//发送Binder通信消息
err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);
}

if (err != NO_ERROR) {
if (reply) reply->setError(err);
return (mLastError = err);
}

if ((flags & TF_ONE_WAY) == 0) {
#if 0
if (code == 4) { // relayout
ALOGI(“>>>>>> CALLING transaction 4”);
} else {
ALOGI(“>>>>>> CALLING transaction %d”, code);
}
#endif
if (reply) {
//等待通讯结果
err = waitForResponse(reply);
} else {
Parcel fakeReply;
err = waitForResponse(&fakeReply);
}

““““““““““““
} else {
err = waitForResponse(NULL, NULL);
}

return err;
}

在IPCThreadState的transact()方法中有两个重要的方法调用, err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL)和err = waitForResponse(reply)。我们先来分析一下writeTransactionData()的实现:

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; //binder_transaction_data 是binder通信的数据结构

tr.target.ptr = 0; /* Don’t pass uninitialized stack data to a remote process */
tr.target.handle = handle; //现在handle是0;0代表ServiceManager。
tr.code = code;
tr.flags = binderFlags;
tr.cookie = 0;
tr.sender_pid = 0;
tr.sender_euid = 0;

const status_t err = data.errorCheck();
if (err == NO_ERROR) {
tr.data_size = data.ipcDataSize();
tr.data.ptr.buffer = data.ipcData();
tr.offsets_size = data.ipcObjectsCount()*sizeof(binder_size_t);
tr.data.ptr.offsets = data.ipcObjects();
} else if (statusBuffer) {
tr.flags |= TF_STATUS_CODE;
*statusBuffer = err;
tr.data_size = sizeof(status_t);
tr.data.ptr.buffer = reinterpret_cast<uintptr_t>(statusBuffer);
tr.offsets_size = 0;
tr.data.ptr.offsets = 0;
} else {
return (mLastError = err);
}
//mOut的类型是Pracel.它是准备发送给Binder的Server端。
mOut.writeInt32(cmd);
//将Binder通信的数据写入到mOut中
mOut.write(&tr, sizeof(tr));

return NO_ERROR;
}

从writeTransactionData()方法的分析发现里边主要做了数据准备并将数据序列化到Pracel中。

我们继续分析分析waitForResponse(reply)函数:

status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
{
uint32_t cmd;
int32_t err;
//循环处理
while (1) {
//重点talkWithDriver()
if ((err=talkWithDriver()) < NO_ERROR) break;
err = mIn.errorCheck();
if (err < NO_ERROR) break;
if (mIn.dataAvail() == 0) continue;

cmd = (uint32_t)mIn.readInt32();

IF_LOG_COMMANDS() {
alog << “Processing waitForResponse Command: ”
<< getReturnString(cmd) << endl;
}

switch (cmd) {
case BR_TRANSACTION_COMPLETE:
if (!reply && !acquireResult) goto finish;
break;

case BR_DEAD_REPLY:
err = DEAD_OBJECT;
goto finish;

case BR_FAILED_REPLY:
err = FAILED_TRANSACTION;
goto finish;

case BR_ACQUIRE_RESULT:
{
ALOG_ASSERT(acquireResult != NULL, “Unexpected brACQUIRE_RESULT”);
const int32_t result = mIn.readInt32();
if (!acquireResult) continue;
*acquireResult = result ? NO_ERROR : INVALID_OPERATION;
}
goto finish;

case BR_REPLY:
{
binder_transaction_data tr;
err = mIn.read(&tr, sizeof(tr));
ALOG_ASSERT(err == NO_ERROR, “Not enough command data for brREPLY”);
if (err != NO_ERROR) goto finish;

if (reply) {
if ((tr.flags & TF_STATUS_CODE) == 0) {
reply->ipcSetDataReference(
reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
tr.data_size,
reinterpret_cast<const binder_size_t*>(tr.data.ptr.offsets),
tr.offsets_size/sizeof(binder_size_t),
freeBuffer, this);
} else {
err = *reinterpret_cast<const status_t*>(tr.data.ptr.buffer);
freeBuffer(NULL,
reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
tr.data_size,
reinterpret_cast<const binder_size_t*>(tr.data.ptr.offsets),
tr.offsets_size/sizeof(binder_size_t), this);
}
} else {
freeBuffer(NULL,
reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
tr.data_size,
reinterpret_cast<const binder_size_t*>(tr.data.ptr.offsets),
tr.offsets_size/sizeof(binder_size_t), this);
continue;
}
}
goto finish;

default:
//重点
err = executeCommand(cmd);
if (err != NO_ERROR) goto finish;
break;
}
}

BR_开头的case都是Binder Server端的,BC_开头的case是Binder Client端使用。
在IPCThreadState的waitForResponse()函数中重要的方法有talkWithDriver()和case 的default:分支中的executeCommand()函数。现在我们先分析一下talkWithDriver():

status_t IPCThreadState::talkWithDriver(bool doReceive)
{
if (mProcess->mDriverFD <= 0) {
return -EBADF;
}

binder_write_read bwr; //binder通信的数据结构

// Is the read buffer empty?
const bool needRead = mIn.dataPosition() >= mIn.dataSize();

// We don’t want to write anything if we are still reading
// from data left in the input buffer and the caller
// has requested to read the next data.
const size_t outAvail = (!doReceive || needRead) ? mOut.dataSize() : 0;

bwr.write_size = outAvail;
bwr.write_buffer = (uintptr_t)mOut.data();

// This is what we’ll read.
if (doReceive && needRead) {
bwr.read_size = mIn.dataCapacity();
bwr.read_buffer = (uintptr_t)mIn.data();
} else {
bwr.read_size = 0;
bwr.read_buffer = 0;
}

IF_LOG_COMMANDS() {
TextOutput::Bundle _b(alog);
if (outAvail != 0) {
alog << “Sending commands to driver: ” << indent;
const void* cmds = (const void*)bwr.write_buffer;
const void* end = ((const uint8_t*)cmds)+bwr.write_size;
alog << HexDump(cmds, bwr.write_size) << endl;
while (cmds < end) cmds = printCommand(alog, cmds);
alog << dedent;
}
alog << “Size of receive buffer: ” << bwr.read_size
<< “, needRead: ” << needRead << “, doReceive: ” << doReceive << endl;
}

// Return immediately if there is nothing to do.
if ((bwr.write_size == 0) && (bwr.read_size == 0)) return NO_ERROR;

bwr.write_consumed = 0;
bwr.read_consumed = 0;
status_t err;
//又是一个循环
do {
IF_LOG_COMMANDS() {
alog << “About to read/write, write size = ” << mOut.dataSize() << endl;
}
#if defined(HAVE_ANDROID_OS)
//通过ioctl()进行读写
if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
err = NO_ERROR;
else
err = -errno;
#else
err = INVALID_OPERATION;
#endif
if (mProcess->mDriverFD <= 0) {
err = -EBADF;
}
IF_LOG_COMMANDS() {
alog << “Finished read/write, write size = ” << mOut.dataSize() << endl;
}
} while (err == -EINTR);

IF_LOG_COMMANDS() {
alog << “Our err: ” << (void*)(intptr_t)err << “, write consumed: ”
<< bwr.write_consumed << ” (of ” << mOut.dataSize()
<< “), read consumed: ” << bwr.read_consumed << endl;
}

if (err >= NO_ERROR) {
//清空客户端的Pracel的内存空间
if (bwr.write_consumed > 0) {
if (bwr.write_consumed < mOut.dataSize())
mOut.remove(0, bwr.write_consumed);
else
mOut.setDataSize(0);
}
//将ioctl()从sever端获取的数据写入到mIn中准备发送给client端
if (bwr.read_consumed > 0) {
mIn.setDataSize(bwr.read_consumed);
mIn.setDataPosition(0);
}

““““““
return NO_ERROR;
}

return err;
}

完成Binder通信其实是通过Binder驱动的共享内存来完成不同进程之间的通信,从对talkWithDriver()函数分析来看完成对Binder驱动的共享内存的操作是ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr)函数来完成的。ioctl()是一个linux系统API。对ioctl()方法调用是在do{}while()中调用的,由此可见这个ioctl()会多次调用来完成通信。在ioctl()完成后请求结果被放到binder_write_read类型的bwr中,然后将bwr的结果内容写入到Pracel的mIn中为下一步返回客户端做做准备。

我们在回到IPCThreadState::waitForResponse()函数中分析一下另外一个executeCommand(cmd)方法:

status_t IPCThreadState::executeCommand(int32_t cmd)
{
BBinder* obj;
RefBase::weakref_type* refs;
status_t result = NO_ERROR;

switch ((uint32_t)cmd) {
case BR_ERROR:
result = mIn.readInt32();
break;

case BR_OK:
break;

case BR_ACQUIRE:
“““““`
break;
case BR_RELEASE:
“““““`
break;

case BR_INCREFS:
““““““`
break;

““““`

case BR_TRANSACTION:
{
binder_transaction_data tr;
result = mIn.read(&tr, sizeof(tr));
ALOG_ASSERT(result == NO_ERROR,
“Not enough command data for brTRANSACTION”);
if (result != NO_ERROR) break;

Parcel buffer;
buffer.ipcSetDataReference(
reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
tr.data_size,
reinterpret_cast<const binder_size_t*>(tr.data.ptr.offsets),
tr.offsets_size/sizeof(binder_size_t), freeBuffer, this);

const pid_t origPid = mCallingPid;
const uid_t origUid = mCallingUid;
const int32_t origStrictModePolicy = mStrictModePolicy;
const int32_t origTransactionBinderFlags = mLastTransactionBinderFlags;

mCallingPid = tr.sender_pid;
mCallingUid = tr.sender_euid;
mLastTransactionBinderFlags = tr.flags;

int curPrio = getpriority(PRIO_PROCESS, mMyThreadId);
if (gDisableBackgroundScheduling) {
if (curPrio > ANDROID_PRIORITY_NORMAL) {
setpriority(PRIO_PROCESS, mMyThreadId, ANDROID_PRIORITY_NORMAL);
}
} else {
if (curPrio >= ANDROID_PRIORITY_BACKGROUND) {
set_sched_policy(mMyThreadId, SP_BACKGROUND);
}
}

Parcel reply;
status_t error;

if (tr.target.ptr) {
//BnService从BBinder派生
//这里的B实际上实现了BnServiceXXX的对象
sp<BBinder> b((BBinder*)tr.cookie);
error = b->transact(tr.code, buffer, &reply, tr.flags);

} else {
error = the_context_object->transact(tr.code, buffer, &reply, tr.flags);
}

if ((tr.flags & TF_ONE_WAY) == 0) {
LOG_ONEWAY(“Sending reply to %d!”, mCallingPid);
if (error < NO_ERROR) reply.setError(error);
sendReply(reply, 0);
} else {
LOG_ONEWAY(“NOT sending reply to %d!”, mCallingPid);
}

mCallingPid = origPid;
mCallingUid = origUid;
mStrictModePolicy = origStrictModePolicy;
mLastTransactionBinderFlags = origTransactionBinderFlags;

}
break;
·····································
return result;
}

看到executeCommand()函数中那么多的switch分支真不知道从哪里分析,脑子已经懵懵的。还是从BR_TRANSACTION:这个分支看看吧。在这个分支中我们可以看到//BnService从BBinder派生sp b((BBinder*)tr.cookie);error = b->transact(tr.code, buffer, &reply, tr.flags);我们又看到了一个BBinder类的对象,有我们从BpBinder是Binder Client的代表可以猜测BBinder可能是Binder的Server端的代表。实际上这里的b是BnSerciceManager对象。那先放下executeCommaond()函数,这里我们先来分析一下BBinder和BnServiceManaer是什么然后在回来分析:

“`/frameworks/native/include/binder/IServiceManager.h
class BnServiceManager : public BnInterface<IServiceManager>
{
public:
virtual status_t onTransact( uint32_t code,
const Parcel& data,
Parcel* reply,
uint32_t flags = 0);
};

开始看张类图直观一些,手画丑的不行:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2YzuoKT4-1593588562797)(https://thumbnail0.baidupcs.com/thumbnail/01a427c58cc69e8688aeddecc50fb5c3?fid=2586003311-250528-868147785755165&time=1546952400&rt=sh&sign=FDTAER-DCb740ccc5511e5e8fedcff06b081203-AvZaBr10el8zdroFSH1cY0BrMYs%3D&expires=8h&chkv=0&chkbd=0&chkpc=&dp-logid=183456904394929954&dp-callid=0&size=c710_u400&quality=100&vuk=-&ft=video)]

BnServiceManager继承自BnInterface,再看一下BnInterface是什么:

template<typename INTERFACE>
class BnInterface : public INTERFACE, public BBinder
{
public:
virtual sp<IInterface> queryLocalInterface(const String16& _descriptor);
virtual const String16& getInterfaceDescriptor() const;

protected:
virtual IBinder* onAsBinder();
};

BnInterface是个模版类继承自BBinder,在BnServiceManager继承BnInterface时模版类中的INTERFACE 是 IServiceManager。用于BnInterface继承了INTERFACE,所以BnServicManager继承了ISeviceManager接口。下面看一下BBinder的定义:

“`/frameworks/native/include/binder/Binder.h
class BBinder : public IBinder
{
………….
}

这样以来继承关系已经很明确。再BnServiceManager的接口声明中重新声明了onTransact()函数并在IServiceManager.cpp中实现了它:

status_t BnServiceManager::onTransact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
//printf(“ServiceManager received: “); data.print();
switch(code) {
case GET_SERVICE_TRANSACTION: {
········
} break;
case CHECK_SERVICE_TRANSACTION: {
········
} break;
case ADD_SERVICE_TRANSACTION: {
CHECK_INTERFACE(IServiceManager, data, reply);
String16 which = data.readString16();
sp<IBinder> b = data.readStrongBinder();
//在这里有调用了addService()完成真正的服务添加。
status_t err = addService(which, b);
reply->writeInt32(err);
return NO_ERROR;
} break;
case LIST_SERVICES_TRANSACTION: {
········
return NO_ERROR;
} break;
default:
//调用父类默认实现
return BBinder::onTransact(code, data, reply, flags);
}
}

我们对BnServiceManager和BBinder的分析先分析到这里,回到executeCommond()方法中再结合sp b((BBinder*)tr.cookie);error = b->transact(tr.code, buffer, &reply,tr.flags);调用bnServiceManager的transact()方法。以分析的addService()*终会进入到case ADD_SERVICE_TRANSACTION:分支当中并在这里调用了addService()方法。
***看到这里我是有点模糊的这个分支中的addService()方法的实现在哪里,也没有找到一个类继承自BnServiceManager。***后来网上看别人的分析发现还是有个文件实现了这里的ServiceManager的功能,具体实现在Service_manager.c中实现的。

不太明白为什么不继续继承BnServiceManager来实现具体逻辑呢?

ServiceManager
ServiceManager的实现在Service_manager.c中,先从这个文件的main()函数开始了解一下它干了啥:

int main(int argc, char **argv)
{
struct binder_state *bs;
//打开binder虚拟设备
bs = binder_open(128*1024);
if (!bs) {
ALOGE(“failed to open binder driver\n”);
return -1;
}
//角色转换
if (binder_become_context_manager(bs)) {
ALOGE(“cannot become context manager (%s)\n”, strerror(errno));
return -1;
}

//重点,处理客户端发过来的请求
binder_loop(bs, svcmgr_handler);

return 0;
}

main()函数比较简单主要通过调用了binder_open()函数打开Binder的虚拟设备.先看一下binder_open()都做了些什么:

/打开Binder设备
struct binder_state *binder_open(size_t mapsize)
{
struct binder_state *bs;
struct binder_version vers;

bs = malloc(sizeof(*bs));
if (!bs) {
errno = ENOMEM;
return NULL;
}
//打开虚拟设备,调用Linux系统方法打开文件
bs->fd = open(“/dev/binder”, O_RDWR);
if (bs->fd < 0) {
fprintf(stderr,”binder: cannot open device (%s)\n”,
strerror(errno));
goto fail_open;
}

if ((ioctl(bs->fd, BINDER_VERSION, &vers) == -1) ||
(vers.protocol_version != BINDER_CURRENT_PROTOCOL_VERSION)) {
fprintf(stderr,
“binder: kernel driver version (%d) differs from user space version (%d)\n”,
vers.protocol_version, BINDER_CURRENT_PROTOCOL_VERSION);
goto fail_open;
}

bs->mapsize = mapsize;
//调用mmap()函数给binder驱动做内存映射
bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);
if (bs->mapped == MAP_FAILED) {
fprintf(stderr,”binder: cannot map device (%s)\n”,
strerror(errno));
goto fail_map;
}

return bs;

fail_map:
close(bs->fd);
fail_open:
free(bs);
return NULL;
}

我在这个方法中又看到了goto的使用。有点兴奋!

在open_binder()函数中打开了Binder驱动,并给起映射了内存空间。我们继续看一下binder_become_context_manager(bs)干了什么:

int binder_become_context_manager(struct binder_state *bs)
{
return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);
}

很简单但是我没有看太懂,主要是对ioctl()这个函数不太懂。大概是给打开的Binder设备指定了映射名称为0;(谁懂给我普及一下)

ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等等。它的参数个数如下:int ioctl(int fd, int cmd, …);其中fd就是用户程序打开设备时使用open函数返回的文件标示符,cmd就是用户程序对设备的控制命令,至于后面的省略号,那是一些补充参数,一般*多一个,有或没有是和cmd的意义相关的。ioctl函数是文件结构中的一个属性分量,就是说如果你的驱动程序提供了对ioctl的支持,用户就能在用户程序中使用ioctl函数控制设备的I/O通道。百度百科

还有一个处理Binder请求的方法binder_loop(bs, svcmgr_handler):

//binder_handler 是个函数指针,func现在是svcmgr_handler
void binder_loop(struct binder_state *bs, binder_handler func)
{
int res;
struct binder_write_read bwr;
uint32_t readbuf[32];

bwr.write_size = 0;
bwr.write_consumed = 0;
bwr.write_buffer = 0;

readbuf[0] = BC_ENTER_LOOPER;
//在binder_write中调用了ioctl函数,调用Binder设备的函数,标志serviceManager进入的Loop 状态。
binder_write(bs, readbuf, sizeof(uint32_t));
//循环不断的
for (;;) {
bwr.read_size = sizeof(readbuf);
bwr.read_consumed = 0;
bwr.read_buffer = (uintptr_t) readbuf;
//通过ioctl()方法从Binder设备中读取缓冲区,检查是不是又IPC请求。
res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
if (res < 0) {
ALOGE(“binder_loop: ioctl failed (%s)\n”, strerror(errno));
break;
}
//接受到请求,然后解析结果 调用func函数
res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func);
if (res == 0) {
ALOGE(“binder_loop: unexpected reply?!\n”);
break;
}
if (res < 0) {
ALOGE(“binder_loop: io error %d %s\n”, res, strerror(errno));
break;
}
}
}

在binder_loop()中让binder设备进入loop状态,通过循环不断通过ioctl()方法获取Binder设备是不是有IPC请求,如果有将获取到的数据交给binder_parse()函数进行解析.

int binder_parse(struct binder_state *bs, struct binder_io *bio,
uintptr_t ptr, size_t size, binder_handler func)
{
……………..数据处理………………………..

case BR_TRANSACTION: {

………………..

if (func) {
unsigned rdata[256/4];
struct binder_io msg;
struct binder_io reply;
int res;
bio_init(&reply, rdata, sizeof(rdata), 4);
bio_init_from_txn(&msg, txn);
//调用了func()函数,有func()函数去做真正的服务管理
res = func(bs, txn, &msg, &reply);
binder_send_reply(bs, &reply, txn->data.ptr.buffer, res);
}
ptr += sizeof(*txn);
break;
}
……………………………..
}
}
return r;
}

在binder_prase()方法的binder_handler func参数指向的是svcmgr_handler函数,下面看看这个函数:

…/frameworks/native/cmds/servicemanager/service_manager.c

//binder 通信处理数据的回调函数
int svcmgr_handler(struct binder_state *bs,
struct binder_transaction_data *txn,
struct binder_io *msg,
struct binder_io *reply)
{
struct svcinfo *si;
uint16_t *s;
size_t len;
uint32_t handle;
uint32_t strict_policy;
int allow_isolated;

//ALOGI(“target=%p code=%d pid=%d uid=%d\n”,
// (void*) txn->target.ptr, txn->code, txn->sender_pid, txn->sender_euid);

if (txn->target.ptr != BINDER_SERVICE_MANAGER)
return -1;

if (txn->code == PING_TRANSACTION)
return 0;

strict_policy = bio_get_uint32(msg);
s = bio_get_string16(msg, &len);
if (s == NULL) {
return -1;
}

if ((len != (sizeof(svcmgr_id) / 2)) ||
memcmp(svcmgr_id, s, sizeof(svcmgr_id))) {
fprintf(stderr,”invalid id %s\n”, str8(s, len));
return -1;
}

if (sehandle && selinux_status_updated() > 0) {
struct selabel_handle *tmp_sehandle = selinux_android_service_context_handle();
if (tmp_sehandle) {
selabel_close(sehandle);
sehandle = tmp_sehandle;
}
}

switch(txn->code) {
case SVC_MGR_GET_SERVICE:
case SVC_MGR_CHECK_SERVICE:
……….

case SVC_MGR_ADD_SERVICE:
//注册服务
s = bio_get_string16(msg, &len);
if (s == NULL) {
return -1;
}
handle = bio_get_ref(msg);
allow_isolated = bio_get_uint32(msg) ? 1 : 0;
//注册服务真正工作的地方
if (do_add_service(bs, s, len, handle, txn->sender_euid,
allow_isolated, txn->sender_pid))
return -1;
break;

case SVC_MGR_LIST_SERVICES: {
……
}
default:
ALOGE(“unknown code %d\n”, txn->code);
return -1;
}

bio_put_uint32(reply, 0);
return 0;
}

在svcmgr_handler()函数中SVC_ADD_SERVICES分支中注册服务,其中注册服务的实际处理交给了do_add_service()方法继续处理。

int do_add_service(struct binder_state *bs,
const uint16_t *s, size_t len,
uint32_t handle, uid_t uid, int allow_isolated,
pid_t spid)
{
struct svcinfo *si;

if (!handle || (len == 0) || (len > 127))
return -1;
//验证UID是否有添加服务的权限。
if (!svc_can_register(s, len, spid)) {
ALOGE(“add_service(‘%s’,%x) uid=%d – PERMISSION DENIED\n”,
str8(s, len), handle, uid);
return -1;
}
//检查服务是不是已经注册
si = find_svc(s, len);
if (si) {
if (si->handle) {
ALOGE(“add_service(‘%s’,%x) uid=%d – ALREADY REGISTERED, OVERRIDE\n”,
str8(s, len), handle, uid);
svcinfo_death(bs, si);
}
si->handle = handle;
} else {
//如果没有注册分配内存
si = malloc(sizeof(*si) + (len + 1) * sizeof(uint16_t));
//si这个结构体
si->handle = handle;
si->len = len;
memcpy(si->name, s, (len + 1) * sizeof(uint16_t));
si->name[len] = ‘\0’;
si->death.func = (void*) svcinfo_death;
si->death.ptr = si;
si->allow_isolated = allow_isolated;
si->next = svclist;
//插入链表
svclist = si;
}

binder_acquire(bs, handle);
binder_link_to_death(bs, handle, &si->death);
return 0;
}

//存储注册服务的结构体,是个链表结构
struct svcinfo
{
struct svcinfo *next;
uint32_t handle;
struct binder_death death;
int allow_isolated;
size_t len;
uint16_t name[0];
};

do_add_service()方法通过坚定要注册的服务是否有权限被注册,然后再检查服务是否已经注册了,如果没有被注册则将其插入到链表中。这样我们的服务注册工作已经完成。
C++层的Binder通信的分析基本上分析完了。想抽时间再分析一下Java层对c++层的封装。

虚拟化和云计算的结合,主要有什么优势?

在当今市场上,客户需要各种灵活的基础设施和解决方案以快速适应不断变化的业务需求。通过有效利用资金上或各种IT相关的资源,云计算(提供服务的途径)和虚拟化(主要的使能技术)为客户提供了越来越多的业务灵活性。

虚拟化从根本上来说就是对技术资产的*充分利用。获得虚拟化基础设施的投资回报和所有潜力的关键在于:在适当的时候,定期使用正确的资源并灵活快速地以一种协调性的方式,实现数据中心端到端虚拟化。

虚拟化技术帮助企业提高投资回报率,提升到利用率70%以上。到目前为止,许多企业一直集中精力进行服务器虚拟化。事实上,实现存储、网络和管理虚拟化的融合基础设施所产生的投资回报往往更大。例如,由于存储管理不善或效率低下,可能导致服务器虚拟化的优势无法真正意义上实现。

为了获得融合基础设施的真正好处,我们需要各种能够简便支持数据扩展并与业务应用程序紧密一致的存储解决方案。采用虚拟化技术的存储基础设施能够帮助设备使用率提高近一倍,同时降低管理成本,有时可节省一半成本。因此,每个企业都应该积*主动地使用虚拟化技术充分利用其带来的益处。

另一方面,云计算则是为用户提供使用便利,帮助其随地获取各种高度可扩展的、灵活的IT资源,并按需使用,按使用付费。云计算是一种“一切皆服务”的模式,通过该模式在网络上或“云”上提供服务。

基于云计算的存储产品正在逐渐改变企业经营大量数据的方式。对于那些希望从这些产品中获得*佳回报的企业而言,硬件基础设施要求服务器和存储器完全基于能够提供可扩展性、可靠性和灵活性而设计。

尽管云计算和虚拟化并非捆绑技术,二者同时使用仍可正常运行并实现优势互补。云计算和虚拟化二者交互工作,云计算解决方案依靠并利用虚拟化提供服务,而那些尚未部署云计算解决方案的公司仍然可以利用端到端虚拟化从内部基础设施中获得更佳的投资回报和收益。

例如,为了提供“按需使用,按使用付费”服务模式,云计算供应商必须利用虚拟化技术。因为只有利用虚拟化,他们才能获得灵活的基础设施以提供终端用户所需的灵活性,这一点对外部(公有或共享的云)供应商和内部(私有云)供应商都适用。

对于许多公司而言,初次利用云计算可能有点令人担忧,因而需要一些支持。现在有很多公司为客户提供了许多云资源以及培训,例如惠普的研讨会等,以此帮助企业开发其自己的云计算方法并找到利用技术的*佳途径,其中包括提供云服务,提供云服务资源或部署私有云计算基础设施。

对于那些希望部署更有效解决方案的用户以及希望创建大量云解决方案的供应商而言,一种完全融合的基础设施无疑是其*佳选择,该基础设施还能为用户提供能够快速适应不断变化需求的技术环境。

写给 Android 应用工程师的 Binder 原理剖析

一. 前言

这篇文章我酝酿了很久,参考了很多资料,读了很多源码,却依旧不敢下笔。生怕自己理解上还有偏差,对大家造成误解,贻笑大方。又怕自己理解不够透彻,无法用清晰直白的文字准确的表达出 Binder 的设计精髓。直到今天提笔写作时还依旧战战兢兢。

Binder 之复杂远远不是一篇文章就能说清楚的,本文想站在一个更高的维度来俯瞰 Binder 的设计,*终帮助大家形成一个完整的概念。对于应用层开发的同学来说,理解到本文这个程度也就差不多了。希望更加深入理解 Binder 实现机制的,可以阅读文末的参考资料以及相关源码。

二. Binder 概述

简单介绍下什么是 Binder。Binder 是一种进程间通信机制,基于开源的 OpenBinder 实现;OpenBinder 起初由 Be Inc. 开发,后由 Plam Inc. 接手。从字面上来解释 Binder 有胶水、粘合剂的意思,顾名思义就是粘和不同的进程,使之实现通信。对于 Binder 更全面的定义,等我们介绍完 Binder 通信原理后再做详细说明。

2.1 为什么必须理解 Binder ?

作为 Android 工程师的你,是不是常常会有这样的疑问:

  • 为什么 Activity 间传递对象需要序列化?
  • Activity 的启动流程是什么样的?
  • 四大组件底层的通信机制是怎样的?
  • AIDL 内部的实现原理是什么?
  • 插件化编程技术应该从何学起?等等…

这些问题的背后都与 Binder 有莫大的关系,要弄懂上面这些问题理解 Bidner 通信机制是必须的。

我们知道 Android 应用程序是由 Activity、Service、Broadcast Receiver 和 Content Provide 四大组件中的一个或者多个组成的。有时这些组件运行在同一进程,有时运行在不同的进程。这些进程间的通信就依赖于 Binder IPC 机制。不仅如此,Android 系统对应用层提供的各种服务如:ActivityManagerService、PackageManagerService 等都是基于 Binder IPC 机制来实现的。Binder 机制在 Android 中的位置非常重要,毫不夸张的说理解 Binder 是迈向 Android 高级工程的*步。

2.2 为什么是 Binder ?

Android 系统是基于 Linux 内核的,Linux 已经提供了管道、消息队列、共享内存和 Socket 等 IPC 机制。那为什么 Android 还要提供 Binder 来实现 IPC 呢?主要是基于性能稳定性安全性几方面的原因。

性能

首先说说性能上的优势。Socket 作为一款通用接口,其传输效率低,开销大,主要用在跨网络的进程间通信和本机上进程间的低速通信。消息队列和管道采用存储-转发方式,即数据先从发送方缓存区拷贝到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,至少有两次拷贝过程。共享内存虽然无需拷贝,但控制复杂,难以使用。Binder 只需要一次数据拷贝,性能上仅次于共享内存。

注:各种IPC方式数据拷贝次数,此表来源于Android Binder 设计与实现 – 设计篇

IPC方式 数据拷贝次数
共享内存 0
Binder 1
Socket/管道/消息队列 2

稳定性

再说说稳定性,Binder 基于 C/S 架构,客户端(Client)有什么需求就丢给服务端(Server)去完成,架构清晰、职责明确又相互独立,自然稳定性更好。共享内存虽然无需拷贝,但是控制负责,难以使用。从稳定性的角度讲,Binder 机制是优于内存共享的。

安全性

另一方面就是安全性。Android 作为一个开放性的平台,市场上有各类海量的应用供用户选择安装,因此安全性对于 Android 平台而言*其重要。作为用户当然不希望我们下载的 APP 偷偷读取我的通信录,上传我的隐私数据,后台偷跑流量、消耗手机电量。传统的 IPC 没有任何安全措施,完全依赖上层协议来确保。首先传统的 IPC 接收方无法获得对方可靠的进程用户ID/进程ID(UID/PID),从而无法鉴别对方身份。Android 为每个安装好的 APP 分配了自己的 UID,故而进程的 UID 是鉴别进程身份的重要标志。传统的 IPC 只能由用户在数据包中填入 UID/PID,但这样不可靠,容易被恶意程序利用。可靠的身份标识只有由 IPC 机制在内核中添加。其次传统的 IPC 访问接入点是开放的,只要知道这些接入点的程序都可以和对端建立连接,不管怎样都无法阻止恶意程序通过猜测接收方地址获得连接。同时 Binder 既支持实名 Binder,又支持匿名 Binder,安全性高。

基于上述原因,Android 需要建立一套新的 IPC 机制来满足系统对稳定性、传输性能和安全性方面的要求,这就是 Binder。

*后用一张表格来总结下 Binder 的优势:

优势 描述
性能 只需要一次数据拷贝,性能上仅次于共享内存
稳定性 基于 C/S 架构,职责明确、架构清晰,因此稳定性好
安全性 为每个 APP 分配 UID,进程的 UID 是鉴别进程身份的重要标志

三. Linux 下传统的进程间通信原理

了解 Linux IPC 相关的概念和原理有助于我们理解 Binder 通信原理。因此,在介绍 Binder 跨进程通信原理之前,我们先聊聊 Linux 系统下传统的进程间通信是如何实现。

3.1 基本概念介绍

这里我们先从 Linux 中进程间通信涉及的一些基本概念开始介绍,然后逐步展开,向大家说明传统的进程间通信的原理。

Linux 背景知识

上图展示了 Liunx 中跨进程通信涉及到的一些基本概念:

  • 进程隔离
  • 进程空间划分:用户空间(User Space)/内核空间(Kernel Space)
  • 系统调用:用户态/内核态

进程隔离

简单的说就是操作系统中,进程与进程间内存是不共享的。两个进程就像两个平行的世界,A 进程没法直接访问 B 进程的数据,这就是进程隔离的通俗解释。A 进程和 B 进程之间要进行数据交互就得采用特殊的通信机制:进程间通信(IPC)。

进程空间划分:用户空间(User Space)/内核空间(Kernel Space)

现在操作系统都是采用的虚拟存储器,对于 32 位系统而言,它的寻址空间(虚拟存储空间)就是 2 的 32 次方,也就是 4GB。操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也可以访问底层硬件设备的权限。为了保护用户进程不能直接操作内核,保证内核的安全,操作系统从逻辑上将虚拟空间划分为用户空间(User Space)和内核空间(Kernel Space)。针对 Linux 操作系统而言,将*高的 1GB 字节供内核使用,称为内核空间;较低的 3GB 字节供各进程使用,称为用户空间。

简单的说就是,内核空间(Kernel)是系统内核运行的空间,用户空间(User Space)是用户程序运行的空间。为了保证安全性,它们之间是隔离的。

图片来自网络

系统调用:用户态与内核态

虽然从逻辑上进行了用户空间和内核空间的划分,但不可避免的用户空间需要访问内核资源,比如文件操作、访问网络等等。为了突破隔离限制,就需要借助系统调用来实现。系统调用是用户空间访问内核空间的唯一方式,保证了所有的资源访问都是在内核的控制下进行的,避免了用户程序对系统资源的越权访问,提升了系统安全性和稳定性。

Linux 使用两级保护机制:0 级供系统内核使用,3 级供用户程序使用。

当一个任务(进程)执行系统调用而陷入内核代码中执行时,称进程处于内核运行态(内核态)。此时处理器处于特权级*高的(0级)内核代码中执行。当进程处于内核态时,执行的内核代码会使用当前进程的内核栈。每个进程都有自己的内核栈。

当进程在执行用户自己的代码的时候,我们称其处于用户运行态(用户态)。此时处理器在特权级*低的(3级)用户代码中运行。

系统调用主要通过如下两个函数来实现:

  1. copy_from_user() //将数据从用户空间拷贝到内核空间
  2. copy_to_user() //将数据从内核空间拷贝到用户空间

3.2 Linux 下的传统 IPC 通信原理

理解了上面的几个概念,我们再来看看传统的 IPC 方式中,进程之间是如何实现通信的。

通常的做法是消息发送方将要发送的数据存放在内存缓存区中,通过系统调用进入内核态。然后内核程序在内核空间分配内存,开辟一块内核缓存区,调用 copy_from_user() 函数将数据从用户空间的内存缓存区拷贝到内核空间的内核缓存区中。同样的,接收方进程在接收数据时在自己的用户空间开辟一块内存缓存区,然后内核程序调用 copy_to_user() 函数将数据从内核缓存区拷贝到接收进程的内存缓存区。这样数据发送方进程和数据接收方进程就完成了一次数据传输,我们称完成了一次进程间通信。如下图:

传统 IPC 通信原理

这种传统的 IPC 通信方式有两个问题:

  1. 性能低下,一次数据传递需要经历:内存缓存区 –> 内核缓存区 –> 内存缓存区,需要 2 次数据拷贝;
  2. 接收数据的缓存区由数据接收进程提供,但是接收进程并不知道需要多大的空间来存放将要传递过来的数据,因此只能开辟尽可能大的内存空间或者先调用 API 接收消息头来获取消息体的大小,这两种做法不是浪费空间就是浪费时间。

四. Binder 跨进程通信原理

理解了 Linux IPC 相关概念和通信原理,接下来我们正式介绍下 Binder IPC 的原理。

4.1 动态内核可加载模块 && 内存映射

正如前面所说,跨进程通信是需要内核空间做支持的。传统的 IPC 机制如管道、Socket 都是内核的一部分,因此通过内核支持来实现进程间通信自然是没问题的。但是 Binder 并不是 Linux 系统内核的一部分,那怎么办呢?这就得益于 Linux 的动态内核可加载模块(Loadable Kernel Module,LKM)的机制;模块是具有独立功能的程序,它可以被单独编译,但是不能独立运行。它在运行时被链接到内核作为内核的一部分运行。这样,Android 系统就可以通过动态添加一个内核模块运行在内核空间,用户进程之间通过这个内核模块作为桥梁来实现通信。

在 Android 系统中,这个运行在内核空间,负责各个用户进程通过 Binder 实现通信的内核模块就叫 Binder 驱动(Binder Dirver)。

那么在 Android 系统中用户进程之间是如何通过这个内核模块(Binder 驱动)来实现通信的呢?难道是和前面说的传统 IPC 机制一样,先将数据从发送方进程拷贝到内核缓存区,然后再将数据从内核缓存区拷贝到接收方进程,通过两次拷贝来实现吗?显然不是,否则也不会有开篇所说的 Binder 在性能方面的优势了。

这就不得不通道 Linux 下的另一个概念:内存映射

Binder IPC 机制中涉及到的内存映射通过 mmap() 来实现,mmap() 是操作系统中一种内存映射的方法。内存映射简单的讲就是将用户空间的一块内存区域映射到内核空间。映射关系建立后,用户对这块内存区域的修改可以直接反应到内核空间;反之内核空间对这段区域的修改也能直接反应到用户空间。

内存映射能减少数据拷贝次数,实现用户空间和内核空间的高效互动。两个空间各自的修改能直接反映在映射的内存区域,从而被对方空间及时感知。也正因为如此,内存映射能够提供对进程间通信的支持。

4.2 Binder IPC 实现原理

Binder IPC 正是基于内存映射(mmap)来实现的,但是 mmap() 通常是用在有物理介质的文件系统上的。

比如进程中的用户区域是不能直接和物理设备打交道的,如果想要把磁盘上的数据读取到进程的用户区域,需要两次拷贝(磁盘–>内核空间–>用户空间);通常在这种场景下 mmap() 就能发挥作用,通过在物理介质和用户空间之间建立映射,减少数据的拷贝次数,用内存读写取代I/O读写,提高文件读取效率。

而 Binder 并不存在物理介质,因此 Binder 驱动使用 mmap() 并不是为了在物理介质和用户空间之间建立映射,而是用来在内核空间创建数据接收的缓存空间。

一次完整的 Binder IPC 通信过程通常是这样:

  1. 首先 Binder 驱动在内核空间创建一个数据接收缓存区;
  2. 接着在内核空间开辟一块内核缓存区,建立内核缓存区内核中数据接收缓存区之间的映射关系,以及内核中数据接收缓存区接收进程用户空间地址的映射关系;
  3. 发送方进程通过系统调用 copy_from_user() 将数据 copy 到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。

如下图:

Binder IPC 原理

五. Binder 通信模型

介绍完 Binder IPC 的底层通信原理,接下来我们看看实现层面是如何设计的。

一次完整的进程间通信必然至少包含两个进程,通常我们称通信的双方分别为客户端进程(Client)和服务端进程(Server),由于进程隔离机制的存在,通信双方必然需要借助 Binder 来实现。

5.1 Client/Server/ServiceManager/驱动

前面我们介绍过,Binder 是基于 C/S 架构的。由一系列的组件组成,包括 Client、Server、ServiceManager、Binder 驱动。其中 Client、Server、Service Manager 运行在用户空间,Binder 驱动运行在内核空间。其中 Service Manager 和 Binder 驱动由系统提供,而 Client、Server 由应用程序来实现。Client、Server 和 ServiceManager 均是通过系统调用 open、mmap 和 ioctl 来访问设备文件 /dev/binder,从而实现与 Binder 驱动的交互来间接的实现跨进程通信。

%title插图%num

Client、Server、ServiceManager、Binder 驱动这几个组件在通信过程中扮演的角色就如同互联网中服务器(Server)、客户端(Client)、DNS域名服务器(ServiceManager)以及路由器(Binder 驱动)之前的关系。

通常我们访问一个网页的步骤是这样的:首先在浏览器输入一个地址,如 www.google.com 然后按下回车键。但是并没有办法通过域名地址直接找到我们要访问的服务器,因此需要首先访问 DNS 域名服务器,域名服务器中保存了 www.google.com 对应的 ip 地址 10.249.23.13,然后通过这个 ip 地址才能放到到 www.google.com 对应的服务器。

互联网通信模型

Android Binder 设计与实现一文中对 Client、Server、ServiceManager、Binder 驱动有很详细的描述,以下是部分摘录:

Binder 驱动
Binder 驱动就如同路由器一样,是整个通信的核心;驱动负责进程之间 Binder 通信的建立,Binder 在进程之间的传递,Binder 引用计数管理,数据包在进程之间的传递和交互等一系列底层支持。

ServiceManager 与实名 Binder
ServiceManager 和 DNS 类似,作用是将字符形式的 Binder 名字转化成 Client 中对该 Binder 的引用,使得 Client 能够通过 Binder 的名字获得对 Binder 实体的引用。注册了名字的 Binder 叫实名 Binder,就像网站一样除了除了有 IP 地址意外还有自己的网址。Server 创建了 Binder,并为它起一个字符形式,可读易记得名字,将这个 Binder 实体连同名字一起以数据包的形式通过 Binder 驱动发送给 ServiceManager ,通知 ServiceManager 注册一个名为“张三”的 Binder,它位于某个 Server 中。驱动为这个穿越进程边界的 Binder 创建位于内核中的实体节点以及 ServiceManager 对实体的引用,将名字以及新建的引用打包传给 ServiceManager。ServiceManger 收到数据后从中取出名字和引用填入查找表。

细心的读者可能会发现,ServierManager 是一个进程,Server 是另一个进程,Server 向 ServiceManager 中注册 Binder 必然涉及到进程间通信。当前实现进程间通信又要用到进程间通信,这就好像蛋可以孵出鸡的前提却是要先找只鸡下蛋!Binder 的实现比较巧妙,就是预先创造一只鸡来下蛋。ServiceManager 和其他进程同样采用 Bidner 通信,ServiceManager 是 Server 端,有自己的 Binder 实体,其他进程都是 Client,需要通过这个 Binder 的引用来实现 Binder 的注册,查询和获取。ServiceManager 提供的 Binder 比较特殊,它没有名字也不需要注册。当一个进程使用 BINDER_SET_CONTEXT_MGR 命令将自己注册成 ServiceManager 时 Binder 驱动会自动为它创建 Binder 实体( 这就是那只预先造好的那只鸡)。其次这个 Binder 实体的引用在所有 Client 中都固定为 0 而无需通过其它手段获得。也就是说,一个 Server 想要向 ServiceManager 注册自己的 Binder 就必须通过这个 0 号引用和 ServiceManager 的 Binder 通信。类比互联网,0 号引用就好比是域名服务器的地址,你必须预先动态或者手工配置好。要注意的是,这里说的 Client 是相对于 ServiceManager 而言的,一个进程或者应用程序可能是提供服务的 Server,但对于 ServiceManager 来说它仍然是个 Client。

Client 获得实名 Binder 的引用
Server 向 ServiceManager 中注册了 Binder 以后, Client 就能通过名字获得 Binder 的引用了。Client 也利用保留的 0 号引用向 ServiceManager 请求访问某个 Binder: 我申请访问名字叫张三的 Binder 引用。ServiceManager 收到这个请求后从请求数据包中取出 Binder 名称,在查找表里找到对应的条目,取出对应的 Binder 引用作为回复发送给发起请求的 Client。从面向对象的角度看,Server 中的 Binder 实体现在有两个引用:一个位于 ServiceManager 中,一个位于发起请求的 Client 中。如果接下来有更多的 Client 请求该 Binder,系统中就会有更多的引用指向该 Binder ,就像 Java 中一个对象有多个引用一样。

5.2 Binder 通信过程

至此,我们大致能总结出 Binder 通信过程:

  1. 首先,一个进程使用 BINDER_SET_CONTEXT_MGR 命令通过 Binder 驱动将自己注册成为 ServiceManager;
  2. Server 通过驱动向 ServiceManager 中注册 Binder(Server 中的 Binder 实体),表明可以对外提供服务。驱动为这个 Binder 创建位于内核中的实体节点以及 ServiceManager 对实体的引用,将名字以及新建的引用打包传给 ServiceManager,ServiceManger 将其填入查找表。
  3. Client 通过名字,在 Binder 驱动的帮助下从 ServiceManager 中获取到对 Binder 实体的引用,通过这个引用就能实现和 Server 进程的通信。

我们看到整个通信过程都需要 Binder 驱动的接入。下图能更加直观的展现整个通信过程(为了进一步抽象通信过程以及呈现上的方便,下图我们忽略了 Binder 实体及其引用的概念):

Binder 通信模型

5.3 Binder 通信中的代理模式

我们已经解释清楚 Client、Server 借助 Binder 驱动完成跨进程通信的实现机制了,但是还有个问题会让我们困惑。A 进程想要 B 进程中某个对象(object)是如何实现的呢?毕竟它们分属不同的进程,A 进程 没法直接使用 B 进程中的 object。

前面我们介绍过跨进程通信的过程都有 Binder 驱动的参与,因此在数据流经 Binder 驱动的时候驱动会对数据做一层转换。当 A 进程想要获取 B 进程中的 object 时,驱动并不会真的把 object 返回给 A,而是返回了一个跟 object 看起来一模一样的代理对象 objectProxy,这个 objectProxy 具有和 object 一摸一样的方法,但是这些方法并没有 B 进程中 object 对象那些方法的能力,这些方法只需要把把请求参数交给驱动即可。对于 A 进程来说和直接调用 object 中的方法是一样的。

当 Binder 驱动接收到 A 进程的消息后,发现这是个 objectProxy 就去查询自己维护的表单,一查发现这是 B 进程 object 的代理对象。于是就会去通知 B 进程调用 object 的方法,并要求 B 进程把返回结果发给自己。当驱动拿到 B 进程的返回结果后就会转发给 A 进程,一次通信就完成了。

%title插图%num

5.4 Binder 的完整定义

现在我们可以对 Binder 做个更加全面的定义了:

  • 从进程间通信的角度看,Binder 是一种进程间通信的机制;
  • 从 Server 进程的角度看,Binder 指的是 Server 中的 Binder 实体对象;
  • 从 Client 进程的角度看,Binder 指的是对 Binder 代理对象,是 Binder 实体对象的一个远程代理
  • 从传输过程的角度看,Binder 是一个可以跨进程传输的对象;Binder 驱动会对这个跨越进程边界的对象对一点点特殊处理,自动完成代理对象和本地对象之间的转换。

六. 手动编码实现跨进程调用

通常我们在做开发时,实现进程间通信用的*多的就是 AIDL。当我们定义好 AIDL 文件,在编译时编译器会帮我们生成代码实现 IPC 通信。借助 AIDL 编译以后的代码能帮助我们进一步理解 Binder IPC 的通信原理。

但是无论是从可读性还是可理解性上来看,编译器生成的代码对开发者并不友好。比如一个 BookManager.aidl 文件对应会生成一个 BookManager.java 文件,这个 java 文件包含了一个 BookManager 接口、一个 Stub 静态的抽象类和一个 Proxy 静态类。Proxy 是 Stub 的静态内部类,Stub 又是 BookManager 的静态内部类,这就造成了可读性和可理解性的问题。

Android 之所以这样设计其实是有道理的,因为当有多个 AIDL 文件的时候把 BookManager、Stub、Proxy 放在同一个文件里能有效避免 Stub 和 Proxy 重名的问题。

因此便于大家理解,下面我们来手动编写代码来实现跨进程调用。

6.1 各 Java 类职责描述

在正式编码实现跨进程调用之前,先介绍下实现过程中用到的一些类。了解了这些类的职责,有助于我们更好的理解和实现跨进程通信。

  • IBinder : IBinder 是一个接口,代表了一种跨进程通信的能力。只要实现了这个借口,这个对象就能跨进程传输。
  • IInterface : IInterface 代表的就是 Server 进程对象具备什么样的能力(能提供哪些方法,其实对应的就是 AIDL 文件中定义的接口)
  • Binder : Java 层的 Binder 类,代表的其实就是 Binder 本地对象。BinderProxy 类是 Binder 类的一个内部类,它代表远程进程的 Binder 对象的本地代理;这两个类都继承自 IBinder, 因而都具有跨进程传输的能力;实际上,在跨越进程的时候,Binder 驱动会自动完成这两个对象的转换。
  • Stub : AIDL 的时候,编译工具会给我们生成一个名为 Stub 的静态内部类;这个类继承了 Binder, 说明它是一个 Binder 本地对象,它实现了 IInterface 接口,表明它具有 Server 承诺给 Client 的能力;Stub 是一个抽象类,具体的 IInterface 的相关实现需要开发者自己实现。

6.2 实现过程讲解

一次跨进程通信必然会涉及到两个进程,在这个例子中 RemoteService 作为服务端进程,提供服务;ClientActivity 作为客户端进程,使用 RemoteService 提供的服务。如下图:

%title插图%num

那么服务端进程具备什么样的能力?能为客户端提供什么样的服务呢?还记得我们前面介绍过的 IInterface 吗,它代表的就是服务端进程具体什么样的能力。因此我们需要定义一个 BookManager 接口,BookManager 继承自 IIterface,表明服务端具备什么样的能力。

  1. /**
  2. * 这个类用来定义服务端 RemoteService 具备什么样的能力
  3. */
  4. public interface BookManager extends IInterface {
  5. void addBook(Book book) throws RemoteException;
  6. }

只定义服务端具备什么要的能力是不够的,既然是跨进程调用,那么接下来我们得实现一个跨进程调用对象 Stub。Stub 继承 Binder, 说明它是一个 Binder 本地对象;实现 IInterface 接口,表明具有 Server 承诺给 Client 的能力;Stub 是一个抽象类,具体的 IInterface 的相关实现需要调用方自己实现。

  1. public abstract class Stub extends Binder implements BookManager {
  2. public static BookManager asInterface(IBinder binder) {
  3. if (binder == null)
  4. return null;
  5. IInterface iin = binder.queryLocalInterface(DESCRIPTOR);
  6. if (iin != null && iin instanceof BookManager)
  7. return (BookManager) iin;
  8. return new Proxy(binder);
  9. }
  10. @Override
  11. protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
  12. switch (code) {
  13. case INTERFACE_TRANSACTION:
  14. reply.writeString(DESCRIPTOR);
  15. return true;
  16. case TRANSAVTION_addBook:
  17. data.enforceInterface(DESCRIPTOR);
  18. Book arg0 = null;
  19. if (data.readInt() != 0) {
  20. arg0 = Book.CREATOR.createFromParcel(data);
  21. }
  22. this.addBook(arg0);
  23. reply.writeNoException();
  24. return true;
  25. }
  26. return super.onTransact(code, data, reply, flags);
  27. }
  28. }

Stub 类中我们重点介绍下 asInterface 和 onTransact

先说说 asInterface,当 Client 端在创建和服务端的连接,调用 bindService 时需要创建一个 ServiceConnection 对象作为入参。在 ServiceConnection 的回调方法 onServiceConnected 中 会通过这个 asInterface(IBinder binder) 拿到 BookManager 对象,这个 IBinder 类型的入参 binder 是驱动传给我们的,正如你在代码中看到的一样,方法中会去调用 binder.queryLocalInterface() 去查找 Binder 本地对象,如果找到了就说明 Client 和 Server 在同一进程,那么这个 binder 本身就是 Binder 本地对象,可以直接使用。否则说明是 binder 是个远程对象,也就是 BinderProxy。因此需要我们创建一个代理对象 Proxy,通过这个代理对象来是实现远程访问。

接下来我们就要实现这个代理类 Proxy 了,既然是代理类自然需要实现 BookManager 接口。

  1. public class Proxy implements BookManager {
  2. public Proxy(IBinder remote) {
  3. this.remote = remote;
  4. }
  5. @Override
  6. public void addBook(Book book) throws RemoteException {
  7. Parcel data = Parcel.obtain();
  8. Parcel replay = Parcel.obtain();
  9. try {
  10. data.writeInterfaceToken(DESCRIPTOR);
  11. if (book != null) {
  12. data.writeInt(1);
  13. book.writeToParcel(data, 0);
  14. } else {
  15. data.writeInt(0);
  16. }
  17. remote.transact(Stub.TRANSAVTION_addBook, data, replay, 0);
  18. replay.readException();
  19. } finally {
  20. replay.recycle();
  21. data.recycle();
  22. }
  23. }
  24. }

我们看看 addBook() 的实现;在 Stub 类中,addBook(Book book) 是一个抽象方法,Client 端需要继承并实现它。

  • 如果 Client 和 Server 在同一个进程,那么直接就是调用这个方法。
  • 如果是远程调用,Client 想要调用 Server 的方法就需要通过 Binder 代理来完成,也就是上面的 Proxy。

在 Proxy 中的 addBook() 方法中首先通过 Parcel 将数据序列化,然后调用 remote.transact()。正如前文所述 Proxy 是在 Stub 的 asInterface 中创建,能走到创建 Proxy 这一步就说明 Proxy 构造函数的入参是 BinderProxy,即这里的 remote 是个 BinderProxy 对象。*终通过一系列的函数调用,Client 进程通过系统调用陷入内核态,Client 进程中执行 addBook() 的线程挂起等待返回;驱动完成一系列的操作之后唤醒 Server 进程,调用 Server 进程本地对象的 onTransact()。*终又走到了 Stub 中的 onTransact() 中,onTransact() 根据函数编号调用相关函数(在 Stub 类中为 BookManager 接口中的每个函数中定义了一个编号,只不过上面的源码中我们简化掉了;在跨进程调用的时候,不会传递函数而是传递编号来指明要调用哪个函数);我们这个例子里面,调用了 Binder 本地对象的 addBook() 并将结果返回给驱动,驱动唤醒 Client 进程里刚刚挂起的线程并将结果返回。

这样一次跨进程调用就完成了。

完整的代码我放到 GitHub 上了,有兴趣的小伙伴可以去看看。源码地址:https://github.com/BaronZ88/HelloBinder

*后建议大家在不借助 AIDL 的情况下手写实现 Client 和 Server 进程的通信,加深对 Binder 通信过程的理解。

受个人能力水平限制,文章中难免会有错误。如果大家发现文章不足之处,欢迎与我沟通交流。

本文在写作过程中参考了很多文章、书籍和源码,其中有很多描述和图片都借鉴了下面的文章,在这里感谢大佬们的无私分享!

参考资料如下:

  • Android Binder 设计与实现 – 设计篇
  • Android 进程间通信(IPC)机制 Binder 简要介绍和学习计划、《Android 系统源代码情景分析》
  • Binder 学习指南
  • Binder 系列文章
  • Android 图文详解 Binder 跨进程通信原理
  • Android 深入浅出之 Binder 机制
  • 用户空间与内核空间
  • 认真分析 mmap :是什么 为什么 怎么用

Android系统的安全功能

安全

Android 采用了业界*的安全功能,并与开发者和设备实现人员密切合作,以确保 Android 平台和生态系统的安全。要打造一个由基于 Android 平台以及围绕 Android 平台开发且由云服务提供支持的应用和设备组成的强大生态系统,稳定可靠的安全模型至关重要。为此,在整个开发生命周期内,Android 都遵循了严格的安全计划。

Android 是一款开放的系统。Android 应用使用通过 Android 平台提供的先进硬件和软件以及本地数据和收到的数据,为消费者带来创新和价值。为了实现这一价值,Android 平台提供了一个应用环境,该环境可以保护用户、数据、应用、设备和网络的机密性、完整性与可用性。

保障开放平台的安全需要强大的安全架构和严格的安全程序。Android 采用了一个多层安全模型,该模型非常灵活,能够在支持开放平台的同时保护平台的所有用户。如需关于报告安全问题以及关于更新流程的信息,请参阅安全更新和资源。

Android 适合开发者使用。Android 中设计了多种旨在减轻开发者负担的安全控制机制。精通安全技术的开发者可以轻松使用并依赖灵活的安全控制机制。不太熟悉安全技术的开发者则由默认的安全机制提供保护。

除了提供一个稳定的平台来供开发者开发应用之外,Android 还以多种方式为开发者提供其他支持。Android 安全团队会检查应用中是否存在潜在漏洞,并会提出关于如何解决这些问题的建议。对于带有 Google Play 的设备,Play 服务会为关键软件库(例如,用于保障应用通信安全的 OpenSSL)提供安全更新。Android 安全团队发布了一款用于测试 SSL 的工具 (Nogotofail),该工具可以协助开发者发现潜在的安全问题,无论他们是在使用什么平台进行开发。

如需面向 Android 应用开发者的更多信息,请访问 developer.android.com。

Android 适合用户使用。用户可以查看每个应用请求的权限,并可以对这些权限加以控制。这种设计考虑到了攻击者可能会尝试进行一些常见的攻击,例如,诱使设备用户安装恶意软件的社会工程攻击,以及对 Android 上的第三方应用的攻击。Android 能够降低受到这些攻击的可能性,并能够大大限制攻击成功时造成的影响。在设备到达用户手中后,Android 的安全性将会不断提升:Android 会与合作伙伴和公众密切合作,为还在继续接收安全更新的所有 Android 设备提供补丁程序。

如需面向*终用户的更多信息,请访问 Nexus 帮助中心、Pixel 帮助中心或设备制造商的帮助中心。

本文档概述了 Android 安全计划的目标,介绍了 Android 安全架构方面的基础知识,并解答了对系统架构师和安全分析人员来说*相关的问题。本文档重点介绍 Android 核心平台的安全功能,而不是讨论具体应用特有的安全问题,例如,与浏览器或短信应用相关的问题。

背景

Android 提供了一个适用于移动设备的开放源代码平台和应用环境。

以下各个部分和页面介绍了 Android 平台的安全功能。图 1 总结了 Android 软件堆栈各个层的安全组件和注意事项。每个组件都假定下面的组件均已采取适当的安全措施。除了作为 Root 代码运行的少量 Android 操作系统代码外,Linux 内核上方的所有代码都受应用沙盒的限制。

图 1:Android 软件堆栈

图 1. Android 软件堆栈。

Android 平台的主要构造块包括:

  • 设备硬件:Android 能够在多种硬件配置中运行,其中包括智能手机、平板电脑、手表、汽车、智能电视、OTT 游戏盒和机顶盒。Android 独立于处理器,但它确实利用了一些针对硬件的安全功能,例如 ARM eXecute-Never。
  • Android 操作系统:核心操作系统是在 Linux 内核之上构建的。所有设备资源(例如,摄像头功能、GPS 数据、蓝牙功能、电话功能、网络连接等)都通过该操作系统访问。
  • Android 应用运行时:Android 应用通常都是使用 Java 编程语言编写的,并在 Android 运行时 (ART) 中运行。不过,仍有许多应用(包括核心 Android 服务和应用)是本机应用或包含本机库。ART 和本机应用在相同的安全环境中运行(包含在应用沙盒内)。应用在文件系统中有一个专用部分,它们可以在其中写入私密数据,包括数据库和原始文件。

Android 应用扩展了 Android 核心操作系统。应用有两个主要来源:

  • 预先安装的应用:Android 包括一套预先安装的应用,其中包括电话、电子邮件、日历、网络浏览器和通讯录应用。这些应用不仅能够用作用户应用,而且能够提供可供其他应用访问的关键设备功能。预先安装的应用可能是开放源代码 Android 平台的一部分,也可能是由具体设备的制造商开发的。
  • 用户安装的应用:Android 提供了一个支持任何第三方应用的开放式开发环境。Google Play 为用户提供了数十万款应用。

Google 安全服务

Google 提供了一套基于云的服务,用户可通过 Google 移动服务将这些服务安装到兼容的 Android 设备上。虽然这些服务不是 Android 开放源代码项目的一部分,但它们包含在许多 Android 设备中。如需关于其中部分服务的更多信息,请参阅 Android 安全团队发布的 2016 年年度回顾报告。

Google 的主要安全服务包括:

  • Google Play:Google Play 是一系列服务的总称。借助这些服务,用户可以通过自己的 Android 设备或网络发现、安装和购买应用。Google Play 可让开发者轻松覆盖 Android 用户和潜在客户。此外,Google Play 还提供社区审核、应用许可验证、应用安全扫描以及其他安全服务。
  • Android 更新:Android 更新服务可为某些 Android 设备提供新功能和安全更新,其中包括通过网络或无线下载 (OTA) 方式提供的更新。
  • 应用服务:可让 Android 应用使用云功能的框架,例如应用数据和设置备份功能,以及用于推送消息的云端至设备消息传递功能 (C2DM)。
  • 验证应用:在用户安装有害应用时发出警告或自动阻止安装;持续扫描设备上的应用,并在发现有害应用时发出警告或将其移除。
  • SafetyNet:一款旨在保护隐私的入侵检测系统,能够帮助 Google 跟踪和降低已知的安全威胁,并能够发现新的安全威胁。
  • SafetyNet Attestation:用于确定设备是否与 CTS 兼容的第三方 API。Attestation 还可以协助识别与应用服务器通信的 Android 应用。
  • Android 设备管理器:既是一款网络应用,也是一款 Android 应用,用于寻找丢失的设备或被盗的设备。

安全计划概述

Android 安全计划的关键组成部分包括:

  • 设计审核:Android 安全流程在开发生命周期的早期便开始了,并会在这一阶段创建大量的可配置安全模型和设计。平台的每项主要功能都会由工程和安全资源进行审核,并且适当的安全控制机制会被集成到系统架构中。
  • 渗透测试和代码审核:在平台开发期间,Android 创建的组件和开放源代码组件都要接受严格的安全审核。这些审核由 Android 安全团队、Google 的信息安全工程团队和独立的安全顾问进行。这些审核的目标是在主要版本发布之前找出存在的缺陷和可能的漏洞,并模拟将由外部安全专家在平台发布时进行的各种类型的分析。
  • 开放源代码和社区审核:Android 开放源代码项目允许任何感兴趣者对其进行广泛的安全审核。Android 还使用已经过重要外部安全审核的开放源代码技术,例如 Linux 内核。Google Play 面向用户和公司开设了一个论坛,以便直接向用户提供与具体应用相关的信息。
  • 事件响应:即使采取了所有这些预防措施,平台发布后也仍可能会出现安全问题,为此,Android 项目制定了一个全面的安全响应流程。Android 安全团队有全职成员负责监控用于讨论潜在漏洞的 Android 专用安全社区和一般安全社区,并且他们会查看提交到 Android 错误数据库中的安全错误。发现确实存在的问题后,Android 团队会启动响应流程,以便快速修复漏洞,确保将所有 Android 用户面临的潜在风险降至*低。这些云支持的响应可能包括更新 Android 平台(无线下载更新)、从 Google Play 中移除应用,以及从现场设备中移除应用。
  • 每月安全更新:Android 安全团队会为 Google Nexus 设备和所有设备制造合作伙伴提供每月更新。

平台安全架构

通过将传统的操作系统安全控制机制扩展到以下用途,Android 致力于成为*安全、*实用的移动平台操作系统:

  • 保护应用和用户数据
  • 保护系统资源(包括网络)
  • 将应用同系统、其他应用和用户隔离开来

为了实现这些目标,Android 提供了以下关键安全功能:

  • 通过 Linux 内核在操作系统级别提供的强大安全功能
  • 针对所有应用的强制性应用沙盒
  • 安全的进程间通信
  • 应用签名
  • 应用定义的权限和用户授予的权限

SOA、SaaS就是云计算么,有什么区别,三者之间有什么关系?

随着云计算的概念受到越来越多的人的推崇和论证,“云上的日子”看起来离我们越来越近了。几乎所有的IT厂商都不约而同了启动了各自的云计算战略。但是由于每个企业的战略出发点不同,企业推出的云计算概念也有所差别。

如果仅仅以自己的产品和业务为标准,片面地将云计算理解为SOA或SaaS,则不仅混淆了云计算概念,模糊了其产品形式, 还阻碍了云计算的推广和应用。那么, SOA、 SaaS等同于云计算么?它们有什么区别?我们应当如何正确理解这三者之间的关系呢?

解惑云计算、SOA与SaaS

云计算涵盖的范围很广泛,内容也很丰富。我们通常可以把云分为三个层次:硬件层的基础架构云(Infrastructure as a Service, IaaS)、平台云(Platform as a Service, PaaS)和软件应用云(Software as a Service, SaaS)。目前大家对于云计算概念的理解,主要有两个误区:一是片面地把SaaS视为云计算;二是以为运用了SOA架构就感觉好像实现了云计算,于是就把SOA和云计算等同起来。

实际上,SOA作为一种面向服务的架构,是一种软件架构设计的模型和方法论。从业务角度来看,一切以*大化“服务”的价值为出发点,SOA利用企业现有的各种软件体系,重新整合并构建起一套新的软件架构。这套软件架构能够随着业务的变化,随时灵活地结合现有服务,组成新软件,共同服务于整个企业的业务体系。简单的理解,我们可以把SOA看作是模块化的组件,每个模块都可以实现独立功能,而不同模块之间的结合则可以提供不同的服务,模块之间的接口遵循统一标准,可以实现低成本的重构和重组。在SOA的技术框架下,可以把杂乱无章的庞大系统整合成一个全面有序的系统,从而增加企业在业务发展过程中应用系统的灵活性,实现*大的IT资产利用率。

SOA技术其实在几年前就已经出现了,但是任何一种IT技术,真正要产生效应,都必须要以大大降低实际系统的使用、维护以及升级的成本为前提。更重要的是,一种技术的普及需要,都只有在这种技术不断成熟,并且形成相应的规模应用之后才能实现。

SaaS则是一种基于互联网技术的软件价值交付的新型业务模式。具体地讲,SaaS是按照使用者的需求提供软件应用服务的业务模式。SOA架构可发挥其在系统界面和接口标准化等方面的优势,为SaaS提供一个较好的技术平台,从服务管理和系统运维角度为SaaS提供有力的技术支撑,从而有助于灵活地构建起一个用户成本*低的SaaS方案。

由此看来,SOA与SaaS有着必然的联系:两者都面向服务。但两者也有着明显的区别:SaaS侧重于运营和交付,SOA侧重于平台架构。所以,SaaS是云计算的*终价值交付运营模式,SOA是实现云计算开放架构的基础理念之一,两者都包含了云计算的某些重要特征,但是他们都不能称之为云计算的全部。

互联网技术催生云计算、SaaS与SOA的汇合

云计算与SaaS、SOA三者本身的发展轨迹和侧重点不同,但是却又互相联系,互相影响。

我们可以从云计算的发展历史来看,云计算的模式就好比以前在大学或者政府的研究机构里面的大型计算机中心。计算机中心把计算机的计算和存储资源以租用时段的方式提供给内部各个科研单位,或者提供给外部用户。从过去的大型计算机时代到个人计算机、企业服务器的兴起,计算、存储资源开始分散于企业。但是如今,企业面对成千上百台服务器以及上千种不同的软件,运维成本越来越高。云计算可以把所有的计算资源虚拟化,进行动态管理,进而大大降低企业的IT运维成本,因此已是一个颇有影响的技术趋势。

另一方面,从可适应性计算、网格计算到云计算,除了硬件资源的集中使用外,企业更希望操作系统、数据库,以及软件、应用等都能通过集中调配的方式满足企业的各种需求。随着互联网技术的迅速发展与普及,SOA则能带来整个软件系统的互联成本、维护成本、升级成本的大幅降低,并成为支撑云计算的技术标准。

所有因素都帮助促成了从硬件层的基础架构云(IaaS)到平台云(PaaS)再到软件应用云(SaaS)不同层次的云计算。

SOA与SaaS合力, 推动云计算产业的成熟与发展

云计算实现了IT基础设施的社会共享;SOA有利于整合技术平台,统一技术标准,推动软件产业价值链中的各成员间的协调配合,充分利用硬件资源共享的有利条件,促使云上的软件系统日趋成熟;SaaS则通过软件交付模式上的创新,为云计算开辟出更大的市场空间,激励众多软件厂商开发出更多应用,从而使云计算产业联盟不断发展壮大。

总而言之,在云计算产业链向前推进的过程中,如果我们能善于发挥SOA和SaaS两者的特点与优势,将其效用结合起来,那必将有助于实现云计算产业的新一轮高速发展。

Android 安全机制概述

1 Android 安全机制概述

Android 是一个权限分离的系统 。 这是利用 Linux 已有的权限管理机制,通过为每一个 Application 分配不同的 uid 和 gid , 从而使得不同的 Application 之间的私有数据和访问( native 以及 java 层通过这种 sandbox 机制,都可以)达到隔离的目的 。 与此同时, Android 还 在此基础上进行扩展,提供了 permission 机制,它主要是用来对 Application 可以执行的某些具体操作进行权限细分和访问控制,同时提供了 per-URI permission 机制,用来提供对某些特定的数据块进行 ad-hoc 方式的访问。

1.1 uid 、 gid 、 gids

Android 的权限分离的基础是建立在 Linux 已有的 uid 、 gid 、 gids 基础上的 。
UID: Android 在 安装一个应用程序,就会为 它 分配一个 uid (参考 PackageManagerService 中的 newUserLP 实现)。其中普通 A ndroid 应用程序的 uid 是从 10000 开始分配 (参见 Process.FIRST_APPLICATION_UID ), 10000 以下是系统进程的 uid 。
GID:对 于普通应用程序来说, gid 等于 uid 。由于每个应用程序的 uid 和 gid 都不相同, 因此不管是 native 层还是 java 层都能够达到保护私有数据的作用 。
GIDS :  gids 是由框架在 Application 安装过程中生成,与 Application 申请的具体权限相关。 如果 Application 申请的相应的 permission 被 granted ,而且 中有对应的 gid s , 那么 这个 Application 的 gids 中将 包含这个 gid s 。

uid gid gids 的 详细 设置过程:

请参考 Act i vityManagerService 中的 startProcessLocked 。在通过 zygote 来启动一个 process 时,直接将 uid 传给 给了 gid 。再通过 zygote 来 fork 出新的进程( zygote.java 中的 forkAndSpecialize ),*终在 native 层( dalvik_system_zygote.c )中的 forkAndSpecializeCommon 中通过 linux 系统调用来进行 gid 和 uid 和 gids 的设置。

1.2 permission

一个权限主要包含三个方面的信息:权限的名称;属于的权限组;保护级别。一个权限组是指把权限按照功能分成的不同的集合。每一个权限组包含若干具体权限,例如在 COST_MONEY 组中包含 android.permission.SEND_SMS , android.permission.CALL_PHONE 等和费用相关的权限。

每个权限通过 protectionLevel 来标识保护级别: normal , dangerous , signature , signatureorsystem 。不同的保护级别代表了程序要使用此权限时的认证方式。 normal 的权限只要申请了就可以使用; dangerous 的权限在安装时需要用户确认才可以使用; signature 和 signatureorsystem 的权限需要使用者的 app 和系统使用同一个数字证书。

Package 的权限信息主要 通过在 AndroidManifest.xml 中通过一些标签来指定。如 <permission> 标签, <permission-group> 标签 <permission-tree> 等标签。如果 package 需要申请使用某个权限,那么需要使用 <use-permission> 标签来指定。

2 Android permission 管理机制

2.1 Framework permission 机制

2.1.1 安装入口

permission 的初始化,是指 permission 的向系统申请,系统进行检测并授权,并建立相应的数据结构。*大多数的情况下 permission 都是从一个 package 中扫描所得,而这发生在 package 安装和升级的时候。一般有如下几种 安装入口:

n packageInstaller , package 被下载安装时会触发使用。 packageInstaller 会通过 AppSecurityPermissions 来检查 dangerous 的权限,并对用户给出提示。
n pm 命令 。
n adb install 。*终还是 调用 pm install 来安装 apk 包。

n 拷贝即安装。 PackageManagerService 中使用 AppDirObserver 对 /data/app/ 进行监视 ,如果有拷贝即触发安装。

这些安装方式 *终都会通过调用 PackageManagerService 中的函数来完成程序的安装。

2.1.2 permission 创建

*步,从 AndroidManifest.xml 中提取 permission 信息。主要提取如下信息:
² shared uid
指定与其它 package 共享同一个 uid 。
² permission
提取 permissions 标签指定属性。它使用 permissionInfo 来描述一个权限的基本信息。需要指定 protectedLevel 信息,并指定所属 group 信息。它将被添加到这个 package 的 permissions 这个 list 结构中。
² permission-tree
提取 permissions-tree 标签属性。 permissions-tree 也通过 permissionInfo 来描述,并被添加到 package 的 permissions 这个 list 结构中。 permission-tree 只是一个名字空间,用来向其中动态添加一些所谓 Dynamic 的 permission ,这些 permission 可以动态修改。这些 permission 名称要以 permission-tree 的名称开头。它本身不是一种权限,没有 protectedLevel 和所属 group 。只是保存了所属的 packge 和权限名(带有 package 前缀的)。
² permission-group

定义 permission 组信息,用 PermissionGroup 表示。本身不代表一个权限,会添加进入 package 的 permissionGroups 这个 list 中。

² uses-permission

定义了 package 需要申请的权限名。将权限名添加到 package 的 requestedPermissions 这个 list 中。

² adopt-permissions

将该标签指定的 name 存入 package 的 mAdoptPermissions 这个 list 中。 Name 指定了这个 package 需要从 name 指定的 package 进行权限领养。在 system package 进行升级时使用。

第二步。获取 Package 中的证书,验证,并将签名信息保存在 Package 结构中。

1. 如果该 package 来自 system img (系统 app ),那么只需要从该 Package 的 AndroidManifest.xml 中获取签名信息,而无需验证其完整性。但是如果这个 package 与其它 package 共享一个 uid ,那么这个共享 uid 对应的 sharedUser 中保存的签名与之不一致,那么签名验证失败。

2. 如果是普通的 package ,那么需要提取证书和签名信息,并对文件的完成性进行验证。

第三步。如果是普通的 package ,那么清除 package 的 mAdoptPermissions 字段信息(系统 package 升级才使用)。

第四步。如果在 AndroidManifest.xml 中指定了 shared user ,那么先查看全局 list 中( mSharedUsers )是否该 uid 对应的 SharedUserSetting 数据结构,若没有则新分配一个 uid ,创建 SharedUserSetting 并保存到全局全局 list ( mSharedUsers )中。

mUserIds 保存了系统中已经分配的 uid 对应的 SharedUserSetting 结构。每次分配时总是从*个开始轮询,找到*个空闲的位置 i ,然后加上 FIRST_APPLICATION_UID 即可。

第五步。创建 PackageSettings 数据结构。并将 PackageSettings 与 SharedUserSetting 进行绑定。其中 PackageSettings 保存了 SharedUserSetting 结构;而 SharedUserSetting 中会使用 PackageSettings 中的签名信息填充自己内部的签名信息,并将 PackageSettings 添加到一个队列中,表示 PackageSettings 为其中的共享者之一。

在创建时,首先会以 packageName 去全局数据结构 mPackages 中查询是否已经有对应的 PackageSettings 数据结构存在。如果已经存在 PackageSettings 数据结构(比如这个 package 已经被 uninstall ,但是还没有删除数据,此时 package 结构已经被释放)。那么比较该 package 中的签名信息(从 AndroidManifest 中扫描得到)与 PackageSettings 中的签名信息是否匹配。如果不匹配但是为 system package ,那么信任此 package ,并将 package 中的签名信息更新到已有的 PackageSettings 中去,同时如果这个 package 与其它 package 共享了 uid ,而且 shared uid 中保存的签名信息与当前 package 不符,那么签名也验证失败。

第六步。如果 mAdoptPermissions 字段不为空,那么处理 permission 的领养(从指定的 package 对应的 PackageSettings 中,将权限的拥有者修改为当前 package ,一般在 system app 升级的时候才发生,在此之前需要验证当被领养的 package 已经被卸载,即检查 package 数据结构是否存在)。

第七步。添加自定义权限。将 package 中定义的 permissionGroup 添加到全局的列表 mPermissionGroups 中去;将 package 中定义的 permissions 添加到全局的列表中去(如果是 permission-tree 类型,那么添加到 mSettings.mPermissionTrees ,如果是一般的 permission 添加到 mSettings.mPermissions 中)。

第八步。清除不一致的 permission 信息。

1. 清除不一致的 permission-tree 信息。如果该 permission-tree 的 packageSettings 字段为空,说明还未对该 package 进行过解析(若代码执行到此处时 packageSettings 肯定已经被创建过),将其 remove 掉。如果 packageSettings 不为空,但是对应的 package 数据结构为空(说明该 package 已经被卸载,但数据还有保留),或者 package 数据结构中根本不含有这个 permission-tree ,那么将这个 permission-tree 清除。

2. 清除不一致的 permission 信息。如果 packageSettings 或者 package 结构为空(未解析该 package 或者被卸载,但数据有保留),或者 package 中根本没有定义该 permission ,那么将该 permission 清除。

第九步。对每一个 package 进行轮询,并进行 permission 授权。

1. 对申请的权限进行检查,并更新 grantedPermissions 列表

2. 如果其没有设置 shared user id ,那么将其 gids 初始化为 mGlobalGids ,它从 permission.xml 中读取。

3. 遍历所有申请的权限,进行如下检查

1 )如果是该权限是 normal 或者 dangerous 的。通过检查。

2 )如果权限需要签名验证。如果签名验证通过。还需要进行如下检查

* 如果程序升级,而且是 system package 。那么是否授予该权限要看原来的 package 是否被授予了该权限。如果被授予了,那么通过检查,否则不通过。

* 如果是新安装的。那么检查通过。

4. 如果 3 中检查通过,那么将这个 permission 添加到 package 的 grantedPermissions 列表中,表示这个 permission 申请成功( granted )。申请成功的同时会将这个申请到的 permission 的 gids 添加到这个 package 的 gids 中去。

5. 将 permissionsFixed 字段标准为 ture ,表示这个 packge 的 permission 进行过修正。后续将禁止对非 system 的 app 的权限进行再次修正。

2.1.3 Dynamic permission 的管理

PackageManagerService 提供了 addPermission/ removePermission 接口用来动态添加和删除一些权限。但是这些权限必须是所谓的动态权限( BasePermission.TYPE_DYNAMIC )。

一个 Package 如果要添加 Dynamic permissions ,首先必须要在 manifest 中申明 <permission-tree> 标签,它实际上是一个权限的名字空间(例如,“ com.foo.far ”这个权限就是 permission-tree “com.foo ”的成员),本身不是一个权限。一个 Package 只能为自己的 permission-tree 或者拥有相同的 uid 的 package 添加或者删除权限。

Package 不能够通过这种接口去修改在 manifest 中静态申请的权限,否则抛出异常。

首先查找这个 permission 在全局 permission 列表 mSettings.mPermissions 中是否存在。如果存在,而且类型为 BasePermission.TYPE_DYNAMIC 那么根据传入的权限信息修改全局表中的权限信息,并触发 permissions.xml 的持久化。

如果在全局的 permission 列表 mSettings.mPermissions 中没有找到,先找到这个 permission 所在 permissionTree ,然后添加到全局 permission 列表 mSettings.mPermissions 中去,并触发 permissions.xml 的持久化。

2.1.4 Uri permission 的管理

下面两个 接口 主要用于 Uri permission 的管理 (其实现在 ActivityManagerService 中)。
// 为指定的 uid 和 targetPkg 添加对某个 content Uri 的读或者写权限。
public void grantUriPermission(IApplicationThread caller, String targetPkg, Uri uri, int mode) throws RemoteException;
// 清除所有通过 grantUriPermission 对某个 Uri 授予的权限。
public void revokeUriPermission(IApplicationThread caller, Uri uri, int mode) throws RemoteException;

grantUriPermission 主要的实现过程分析。

grantUriPermission 分析:

1. 验证 caller 的 ProcessRecord 和 targetPkg 不为空。否则检测不通过。

2. 验证所请求的 mode 为 Intent.FLAG_GRANT_READ_URI_PERMISSION 或者为 Intent.FLAG_GRANT_WRITE_URI_PERMISSION ,否则不通过。

3. 确保参数 Uri 是一个 content Uri 。否则,则检测不通过。

4. 通过 Uri 得到目标 ContentProvider ,如果不存在,则检测不通过。

5. 从 PackageManagerService 中获得 targetPkg 对应的 uid 。

6. 检查 target uid 所对应的 package 是否真正需要这个权限?

先判断要申请的是读还是写权限,然后查看对应的 ContentProvider 中对应的 readPermission writePermission 字段是否保存了权限名称。 如果该字段不为空,则以 target uid 和该权限名去PackageManagerService 中去查找该 uid 是否被 granted 了该权限。如果已经获得了该权限,那么无需再去为这个 Activity 去申请这个 Uri 权限了,返回。否者继续执行如下操作。

7. 检查这个 ContentProvider 的 grantUriPermissions 开关变量,是否允许对其它 package 进行权限的 grant 操作。如果禁止,那么抛出异常。

8. 检查这个 ContentProvider 是否设置了 Uri 的过滤类型 uriPermissionPatterns ,如果设置了过滤类型,则将需要申请权限的 Uri 与之匹配。匹配不同过,则抛出异常。

9. 检查调用者自己是否有权限访问这个 Uri 。如果没有,抛出异常。

10. 从 mGrantedUriPermissions 中取得 target uid 对应的 HashMap<Uri, UriPermission> 数据结构。用 target uid 和 Uri 生成 UriPermission 并保存在 mGrantedUriPermissions 中。

revokeUriPermission 实现分析。

找到该 Uri 对应的 ContentProvider ,然后删除 mGrantedUriPermissions 中与 Uri 对应的所有权限。

2.2 permission 的动态检查

这里的动态检查是指是 package 在程序运行过程中进行某些操作或者数据访问时才进行的 check ,与之对应的是应用程序安装或者升级时 PackageManagerService 通过扫描包中的静态权限信息相对应。

系统与权限 检查 相关的机制的实现主要集中在 PackageManagerService 和 ActivityManagerService 中。 ActivityManagerService 主要负责的是底层的 uid 层次的身份检查; PackageManagerService 则维护了 uid 到自己拥有的和被授予的权限的一张表。在通过 ActivityManagerService 的身份检查后, PackageManagerService 根据请求者的 uid 来查看这张表,判断其是否具有相应的权限。

除此之外, per-URI permission 机制的实现也需要一张表,它维护在 ActivityManagerService 中,它建立了从 content URI 到被授权访问这个 URI 的 component 之间的映射。但是它也需要借助 PackageManagerService 的机制来辅助实现。

2.2.1 framework 提供的接口

Android framework 中提供了一些接口用来对外来的访问(包括自己)进行权限检查 。 这些接口 主要通过 ContextWrapper 提供,具体实现在 ContextImpl 中 。如果 package 接受到外来访问者的操作请求,那么可以调用这些接口进行权限检查。一般情况下可以把这些接口的检查接口分为两种,一种是返回错误,另一种是抛出异常。

主要包含如下几组:

n permission 和 uid 检查 API

下面这一组接口主要用来检查某个调用(或者是其它 package 或者是自己)是否拥有访问某个 permission 的权限。参数中 pid 和 uid 可以指定,如果没有指定,那么 framework 会通过 Binder 来获取调用者的 uid 和 pid 信息,加以填充。返回值为 PackageManager.PERMISSION_GRANTED 或者 PackageManager.PERMISSION_DENIED 。

public int checkPermission(String permission, int pid, int uid) // 检查某个 uid 和 pid 是否有 permission 权限

public int checkCallingPermission(String permission) // 检查调用者是否有 permission 权限,如果调用者是自己那么返回 PackageManager.PERMISSION_DENIED

public int checkCallingOrSelfPermission(String permission) // 检查自己或者其它调用者是否有 permission 权限

下面这一组和上面类似,如果遇到检查不通过时,会抛出异常,打印消息 。

public void enforcePermission(String permission, int pid, int uid, String message)

public void enforceCallingPermission(String permission, String message)

public void enforceCallingOrSelfPermission(String permission, String message)

n per-URI 检查 API

为某个 package 添加访问 content Uri 的读或者写权限。

public void grantUriPermission(String toPackage, Uri uri, int modeFlags)

public void revokeUriPermission(Uri uri, int modeFlags)

检查某个 pid 和 uid 的 package 是否拥有 uri 的读写权限,返回值表示是否被 granted 。

public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags)

public int checkCallingUriPermission(Uri uri, int modeFlags)

public int checkCallingOrSelfUriPermission(Uri uri, int modeFlags)

public int checkUriPermission(Uri uri, String readPermission,String writePermission, int pid, int uid, int modeFlags)

检查某个 pid 和 uid 的 package 是否拥有 uri 的读写权限,如果失败则抛出异常,打印消息 。

public void enforceUriPermission(Uri uri, int pid, int uid, int modeFlags, String message)

public void enforceCallingUriPermission(Uri uri, int modeFlags, String message)

public void enforceCallingOrSelfUriPermission(Uri uri, int modeFlags, String message)

public void enforceUriPermission(Uri uri, String readPermission, String writePermission,int pid, int uid, int modeFlags, String message)

2.2.2 实现分析

ContextImpl.java 中提供的 API ,其实都是由 ActivityManagerService 中的如下几个接口进行的封装。

public int checkPermission(String permission, int pid, int uid) throws RemoteException; // 主要用于一般的 permission 检查

public int checkUriPermission(Uri uri, int pid, int uid, int mode) throws RemoteException; // 主要用于 Content Uri 的 permission 检查

n checkPermission 的实现分析

1. 如果传入的 permission 名称为 null ,那么返回 PackageManager.PERMISSION_DENIED 。

2. 判断调用者 uid 是否符合要求 。

1 ) 如果 uid 为 0 ,说明是 root 权限的进程,对权限不作控制。

2 ) 如果 uid 为 system server 进程的 uid ,说明是 system server ,对权限不作控制。

3 ) 如果是 ActivityManager 进程本身,对权限不作控制。

4 )如果调用者 uid 与参数传入的 req uid 不一致,那么返回 PackageManager.PERMISSION_DENIED 。

3. 如果通过 2 的检查后,再 调用 PackageManagerService.checkUidPermission ,判断 这个 uid 是否拥有相应的权限,分析如下 。

1 ) 首先它通过调用 getUserIdLP ,去 PackageManagerService.Setting.mUserIds 数组中,根据 uid 查找 uid (也就是 package )的权限列表。一旦找到,就表示有相应的权限。

2 ) 如果没有找到,那么再去 PackageManagerService.mSystemPermissions 中找。这些信息是启动时,从 /system/etc/permissions/platform.xml 中读取的。这里记录了一些系统级的应用的 uid 对应的 permission 。

3 )返回结果 。

n 同样 checkUriPermission 的实现 主要在 ActivityManagerService 中,分析如下:

1. 如果 uid 为 0 ,说明是 root 用户,那么不控制权限。

2. 否则,在 ActivityManagerService 维护的 mGrantedUriPermissions 这个表中查找这个 uid 是否含有这个权限,如果有再检查其请求的是读还是写权限。
3 Android 签名机制

关于签名机制,其实分两个阶段。

包扫描阶段需要进行完整性和证书的验证。普通 package 的签名和证书是必须要先经过验证的。具体做法是对 manifest 下面的几个文件进行完整性检查。完整性检查包括这个 jar 包中的所有文件。如果是系统 package 的话,只需要使用 AndroidMenifest.xml 这个文件去提取签名和验证信息就可以了。

在权限创建阶段。如果该 package 来自 system img (系统 app ),那么 trust it ,而且使用新的签名信息去替换就的信息。前提是如果这个 package 与其它 package 共享一个 uid ,那么这个共享 uid 对应的 sharedUser 中保存的签名与之不一致,那么签名验证失败。有些时候系卸载一个 app ,但是不删除数据,那么其 PackageSettings 信息会保留,其中会保存签名信息。这样再安装是就会出现不一致。

3.1 Android Package 签名原理

android 中系统和 app 都是需要签名的。可以自己通过 development/tools/make_key 来生成公钥和私钥。

android 源代码中提供了工具 ./out/host/linux-x86/framework/signapk.jar 来进行手动签名。签名的主要作用在于限制对于程序的修改仅限于同一来源。系统中主要有两个地方会检查。如果是程序升级的安装,则要检查新旧程序的签名证书是否一致,如果不一致则会安装失败;对于申请权限的 protectedlevel 为 signature 或者 signatureorsystem 的,会检查权限申请者和权限声明者的证书是否是一致的。签名相关文件可以从 apk 包中的 META-INF 目录下找到。

signapk.jar 的源代码在 build/tools/signapk ,签名主要有以下几步:

l 将除去 CERT.RSA , CERT.SF , MANIFEST.MF 的所有文件生成 SHA1 签名

首先将除了 CERT.RSA , CERT.SF , MANIFEST.MF 之外的所有非目录文件分别用 SHA-1 计算摘要信息,然后使用 base64 进行编码,存入 MANIFEST.MF 中。 如果 MANIFEST.MF 不存在,则需要创建。存放格式是 entry name 以及对应的摘要

l 根据 之前计算的 SHA1 摘要信息,以及 私钥生成 一系列的 signature 并写入 CERT.SF

对 整个 MANIFEST.MF 进行 SHA1 计算,并将摘要信息存入 CERT.SF 中 。然后对之前计算的所有摘要信息使用 SHA1 再次计算数字签名,并写入 CERT.SF 中。

l 把公钥和签名信息写入 CERT.RST

把之前整个的签名输出文件 使用私有密钥计算签名。同时将签名结果,以及之前声称的公钥信息写入 CERT.RSA 中保存。

3.2 Package 的签名验证

安装时对一个 package 的签名验证的主要逻辑在 JarVerifier.java 文件的 verifyCertificate 函数中实现。 其主要的思路是通过提取 cert.rsa 中的证书和签名信息,获取签名算法等信息,然后按照之前对 apk 签名的方法进行计算,比较得到的签名和摘要信息与 apk 中保存的匹配。

*步。提取证书信息,并对 cert.sf 进行完整性验证。

1. 先找到是否有 DSA 和 RSA 文件 ,如果找到则对其进行 decode ,然后读取其中的所有的证书列表(这些证书会被保存在 Package 信息中,供后续使用)。

2. 读取这个文件中的签名数据信息块列表,只取*个签名数据块。读取其中的发布者和证书序列号。

3. 根据证书序列号,去匹配之前得到的所有证书,找到与之匹配的证书。

4. 从之前得到的签名数据块中读取签名算法和编码方式等信息

5. 读取 cert.sf 文件,并计算整个的签名,与数据块中的签名(编码格式的)进行比较,如果相同则完整性校验成功。

第二步。使用 cert.sf 中的摘要信息,验证 MANIFEST.MF 的完整性。

在 cert.sf 中提取 SHA1-Digest-Manifest 或者 SHA1-Digest 开头的签名 数据块 ( -Digest-Manifest 这个是整个 MANIFEST.MF 的摘要 信息,其它的是 jar 包中其它文件的摘要信息 ), 并逐个对这些数据块 进行验证。验证的方法是,现将 cert.sf 看做是很多的 entries ,每个 entries 包含了一些基本信息,如这个 entry 中使用的摘要算法( SHA1 等),对 jar 包中的哪个文件计算了摘要,摘要结果是什么。 处理时先找到每个摘要数据开中的文件信息,然后从 jar 包中读取,然后使用 -Digest 之前的摘要算法进行计算,如果计算结果与摘要数据块中保存的信息的相匹配,那么就完成验证。

根据不同的厂商,分析师和IT用户对云计算的看法,云计算应该这样细分

云是个大家熟悉的名词,但当它与”计算”相结合,它的含义就演变的泛泛而且虚无缥缈。一些分析师和厂商将云计算狭义的定义为效用计算(Utility computing)的升级版本:*基本的就是在网络范围内应用虚拟服务器。其他方面的应用也很广泛。

当我们考虑到IT的实际需求时,云计算的概念也逐渐清晰起来:那就是在不需要增加基础设施投入,新员工培训或者*新软件授权的前提下提升闲置资源性能和能力的一种方法。云计算包含了任何通过网络实时提供订阅型(subscription-based)或者按照使用量付费(pay-per-use)的服务模式,扩展了IT行业现有的能力。

云计算目前还处于萌芽阶段,有大大小小鱼龙混杂的各色厂商在开发不同的云计算服务,从成熟的应用程序到存储服务再到垃圾邮件过滤不一而足。不错,效用模式基础架构供应商是提供多种服务,诸如Salesforce.com这样的SaaS(软件即服务)的供应商亦是如此。如今在很大程度上,IT业界必须个别的去接受云服务,不过云计算的开发商和集成商已经开始初具规模。

根据不同的厂商,分析师和IT用户对云计算的看法,我们将云计算细分如下:

1.软件即服务(SaaS)

这种类型的云计算是采用multitenant架构通过网络浏览器将单个的应用软件推广到数千用户。从用户角度来说,这意味着他们前期无需在服务器或软件许可证授权上进行投资;从供应商角度来看,与常规的软件服务模式相比,维护一个应用软件的成本要相对低廉。迄今为止Salesforce.com是企业应用软件领域中*为知名的供应商,但是软件即服务(SaaS)在人力资源管理软件方面运用比较普遍,还有诸如Workday这样的ERP软件供应商。谁又能预测来自Google和oho Office的软件即服务(SaaS)桌面系统应用软件是否会出现突然的飞跃呢?

2.效用计算(Utility computing)

这种想法本来并无新意,但这种类型的云计算有了Amazon.com, Sun, IBM和其他从事存储服务和IT随需访问的虚拟机厂商的参与就焕发出了新的生命力。早期的企业主要将效用计算作为补充,不会应用在关键性任务需求上。但是时至今日效用计算逐渐在数据中心开始占据一席之地。一些供应商向用户提供解决方案来帮助IT企业从商业服务器开始创建数据中心,诸如3Tera的AppLogic和Cohesive Flexible Technologies的Elastic Server都提供这种随需服务。Liquid Computing公司的LiquidQ也有类似的服务,能帮助企业将内存,I/0,存储和计算容量通过网络集成为一个虚拟的资源池来使用。

3.云计算的网络服务

网络服务与软件即服务(SaaS)是密切相关的,网络服务供应商提供API能帮助开发商通过网络拓展功能性,而不只是提供成熟的应用软件。他们的服务范围从提供分散的商业服务(诸如Strike Iron和Xignite )到涉及到Google Maps, ADP薪资处理流程,美国邮电服务,Bloomberg和常规的信用卡处理服务等的全套API服务。

4.平台即服务(Platform as a service)

平台即服务(Platform as a service)是软件即服务(SaaS)的变种,这种形式的云计算将开发环境作为服务来提供。你可以创建自己的应用软件在供应商的基础架构上运行,然后通过网络从供应商的服务器上传递给用户。比如乐高公司(Legos)就是这么做的。但这些服务会受到厂商设计和容量的限制,因此用户就没有足够的自由。代表公司包括Salesforce.com的Force.com和Coghead。

5.管理服务供应商(MSP)

管理服务是云计算*古老的形式之一,管理服务是面向IT厂商而并非*终用户的一种应用软件,诸如用于电子邮件的病毒扫描服务或者应用软件监控服务。由SecureWorks, IBM和Verizon公司提供的管理安全服务就归为此类,还有目前被Google收购的Postini以云为基础的反垃圾邮件服务。其他的产品还包含桌面系统管理服务,诸如CenterBeam和Everdream提供的产品。

6.服务商业平台

服务商业平台是软件即服务(SaaS)和管理服务供应商(MSP)的混合体,这种云计算服务提供了一种与用户相结合的服务采集器。在贸易领域中应用*为普遍,诸如费用管理系统能允许用户在用户设定的规格范围内从普通平台上订购与所要求的服务和价格相符的旅游产品或者秘书台服务,就好比一个自动化服务局,知名公司包括Rearden Commerce和Ariba。

7.网络集成

云基础服务的集成尚处于初始阶段。软件服务供应商OpSource目前推出了OpSource Services Bus,使用的就是被成为Boomi的云集成技术。软件即服务供应商Workday*近收购了这一领域中的另外一家公司CapeClear,这家ESB(企业服务总线)供应商主要从事B-TO-B商业模式的服务。Grand Central公司也致力于向用户提供集成解决方案,日前被Google所收购。

如今,云计算的运用还不是非常广泛,对于云计算更精确的描述可能是”天空运算”。同时,随着虚拟化和SOA在企业中的逐渐普及,这种想法也开始为大家所认同。可扩展的基础架构应该*终能将每一家企业都*为云的节点。这是个长期可发展的趋势,但是不可否认的是,云计算在很长的时期内还将是业界争论的难点之一。
————————————————

虚拟化就是云计算吗,两者之间有哪些不同之处?

虚拟化就是云计算,这个说法很早就有,尤其商业厂商, vmware,微软,都是把以前叫虚拟化的产品,改名为云计算。

其实某种意义上,也对,虚拟化是云计算的初级阶段。对于企业来说,虚拟化,其实就已经能完全满足需求,那其实这就是云计算。相信云计算也是有不同的阶段,不同的层次。

API接口,没有api接口的,就是虚拟化。有api接口的,就是云计算。其实也挺有道理的。因为有api接口,你才可能和第三方调用。没有api接口,你就只能通过管理界面,一个一个虚拟机创建。

不过现在很多虚拟化厂商也开始提供api接口,不过这只是部分功能的api接口。

如果说IaaS,云计算,必须提供全部功能的API接口,这个定义我还是很赞同的。

节点规模,有人说,10台的规模,就是虚拟化,1000台,就是云计算。其实也是有道理的。你管理机器的规模和你的管理方式有很大的联系。一个简单的例子,你10台机器的时候,创建虚拟机,制定物理节点,就是一个刚需。当你的设备超过1k,那么你更多的是考虑放到哪个zone里。

分布式技术,有人认为采用分布式的技术,就是云计算,例如如果你的存储是用本地存储,那么还是虚拟化,用了分布式,那么就是云计算,网络也是类似。

这个观点,还是很深入人心,符合中国人很多观念。虚拟机都是分布式的,肯定不会有所谓的单点故障。

弹性扩展

这个就更加深入人心。有弹性扩展的功能,就是云计算,没有就是虚拟化。不过大家对弹性扩展的理解,其实差异很大。对于虚拟机来说,是横向还是纵向扩展呢?

横向是指自动增加和减少机器的数量。

纵向是指自动增加和减少cpu和内存

在这个行业混了那么久,坦白说,见到和我的理解的横向弹性扩展,就是fit2cloud,真的是基于青云的上实现了自动扩展。纵向的就是刻通云给我演示过。不过这个都是局限在linux下,windows下,目前还是很难做一个demo。
其实外面的很多demo演示。在真实场景下,其实根本是无法使用的。增加虚拟机容易,减少呢?

Android的安全机制

Android是一个基于Linux内核的移动操作系统。Linux是一个支持多用户的系统,系统中的文件的访问权限是通过用户ID(UID)和用户组ID(GID)来控制的。换句话说,就是Linux的安全机制是基于UID和GID来实现的。Android在Linux内核提供的基于UID和GID的安全机制的基础上,又实现了一套称为Permission的安全机制,如图1所示:

 

 

%title插图%num

图1 Linux的UID/GID安全机制与Android的Permission安全机制

那么,这两个安全机制是如何对应起来的呢?

我们首先看一下Linux基于UID和GID的安全机制,它包含三个基本角色:用户、进程和文件,如图2所示:

%title插图%num

图2 Linux基于UID/GID的安全机制的三个角色

Linux中的每一个用户都分配有一个UID,然后所有的用户又按组来进划分,每一个用户组都分配有一个GID。注意,一个用户可以属于多个用户组,也就是说,一个UID可以对应多个GID。在一个用户所对应的用户组中,其中有一个称为主用户组,其它的称为补充用户组。

Linux中的每一个文件都具有三种权限:Read、Write和Execute。这三种权限又按照用户属性划分为三组:Owner、Group和Other。如图3所示:

%title插图%num

图3 Linux的文件权限划分

从图3就可以看出文件acct:1. 所有者为root,可读可写可执行;2. 所有者所属的主用户组为root,在这个组中的其它用户可读可执行;3. 其余的用户可读可执行。

Linux中的每一个进程都关联有一个用户,也就是对应有一个UID,如图4所示:

%title插图%num

图4 Linux的进程

由于每一个用户都对应有一个主用户组,以及若干个补充用户组,因此,每一个进程除了有一个对应的UID之外,还对应有一个主GID,以及若干个Supplementary GIDs。这些UID和GID就决定了一个进程所能访问的文件或者所能调用的系统API。例如,在图4中,PID为340的进程一般来说,就只能访问所有者为u0_a19的文件。

一个进程的UID是怎么来的呢?在默认情况下,就等于创建它的进程的UID,也就是它的父进程的UID。Linux的*个进程是init进程,它是由内核在启动完成后创建的,它的UID是root。然后系统中的所有其它进程都是直接由init进程或者间接由init进程的子进程来创建。所以默认情况下,系统的所有进程的UID都应该是root。但是实际情况并非如此,因为父进程在创建子进程之后,也就是在fork之后,可以调用setuid来改变它的UID。例如,在PC中,init进程启动之后,会先让用户登录。用户登录成功后,就对应有一个shell进程。该shell进程的UID就会被setuid修改为所登录的用户。之后系统中创建的其余进程的UID为所登录的用户。

进程的UID除了来自于父进程之外,还有另外一种途径。上面我们说到,Linux的文件有三种权限,分别是Read、Wirte和Execute。其实还有另外一个种权限,叫做SUID。例如,我们对Android手机进行root的过程中,会在里面放置一个su文件。这个su文件就具有SUID权限,如图5所示:

%title插图%num

图5 su的SUID和SGID

一个可执行文件一旦被设置了SUID位,那么当它被一个进程通过exec加载之后,该进程的UID就会变成该可执行文件的所有者的UID。也就是说,当上述的su被执行的时候,它所运行在的进程的UID是root,于是它就具有*高级别的权限,想干什么就干什么。

与SUI类似,文件还有另外一个称为SGID的权限,不过它描述的是用户组。也就是说,一个可执行文件一旦被设置了GUID位,么当它被一个进程通过exec加载之后,该进程的主UID就会变成该可执行文件的所有者的主UID。

现在,小伙伴们应该可以理解Android手机的root原理了吧:一个普通的进程通过执行su,从而获得一个具有root权限的进程。有了这个具有root权限的进程之后,就可以想干什么就干什么了。su所做的事情其实很简单,它再fork另外一个子进程来做真正的事情,也就是我们在执行su的时候,后面所跟的那些参数。由于su所运行在的进程的UID是root,因此由它fork出来的子进程的UID也是root。于是,子进程也可以想干什么就干什么了。

不过呢,用来root手机的su还会配合另外一个称为superuser的app来使用。su在fork子进程来做真正的事情之前,会将superuser启动起来,询问用户是否允许fork一个UID是root的子进程。这样就可以对root权限进行控制,避免被恶意应用偷偷地使用。

这里是su的源代码,小伙伴们可以根据上面所讲的知识读一读:https://code.google.com/p/superuser/source/browse/trunk/su/su.c?r=2。

在传统的UNIX以及类UNIX系统中,进程的权限只划分两种:特权和非特权。UID等于0的进程就是特权进程,它们可以通过一切的权限检查。UID不等于0的进程就非特权进程,它们在访问一些敏感资源或者调用一个敏感API时,需要进行权限检查。这种纯粹通过UID来做权限检查的安全机制来粗放了。于是,Linux从2.2开始,从进程的权限进行了细分,称为Capabilities。一个进程所具有Capabilities可以通过capset和prctl等系统API来设置。也就是说,当一个进程调用一个敏感的系统API时,Linux内核除了考虑它的UID之外,还会考虑它是否具有对应的Capability。

这里就是Linux所设计的Capabilities列表,有兴趣的小伙伴可以再读一读:http://man7.org/linux/man-pages/man7/capabilities.7.html。

以上就是Linux基于UID/GID的安全机制的核心内容。接下来我们再看Android基于Permission的安全机制,它也有三个角色:apk、signature和permission,如图6所示:

%title插图%num

图6 Android的Permission安全机制

 

Android的APK经过PackageManagerService安装之后,就相当于Linux里面的User,它们都会被分配到一个UID和一个主GID,而APK所申请的Permission就相当于是Linux里面的Supplementary GID。

我们知道,Android的APK都是运行在独立的应用程序进程里面的,并且这些应用程序进程都是Zygote进程fork出来的。Zygote进程又是由init进程fork出来的,并且它被init进程fork出来后,没有被setuid降权,也就是它的uid仍然是root。按照我们前面所说的,应用程序进程被Zygote进程fork出来的时候,它的UID也应当是root。但是,它们的UID会被setuid修改为所加载的APK被分配的UID。

参照Android应用程序进程启动过程的源代码分析一文的分析,ActivityManagerService在请求Zygote创建应用程序进程的时候,会将这个应用程序所加载的APK所分配得到的UID和GID(包括主GID和Supplementary GID)都收集起来,并且将它们作为参数传递给Zygote进程。Zygote进程通过执行函数来fork应用程序进程:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片

  1. /*
  2.  * Utility routine to fork zygote and specialize the child process.
  3.  */
  4. static pid_t forkAndSpecializeCommon(const u4* args, bool isSystemServer)
  5. {
  6.     pid_t pid;
  7.     uid_t uid = (uid_t) args[0];
  8.     gid_t gid = (gid_t) args[1];
  9.     ArrayObject* gids = (ArrayObject *)args[2];
  10.     ……
  11.     pid = fork();
  12.     if (pid == 0) {
  13.         ……
  14.         err = setgroupsIntarray(gids);
  15.         ……
  16.         err = setgid(gid);
  17.         ……
  18.         err = setuid(uid);
  19.         ……
  20.     }
  21.     …..
  22.     return pid;
  23. }

参数args[0]、args[1]和args[]保存的就是APK分配到的UID、主GID和Supplementary GID,它们分别通过setuid、setgid和setgroupsIntarray设置给当前fork出来的应用程序进程,于是应用程序进程就不再具有root权限了。

那么,Signature又充当什么作用呢?两个作用:1. 控制哪些APK可以共享同一个UID;2. 控制哪些APK可以申请哪些Permission。

我们知道,如果要让两个APK共享同一个UID,那么就需要在AndroidManifest中配置android:sharedUserId属性。PackageManagerService在安装APK的时候,如果发现两个APK具有相同的android:sharedUserId属性,那么它们就会被分配到相同的UID。当然这有一个前提,就是这两个APK必须具有相同的Signature。这很重要,否则的话,如果我知道别人的APK设置了android:sharedUserId属性,那么我也在自己的APK中设置相同的android:sharedUserId属性,就可以去访问别人APK的数据了。

除了可以通过android:sharedUserId属性申请让两个APK共享同一个UID之外,我们还可以将android:sharedUserId属性的值设置为“android.uid.system”,从而让一个APK的UID设置为1000。UID是1000的用户是system,系统的关键服务都是运行在的进程的UID就是它。它的权限虽然不等同于root,不过也足够大了。我们可以通过Master Key漏洞来看一下有多大。

Master Key漏洞发布时,曾轰动了整个Android界,它的具体情况老罗就不分析了,网上很多,这里是一篇官方的文章:http://bluebox.com/corporate-blog/bluebox-uncovers-android-master-key/。现在就简单说说它是怎么利用的:

1. 找到一个具有系统签名的APP,并且这个APP通过android:sharedUserId属性申请了android.uid.system这个UID。

2. 通过Master Key向这个APP注入恶意代码。

3. 注入到这个APP的恶意代码在运行时就获得了system用户身份。

4. 修改/data/local.prop文件,将属性ro.kernel.qemu的值设置为1。

5. 重启手机,由于ro.kernel.qemu的值等于1,这时候手机里面的adb进程不会被setuid剥夺掉root权限。

6. 通过具有root权限的adb进程就可以向系统注入我们熟悉的su和superuser.apk,于是整个root过程完成。

注意,第1步之所以要找一个具有系统签名的APP,是因为通过android:sharedUserId属性申请android.uid.system这个UID需要有系统签名,也就是说不是谁可以申请system这个UID的。另外,/data/local.prop文件的Owner是system,因此,只有获得了system这个UID的进程,才可以对它进行修改。

再说说Signature与Permission的关系。有些Permission,例如INSTALL_PACKAGE,不是谁都可以申请的,必须要具有系统签名才可以,这样就可以控制Suppementary GID的分配,从而控制应用程序进程的权限。具有哪些Permission是具有系统签名才可以申请的,可以参考官方文档:http://developer.android.com/reference/android/Manifest.html,就是哪些标记为“Not for use by third-party applications”的Permission。

了解了Android的Permission机制之后,我们就可以知道:

1. Android的APK就相当于是Linux的UID。

2. Android的Permission就相当于是Linux的GID。

3. Android的Signature就是用来控制APK的UID和GID分配的。

这就是Android基于Permission的安全机制与Linux基于UID/GID的安全机制的关系,概括来说,我们常说的应用程序沙箱就是这样的:

%title插图%num

图7 Android的Application Sandbox

Android是一个基于Linux内核的移动操作系统。Linux是一个支持多用户的系统,系统中的文件的访问权限是通过用户ID(UID)和用户组ID(GID)来控制的。换句话说,就是Linux的安全机制是基于UID和GID来实现的。Android在Linux内核提供的基于UID和GID的安全机制的基础上,又实现了一套称为Permission的安全机制,如图1所示:

%title插图%num

图1 Linux的UID/GID安全机制与Android的Permission安全机制

那么,这两个安全机制是如何对应起来的呢?

我们首先看一下Linux基于UID和GID的安全机制,它包含三个基本角色:用户、进程和文件,如图2所示:

%title插图%num

图2 Linux基于UID/GID的安全机制的三个角色

Linux中的每一个用户都分配有一个UID,然后所有的用户又按组来进划分,每一个用户组都分配有一个GID。注意,一个用户可以属于多个用户组,也就是说,一个UID可以对应多个GID。在一个用户所对应的用户组中,其中有一个称为主用户组,其它的称为补充用户组。

Linux中的每一个文件都具有三种权限:Read、Write和Execute。这三种权限又按照用户属性划分为三组:Owner、Group和Other。如图3所示:

%title插图%num

图3 Linux的文件权限划分

从图3就可以看出文件acct:1. 所有者为root,可读可写可执行;2. 所有者所属的主用户组为root,在这个组中的其它用户可读可执行;3. 其余的用户可读可执行。

Linux中的每一个进程都关联有一个用户,也就是对应有一个UID,如图4所示:

%title插图%num

图4 Linux的进程

由于每一个用户都对应有一个主用户组,以及若干个补充用户组,因此,每一个进程除了有一个对应的UID之外,还对应有一个主GID,以及若干个Supplementary GIDs。这些UID和GID就决定了一个进程所能访问的文件或者所能调用的系统API。例如,在图4中,PID为340的进程一般来说,就只能访问所有者为u0_a19的文件。

一个进程的UID是怎么来的呢?在默认情况下,就等于创建它的进程的UID,也就是它的父进程的UID。Linux的*个进程是init进程,它是由内核在启动完成后创建的,它的UID是root。然后系统中的所有其它进程都是直接由init进程或者间接由init进程的子进程来创建。所以默认情况下,系统的所有进程的UID都应该是root。但是实际情况并非如此,因为父进程在创建子进程之后,也就是在fork之后,可以调用setuid来改变它的UID。例如,在PC中,init进程启动之后,会先让用户登录。用户登录成功后,就对应有一个shell进程。该shell进程的UID就会被setuid修改为所登录的用户。之后系统中创建的其余进程的UID为所登录的用户。

进程的UID除了来自于父进程之外,还有另外一种途径。上面我们说到,Linux的文件有三种权限,分别是Read、Wirte和Execute。其实还有另外一个种权限,叫做SUID。例如,我们对Android手机进行root的过程中,会在里面放置一个su文件。这个su文件就具有SUID权限,如图5所示:

%title插图%num

图5 su的SUID和SGID

一个可执行文件一旦被设置了SUID位,那么当它被一个进程通过exec加载之后,该进程的UID就会变成该可执行文件的所有者的UID。也就是说,当上述的su被执行的时候,它所运行在的进程的UID是root,于是它就具有*高级别的权限,想干什么就干什么。

与SUI类似,文件还有另外一个称为SGID的权限,不过它描述的是用户组。也就是说,一个可执行文件一旦被设置了GUID位,么当它被一个进程通过exec加载之后,该进程的主UID就会变成该可执行文件的所有者的主UID。

现在,小伙伴们应该可以理解Android手机的root原理了吧:一个普通的进程通过执行su,从而获得一个具有root权限的进程。有了这个具有root权限的进程之后,就可以想干什么就干什么了。su所做的事情其实很简单,它再fork另外一个子进程来做真正的事情,也就是我们在执行su的时候,后面所跟的那些参数。由于su所运行在的进程的UID是root,因此由它fork出来的子进程的UID也是root。于是,子进程也可以想干什么就干什么了。

不过呢,用来root手机的su还会配合另外一个称为superuser的app来使用。su在fork子进程来做真正的事情之前,会将superuser启动起来,询问用户是否允许fork一个UID是root的子进程。这样就可以对root权限进行控制,避免被恶意应用偷偷地使用。

这里是su的源代码,小伙伴们可以根据上面所讲的知识读一读:https://code.google.com/p/superuser/source/browse/trunk/su/su.c?r=2。

在传统的UNIX以及类UNIX系统中,进程的权限只划分两种:特权和非特权。UID等于0的进程就是特权进程,它们可以通过一切的权限检查。UID不等于0的进程就非特权进程,它们在访问一些敏感资源或者调用一个敏感API时,需要进行权限检查。这种纯粹通过UID来做权限检查的安全机制来粗放了。于是,Linux从2.2开始,从进程的权限进行了细分,称为Capabilities。一个进程所具有Capabilities可以通过capset和prctl等系统API来设置。也就是说,当一个进程调用一个敏感的系统API时,Linux内核除了考虑它的UID之外,还会考虑它是否具有对应的Capability。

这里就是Linux所设计的Capabilities列表,有兴趣的小伙伴可以再读一读:http://man7.org/linux/man-pages/man7/capabilities.7.html。

以上就是Linux基于UID/GID的安全机制的核心内容。接下来我们再看Android基于Permission的安全机制,它也有三个角色:apk、signature和permission,如图6所示:

%title插图%num

图6 Android的Permission安全机制

 

Android的APK经过PackageManagerService安装之后,就相当于Linux里面的User,它们都会被分配到一个UID和一个主GID,而APK所申请的Permission就相当于是Linux里面的Supplementary GID。

我们知道,Android的APK都是运行在独立的应用程序进程里面的,并且这些应用程序进程都是Zygote进程fork出来的。Zygote进程又是由init进程fork出来的,并且它被init进程fork出来后,没有被setuid降权,也就是它的uid仍然是root。按照我们前面所说的,应用程序进程被Zygote进程fork出来的时候,它的UID也应当是root。但是,它们的UID会被setuid修改为所加载的APK被分配的UID。

参照Android应用程序进程启动过程的源代码分析一文的分析,ActivityManagerService在请求Zygote创建应用程序进程的时候,会将这个应用程序所加载的APK所分配得到的UID和GID(包括主GID和Supplementary GID)都收集起来,并且将它们作为参数传递给Zygote进程。Zygote进程通过执行函数来fork应用程序进程:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片

  1. /*
  2.  * Utility routine to fork zygote and specialize the child process.
  3.  */
  4. static pid_t forkAndSpecializeCommon(const u4* args, bool isSystemServer)
  5. {
  6.     pid_t pid;
  7.     uid_t uid = (uid_t) args[0];
  8.     gid_t gid = (gid_t) args[1];
  9.     ArrayObject* gids = (ArrayObject *)args[2];
  10.     ……
  11.     pid = fork();
  12.     if (pid == 0) {
  13.         ……
  14.         err = setgroupsIntarray(gids);
  15.         ……
  16.         err = setgid(gid);
  17.         ……
  18.         err = setuid(uid);
  19.         ……
  20.     }
  21.     …..
  22.     return pid;
  23. }

参数args[0]、args[1]和args[]保存的就是APK分配到的UID、主GID和Supplementary GID,它们分别通过setuid、setgid和setgroupsIntarray设置给当前fork出来的应用程序进程,于是应用程序进程就不再具有root权限了。

那么,Signature又充当什么作用呢?两个作用:1. 控制哪些APK可以共享同一个UID;2. 控制哪些APK可以申请哪些Permission。

我们知道,如果要让两个APK共享同一个UID,那么就需要在AndroidManifest中配置android:sharedUserId属性。PackageManagerService在安装APK的时候,如果发现两个APK具有相同的android:sharedUserId属性,那么它们就会被分配到相同的UID。当然这有一个前提,就是这两个APK必须具有相同的Signature。这很重要,否则的话,如果我知道别人的APK设置了android:sharedUserId属性,那么我也在自己的APK中设置相同的android:sharedUserId属性,就可以去访问别人APK的数据了。

除了可以通过android:sharedUserId属性申请让两个APK共享同一个UID之外,我们还可以将android:sharedUserId属性的值设置为“android.uid.system”,从而让一个APK的UID设置为1000。UID是1000的用户是system,系统的关键服务都是运行在的进程的UID就是它。它的权限虽然不等同于root,不过也足够大了。我们可以通过Master Key漏洞来看一下有多大。

Master Key漏洞发布时,曾轰动了整个Android界,它的具体情况老罗就不分析了,网上很多,这里是一篇官方的文章:http://bluebox.com/corporate-blog/bluebox-uncovers-android-master-key/。现在就简单说说它是怎么利用的:

1. 找到一个具有系统签名的APP,并且这个APP通过android:sharedUserId属性申请了android.uid.system这个UID。

2. 通过Master Key向这个APP注入恶意代码。

3. 注入到这个APP的恶意代码在运行时就获得了system用户身份。

4. 修改/data/local.prop文件,将属性ro.kernel.qemu的值设置为1。

5. 重启手机,由于ro.kernel.qemu的值等于1,这时候手机里面的adb进程不会被setuid剥夺掉root权限。

6. 通过具有root权限的adb进程就可以向系统注入我们熟悉的su和superuser.apk,于是整个root过程完成。

注意,第1步之所以要找一个具有系统签名的APP,是因为通过android:sharedUserId属性申请android.uid.system这个UID需要有系统签名,也就是说不是谁可以申请system这个UID的。另外,/data/local.prop文件的Owner是system,因此,只有获得了system这个UID的进程,才可以对它进行修改。

再说说Signature与Permission的关系。有些Permission,例如INSTALL_PACKAGE,不是谁都可以申请的,必须要具有系统签名才可以,这样就可以控制Suppementary GID的分配,从而控制应用程序进程的权限。具有哪些Permission是具有系统签名才可以申请的,可以参考官方文档:http://developer.android.com/reference/android/Manifest.html,就是哪些标记为“Not for use by third-party applications”的Permission。

了解了Android的Permission机制之后,我们就可以知道:

1. Android的APK就相当于是Linux的UID。

2. Android的Permission就相当于是Linux的GID。

3. Android的Signature就是用来控制APK的UID和GID分配的。

这就是Android基于Permission的安全机制与Linux基于UID/GID的安全机制的关系,概括来说,我们常说的应用程序沙箱就是这样的:

%title插图%num

图7 Android的Application Sandbox

友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速