1. 程式人生 > >Android Binder設計與實現 – 設計篇

Android Binder設計與實現 – 設計篇

這篇文章,最早是在老羅的文章上看到的:http://blog.csdn.net/luoshengyang/article/details/6618363

文章寫的相當不錯,最早看的時候,對linux kernel不太熟悉,看的也是雲裡霧裡的,沒太上心。後來看驅動程式碼,雖然程式碼量不大,但看起來還是非常難受。

翻找資料,找出這篇文章,對照著看原始碼,收穫非常大, share出來。

============================================

Binder Android IPC Linux 核心驅動

摘要

Binder是Android系統程序間通訊(IPC)方式之一。Linux已經擁有管道,system V IPC,socket等IPC手段,卻還要倚賴Binder來實現程序間通訊,說明Binder具有無可比擬的優勢。深入瞭解Binder並將之與傳統IPC做對比有助於我們深入領會程序間通訊的實現和效能優化。本文將對Binder的設計細節做一個全面的闡述,首先通過介紹Binder通訊模型和Binder通訊協議瞭解Binder的設計需求;然後分別闡述Binder在系統不同部分的表述方式和起的作用;最後還會解釋Binder在資料接收端的設計考慮,包括執行緒池管理,記憶體對映和等待佇列管理等。通過本文對Binder的詳細介紹以及與其它IPC通訊方式的對比,讀者將對Binder的優勢和使用Binder作為Android主要IPC方式的原因有深入瞭解。

1 引言

基於Client-Server的通訊方式廣泛應用於從網際網路和資料庫訪問到嵌入式手持裝置內部通訊等各個領域。智慧手機平臺特別是Android系統中,為了嚮應用開發者提供豐富多樣的功能,這種通訊方式更是無處不在,諸如媒體播放,視音訊頻捕獲,到各種讓手機更智慧的感測器(加速度,方位,溫度,光亮度等)都由不同的Server負責管理,應用程式只需做為Client與這些Server建立連線便可以使用這些服務,花很少的時間和精力就能開發出令人眩目的功能。Client-Server方式的廣泛採用對程序間通訊(IPC)機制是一個挑戰。目前linux支援的IPC包括傳統的管道,System V IPC,即訊息佇列/共享記憶體/訊號量,以及socket中只有socket支援Client-Server的通訊方式。當然也可以在這些底層機制上架設一套協議來實現Client-Server通訊,但這樣增加了系統的複雜性,在手機這種條件複雜,資源稀缺的環境下可靠性也難以保證。

另一方面是傳輸效能。socket作為一款通用介面,其傳輸效率低,開銷大,主要用在跨網路的程序間通訊和本機上程序間的低速通訊。訊息佇列和管道採用儲存-轉發方式,即資料先從傳送方快取區拷貝到核心開闢的快取區中,然後再從核心快取區拷貝到接收方快取區,至少有兩次拷貝過程。共享記憶體雖然無需拷貝,但控制複雜,難以使用。

表 1 各種IPC方式資料拷貝次數

IPC

資料拷貝次數

共享記憶體

0

Binder

1

Socket/管道/訊息佇列

2

還有一點是出於安全性考慮。Android作為一個開放式,擁有眾多開發者的的平臺,應用程式的來源廣泛,確保智慧終端的安全是非常重要的。終端使用者不希望從網上下載的程式在不知情的情況下偷窺隱私資料,連線無線網路,長期操作底層裝置導致電池很快耗盡等等。傳統IPC沒有任何安全措施,完全依賴上層協議來確保。首先傳統IPC的接收方無法獲得對方程序可靠的UID/PID(使用者ID/程序ID),從而無法鑑別對方身份。Android為每個安裝好的應用程式分配了自己的UID,故程序的UID是鑑別程序身份的重要標誌。使用傳統IPC只能由使用者在資料包裡填入UID/PID,但這樣不可靠,容易被惡意程式利用。可靠的身份標記只有由IPC機制本身在核心中新增。其次傳統IPC訪問接入點是開放的,無法建立私有通道。比如命名管道的名稱,system V的鍵值,socket的ip地址或檔名都是開放的,只要知道這些接入點的程式都可以和對端建立連線,不管怎樣都無法阻止惡意程式通過猜測接收方地址獲得連線。

基於以上原因,Android需要建立一套新的IPC機制來滿足系統對通訊方式,傳輸效能和安全性的要求,這就是Binder。Binder基於Client-Server通訊模式,傳輸過程只需一次拷貝,為傳送方新增UID/PID身份,既支援實名Binder也支援匿名Binder,安全性高。

2 面向物件的 Binder IPC

Binder使用Client-Server通訊方式:一個程序作為Server提供諸如視訊/音訊解碼,視訊捕獲,地址本查詢,網路連線等服務;多個程序作為Client向Server發起服務請求,獲得所需要的服務。要想實現Client-Server通信據必須實現以下兩點:一是server必須有確定的訪問接入點或者說地址來接受Client的請求,並且Client可以通過某種途徑獲知Server的地址;二是制定Command-Reply協議來傳輸資料。例如在網路通訊中Server的訪問接入點就是Server主機的IP地址+埠號,傳輸協議為TCP協議。對Binder而言,Binder可以看成Server提供的實現某個特定服務的訪問接入點, Client通過這個‘地址’向Server傳送請求來使用該服務;對Client而言,Binder可以看成是通向Server的管道入口,要想和某個Server通訊首先必須建立這個管道並獲得管道入口。

與其它IPC不同,Binder使用了面向物件的思想來描述作為訪問接入點的Binder及其在Client中的入口:Binder是一個實體位於Server中的物件,該物件提供了一套方法用以實現對服務的請求,就象類的成員函式。遍佈於client中的入口可以看成指向這個binder物件的‘指標’,一旦獲得了這個‘指標’就可以呼叫該物件的方法訪問server。在Client看來,通過Binder‘指標’呼叫其提供的方法和通過指標呼叫其它任何本地物件的方法並無區別,儘管前者的實體位於遠端Server中,而後者實體位於本地記憶體中。‘指標’是C++的術語,而更通常的說法是引用,即Client通過Binder的引用訪問Server。而軟體領域另一個術語‘控制代碼’也可以用來表述Binder在Client中的存在方式。從通訊的角度看,Client中的Binder也可以看作是Server Binder的‘代理’,在本地代表遠端Server為Client提供服務。本文中會使用‘引用’或‘控制代碼’這個兩廣泛使用的術語。

面向物件思想的引入將程序間通訊轉化為通過對某個Binder物件的引用呼叫該物件的方法,而其獨特之處在於Binder物件是一個可以跨程序引用的物件,它的實體位於一個程序中,而它的引用卻遍佈於系統的各個程序之中。最誘人的是,這個引用和java裡引用一樣既可以是強型別,也可以是弱型別,而且可以從一個程序傳給其它程序,讓大家都能訪問同一Server,就象將一個物件或引用賦值給另一個引用一樣。Binder模糊了程序邊界,淡化了程序間通訊過程,整個系統彷彿運行於同一個面向物件的程式之中。形形色色的Binder物件以及星羅棋佈的引用彷彿粘接各個應用程式的膠水,這也是Binder在英文裡的原意。

當然面向物件只是針對應用程式而言,對於Binder驅動和核心其它模組一樣使用C語言實現,沒有類和物件的概念。Binder驅動為面向物件的程序間通訊提供底層支援。

Binder框架定義了四個角色:Server,Client,ServiceManager(以後簡稱SMgr)以及Binder驅動。其中Server,Client,SMgr運行於使用者空間,驅動運行於核心空間。這四個角色的關係和網際網路類似:Server是伺服器,Client是客戶終端,SMgr是域名伺服器(DNS),驅動是路由器。

和路由器一樣,Binder驅動雖然默默無聞,卻是通訊的核心。儘管名叫‘驅動’,實際上和硬體裝置沒有任何關係,只是實現方式和裝置驅動程式是一樣的:它工作於核心態,提供open(),mmap(),poll(),ioctl()等標準檔案操作,以字元驅動裝置中的misc設備註冊在裝置目錄/dev下,使用者通過/dev/binder訪問該它。驅動負責程序之間Binder通訊的建立,Binder在程序之間的傳遞,Binder引用計數管理,資料包在程序之間的傳遞和互動等一系列底層支援。驅動和應用程式之間定義了一套介面協議,主要功能由ioctl()介面實現,不提供read(),write()介面,因為ioctl()靈活方便,且能夠一次呼叫實現先寫後讀以滿足同步互動,而不必分別呼叫write()和read()。Binder驅動的程式碼位於linux目錄的drivers/misc/binder.c中。

和DNS類似,SMgr的作用是將字元形式的Binder名字轉化成Client中對該Binder的引用,使得Client能夠通過Binder名字獲得對Server中Binder實體的引用。註冊了名字的Binder叫實名Binder,就象每個網站除了有IP地址外還有自己的網址。Server建立了Binder實體,為其取一個字元形式,可讀易記的名字,將這個Binder連同名字以資料包的形式通過Binder驅動傳送給SMgr,通知SMgr註冊一個名叫張三的Binder,它位於某個Server中。驅動為這個穿過程序邊界的Binder建立位於核心中的實體節點以及SMgr對實體的引用,將名字及新建的引用打包傳遞給SMgr。SMgr收資料包後,從中取出名字和引用填入一張查詢表中。

細心的讀者可能會發現其中的蹊蹺:SMgr是一個程序,Server是另一個程序,Server向SMgr註冊Binder必然會涉及程序間通訊。當前實現的是程序間通訊卻又要用到程序間通訊,這就好象蛋可以孵出雞前提卻是要找只雞來孵蛋。Binder的實現比較巧妙:預先創造一隻雞來孵蛋:SMgr和其它程序同樣採用Binder通訊,SMgr是Server端,有自己的Binder物件(實體),其它程序都是Client,需要通過這個Binder的引用來實現Binder的註冊,查詢和獲取。SMgr提供的Binder比較特殊,它沒有名字也不需要註冊,當一個程序使用BINDER_SET_CONTEXT_MGR命令將自己註冊成SMgr時Binder驅動會自動為它建立Binder實體(這就是那隻預先造好的雞)。其次這個Binder的引用在所有Client中都固定為0而無須通過其它手段獲得。也就是說,一個Server若要向SMgr註冊自己Binder就必需通過0這個引用號和SMgr的Binder通訊。類比網路通訊,0號引用就好比域名伺服器的地址,你必須預先手工或動態配置好。要注意這裡說的Client是相對SMgr而言的,一個應用程式可能是個提供服務的Server,但對SMgr來說它仍然是個Client。

Server向SMgr註冊了Binder實體及其名字後,Client就可以通過名字獲得該Binder的引用了。Client也利用保留的0號引用向SMgr請求訪問某個Binder:我申請獲得名字叫張三的Binder的引用。SMgr收到這個連線請求,從請求資料包裡獲得Binder的名字,在查詢表裡找到該名字對應的條目,從條目中取出Binder的引用,將該引用作為回覆傳送給發起請求的Client。從面向物件的角度,這個Binder物件現在有了兩個引用:一個位於SMgr中,一個位於發起請求的Client中。如果接下來有更多的Client請求該Binder,系統中就會有更多的引用指向該Binder,就象java裡一個物件存在多個引用一樣。而且類似的這些指向Binder的引用是強型別,從而確保只要有引用Binder實體就不會被釋放掉。通過以上過程可以看出,SMgr象個火車票代售點,收集了所有火車的車票,可以通過它購買到乘坐各趟火車的票-得到某個Binder的引用。

3.4 匿名 Binder

並不是所有Binder都需要註冊給SMgr廣而告之的。Server端可以通過已經建立的Binder連線將建立的Binder實體傳給Client,當然這條已經建立的Binder連線必須是通過實名Binder實現。由於這個Binder沒有向SMgr註冊名字,所以是個匿名Binder。Client將會收到這個匿名Binder的引用,通過這個引用向位於Server中的實體傳送請求。匿名Binder為通訊雙方建立一條私密通道,只要Server沒有把匿名Binder發給別的程序,別的程序就無法通過窮舉或猜測等任何方式獲得該Binder的引用,向該Binder傳送請求。

下圖展示了參與Binder通訊的所有角色,將在以後章節中一一提到。

圖 1 Binder通訊示例

Binder協議基本格式是(命令+資料),使用ioctl(fd, cmd, arg)函式實現互動。命令由引數cmd承載,資料由引數arg承載,隨cmd不同而不同。下表列舉了所有命令及其所對應的資料:

表 2 Binder通訊命令字

命令

含義

arg

BINDER_WRITE_READ

該命令向Binder寫入或讀取資料。引數分為兩段:寫部分和讀部分。如果write_size不為0就先將write_buffer裡的資料寫入Binder;如果read_size不為0再從Binder中讀取資料存入read_buffer中。write_consumed和read_consumed表示操作完成時Binder驅動實際寫入或讀出的資料個數。

struct binder_write_read {

signed long write_size;

signed long write_consumed;

unsigned long write_buffer;

signed long read_size;

signed long read_consumed;

unsigned long read_buffer;

};

BINDER_SET_MAX_THREADS

該命令告知Binder驅動接收方(通常是Server端)執行緒池中最大的執行緒數。由於Client是併發向Server端傳送請求的,Server端必須開闢執行緒池為這些併發請求提供服務。告知驅動執行緒池的最大值是為了讓驅動發現執行緒數達到該值時不要再命令接收端啟動新的執行緒。

int max_threads;

BINDER_SET_CONTEXT_MGR

將當前程序註冊為SMgr。系統中同時只能存在一個SMgr。只要當前的SMgr沒有呼叫close()關閉Binder驅動就不能有別的程序可以成為SMgr。

---

BINDER_THREAD_EXIT

通知Binder驅動當前執行緒退出了。Binder會為所有參與Binder通訊的執行緒(包括Server執行緒池中的執行緒和Client發出請求的執行緒)建立相應的資料結構。這些執行緒在退出時必須通知驅動釋放相應的資料結構。

---

BINDER_VERSION

獲得Binder驅動的版本號。

---

這其中最常用的命令是BINDER_WRITE_READ。該命令的引數包括兩部分資料:一部分是向Binder寫入的資料,一部分是要從Binder讀出的資料,驅動程式先處理寫部分再處理讀部分。這樣安排的好處是應用程式可以很靈活地處理命令的同步或非同步。例如若要傳送非同步命令可以只填入寫部分而將read_size置成0;若要只從Binder獲得資料可以將寫部分置空即write_size置成0;若要傳送請求並同步等待返回資料可以將兩部分都置上。

Binder寫操作的資料時格式同樣也是(命令+資料)。這時候命令和資料都存放在binder_write_read 結構write_buffer域指向的記憶體空間裡,多條命令可以連續存放。資料緊接著存放在命令後面,格式根據命令不同而不同。下表列舉了Binder寫操作支援的命令:

表 3 Binder寫操作命令字

cmd

含義

arg

BC_TRANSACTION 
BC_REPLY

BC_TRANSACTION用於Client向Server傳送請求資料;BC_REPLY用於Server向Client傳送回覆(應答)資料。其後面緊接著一個binder_transaction_data結構體表明要寫入的資料。

struct binder_transaction_data

BC_ACQUIRE_RESULT 
BC_ATTEMPT_ACQUIRE

暫未實現

---

BC_FREE_BUFFER

釋放一塊對映的記憶體。Binder接收方通過mmap()對映一塊較大的記憶體空間,Binder驅動基於這片記憶體採用最佳匹配演算法實現接收資料快取的動態分配和釋放,滿足併發請求對接收快取區的需求。應用程式處理完這片資料後必須儘快使用該命令釋放快取區,否則會因為快取區耗盡而無法接收新資料。

指向需要釋放的快取區的指標;該指標位於收到的Binder資料包中

BC_INCREFS 
BC_ACQUIRE 
BC_RELEASE 
BC_DECREFS

這組命令增加或減少Binder的引用計數,用以實現強指標或弱指標的功能。

32位Binder引用號

BC_INCREFS_DONE 
BC_ACQUIRE_DONE

第一次增加Binder實體引用計數時,驅動向Binder實體所在的程序傳送BR_INCREFS, BR_ACQUIRE訊息;Binder實體所在的程序處理完畢回饋BC_INCREFS_DONE,BC_ACQUIRE_DONE

void *ptr:Binder實體在使用者空間中的指標

void *cookie:與該實體相關的附加資料

BC_REGISTER_LOOPER 
BC_ENTER_LOOPER 
BC_EXIT_LOOPER

這組命令同BINDER_SET_MAX_THREADS一道實現Binder驅動對接收方執行緒池管理。BC_REGISTER_LOOPER通知驅動執行緒池中一個執行緒已經建立了;BC_ENTER_LOOPER通知驅動該執行緒已經進入主迴圈,可以接收資料;BC_EXIT_LOOPER通知驅動該執行緒退出主迴圈,不再接收資料。

---

BC_REQUEST_DEATH_NOTIFICATION

獲得Binder引用的程序通過該命令要求驅動在Binder實體銷燬得到通知。雖說強指標可以確保只要有引用就不會銷燬實體,但這畢竟是個跨程序的引用,誰也無法保證實體由於所在的Server關閉Binder驅動或異常退出而消失,引用者能做的是要求Server在此刻給出通知。

uint32 *ptr; 需要得到死亡通知的Binder引用

void **cookie: 與死亡通知相關的資訊,驅動會在發出死亡通知時返回給發出請求的程序。

BC_DEAD_BINDER_DONE

收到實體死亡通知書的程序在刪除引用後用本命令告知驅動。

void **cookie

在這些命令中,最常用的是BC_TRANSACTION/BC_REPLY命令對,Binder請求和應答資料就是通過這對命令傳送給接收方。這對命令所承載的資料包由結構體struct binder_transaction_data定義。Binder互動有同步和非同步之分,利用binder_transaction_data中flag域區分。如果flag域的TF_ONE_WAY位為1則為非同步互動,即Client端傳送完請求互動即結束, Server端不再返回BC_REPLY資料包;否則Server會返回BC_REPLY資料包,Client端必須等待接收完該資料包方才完成一次互動。

從Binder裡讀出的資料格式和向Binder中寫入的資料格式一樣,採用(訊息ID+資料)形式,並且多條訊息可以連續存放。下表列舉了從Binder讀出的命令字及其相應的引數:

表 4 Binder讀操作訊息ID

訊息

含義

引數

BR_ERROR

發生內部錯誤(如記憶體分配失敗)

---

BR_OK 
BR_NOOP

操作完成

---

BR_SPAWN_LOOPER

該訊息用於接收方執行緒池管理。當驅動發現接收方所有執行緒都處於忙碌狀態且執行緒池裡的執行緒總數沒有超過BINDER_SET_MAX_THREADS設定的最大執行緒數時,向接收方傳送該命令要求建立更多執行緒以備接收資料。

---

BR_TRANSACTION 
BR_REPLY

這兩條訊息分別對應傳送方的BC_TRANSACTION和BC_REPLY,表示當前接收的資料是請求還是回覆。

binder_transaction_data

BR_ACQUIRE_RESULT              
BR_ATTEMPT_ACQUIRE 
BR_FINISHED

尚未實現

---

BR_DEAD_REPLY

互動過程中如果發現對方程序或執行緒已經死亡則返回該訊息

---

BR_TRANSACTION_COMPLETE

傳送方通過BC_TRANSACTION或BC_REPLY傳送完一個數據包後,都能收到該訊息做為成功傳送的反饋。這和BR_REPLY不一樣,是驅動告知傳送方已經發送成功,而不是Server端返回請求資料。所以不管同步還是非同步互動接收方都能獲得本訊息。

---

BR_INCREFS 
BR_ACQUIRE 
BR_RELEASE 
BR_DECREFS

這一組訊息用於管理強/弱指標的引用計數。只有提供Binder實體的程序才能收到這組訊息。

void *ptr:Binder實體在使用者空間中的指標

void *cookie:與該實體相關的附加資料

BR_DEAD_BINDER 
BR_CLEAR_DEATH_NOTIFICATION_DONE

向獲得Binder引用的程序傳送Binder實體死亡通知書;收到死亡通知書的程序接下來會返回BC_DEAD_BINDER_DONE做確認。

void **cookie:在使用BC_REQUEST_DEATH_NOTIFICATION註冊死亡通知時的附加引數。

BR_FAILED_REPLY

如果傳送非法引用號則返回該訊息

---

和寫資料一樣,其中最重要的訊息是BR_TRANSACTION 或BR_REPLY,表明收到了一個格式為binder_transaction_data的請求資料包(BR_TRANSACTION)或返回資料包(BR_REPLY)。

該結構是Binder接收/傳送資料包的標準格式,每個成員定義如下:

表 5 Binder收發資料包結構:binder_transaction_data

成員

含義

union {

size_t handle;

void *ptr;

} target;

對於傳送資料包的一方,該成員指明發送目的地。由於目的是在遠端,所以這裡填入的是對Binder實體的引用,存放在target.handle中。如前述,Binder的引用在程式碼中也叫控制代碼(handle)。

當資料包到達接收方時,驅動已將該成員修改成Binder實體,即指向Binder物件記憶體的指標,使用target.ptr來獲得。該指標是接收方在將Binder實體傳輸給其它程序時提交給驅動的,驅動程式能夠自動將傳送方填入的引用轉換成接收方Binder物件的指標,故接收方可以直接將其當做物件指標來使用(通常是將其reinterpret_cast成相應類)。

void *cookie;

傳送方忽略該成員;接收方收到資料包時,該成員存放的是建立Binder實體時由該接收方自定義的任意數值,做為與Binder指標相關的額外資訊存放在驅動中。驅動基本上不關心該成員。

unsigned int code;

該成員存放收發雙方約定的命令碼,驅動完全不關心該成員的內容。通常是Server端定義的公共介面函式的編號。

unsigned int flags;

與互動相關的標誌位,其中最重要的是TF_ONE_WAY位。如果該位置上表明這次互動是非同步的,Server端不會返回任何資料。驅動利用該位來決定是否構建與返回有關的資料結構。另外一位TF_ACCEPT_FDS是出於安全考慮,如果發起請求的一方不希望在收到的回覆中接收檔案形式的Binder可以將該位置上。因為收到一個檔案形式的Binder會自動為資料接收方開啟一個檔案,使用該位可以防止開啟檔案過多。

pid_t sender_pid;

uid_t sender_euid;

該成員存放傳送方的程序ID和使用者ID,由驅動負責填入,接收方可以讀取該成員獲知傳送方的身份。

size_t data_size;

該成員表示data.buffer指向的緩衝區存放的資料長度。傳送資料時由傳送方填入,表示即將傳送的資料長度;在接收方用來告知接收到資料的長度。

size_t offsets_size;

驅動一般情況下不關心data.buffer裡存放什麼資料,但如果有Binder在其中傳輸則需要將其相對data.buffer的偏移位置指出來讓驅動知道。有可能存在多個Binder同時在資料中傳遞,所以須用陣列表示所有偏移位置。本成員表示該陣列的大小。

union {

struct {

const void *buffer;

const void *offsets;

} ptr;

uint8_t buf[8];

} data;

data.bufer存放要傳送或接收到的資料;data.offsets指向Binder偏移位置陣列,該陣列可以位於data.buffer中,也可以在另外的記憶體空間中,並無限制。buf[8]是為了無論保證32位還是64位平臺,成員data的大小都是8個位元組。

這裡有必要再強調一下offsets_size和data.offsets兩個成員,這是Binder通訊有別於其它IPC的地方。如前述,Binder採用面向物件的設計思想,一個Binder實體可以傳送給其它程序從而建立許多跨程序的引用;另外這些引用也可以在程序之間傳遞,就象java裡將一個引用賦給另一個引用一樣。為Binder在不同程序中建立引用必須有驅動的參與,由驅動在核心建立並註冊相關的資料結構後接收方才能使用該引用。而且這些引用可以是強型別,需要驅動為其維護引用計數。然而這些跨程序傳遞的Binder混雜在應用程式傳送的資料包裡,資料格式由使用者定義,如果不把它們一一標記出來告知驅動,驅動將無法從資料中將它們提取出來。於是就使用陣列data.offsets存放使用者資料中每個Binder相對data.buffer的偏移量,用offsets_size表示這個陣列的大小。驅動在傳送資料包時會根據data.offsets和offset_size將散落於data.buffer中的Binder找出來並一一為它們建立相關的資料結構。在資料包中傳輸的Binder是型別為struct flat_binder_object的結構體,詳見後文。

對於接收方來說,該結構只相當於一個定長的訊息頭,真正的使用者資料存放在data.buffer所指向的快取區中。如果傳送方在資料中內嵌了一個或多個Binder,接收到的資料包中同樣會用data.offsets和offset_size指出每個Binder的位置和總個數。不過通常接收方可以忽略這些資訊,因為接收方是知道資料格式的,參考雙方約定的格式定義就能知道這些Binder在什麼位置。

圖 2 BINDER_WRITE_READ資料包例項

考察一次Binder通訊的全過程會發現,Binder存在於系統以下幾個部分中:

· 應用程式程序:分別位於Server程序和Client程序中

· Binder驅動:分別管理為Server端的Binder實體和Client端的引用

· 傳輸資料:由於Binder可以跨程序傳遞,需要在傳輸資料中予以表述

在系統不同部分,Binder實現的功能不同,表現形式也不一樣。接下來逐一探討Binder在各部分所扮演的角色和使用的資料結構。

雖然Binder用到了面向物件的思想,但並不限制應用程式一定要使用面向物件的語言,無論是C語言還是C++語言都可以很容易的使用Binder來通訊。例如儘管Android主要使用java/C++,象SMgr這麼重要的程序就是用C語言實現的。不過面向物件的方式表述起來更方便,所以本文假設應用程式是用面嚮物件語言實現的。

Binder本質上只是一種底層通訊方式,和具體服務沒有關係。為了提供具體服務,Server必須提供一套介面函式以便Client通過遠端訪問使用各種服務。這時通常採用Proxy設計模式:將介面函式定義在一個抽象類中,Server和Client都會以該抽象類為基類實現所有介面函式,所不同的是Server端是真正的功能實現,而Client端是對這些函式遠端呼叫請求的包裝。如何將Binder和Proxy設計模式結合起來是應用程式實現面向物件Binder通訊的根本問題。

5.1.1 Binder 在Server端的表述 – Binder實體

做為Proxy設計模式的基礎,首先定義一個抽象介面類封裝Server所有功能,其中包含一系列純虛擬函式留待Server和Proxy各自實現。由於這些函式需要跨程序呼叫,須為其一一編號,從而Server可以根據收到的編號決定呼叫哪個函式。其次就要引入Binder了。Server端定義另一個Binder抽象類處理來自Client的Binder請求資料包,其中最重要的成員是虛擬函式onTransact()。該函式分析收到的資料包,呼叫相應的介面函式處理請求。

接下來採用繼承方式以介面類和Binder抽象類為基類構建Binder在Server中的實體,實現基類裡所有的虛擬函式,包括公共介面函式以及資料包處理函式:onTransact()。這個函式的輸入是來自Client的binder_transaction_data結構的資料包。前面提到,該結構裡有個成員code,包含這次請求的介面函式編號。onTransact()將case-by-case地解析code值,從資料包裡取出函式引數,呼叫介面類中相應的,已經實現的公共介面函式。函式執行完畢,如果需要返回資料就再構建一個binder_transaction_data包將返回資料包填入其中。

那麼各個Binder實體的onTransact()又是什麼時候呼叫呢?這就需要驅動參與了。前面說過,Binder實體須要以Binde傳輸結構flat_binder_object形式傳送給其它程序才能建立Binder通訊,而Binder實體指標就存放在該結構的handle域中。驅動根據Binder位置陣列從傳輸資料中獲取該Binder的傳輸結構,為它建立位於核心中的Binder節點,將Binder實體指標記錄在該節點中。如果接下來有其它程序向該Binder傳送資料,驅動會根據節點中記錄的資訊將Binder實體指標填入binder_transaction_data的target.ptr中返回給接收執行緒。接收執行緒從資料包中取出該指標,reinterpret_cast成Binder抽象類並呼叫onTransact()函式。由於這是個虛擬函式,不同的Binder實體中有各自的實現,從而可以呼叫到不同Binder實體提供的onTransact()。

5.1.2 Binder 在Client端的表述 – Binder引用

做為Proxy設計模式的一部分,Client端的Binder同樣要繼承Server提供的公共介面類並實現公共函式。但這不是真正的實現,而是對遠端函式呼叫的包裝:將函式引數打包,通過Binder向Server傳送申請並等待返回值。為此Client端的Binder還要知道Binder實體的相關資訊,即對Binder實體的引用。該引用或是由SMgr轉發過來的,對實名Binder的引用或是由另一個程序直接傳送過來的,對匿名Binder的引用。

由於繼承了同樣的公共介面類,Client Binder提供了與Server Binder一樣的函式原型,使使用者感覺不出Server是執行在本地還是遠端。Client Binder中,公共介面函式的包裝方式是:建立一個binder_transaction_data資料包,將其對應的編碼填入code域,將呼叫該函式所需的引數填入data.buffer指向的快取中,並指明資料包的目的地,那就是已經獲得的對Binder實體的引用,填入資料包的target.handle中。注意這裡和Server的區別:實際上target域是個聯合體,包括ptr和handle兩個成員,前者用於接收資料包的Server,指向 Binder實體對應的記憶體空間;後者用於作為請求方的Client,存放Binder實體的引用,告知驅動資料包將路由給哪個實體。資料包準備好後,通過驅動介面傳送出去。經過BC_TRANSACTION/BC_REPLY回合完成函式的遠端呼叫並得到返回值。

Binder可以塞在資料包的有效資料中越程序邊界從一個程序傳遞給另一個程序,這些傳輸中的Binder用結構flat_binder_object表示,如下表所示:

表 6 Binder傳輸結構:flat_binder_object

成員

含義

unsigned long type

表明該Binder的型別,包括以下幾種:

BINDER_TYPE_BINDER:表示傳遞的是Binder實體,並且指向該實體的引用都是強型別;

BINDER_TYPE_WEAK_BINDER:表示傳遞的是Binder實體,並且指向該實體的引用都是弱型別;

BINDER_TYPE_HANDLE:表示傳遞的是Binder強型別的引用

BINDER_TYPE_WEAK_HANDLE:表示傳遞的是Binder弱型別的引用

BINDER_TYPE_FD:表示傳遞的是檔案形式的Binder,詳見下節

unsigned long flags

該域只對第一次傳遞Binder實體時有效,因為此刻驅動需要在核心中建立相應的實體節點,有些引數需要從該域取出:

第0-7位:程式碼中用FLAT_BINDER_FLAG_PRIORITY_MASK取得,表示處理本實體請求資料包的執行緒的最低優先順序。當一個應用程式提供多個實體時,可以通過該引數調整分配給各個實體的處理能力。

第8位:程式碼中用FLAT_BINDER_FLAG_ACCEPTS_FDS取得,置1表示該實體可以接收其它程序發過來的檔案形式的Binder。由於接收檔案形式的Binder會在本程序中自動開啟檔案,有些Server可以用該標誌禁止該功能,以防開啟過多檔案。

union {

void *binder;

signed long handle;

};

當傳遞的是Binder實體時使用binder域,指向Binder實體在應用程式中的地址。

當傳遞的是Binder引用時使用handle域,存放Binder在程序中的引用號。

void *cookie;

該域只對Binder實體有效,存放與該Binder有關的附加資訊。

無論是Binder實體還是對實體的引用都從屬與某個程序,所以該結構不能透明地在程序之間傳輸,必須經過驅動翻譯。例如當Server把Binder實體傳遞給Client時,在傳送資料流中,flat_binder_object中的type是BINDER_TYPE_BINDER,binder指向Server程序使用者空間地址。如果透傳給接收端將毫無用處,驅動必須對資料流中的這個Binder做修改:將type該成BINDER_TYPE_HANDLE;為這個Binder在接收程序中建立位於核心中的引用並將引用號填入handle中。對於發生資料流中引用型別的Binder也要做同樣轉換。經過處理後接收程序從資料流中取得的Binder引用才是有效的,才可以將其填入資料包binder_transaction_data的target.handle域,向Binder實體傳送請求。

這樣做也是出於安全性考慮:應用程式不能隨便猜測一個引用號填入target.handle中就可以向Server請求服務了,因為驅動並沒有為你在核心中建立該引用,必定會被驅動拒絕。唯有經過身份認證確認合法後,由‘權威機構’(Binder驅動)親手授予你的Binder才能使用,因為這時驅動已經在核心中為你使用該Binder做了註冊,交給你的引用號是合法的。

下表總結了當flat_binder_object結構穿過驅動時驅動所做的操作:

表 7 驅動對flat_binder_object的操作

Binder型別(type域)

在傳送方的操作

在接收方的操作

BINDER_TYPE_BINDER

BINDER_TYPE_WEAK_BINDER

只有實體所在的程序能傳送該型別的Binder。如果是第一次傳送驅動將建立實體在核心中的節點,並儲存binder,cookie,flag域。

如果是第一次接收該Binder則建立實體在核心中的引用;將handle域替換為新建的引用號;將type域替換為BINDER_TYPE_(WEAK_)HANDLE

BINDER_TYPE_HANDLE

BINDER_TYPE_WEAK_HANDLE

獲得Binder引用的程序都能傳送該型別Binder。驅動根據handle域提供的引用號查詢建立在核心的引用。如果找到說明引用號合法,否則拒絕該傳送請求。

如果收到的Binder實體位於接收程序中:將ptr域替換為儲存在節點中的binder值;cookie替換為儲存在節點中的cookie值;type替換為BINDER_TYPE_(WEAK_)BINDER。

如果收到的Binder實體不在接收程序中:如果是第一次接收則建立實體在核心中的引用;將handle域替換為新建的引用號

BINDER_TYPE_FD

驗證handle域中提供的開啟檔案號是否有效,無效則拒絕該傳送請求。

在接收方建立新的開啟檔案號並將其與提供的開啟檔案描述結構繫結。

除了通常意義上用來通訊的Binder,還有一種特殊的Binder:檔案Binder。這種Binder的基本思想是:將檔案看成Binder實體,程序開啟的檔案號看成Binder的引用。一個程序可以將它開啟檔案的檔案號傳遞給另一個程序,從而另一個程序也打開了同一個檔案,就象Binder的引用在程序之間傳遞一樣。

一個程序開啟一個檔案,就獲得與該檔案繫結的開啟檔案號。從Binder的角度,linux在核心建立的開啟檔案描述結構struct file是Binder的實體,開啟檔案號是該程序對該實體的引用。既然是Binder那麼就可以在程序之間傳遞,故也可以用flat_binder_object結構將檔案Binder通過資料包傳送至其它程序,只是結構中type域的值為BINDER_TYPE_FD,表明該Binder是檔案Binder。而結構中的handle域則存放檔案在傳送方程序中的開啟檔案號。我們知道開啟檔案號是個侷限於某個程序的值,一旦跨程序就沒有意義了。這一點和Binder實體使用者指標或Binder引用號是一樣的,若要跨程序同樣需要驅動做轉換。驅動在接收Binder的程序空間建立一個新的開啟檔案號,將它與已有的開啟檔案描述結構struct file勾連上,從此該Binder實體又多了一個引用。新建的開啟檔案號覆蓋flat_binder_object中原來的檔案號交給接收程序。接收程序利用它可以執行read(),write()等檔案操作。

傳個檔案為啥要這麼麻煩,直接將檔名用Binder傳過去,接收方用open()開啟不就行了嗎?其實這還是有區別的。首先對同一個開啟檔案共享的層次不同:使用檔案Binder開啟的檔案共享linux VFS中的struct file,struct dentry,struct inode結構,這意味著一個程序使用read()/write()/seek()改變了檔案指標,另一個程序的檔案指標也會改變;而如果兩個程序分別使用同一檔名開啟檔案則有各自的struct file結構,從而各自獨立維護檔案指標,互不干擾。其次是一些特殊裝置檔案要求在struct file一級共享才能使用,例如android的另一個驅動ashmem,它和Binder一樣也是misc裝置,用以實現程序間的共享記憶體。一個程序開啟的ashmem檔案只有通過檔案Binder傳送到另一個程序才能實現記憶體共享,這大大提高了記憶體共享的安全性,道理和Binder增強了IPC的安全性是一樣的。

驅動是Binder通訊的核心,系統中所有的Binder實體以及每個實體在各個程序中的引用都登記在驅動中;驅動需要記錄Binder引用->實體之間多對一的關係;為引用找到對應的實體;在某個程序中為實體建立或查詢到對應的引用;記錄Binder的歸屬地(位於哪個程序中);通過管理Binder的強/弱引用建立/銷燬Binder實體等等。

驅動裡的Binder是什麼時候建立的呢?前面提到過,為了實現實名Binder的註冊,系統必須建立第一隻雞–為SMgr建立的,用於註冊實名Binder的Binder實體,負責實名Binder註冊過程中的程序間通訊。既然建立了實體就要有對應的引用:驅動將所有程序中的0號引用都預留給該Binder實體,即所有程序的0號引用天然地都指向註冊實名Binder專用的Binder,無須特殊操作即可以使用0號引用來註冊實名Binder。接下來隨著應用程式不斷地註冊實名Binder,不斷向SMgr索要Binder的引用,不斷將Binder從一個程序傳遞給另一個程序,越來越多的Binder以傳輸結構 - flat_binder_object的形式穿越驅動做跨程序的遷徙。由於binder_transaction_data中data.offset陣列的存在,所有流經驅動的Binder都逃不過驅動的眼睛。Binder將對這些穿越程序邊界的Binder做如下操作:檢查傳輸結構的type域,如果是BINDER_TYPE_BINDER或BINDER_TYPE_WEAK_BINDER則建立Binder的實體;如果是BINDER_TYPE_HANDLE或BINDER_TYPE_WEAK_HANDLE則建立Binder的引用;如果是BINDER_TYPE_HANDLE則為程序開啟檔案,無須建立任何資料結構。詳細過程可參考表7。隨著越來越多的Binder實體或引用在程序間傳遞,驅動會在核心裡建立越來越多的節點或引用,當然這個過程對使用者來說是透明的。

驅動中的Binder實體也叫‘節點’,隸屬於提供實體的程序,由struct binder_node結構來表示:

表 8 Binder節點描述結構:binder_node

成員

含義

int debug_id;

用於除錯

struct binder_work work;

當本節點引用計數發生改變,需要通知所屬程序時,通過該成員掛入所屬程序的to-do佇列裡,喚醒所屬程序執行Binder實體引用計數的修改。

union {

struct rb_node rb_node;

struct hlist_node dead_node;

};

每個程序都維護一棵紅黑樹,以Binder實體在使用者空間的指標,即本結構的ptr成員為索引存放該程序所有的Binder實體。這樣驅動可以根據Binder實體在使用者空間的指標很快找到其位於核心的節點。rb_node用於將本節點鏈入該紅黑樹中。

銷燬節點時須將rb_node從紅黑樹中摘除,但如果本節點還有引用沒有切斷,就用dead_node將節點隔離到另一個連結串列中,直到通知所有程序切斷與該節點的引用後,該節點才可能被銷燬。

struct binder_proc *proc;

本成員指向節點所屬的程序,即提供該節點的程序。

struct hlist_head refs;

本成員是佇列頭,所有指向本節點的引用都連結在該佇列裡。這些引用可能隸屬於不同的程序。通過該佇列可以遍歷指向該節點的所有引用。

int internal_strong_refs;

用以實現強指標的計數器:產生一個指向本節點的強引用該計數就會加1。

int local_weak_refs;

驅動為傳輸中的Binder設定的弱引用計數。如果一個Binder打包在資料包中從一個程序傳送到另一個程序,驅動會為該Binder增加引用計數,直到接收程序通過BC_FREE_BUFFER通知驅動釋放該資料包的資料區為止。

int local_strong_refs;

驅動為傳輸中的Binder設定的強引用計數。同上。

void __user *ptr;

指向使用者空間Binder實體的指標,來自於flat_binder_object的binder成員

void __user *cookie;

指向使用者空間的附加指標,來自於flat_binder_object的cookie成員

unsigned has_strong_ref;

unsigned pending_strong_ref;

unsigned has_weak_ref;

unsigned pending_weak_ref

這一組標誌用於控制驅動與Binder實體所在程序互動式修改引用計數

unsigned has_async_transaction;

該成員表明該節點在to-do佇列中有非同步互動尚未完成。驅動將所有傳送往接收端的資料包暫存在接收程序或執行緒開闢的to-do佇列裡。對於非同步互動,驅動做了適當流控:如果to-do佇列裡有非同步互動尚待處理則該成員置1,這將導致新到的非同步互動存放在本結構成員 – asynch_todo佇列中,而不直接送到to-do佇列裡。目的是為同步互動讓路,避免長時間阻塞傳送端。

unsigned accept_fds

表明節點是否同意接受檔案方式的Binder,來自flat_binder_object中flags成員的FLAT_BINDER_FLAG_ACCEPTS_FDS位。由於接收檔案Binder會為程序自動開啟一個檔案,佔用有限的檔案描述符,節點可以設定該位拒絕這種行為。

int min_priority

設定處理Binder請求的執行緒的最低優先順序。傳送執行緒將資料提交給接收執行緒處理時,驅動會將傳送執行緒的優先順序也賦予接收執行緒,使得資料即使跨了程序也能以同樣優先順序得到處理。不過如果傳送執行緒優先順序過低,接收執行緒將以預設的最小值執行。

該域的值來自於flat_binder_object中flags成員。

struct list_head async_todo

非同步互動等待佇列;用於分流發往本節點的非同步互動包

每個程序都有一棵紅黑樹用於存放建立好的節點,以Binder在使用者空間的指標作為索引。每當在傳輸資料中偵測到一個代表Binder實體的flat_binder_object,先以該結構的binder指標為索引搜尋紅黑樹;如果沒找到就建立一個新節點新增到樹中。由於對於同一個程序來說記憶體地址是唯一的,所以不會重複建設造成混亂。

和實體一樣,Binder的引用也是驅動根據傳輸資料中的flat_binder_object建立的,隸屬於獲得該引用的程序,用struct binder_ref結構體表示:

表 9 Binder引用描述結構:binder_ref

成員

含義

int debug_id;

除錯用

struct rb_node rb_node_desc;

每個程序有一棵紅黑樹,程序所有引用以引用號(即本結構的desc域)為索引添入該樹中。本成員用做連結到該樹的一個節點。

struct rb_node rb_node_node;

每個程序又有一棵紅黑樹,程序所有引用以節點實體在驅動中的記憶體地址(即本結構的node域)為所引添入該樹中。本成員用做連結到該樹的一個節點。

struct hlist_node node_entry;

該域將本引用做為節點鏈入所指向的Binder實體結構binder_node中的refs佇列

struct binder_proc *proc;

相關推薦

Android Binder設計實現設計

這篇文章,最早是在老羅的文章上看到的:http://blog.csdn.net/luoshengyang/article/details/6618363 文章寫的相當不錯,最早看的時候,對linux kernel不太熟悉,看的也是雲裡霧裡的,沒太上心。後來看驅動程式碼,雖

通過極簡模擬框架讓你瞭解ASP.NET Core MVC框架的設計實現[上]

《200行程式碼,7個物件——讓你瞭解ASP.NET Core框架的本質》讓很多讀者對ASP.NET Core管道有了真實的瞭解。在過去很長一段時間中,有很多人私信給我:能否按照相同的方式分析一下MVC框架的設計與實現原理,希望這篇文章能夠滿足你們的需求。在對本章內容展開介紹之前,順便作一下廣告:《ASP.N

20155338課程設計個人報告——基於ARM實驗箱的Android交友軟件的設計實現

ive serve clip spl ogl help software 開拓 按鈕 課程設計個人報告——基於ARM實驗箱的Android交友軟件的設計與實現 個人貢獻 實驗環境的搭建 代碼調試 在電腦上成功運行 研究程序代碼撰寫小組報告 一、實驗環境 1、Eclips

基於Android簡單備忘錄的設計實現(附git原始碼連結)

前言 課程作業需要,於是忙活兩天寫了一個簡單的備忘錄,使用了ListView,SQLite。 開發環境:Android Studio 原始碼連結:https://gitee.com/zg0212/Memoire 功能截圖 主頁面 新建頁面

Android:答題APP的設計實現(mysql+jsp+AndroidAndroid:答題APP的設計實現(mysql+jsp+Android

Android:答題APP的設計與實現(mysql+jsp+Android) 還沒有整理完,待續…… 學校開了Android課,最後讓交一個大作業。正好拿來練練手,記錄下思路。也希望能給有需要的朋友們一些幫助。恩,純小白教程,大神們可以繞路了。 作業的題目是這樣的: 考試A

基於android的天氣預報的設計實現

目錄 應用開發技術及開發平臺介紹 應用需求分析 應用功能設計及其描述 應用UI展示 ①開發技術:   本系統是採用面向物件的軟體開發方法,基於Android studio開發平臺,以Android作為本系統的開發語言實現音樂播放器預定的需求功能。   ②平臺介紹 硬體平臺

《redis設計實現》1-資料結構物件

前言 redis效能為什麼這麼出色?它與其他快取中介軟體有什麼區別? redis底層使用了哪些資料結構支撐它如此高效的效能? 內部豐富的資料型別底層為什麼都使用至少兩種資料結構實現?分別是什麼? 如果合理的使用redis才能發揮它最大的優勢? 學習完《redis設計與實現》前面關於資

《redis設計實現》2-資料庫實現

上一篇文章介紹了redis基本的資料結構和物件《redis設計與實現》1-資料結構與物件篇 本文主要關於: redis資料庫實現的介紹 前面介紹的各種資料,在redis伺服器中的記憶體模型是什麼樣的的。 RDB檔案將這些記憶體資料持久化後的格式是什麼樣的 RDB和AOF序列化的區別是什麼

Android Bander設計實現

關鍵詞 Binder Android IPC Linux 核心 驅動 摘要 Binder是Android系統程序間通訊(IPC)方式之一。Linux已經擁有管道,system V IPC,socket等IPC手段,卻還要倚賴Binder來實現程序間通訊,說明Binde

Android:答題APP的設計實現(mysql+jsp+Android

還沒有整理完,待續…… 學校開了Android課,最後讓交一個大作業。正好拿來練練手,記錄下思路。也希望能給有需要的朋友們一些幫助。恩,純小白教程,大神們可以繞路了。 作業的題目是這樣的: 考試APP系統: 1)要求有使用者登陸功能:從遠端伺服器進行登

第一個Android專案--簡易計算器的設計實現

           這個簡易計算器的實現我是參照慕課網上的視訊課程學習的,下面梳理我的開發過程以及DEBUG         在這個專案中實現計算器的第一步驟是對介面UI的設計,UI的設計並不難,一個總的Lnearlayout的佈局下orientation設定為verti

基於android的小學生心口算APP設計實現

**基於android的小學生心口算APP設計與實現** 基於android的小學生心口算APP設計與實現mysql資料庫建立語句 基於android的小學生心口算APP設計與實現oracle資料庫建立語句 基於android的小學生心口算APP設計與實現sqlserve

android 超炫的懸浮窗設計實現

現在很多軟體都有懸浮框功能,比如Facebook,體驗效果極佳。 其他不說,直接步入正題看看具體的實現吧。 一、效果圖: 二、實現知識點: 1.     WindowManager addView(View view, LayoutParams params)

基於Android手機平臺的“晨起”app設計實現

**基於Android手機平臺的“晨起”app設計與實現** 基於Android手機平臺的“晨起”app設計與實現mysql資料庫建立語句 基於Android手機平臺的“晨起”app設計與實現oracle資料庫建立語句 基於Android手機平臺的“晨起”app設計與實現

Android設計實現(卷1)pdf

下載地址:網盤下載《android的設計與實現:卷i》是android應用開發工程師和android系統工程師進階修煉的必讀之作。它由資深android核心專家親自執筆,從原始碼角度,系統、深入、透徹剖析android系統框架層(framework)的設計思想和實現原理,為a

【大型網站技術實踐】初級:海量圖片的分散式儲存設計實現

一、研究背景:效能與資金,二者可兼得乎? 1.1 那麼問題來了?   隨著網際網路的發展,許多大中型的網站都儲存了大量的圖片資源,使用者在訪問這些圖片資源異常豐富的網站(如淘寶、京東等電子商務網站)時,網頁中的圖片資訊佔據了頁面資料流量的很大部分,那麼問題也來了:  

Android開發之檔案記事本設計實現

(一)實驗型別:設計性(二)實驗目的:1.掌握檔案讀寫的方法。2.熟悉XML檔案的佈局設定,掌握利用DDMS檢視AVD檔案目錄的方法。(三)實驗內容與要求: 1.自己設定頁面佈局,當在頁面輸入“檔名”和“內容”後,單擊“儲存”按鈕可以實現將內容儲存到檔名所在的檔案中。2.通過

Android路由框架設計實現

http://www.sixwolf.net/blog/2016/03/23/Android%E8%B7%AF%E7%94%B1%E6%A1%86%E6%9E%B6%E8%AE%BE%E8%AE%A1/

jQuery架構設計實現(2.1.4版本)

需要 引入 hasclass 8.4 uri and hub 組織 移除 市面上的jQuery書太多了,良莠不齊,看了那麽多總覺得少點什麽 對"幹貨",我不喜歡就事論事的寫代碼,我想把自己所學的知識點,代碼技巧,設計思想,代碼模式能很好的表達出來,所以考慮通過分析jQuer

畢業設計-證券宣傳手機微網站的設計實現

信息 browser .com 接受 熱點 互聯網 計算機網絡 業務 結構 本文介紹基於.net的證券公司宣傳微網站手機網頁的設計與實現方法。 隨著計算機技術的快速發展,基於Web的計算機網絡金融、證券宣傳或交易網站已成為現代金融理財發展的熱點,B/S(Browser/Se