javascript:void(0)
侯亮
1.先說一個大概
Android平台的一個基本設計理念是構造一個相對平坦的功能集合,這些功能可能會身處于不同的程序中,然而卻可以高效地整合到一起,實作不同的使用者需求。這就必須打破過去各個孤立App所形成的天然藩籬。為此,Android提供了Binder機制。
在Android中,系統提供的服務被包裝成一個個系統級service,這些service往往會在裝置啟動之時添加進Android系統。在上一篇文檔中,我們已經了解了BpBinder和BBinder的概念,而service實體的底層說到底就是一個BBinder實體。
我們知道,如果某個程式希望享受系統提供的服務,它就必須調用系統提供的外部接口,向系統發出相應的請求。是以,Android中的程式必須先拿到和某個系統service對應的代理接口,然後才能通過這個接口,享受系統提供的服務。說白了就是我們得先拿到一個和目标service對應的合法BpBinder。
然而,該怎麼擷取和系統service對應的代理接口呢?Android是這樣設計的:先啟動一個特殊的系統服務,叫作Service Manager Service(簡稱SMS),它的基本任務就是管理其他系統服務。其他系統服務在系統啟動之時,就會向SMS注冊自己,于是SMS先記錄下與那個service對應的名字和句柄值。有了句柄值就可以用來建立合法的BpBinder了。隻不過在實際的代碼中,SMS并沒有用句柄值建立出BpBinder,這個其實沒什麼,反正指代目标service實體的目的已經達到了。後續當某程式需要享受某系統服務時,它必須先以“特定手法”擷取SMS代理接口,并經由這個接口查詢出目标service對應的合法Binder句柄,然後再建立出合法的BpBinder對象。
在此,我們有必要交代一下“Binder句柄”的作用。句柄說穿了是個簡單的整數值,用來告訴Binder驅動我們想找的目标Binder實體是哪個。但是請注意,句柄隻對發起端程序和Binder驅動有意義,A程序的句柄直接拿到B程序,是沒什麼意義的。也就是說,不同程序中指代相同Binder實體的句柄值可能是不同的。示意圖如下:
SMS記錄了所有系統service所對應的Binder句柄,它的核心功能就是維護好這些句柄值。後續,當使用者程序需要擷取某個系統service的代理時,SMS就會在内部按service名查找到合适的句柄值,并“邏輯上”傳遞給使用者程序,于是使用者程序會得到一個新的合法句柄值,這個新句柄值可能在數值上和SMS所記錄的句柄值不同,然而,它們指代的卻是同一個Service實體。句柄的合法性是由Binder驅動保證的,這一點我們不必擔心。
前文我們提到要以“特定手法”擷取SMS代理接口,這是什麼意思呢?在IServiceManager.cpp檔案中,我們可以看到一個defaultServiceManager()函數,代碼如下:
【frameworks/native/libs/binder/IServiceManager.cpp】
sp<IServiceManager> defaultServiceManager()
{
if (gDefaultServiceManager != NULL)
return gDefaultServiceManager;
{
AutoMutex _l(gDefaultServiceManagerLock);
if (gDefaultServiceManager == NULL)
{
gDefaultServiceManager = interface_cast<IServiceManager>(
ProcessState::self()->getContextObject(NULL));
}
}
return gDefaultServiceManager;
}
這個函數裡調用interface_cast的地方是用一句getContextObject(NULL)來擷取BpBinder對象的。我們先不深入講解這個函數,隻需要知道這一句裡的getContextObject(NULL)實際上相當于new BpBinder(0)就可以了。噢,看來要得到BpBinder對象并不複雜嘛,直接new就好了。然而,我之是以使用“特定手法”一詞,是因為這種直接new BpBinder(xxx)的做法,隻能用于擷取SMS的代理接口。大家可不要想當然地随便用這種方法去建立其他服務的代理接口噢。
在Android裡,對于Service Manager Service這個特殊的服務而言,其對應的代理端的句柄值已經預先定死為0了,是以我們直接new BpBinder(0)拿到的就是個合法的BpBinder,其對端為“Service Manager Service實體”(至少目前可以先這麼了解)。那麼對于其他“服務實體”對應的代理,句柄值又是多少呢?使用方又該如何得到這個句柄值呢?我們總不能随便蒙一個句柄值吧。正如我們前文所述,要得到某個服務對應的BpBinder,主要得借助Service
Manager Service系統服務,查詢出一個合法的Binder句柄,并進而建立出合法的BpBinder。
這裡有必要澄清一下,利用SMS擷取合法BpBinder的方法,并不是Android中得到BpBinder的唯一方法。另一種方法是,“起始端”經由一個已有的合法BpBinder,将某個binder實體或代理對象作為跨程序調用的參數,“傳遞”給“目标端”,這樣目标端也可以拿到一個合法的BpBinder。
我們把以上介紹的知識繪制成示意圖,如下:
請順着圖中标出的1)、2)、3)、4)序号,讀一下圖中的說明。
在跨程序通信方面,所謂的“傳遞”一般指的都是邏輯上的傳遞,是以應該打上引号。事實上,binder實體對象是不可能完全打包并傳遞到另一個程序的,而且也沒有必要這麼做。目前我們隻需了解,binder架構會保證“傳遞”動作的目标端可以拿到一個和binder實體對象對應的代理對象即可。詳細情況,要到分析binder驅動的部分再闡述。
既然SMS承擔着讓用戶端擷取合法BpBinder的責任,那麼它的重要性就不言而喻了。現在我們就來詳細看看具體如何使用它。
2.具體使用Service Manager Service
2.1 必須先得到IServiceManager代理接口
要擷取某系統service的代理接口,必須先得到IServiceManager代理接口。還記得前文C++代碼中擷取IServiceManager代理接口的句子嗎?
gDefaultServiceManager = interface_cast<IServiceManager>(
ProcessState::self()->getContextObject(NULL));
我們在前一篇文檔中已經介紹過interface_cast了,現在再貼一下這個函數的代碼:
template<typename INTERFACE>
inline sp<INTERFACE> interface_cast(const sp<IBinder>& obj)
{
return INTERFACE::asInterface(obj);
}
也就是說,其實調用的是IServiceManager::asInterface(obj),而這個obj參數就是new BpBinder(0)得到的對象。當然,這些都是C++層次的概念,Java層次把這些概念都包裝起來了。
在Java層次,是這樣擷取IServiceManager接口的:
【frameworks/base/core/java/android/os/ServiceManager.java】
private static IServiceManager getIServiceManager()
{
if (sServiceManager != null) {
return sServiceManager;
}
// Find the service manager
sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());
return sServiceManager;
}
噢,又出現了一個asInterface,看來Java層次和C++層的代碼在本質上是一緻的。
ServiceManagerNative的asInterface()代碼如下:
static public IServiceManager asInterface(IBinder obj)
{
if (obj == null)
{
return null;
}
IServiceManager in = (IServiceManager)obj.queryLocalInterface(descriptor);
if (in != null)
{
return in;
}
return new ServiceManagerProxy(obj);
}
目前我們隻需了解,使用者程序在調用到getIServiceManager()時,最終會走到return new ServiceManagerProxy(obj)即可。
哎呀,又出現了兩個名字:ServiceManagerProxy和ServiceManagerNative。簡單地說:
1) ServiceManagerProxy就是IServiceManager代理接口;
2) ServiceManagerNative顯得很雞肋;
它們的繼承關系圖如下:
下面我們分别來說明。
2.1.1 ServiceManagerProxy就是IServiceManager代理接口
使用者要通路Service Manager Service服務,必須先拿到IServiceManager代理接口,而ServiceManagerProxy就是代理接口的實作。這個從前文代碼中的new ServiceManagerProxy(obj)一句就可以看出來了。ServiceManagerProxy的構造函數内部會把obj參數記錄到mRemote域中:
public ServiceManagerProxy(IBinder remote)
{
mRemote = remote;
}
mRemote的定義是:
private IBinder mRemote;
其實說白了,mRemote的核心包裝的就是句柄為0的BpBinder對象,這個應該很容易了解。
日後,當我們通過IServiceManager代理接口通路SMS時,其實調用的就是ServiceManagerProxy的成員函數。比如getService()、checkService()等等。
2.1.2 ServiceManagerNative顯得很雞肋
另一方面,ServiceManagerNative就顯得很雞肋了。
ServiceManagerNative是個抽象類:
public abstract class ServiceManagerNative extends Binder implements IServiceManager
它繼承了Binder,實作了IServiceManager,然而卻是個虛有其表的class。它唯一有用的大概就是前文列出的那個靜态成員函數asInterface()了,而其他成員函數(像onTransact())就基本上沒什麼用。
如果我們花點兒時間在工程裡搜尋一下ServiceManagerNative,會發現根本找不到它的子類。一個沒有子類的抽象類不就是虛有其表嗎。到頭來我們發現,關于ServiceManagerNative的用法隻有一種,就是:
ServiceManagerNative.asInterface(BinderInternal.getContextObject());
用一下它的asInterface()靜态函數而已。
為什麼會這樣呢?我想這可能是某種曆史的遺迹吧。同理,我們看它的onTransact()函數,也會發現裡面調用的類似addService()那樣的函數,也都是找不到對應的實作體的。當然,因為ServiceManagerNative本身是個抽象類,是以即便它沒有實作IServiceManager的addService()等成員函數,也是可以編譯通過的。
這裡透出一個資訊,既然Java層的ServiceManagerNative沒什麼大用處,是不是表示C++層也缺少對應的SMS服務實體呢?在後文我們可以看到,的确是這樣的,Service Manager Service在C++層被實作成一個獨立的程序,而不是常見的Binder實體。
2.2 通過addService()來注冊系統服務
我們還是回過頭接着說對于IServiceManager接口的使用吧。最重要的當然是注冊系統服務。比如在System Server程序中,是這樣注冊PowerManagerService系統服務的:
public void run()
{
. . . . . .
power = new PowerManagerService();
ServiceManager.addService(Context.POWER_SERVICE, power);
. . . . . .
addService()的第一個參數就是所注冊service的名字,比如上面的POWER_SERVICE對應的字元串就是"power"。第二個參數傳入的是service Binder實體。Service實體在Service Manager Service一側會被記錄成相應的句柄值,如圖:
有關addService()内部機理,我們會在後文講述,這裡先不細說。
2.3 通過getService()來擷取某系統服務的代理接口
除了注冊系統服務,Service Manager Service的另一個主要工作就是讓使用者程序可以擷取系統service的代理接口,是以其getService()函數就非常重要了。
其實,ServiceManagerProxy中的getService()等成員函數,僅僅是把語義整理進parcel,并通過mRemote将parcel傳遞到目标端而已。是以我們隻看看getService()就行了,其他的函數都大同小異。
public IBinder getService(String name) throws RemoteException
{
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IServiceManager.descriptor);
data.writeString(name);
mRemote.transact(GET_SERVICE_TRANSACTION, data, reply, 0);
IBinder binder = reply.readStrongBinder();
reply.recycle();
data.recycle();
return binder;
}
傳遞的語義就是GET_SERVICE_TRANSACTION,非常簡單。mRemote從本質上看就是句柄為0的BpBinder,是以binder驅動很清楚這些語義将去向何方。
關于Service Manager Service的使用,我們就先說這麼多。下面我們要開始探索SMS内部的運作機制了。
3.Service Manager Service的運作機制
3.1 Service Manager Service服務的啟動
既然前文說ServiceManagerNative虛有其表,而且沒有子類,那麼Service Manager Service服務的真正實作代碼位于何處呢?答案就在init.rc腳本裡。關于init.rc的詳細情況,可參考其他闡述Android啟動流程的文檔,此處不再贅述。
init.rc腳本中,在描述zygote service之前就已經寫明service manager service的資訊了:
service servicemanager /system/bin/servicemanager
user system
critical
onrestart restart zygote
onrestart restart media
可以看到,servicemanager是一種native service。這種native service都是需要用C/C++編寫的。Service Manager Service對應的實作代碼位于frameworks/base/cmds/servicemanager/Service_manager.c檔案中。這個檔案中有每個C程式員都熟悉的main()函數,其編譯出的可執行程式就是/system/bin/servicemanager。
另外,還有一個幹擾我們視線的cpp檔案,名為IServiceManager.cpp,位于frameworks/base/libs/binder/目錄中,這個檔案裡的BnServiceManager應該和前文的ServiceManagerNative類似,它的onTransact()也不起什麼作用。
3.2 Service Manager Service是如何管理service句柄的?
在C語言層次,簡單地說并不存在一個單獨的ServiceManager結構。整個service管理機制都被放在一個獨立的程序裡了,該程序對應的實作檔案就是Service_manager.c。
程序裡有一個全局性的svclist變量:
struct svcinfo *svclist = 0;
它記錄着所有添加進系統的“service代理”資訊,這些資訊被組織成一條單向連結清單,我們不妨稱這條連結清單為“服務向量表”。示意圖如下:
連結清單節點類型為svcinfo。
因為svcinfo裡要記錄下service的名字字元串,是以它需要的buffer長度是(len + 1) * sizeof(uint16_t),記得要留一個’\0’的結束位置。另外,svcinfo的ptr域,實際上記錄的就是系統service對應的binder句柄值。
日後,當應用調用getService()擷取系統服務的代理接口時,SMS就會搜尋這張“服務向量表”,查找是否有節點能和使用者傳來的服務名比對,如果能查到,就傳回對應的sp<IBinder>,這個接口在遠端對應的實體就是“目标Service實體”。如此一來,系統中就會出現如下關系:
3.3 Service Manager Service的主程式(C++層)
要更加深入地了解Service Manager程序的運作,我們必須研究其主程式。參考代碼是frameworks\base\cmds\servicemanager\Service_manager.c。
Service_manager.c中的main()函數如下:
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))
{
ALOGE("cannot become context manager (%s)\n", strerror(errno));
return -1;
}
svcmgr_handle = svcmgr;
binder_loop(bs, svcmgr_handler);
return 0;
}
main()函數一開始就打開了binder驅動,然後調用binder_become_context_manager()讓自己成為整個系統中唯一的上下文管理器,其實也就是service管理器啦。接着main()函數調用binder_loop()進入無限循環,不斷監聽并解析binder驅動發來的指令。
binder_loop()中解析驅動指令的函數是binder_parse(),其最後一個參數func來自于binder_loop()的最後一個參數——svcmgr_handler函數指針。這個svcmgr_handler()應該算是Service Manager Service的核心回調函數了。
為了友善檢視,我把main()函數以及其間接調用的ioctl()語句繪制成如下的調用關系圖:
下面我們逐個分析其中調用的函數。
3.3.1 binder_open()
Service Manager Service必須先調用binder_open()來打開binder驅動,驅動檔案為“/dev/binder”。binder_open()的代碼截選如下:
struct binder_state * binder_open(unsigned mapsize)
{
struct binder_state *bs;
bs = malloc(sizeof(*bs));
. . . . . .
bs->fd = open("/dev/binder", O_RDWR);
. . . . . .
bs->mapsize = mapsize;
bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);
. . . . . .
return bs;
. . . . . .
}
binder_open()的參數mapsize表示它希望把binder驅動檔案的多少位元組映射到本地空間。可以看到,Service Manager Service和普通程序所映射的binder大小并不相同。它把binder驅動檔案的128K位元組映射到記憶體空間,而普通程序則會映射binder檔案裡的BINDER_VM_SIZE(即1M減去8K)位元組。
具體的映射動作由mmap()一句完成,該函數将binder驅動檔案的一部分映射到程序空間。mmap()的函數原型如下:
void* mmap ( void * addr , size_t len , int prot , int flags , int fd , off_t offset );
該函數會把“參數fd所指代的檔案”中的一部分映射到程序空間去。這部分檔案内容以offset為起始位置,以len為位元組長度。其中,參數offset表明從檔案起始處開始算起的偏移量。參數prot表明對這段映射空間的通路權限,可以是PROT_READ(可讀)、PROT_WRITE (可寫)、PROT_EXEC (可執行)、PROT_NONE(不可通路)。參數addr用于指出檔案應被映射到程序空間的起始位址,一般指定為空指針,此時會由核心來決定起始位址。
binder_open()的傳回值類型為binder_state*,裡面記錄着剛剛打開的binder驅動檔案句柄以及mmap()映射到的最終目标位址。
struct binder_state
{
int fd;
void *mapped;
unsigned mapsize;
};
以後,SMS會不斷讀取這段映射空間,并做出相應的動作。
3.3.2 binder_become_context_manager()
我們前面已經說過,binder_become_context_manager()的作用是讓目前程序成為整個系統中唯一的上下文管理器,即service管理器。其代碼非常簡單:
int binder_become_context_manager(struct binder_state *bs)
{
return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);
}
僅僅是把BINDER_SET_CONTEXT_MGR發送到binder驅動而已。驅動中與ioctl()對應的binder_ioctl()是這樣處理的:
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int ret;
struct binder_proc *proc = filp->private_data;
struct binder_thread *thread;
unsigned int size = _IOC_SIZE(cmd);
void __user *ubuf = (void __user *)arg;
. . . . . .
. . . . . .
case BINDER_SET_CONTEXT_MGR:
. . . . . .
. . . . . .
binder_context_mgr_uid = current->cred->euid;
binder_context_mgr_node = binder_new_node(proc, NULL, NULL);
if (binder_context_mgr_node == NULL)
{
ret = -ENOMEM;
goto err;
}
binder_context_mgr_node->local_weak_refs++;
binder_context_mgr_node->local_strong_refs++;
binder_context_mgr_node->has_strong_ref = 1;
binder_context_mgr_node->has_weak_ref = 1;
break;
. . . . . .
. . . . . .
}
代碼的意思很明确,要為整個系統的上下文管理器專門生成一個binder_node節點,并記入靜态變量binder_context_mgr_node。
我們在這裡多說兩句,一般情況下,應用層的每個binder實體都會在binder驅動層對應一個binder_node節點,然而binder_context_mgr_node比較特殊,它沒有對應的應用層binder實體。在整個系統裡,它是如此特殊,以至于系統規定,任何應用都必須使用句柄0來跨程序地通路它。現在大家可以回想一下前文在擷取SMS接口時說到的那句new BpBinder(0),是不是能加深一點兒了解。
3.3.3 binder_loop()
我們再回到SMS的main()函數。
接下來的binder_loop()會先向binder驅動發出了BC_ENTER_LOOPER指令,接着進入一個for循環不斷調用ioctl()讀取發來的資料,接着解析這些資料。參考代碼在:
【frameworks/base/cmds/servicemanager/Binder.c】(注意!這個Binder.c檔案不是binder驅動層那個Binder.c檔案噢。)
void binder_loop(struct binder_state *bs, binder_handler func)
{
int res;
struct binder_write_read bwr;
unsigned readbuf[32];
bwr.write_size = 0;
bwr.write_consumed = 0;
bwr.write_buffer = 0;
readbuf[0] = BC_ENTER_LOOPER;
binder_write(bs, readbuf, sizeof(unsigned));
for (;;)
{
bwr.read_size = sizeof(readbuf);
bwr.read_consumed = 0;
bwr.read_buffer = (unsigned) readbuf;
res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
if (res < 0) {
LOGE("binder_loop: ioctl failed (%s)\n", strerror(errno));
break;
}
res = binder_parse(bs, 0, readbuf, bwr.read_consumed, func);
if (res == 0) {
LOGE("binder_loop: unexpected reply?!\n");
break;
}
if (res < 0) {
LOGE("binder_loop: io error %d %s\n", res, strerror(errno));
break;
}
}
}
注意binder_loop()的參數func,它的值是svcmgr_handler()函數指針。而且這個參數會進一步傳遞給binder_parse()。
3.3.3.1 BC_ENTER_LOOPER
binder_loop()中發出BC_ENTER_LOOPER指令的目的,是為了告訴binder驅動“本線程要進入循環狀态了”。在binder驅動中,凡是用到跨程序通信機制的線程,都會對應一個binder_thread節點。這裡的BC_ENTER_LOOPER指令會導緻這個節點的looper狀态發生變化:
thread->looper |= BINDER_LOOPER_STATE_ENTERED;
有關binder_thread的細節,也會在闡述Binder驅動一節進行說明。
3.3.3.2 binder_parse()
在binder_loop()進入for循環之後,最顯眼的就是那句binder_parse()了。binder_parse()負責解析從binder驅動讀來的資料,其代碼截選如下:
int binder_parse(struct binder_state *bs, struct binder_io *bio,
uint32_t *ptr, uint32_t size, binder_handler func)
{
int r = 1;
uint32_t *end = ptr + (size / 4);
while (ptr < end)
{
uint32_t cmd = *ptr++;
. . . . . .
case BR_TRANSACTION:
{
struct binder_txn *txn = (void *) ptr;
if ((end - ptr) * sizeof(uint32_t) < sizeof(struct binder_txn)) {
ALOGE("parse: txn too small!\n");
return -1;
}
binder_dump_txn(txn);
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);
res = func(bs, txn, &msg, &reply);
binder_send_reply(bs, &reply, txn->data, res);
}
ptr += sizeof(*txn) / sizeof(uint32_t);
break;
}
. . . . . .
. . . . . .
}
return r;
}
從前文的代碼我們可以看到,binder_loop()聲明了一個128位元組的buffer(即unsigned readbuf[32]),每次用BINDER_WRITE_READ指令從驅動讀取一些内容,并傳入binder_parse()。
binder_parse()在合适的時機,會回調其func參數(binder_handler func)指代的回調函數,即前文說到的svcmgr_handler()函數。
binder_loop()就這樣一直循環下去,完成了整個service manager service的工作。
4.Service Manager Service解析收到的指令
現在,我們專門用一個小節來說說Service Manager Service内循環解析指令時的一些細節。我們要确定binder_loop()從驅動側讀到的資料到底如何解析?我們重貼一下binder_parse()的聲明部分:
int binder_parse(struct binder_state *bs,
struct binder_io *bio,
uint32_t *ptr,
uint32_t size,
binder_handler func)
之前利用ioctl()讀取到的資料都記錄在第三個參數ptr所指的緩沖區中,資料大小由size參數記錄。其實這個buffer就是前文那個128位元組的buffer。
從驅動層讀取到的資料,實際上是若幹BR指令。每個BR指令是由一個指令号(uint32)以及若幹相關資料組成的,不同BR指令的長度可能并不一樣。如下表所示:
BR指令 | 需進一步讀取的uint32數 |
BR_NOOP | |
BR_TRANSACTION_COMPLETE | |
BR_INCREFS | 2 |
BR_ACQUIRE | 2 |
BR_RELEASE | 2 |
BR_DECREFS | 2 |
BR_TRANSACTION | sizeof(binder_txn) / sizeof(uint32_t) |
BR_REPLY | sizeof(binder_txn) / sizeof(uint32_t) |
BR_DEAD_BINDER | 1 |
BR_FAILED_REPLY | |
BR_DEAD_REPLY |
每次ioctl()操作所讀取的資料,可能會包含多個BR指令,是以binder_parse()需要用一個while循環來解析buffer中所有的BR指令。我們随便畫個示意圖,如下:
圖中的buffer中含有3條BR指令,分别為BR_TRANSACTION、BR_TRANSACTION_COMPLETE、BR_NOOP指令。一般而言,我們最關心的就是BR_TRANSACTION指令啦,是以前文截選的binder_parse()代碼,主要摘錄了處理BR_TRANSACTION指令的代碼,該指令的指令号之後跟着的是一個binder_txn結構。現在我們來詳細看這個結構。
4.1 解析binder_txn資訊
binder_txn的定義如下:
【frameworks/base/cmds/servicemanager/Binder.h】
struct binder_txn
{
void *target;
void *cookie;
uint32_t code; // 所傳輸的語義碼
uint32_t flags;
uint32_t sender_pid;
uint32_t sender_euid;
uint32_t data_size;
uint32_t offs_size;
void *data;
void *offs;
};
binder_txn說明了transaction到底在傳輸什麼語義,而語義碼就記錄在其code域中。不同語義碼需要攜帶的資料也是不同的,這些資料由data域指定。示意圖如下:
簡單地說,我們從驅動側讀來的binder_txn隻是一種“傳輸控制資訊”,它本身并不包含傳輸的具體内容,而隻是指出具體内容位于何處。現在,工作的重心要轉到如何解析傳輸的具體内容了,即binder_txn的data域所指向的那部分内容。
為了解析具體内容,binder_parse()聲明了兩個類型為binder_io的局部變量:msg和reply。從binder_io這個類型的名字,我們就可以看出要用它來讀取binder傳遞來的資料了。其實,為了便于讀取binder_io所指代的内容,工程提供了一系列以bio_打頭的輔助函數。在讀取實際資料之前,我們必須先調用bio_init_from_txn(),把binder_io變量(比如msg變量)和binder_txn所指代的緩沖區聯系起來。示意圖如下:
從圖中可以看到,binder_io結構已經用binder_txn結構初始化了自己,以後我們就可以調用類似bio_get_uint32()、bio_get_string16()這樣的函數,來讀取這塊buffer了。
4.2 svcmgr_handler()回調函數
初始化後的binder_io資料,就可以傳給svcmgr_handler()回調函數做進一步的解析了。
此時我們可以調用下面這些輔助函數進行讀寫:
void bio_put_uint32(struct binder_io *bio, uint32_t n)
void bio_put_obj(struct binder_io *bio, void *ptr)
uint32_t bio_get_uint32(struct binder_io *bio)
uint16_t *bio_get_string16(struct binder_io *bio, unsigned *sz)
void *bio_get_ref(struct binder_io *bio)
. . . . . .
其中,bio_get_xxx()函數在讀取資料時,是以binder_io的data域為讀取光标的,每讀取一些資料,data值就會增加,并且data_avail域會相應減少。而data0域的值則保持不變,一直指着資料區最開始的位置,它的作用就是作為計算偏移量的基準值。
bio_get_uint32()非常簡單,會從binder_io.data所指的地方,讀取4個位元組的内容。bio_get_string16()就稍微複雜一點兒,先要讀取一個32bits的整數,這個整數值就是字元串的長度,因為字元串都要包含最後一個’\0’,是以需要讀取((len + 1) * sizeof(uint16_t))位元組的内容。還有一個是bio_get_ref(),它會讀取一個binder_object結構。binder_object的定義如下:
struct binder_object
{
uint32_t type;
uint32_t flags;
void *pointer;
void *cookie;
};
在svcmgr_handler()函數中,一個傳輸語義碼(txn->code)可能會對應幾次bio_get操作,比如後文我們要說的SVC_MGR_ADD_SERVICE語義碼。具體情況請大家參考svcmgr_handler()的代碼。svcmgr_handler()的調用示意圖如下:
4.2.1 如何解析add service
我們先研究add service的動作。前文我們已經介紹過,service manager程序裡有一個全局性的svclist變量,記錄着所有添加進系統的“service代理”資訊,這些資訊被組織成一條單向連結清單,即“服務向量表”。現在我們要看service manager是如何向這張表中添加新節點的。
假設某個服務程序調用Service Manager Service接口,向其注冊service。這個注冊動作到最後就會走到svcmgr_handler()的case SVC_MGR_ADD_SERVICE分支。此時會先擷取三個資料,而後再調用do_add_service()函數,代碼如下:
uint16_t * s;
void * ptr;
. . . . . .
s = bio_get_string16(msg, &len);
ptr = bio_get_ref(msg);
allow_isolated = bio_get_uint32(msg) ? 1 : 0;
do_add_service(bs, s, len, ptr, txn->sender_euid);
也就是說,當binder_txn的code為SVC_MGR_ADD_SERVICE時,binder_txn所指的資料區域中應該包含一個字元串,一個binder對象以及一個uint32資料。示意圖如下:
其中那個binder_object,記錄的就是新注冊的service所對應的代理資訊。此時binder_object的pointer域實際上已經不是指針值了,而是一個binder句柄值。
do_add_service()的函數截選如下:
struct svcinfo *svclist = 0; // 核心service連結清單(即服務向量表)
int do_add_service(struct binder_state *bs, uint16_t *s, unsigned len,
void *ptr, unsigned uid)
{
struct svcinfo *si;
if (!ptr || (len == 0) || (len > 127))
return -1;
if (!svc_can_register(uid, s)) {
ALOGE("add_service('%s',%p) uid=%d - PERMISSION DENIED\n",
str8(s), ptr, uid);
return -1;
}
si = find_svc(s, len);
if (si) {
if (si->ptr) {
svcinfo_death(bs, si);
}
si->ptr = ptr;
} else {
// 新建立一個svcinfo節點。
si = malloc(sizeof(*si) + (len + 1) * sizeof(uint16_t));
if (!si) {
return -1;
}
si->ptr = ptr; // 在svcinfo節點的ptr域中,記錄下service對應的binder句柄值
si->len = len;
memcpy(si->name, s, (len + 1) * sizeof(uint16_t));
si->name[len] = '\0';
si->death.func = svcinfo_death;
si->death.ptr = si;
// 把新節點插入svclist連結清單
si->next = svclist;
svclist = si;
}
binder_acquire(bs, ptr);
binder_link_to_death(bs, ptr, &si->death);
return 0;
}
現在我們來解讀這部分代碼。首先,并不是随便找個程序就能向系統注冊service噢。do_add_service()函數一開始先調用svc_can_register(),判斷發起端是否可以注冊service。如果不可以,do_add_service()就傳回-1值。svc_can_register()的代碼如下:
int svc_can_register(unsigned uid, uint16_t *name)
{
unsigned n;
if ((uid == 0) || (uid == AID_SYSTEM))
return 1;
for (n = 0; n < sizeof(allowed) / sizeof(allowed[0]); n++)
if ((uid == allowed[n].uid) && str16eq(name, allowed[n].name))
return 1;
return 0;
}
上面的代碼表示,如果發起端是root程序或者system server程序的話,是可以注冊service的,另外,那些在allowed[]數組中有明确記錄的使用者程序,也是可以注冊service的,至于其他絕大部分普通程序,很抱歉,不允許注冊service。在以後的軟體開發中,我們有可能需要編寫新的帶service的使用者程序(uid不為0或AID_SYSTEM),并且希望把service注冊進系統,此時不要忘了修改allowed[]數組。下面是allowed[]數組的一部分截選:
static struct {
unsigned uid;
const char *name;
} allowed[] = {
{ AID_MEDIA, "media.audio_flinger" },
{ AID_MEDIA, "media.player" },
{ AID_MEDIA, "media.camera" },
. . . . . .
接下來,do_add_service()開始嘗試在service連結清單裡查詢對應的service是否已經添加過了。如果可以查到,那麼就不用生成新的service節點了。否則就需要在連結清單起始處再加一個新節點。節點類型為svcinfo。請注意上面代碼的si->ptr = ptr一句,此時的ptr參數其實來自于前文所說的binder_object的pointer域。
為了說明問題,我們重新列一下剛剛的case SVC_MGR_ADD_SERVICE代碼:
case SVC_MGR_ADD_SERVICE:
s = bio_get_string16(msg, &len);
ptr = bio_get_ref(msg);
allow_isolated = bio_get_uint32(msg) ? 1 : 0;
if (do_add_service(bs, s, len, ptr, txn->sender_euid, allow_isolated))
return -1;
break;
那個ptr來自于bio_get_ref(msg),而bio_get_ref()的實作代碼如下:
void *bio_get_ref(struct binder_io *bio)
{
struct binder_object *obj;
obj = _bio_get_obj(bio);
if (!obj)
return 0;
if (obj->type == BINDER_TYPE_HANDLE)
return obj->pointer;
return 0;
}
因為現在是要向service manager注冊服務,是以obj->type一定是BINDER_TYPE_HANDLE,也就是說會傳回binder_object的pointer域。這個域的類型雖為void*,實際上換成uint32可能更合适。通過這個binder句柄值,我們最終可以找到遠端的具體service實體。
4.2.2 如何解析get service
現在我們接着來看get service動作。我們知道,在service被注冊進service manager之後,其他應用都可以調用ServiceManager的getService()來擷取相應的服務代理,并調用代理的成員函數。這個getService()函數最終會向service manager程序發出SVC_MGR_GET_SERVICE指令,并由svcmgr_handler()函數這樣處理:
switch(txn->code)
{
case SVC_MGR_GET_SERVICE:
case SVC_MGR_CHECK_SERVICE:
s = bio_get_string16(msg, &len);
ptr = do_find_service(bs, s, len, txn->sender_euid);
if (!ptr)
break;
bio_put_ref(reply, ptr);
return 0;
一開始從msg中讀取希望get的服務名,然後調用do_find_service()函數查詢服務名對應的句柄值,最後把句柄值寫入reply。do_find_service()的代碼如下:
void *do_find_service(struct binder_state *bs, uint16_t *s, unsigned len, unsigned uid)
{
struct svcinfo *si;
si = find_svc(s, len);
if (si && si->ptr)
{
if (!si->allow_isolated)
{
unsigned appid = uid % AID_USER;
if (appid >= AID_ISOLATED_START && appid <= AID_ISOLATED_END)
{
return 0;
}
}
return si->ptr; // 傳回service代理的句柄!
}
else
{
return 0;
}
}
可以看到,do_find_service()傳回的就是所找到的服務代理對應的句柄值(si->ptr)。而svcmgr_handler()在拿到這個句柄值後,會把它寫入reply對象:
bio_put_ref(reply, ptr);
bio_put_ref()的代碼如下:
void bio_put_ref(struct binder_io *bio, void *ptr)
{
struct binder_object *obj;
if (ptr)
obj = bio_alloc_obj(bio);
else
obj = bio_alloc(bio, sizeof(*obj));
if (!obj)
return;
obj->flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
obj->type = BINDER_TYPE_HANDLE;
obj->pointer = ptr;
obj->cookie = 0;
}
bio_alloc_obj()一句說明會從reply所關聯的buffer中劃分出一個binder_object區域,然後開始對這個區域寫值。于是BINDER_TYPE_HANDLE賦給了obj->type,句柄值賦給了obj->pointer。另外,reply所關聯的buffer隻是binder_parse()裡的局部數組噢:
unsigned rdata[256/4];
大家應該還記得svcmgr_handler()是被binder_parse()回調的,當svcmgr_handler()傳回後,會接着把整理好的reply對象send出去:
bio_init(&reply, rdata, sizeof(rdata), 4);
bio_init_from_txn(&msg, txn);
res = func(bs, txn, &msg, &reply);
binder_send_reply(bs, &reply, txn->data, res);
也就是把查找到的資訊,發送給發起查找的一方。
binder_send_reply()的代碼如下:
void binder_send_reply(struct binder_state *bs, struct binder_io *reply,
void *buffer_to_free, int status)
{
struct
{
uint32_t cmd_free;
void *buffer;
uint32_t cmd_reply;
struct binder_txn txn;
} __attribute__((packed)) data;
data.cmd_free = BC_FREE_BUFFER;
data.buffer = buffer_to_free;
data.cmd_reply = BC_REPLY;
data.txn.target = 0;
data.txn.cookie = 0;
data.txn.code = 0;
if (status)
{
data.txn.flags = TF_STATUS_CODE;
data.txn.data_size = sizeof(int);
data.txn.offs_size = 0;
data.txn.data = &status;
data.txn.offs = 0;
}
else
{
data.txn.flags = 0;
data.txn.data_size = reply->data - reply->data0;
data.txn.offs_size = ((char*) reply->offs) - ((char*) reply->offs0);
data.txn.data = reply->data0;
data.txn.offs = reply->offs0;
}
binder_write(bs, &data, sizeof(data));
}
觀察代碼中最後那幾行,看來還是在擺弄reply所指代的那個buffer。當初binder_parse()在建立reply對象之時,就給它初始化了一個局部buffer,即前文所說的unsignedrdata[256/4],在svcmgr_handler()中又調用bio_put_ref()在這個buffer中開辟了一塊binder_object,并在其中賦予了ptr句柄。現在終于要向binder驅動傳遞reply資訊了,此時調用的binder_write()的代碼如下:
int binder_write(struct binder_state *bs, void *data, unsigned len)
{
struct binder_write_read bwr;
int res;
bwr.write_size = len;
bwr.write_consumed = 0;
bwr.write_buffer = (unsigned) data;
bwr.read_size = 0;
bwr.read_consumed = 0;
bwr.read_buffer = 0;
res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
if (res < 0) {
fprintf(stderr,"binder_write: ioctl failed (%s)\n",
strerror(errno));
}
return res;
}
噢,又見ioctl(),資料就在bwr.write_buffer,資料裡打出了兩個binder指令,BC_FREE_BUFFER和BC_REPLY。
這些資料被傳遞給get service動作的發起端,雖然這些資料會被binder驅動做少許修改,不過語義是不會變的,于是發起端就獲得了所查service的合法句柄。
5.小結
至此,有關ServiceManager的基本知識就大體交代完畢了,文行于此,暫告段落。必須承認,受限于個人的認識和文章的篇幅,我不可能涉及其中所有的細節,這裡隻能摘其重點進行闡述。如果以後又發現什麼有趣的東西,我還會補充進來。