Binder系列6—獲取服務(getService)
一、 獲取服務
在Native層的服務註冊,我們選擇以media為例來展開講解,先來看看media的類關係圖。
1.1 類圖
點選檢視大圖
圖解:
- 藍色: 代表獲取MediaPlayerService服務相關的類;
- 綠色: 代表Binder架構中與Binder驅動通訊過程中的最為核心的兩個類;
- 紫色: 代表註冊服務和獲取服務的公共介面/父類;
二. 獲取Media服務
2.1 getMediaPlayerService
[-> framework/av/media/libmedia/IMediaDeathNotifier.cpp]
sp<IMediaPlayerService>&
IMediaDeathNotifier::getMediaPlayerService()
{
Mutex::Autolock _l(sServiceLock);
if (sMediaPlayerService == 0) {
sp<IServiceManager> sm = defaultServiceManager(); //獲取ServiceManager
sp<IBinder> binder;
do {
//獲取名為"media.player"的服務 【見2.2】
binder = sm->getService(String16("media.player"));
if (binder != 0) {
break ;
}
usleep(500000); // 0.5s
} while (true);
if (sDeathNotifier == NULL) {
sDeathNotifier = new DeathNotifier(); //建立死亡通知物件
}
//將死亡通知連線到binder 【見流程14】
binder->linkToDeath(sDeathNotifier);
sMediaPlayerService = interface_cast<IMediaPlayerService>(binder);
}
return sMediaPlayerService;
}
其中defaultServiceManager()過程在上一篇文章獲取ServiceManager已講過,返回BpServiceManager。
在請求獲取名為”media.player”的服務過程中,採用不斷迴圈獲取的方法。由於MediaPlayerService服務可能還沒向ServiceManager註冊完成或者尚未啟動完成等情況,故則binder返回為NULL,休眠0.5s後繼續請求,直到獲取服務為止。
2.2 BpSM.getService
[-> IServiceManager.cpp ::BpServiceManager]
virtual sp<IBinder> getService(const String16& name) const
{
unsigned n;
for (n = 0; n < 5; n++){
sp<IBinder> svc = checkService(name); //【見2.3】
if (svc != NULL) return svc;
sleep(1);
}
return NULL;
}
通過BpServiceManager來獲取MediaPlayer服務:檢索服務是否存在,當服務存在則返回相應的服務,當服務不存在則休眠1s再繼續檢索服務。該迴圈進行5次。為什麼是迴圈5次呢,這估計跟Android的ANR時間為5s相關。如果每次都無法獲取服務,迴圈5次,每次迴圈休眠1s,忽略checkService()
的時間,差不多就是5s的時間
2.3 BpSM.checkService
[-> IServiceManager.cpp ::BpServiceManager]
virtual sp<IBinder> checkService( const String16& name) const
{
Parcel data, reply;
//寫入RPC頭
data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
//寫入服務名
data.writeString16(name);
remote()->transact(CHECK_SERVICE_TRANSACTION, data, &reply); //【見2.4】
return reply.readStrongBinder(); //【見小節2.9】
}
檢索指定服務是否存在, 其中remote()為BpBinder。
2.4 BpBinder::transact
[-> BpBinder.cpp]
status_t BpBinder::transact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
if (mAlive) {
//【見流程2.5】
status_t status = IPCThreadState::self()->transact(
mHandle, code, data, reply, flags);
if (status == DEAD_OBJECT) mAlive = 0;
return status;
}
return DEAD_OBJECT;
}
Binder代理類呼叫transact()方法,真正工作還是交給IPCThreadState來進行transact工作,
2.4.1 IPCThreadState::self
[-> IPCThreadState.cpp]
IPCThreadState* IPCThreadState::self()
{
if (gHaveTLS) {
restart:
const pthread_key_t k = gTLS;
IPCThreadState* st = (IPCThreadState*)pthread_getspecific(k);
if (st) return st;
return new IPCThreadState; //初始IPCThreadState 【見小節2.4.2】
}
if (gShutdown) return NULL;
pthread_mutex_lock(&gTLSMutex);
if (!gHaveTLS) { //首次進入gHaveTLS為false
if (pthread_key_create(&gTLS, threadDestructor) != 0) { //建立執行緒的TLS
pthread_mutex_unlock(&gTLSMutex);
return NULL;
}
gHaveTLS = true;
}
pthread_mutex_unlock(&gTLSMutex);
goto restart;
}
TLS是指Thread local storage(執行緒本地儲存空間),每個執行緒都擁有自己的TLS,並且是私有空間,執行緒之間不會共享。通過pthread_getspecific/pthread_setspecific函式可以獲取/設定這些空間中的內容。從執行緒本地儲存空間中獲得儲存在其中的IPCThreadState物件。
2.4.2 IPCThreadState初始化
[-> IPCThreadState.cpp]
IPCThreadState::IPCThreadState()
: mProcess(ProcessState::self()),
mMyThreadId(gettid()),
mStrictModePolicy(0),
mLastTransactionBinderFlags(0)
{
pthread_setspecific(gTLS, this);
clearCaller();
mIn.setDataCapacity(256);
mOut.setDataCapacity(256);
}
每個執行緒都有一個IPCThreadState
,每個IPCThreadState
中都有一個mIn、一個mOut。成員變數mProcess儲存了ProcessState變數(每個程序只有一個)。
- mIn 用來接收來自Binder裝置的資料,預設大小為256位元組;
- mOut用來儲存發往Binder裝置的資料,預設大小為256位元組。
2.5 IPC::transact
[-> IPCThreadState.cpp]
status_t IPCThreadState::transact(int32_t handle,
uint32_t code, const Parcel& data,
Parcel* reply, uint32_t flags)
{
status_t err = data.errorCheck(); //資料錯誤檢查
flags |= TF_ACCEPT_FDS;
....
if (err == NO_ERROR) {
// 傳輸資料 【見流程2.6】
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) { //flags=0進入該分支
if (reply) {
//等待響應 【見流程2.7】
err = waitForResponse(reply);
} else {
Parcel fakeReply;
err = waitForResponse(&fakeReply);
}
} else {
//不需要響應訊息的binder則進入該分支
err = waitForResponse(NULL, NULL);
}
return err;
}
2.6 IPC.writeTransactionData
[-> IPCThreadState.cpp]
status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags,
int32_t handle, uint32_t code, const Parcel& data, status_t* statusBuffer)
{
binder_transaction_data tr;
tr.target.ptr = 0;
tr.target.handle = handle; // handle = 0
tr.code = code; // code = CHECK_SERVICE_TRANSACTION
tr.flags = binderFlags; // binderFlags = 0
tr.cookie = 0;
tr.sender_pid = 0;
tr.sender_euid = 0;
// data為記錄Media服務資訊的Parcel物件
const status_t err = data.errorCheck();
if (err == NO_ERROR) {
tr.data_size = data.ipcDataSize(); // mDataSize
tr.data.ptr.buffer = data.ipcData(); //mData
tr.offsets_size = data.ipcObjectsCount()*sizeof(binder_size_t); //mObjectsSize
tr.data.ptr.offsets = data.ipcObjects(); //mObjects
} else if (statusBuffer) {
...
} else {
return (mLastError = err);
}
mOut.writeInt32(cmd); //cmd = BC_TRANSACTION
mOut.write(&tr, sizeof(tr)); //寫入binder_transaction_data資料
return NO_ERROR;
}
其中handle的值用來標識目的端,註冊服務過程的目的端為service manager,此處handle=0所對應的是binder_context_mgr_node物件,正是service manager所對應的binder實體物件。binder_transaction_data結構體是binder驅動通訊的資料結構,該過程最終是把Binder請求碼BC_TRANSACTION和binder_transaction_data結構體寫入到mOut
。
2.7 IPC.waitForResponse
[-> IPCThreadState.cpp]
status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
{
int32_t cmd;
int32_t err;
while (1) {
if ((err=talkWithDriver()) < NO_ERROR) break; // 【見流程2.8】
err = mIn.errorCheck();
if (err < NO_ERROR) break;
if (mIn.dataAvail() == 0) continue;
cmd = mIn.readInt32();
switch (cmd) {
case BR_TRANSACTION_COMPLETE: ...
case BR_DEAD_REPLY: ...
case BR_FAILED_REPLY: ...
case BR_ACQUIRE_RESULT: ...
case BR_REPLY:
{
binder_transaction_data tr;
err = mIn.read(&tr, sizeof(tr));
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 {
...
}
}
}
goto finish;
default:
err = executeCommand(cmd);
if (err != NO_ERROR) goto finish;
break;
}
}
...
return err;
}
2.8 IPC.talkWithDriver
[-> IPCThreadState.cpp]
status_t IPCThreadState::talkWithDriver(bool doReceive)
{
...
binder_write_read bwr;
const bool needRead = mIn.dataPosition() >= mIn.dataSize();
const size_t outAvail = (!doReceive || needRead) ? mOut.dataSize() : 0;
bwr.write_size = outAvail;
bwr.write_buffer = (uintptr_t)mOut.data();
if (doReceive && needRead) {
//接收資料緩衝區資訊的填充。如果以後收到資料,就直接填在mIn中了。
bwr.read_size = mIn.dataCapacity();
bwr.read_buffer = (uintptr_t)mIn.data();
} else {
bwr.read_size = 0;
bwr.read_buffer = 0;
}
//當讀緩衝和寫緩衝都為空,則直接返回
if ((bwr.write_size == 0) && (bwr.read_size == 0)) return NO_ERROR;
bwr.write_consumed = 0;
bwr.read_consumed = 0;
status_t err;
do {
//通過ioctl不停的讀寫操作,跟Binder Driver進行通訊【2.8.1】
if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
err = NO_ERROR;
...
} while (err == -EINTR); //當被中斷,則繼續執行
...
return err;
}
binder_write_read結構體用來與Binder裝置交換資料的結構, 通過ioctl與mDriverFD通訊,是真正與Binder驅動進行資料讀寫互動的過程。 先向service manager程序傳送查詢服務的請求(BR_TRANSACTION),見Binder系列3—啟動ServiceManager。當service manager程序收到該命令後,會執行do_find_service() 查詢服務所對應的handle,然後再binder_send_reply()應答 發起者,傳送BC_REPLY協議,然後呼叫binder_transaction(),再向服務請求者的Todo佇列 插入事務。
接下來,再看看binder_transaction過程。
2.8.1 binder_transaction
static void binder_transaction(struct binder_proc *proc,
struct binder_thread *thread,
struct binder_transaction_data *tr, int reply){
//根據各種判定,獲取以下資訊:
struct binder_thread *target_thread; //目標執行緒
struct binder_proc *target_proc; //目標程序
struct binder_node *target_node; //目標binder節點
struct list_head *target_list; //目標TODO佇列
wait_queue_head_t *target_wait; //目標等待佇列
...
//分配兩個結構體記憶體
struct binder_transaction *t = kzalloc(sizeof(*t), GFP_KERNEL);
struct binder_work *tcomplete = kzalloc(sizeof(*tcomplete), GFP_KERNEL);
//從target_proc分配一塊buffer
t->buffer = binder_alloc_buf(target_proc, tr->data_size,
for (; offp < off_end; offp++) {
switch (fp->type) {
case BINDER_TYPE_BINDER: ...
case BINDER_TYPE_WEAK_BINDER: ...
case BINDER_TYPE_HANDLE:
case BINDER_TYPE_WEAK_HANDLE: {
struct binder_ref *ref = binder_get_ref(proc, fp->handle,
fp->type == BINDER_TYPE_HANDLE);
...
//此時執行在servicemanager程序,故ref->node是指向服務所在程序的binder實體,
//而target_proc為請求服務所在的程序,此時並不相等。
if (ref->node->proc == target_proc) {
if (fp->type == BINDER_TYPE_HANDLE)
fp->type = BINDER_TYPE_BINDER;
else
fp->type = BINDER_TYPE_WEAK_BINDER;
fp->binder = ref->node->ptr;
fp->cookie = ref->node->cookie; //BBinder服務的地址
binder_inc_node(ref->node, fp->type == BINDER_TYPE_BINDER, 0, NULL);
} else {
struct binder_ref *new_ref;
//請求服務所在程序並非服務所在程序,則為請求服務所在程序建立binder_ref
new_ref = binder_get_ref_for_node(target_proc, ref->node);
fp->binder = 0;
fp->handle = new_ref->desc; //重新賦予handle值
fp->cookie = 0;
binder_inc_ref(new_ref, fp->type == BINDER_TYPE_HANDLE, NULL);
}
} break;
case BINDER_TYPE_FD: ...
}
}
//分別target_list和當前執行緒TODO佇列插入事務
t->work.type = BINDER_WORK_TRANSACTION;
list_add_tail(&t->work.entry, target_list);
tcomplete->type = BINDER_WORK_TRANSACTION_COMPLETE;
list_add_tail(&tcomplete->entry, &thread->todo);
if (target_wait)
wake_up_interruptible(target_wait);
return;
}
這個過程非常重要,分兩種情況來說:
- 當請求服務的程序與服務屬於不同程序,則為請求服務所在程序建立binder_ref物件,指向服務程序中的binder_node;
- 當請求服務的程序與服務屬於同一程序,則不再建立新物件,只是引用計數加1,並且修改type為BINDER_TYPE_BINDER或BINDER_TYPE_WEAK_BINDER。
2.8.2 binder_thread_read
binder_thread_read(...){
...
//當執行緒todo佇列有資料則執行往下執行;當執行緒todo佇列沒有資料,則進入休眠等待狀態
ret = wait_event_freezable(thread->wait, binder_has_thread_work(thread));
...
while (1) {
uint32_t cmd;
struct binder_transaction_data tr;
struct binder_work *w;
struct binder_transaction *t = NULL;
//先從執行緒todo佇列獲取事務資料
if (!list_empty(&thread->todo)) {
w = list_first_entry(&thread->todo, struct binder_work, entry);
// 執行緒todo佇列沒有資料, 則從程序todo對獲取事務資料
} else if (!list_empty(&proc->todo) && wait_for_proc_work) {
...
}
switch (w->type) {
case BINDER_WORK_TRANSACTION:
//獲取transaction資料
t = container_of(w, struct binder_transaction, work);
break;
case : ...
}
//只有BINDER_WORK_TRANSACTION命令才能繼續往下執行
if (!t) continue;
if (t->buffer->target_node) {
...
} else {
tr.target.ptr = NULL;
tr.cookie = NULL;
cmd = BR_REPLY; //設定命令為BR_REPLY
}
tr.code = t->code;
tr.flags = t->flags;
tr.sender_euid = t->sender_euid;
if (t->from) {
struct task_struct *sender = t->from->proc->tsk;
//當非oneway的情況下,將呼叫者程序的pid儲存到sender_pid
tr.sender_pid = task_tgid_nr_ns(sender, current->nsproxy->pid_ns);
} else {
...
}
tr.data_size = t->buffer->data_size;
tr.offsets_size = t->buffer->offsets_size;
tr.data.ptr.buffer = (void *)t->buffer->data +
proc->user_buffer_offset;
tr.data.ptr.offsets = tr.data.ptr.buffer +
ALIGN(t->buffer->data_size,
sizeof(void *));
//將cmd和資料寫回使用者空間
put_user(cmd, (uint32_t __user *)ptr);
ptr += sizeof(uint32_t);
copy_to_user(ptr, &tr, sizeof(tr));
ptr += sizeof(tr);
list_del(&t->work.entry);
t->buffer->allow_user_free = 1;
if (cmd == BR_TRANSACTION && !(t->flags & TF_ONE_WAY)) {
...
} else {
t->buffer->transaction = NULL;
kfree(t); //通訊完成則執行釋放
}
break;
}
done:
*consumed = ptr - buffer;
if (proc->requested_threads + proc->ready_threads == 0 &&
proc->requested_threads_started < proc->max_threads &&
(thread->looper & (BINDER_LOOPER_STATE_REGISTERED |
BINDER_LOOPER_STATE_ENTERED))) {
proc->requested_threads++;
// 生成BR_SPAWN_LOOPER命令,用於建立新的執行緒
put_user(BR_SPAWN_LOOPER, (uint32_t __user *)buffer);
}
return 0;
}
2.9 readStrongBinder
[-> Parcel.cpp]
sp<IBinder> Parcel::readStrongBinder() const
{
sp<IBinder> val;
//【見小節2.9.1】
unflatten_binder(ProcessState::self(), *this, &val);
return val;
}
2.9.1 unflatten_binder
[-> Parcel.cpp]
相關推薦
Binder系列6—獲取服務(getService)
一、 獲取服務在Native層的服務註冊,我們選擇以media為例來展開講解,先來看看media的類關係圖。1.1 類圖點選檢視大圖圖解:藍色: 代表獲取MediaPlayerService服務相關的類;綠色: 代表Binder架構中與Binder驅動通訊過程中的最為核心的兩
Docker Swarm系列——6.Swarm服務面板
在這篇文章中,大家將會了解如何通過docker CLI命令或docker-compose.yml檔案的形式部署一個簡單的網頁視覺化面板,不用登陸每臺伺服器就可以方便地檢視整個Swarm叢集、服務、容器等的執行情況。 1. 建立服務 通過前面幾篇文章地介紹,我們
Binder系列5—註冊服務(addService)
framework/native/libs/binder/ - Binder.cpp - BpBinder.cpp - IPCThreadState.cpp - ProcessState.cpp - IServiceManager.cpp - IInt
C#開發BIMFACE系列6 服務端API之獲取檔案資訊
在《C#開發BIMFACE系列4 服務端API之源上傳檔案》、《C#開發BIMFACE系列5 服務端API之檔案直傳》兩篇文章中詳細介紹瞭如何將本地檔案上傳到BIMFACE伺服器及BIMFACE後臺的分散式儲存系統中。檔案上傳成功後,BIMFACE的服務會返回與該檔案相關的資訊,如下圖: 開
Azure手把手系列6:存儲服務介紹
雲計算 雲平臺 在使用Azure的過程中,在大多數情況下我們都會使用到存儲服務,對於虛擬機來說就是我們的磁盤存儲。Azure對於存儲來說是劃分的非常全面和細致的,在使用各種存儲服務之前我們需要創建存儲帳戶,然後即可將數據傳入/傳出該存儲帳戶中的特定服務。 ? ?首先,我們來看下Azure提供了什麽類型
系列6-springCloud微服務-config配置中心
config配置中心分為服務端和客戶端,服務端根據檔案儲存位置分為三種設定方式:git\githupSVN本地儲存其中git\githup,SVN的設定方式基本一樣。此處僅記錄git的設定,githup與git的設定完全一樣。1.config Server基於git步驟1:本
Oracle Apex 有用筆記系列 6 - 可編輯交互報告 Editable Interactive Report
gin where 表格 報告 查詢 查詢語句 item 提醒 lis 據筆者所知。Apex 4.x 是沒有提供可編輯交互報告組件的。這就須要我們手動實現。事實上這也並非非常復雜,僅僅須要簡單幾步。 1. 依據向導建立一個interactive report。查詢語句能
問題解決系列: 後臺服務流量控制- 控制訪問別的服務的速度
發送 template 個人 exce 保護 rms 這一 ole 每分鐘 互聯網的後臺提倡大系統小做,微服務化。所以後臺服務之間相互依賴,我依賴別人的,別人也依賴我的,這很正常。但是後臺服務講穩定性。只有一切可控,才能談穩定性。為了不沖垮下遊的服務,我們有兩種做法:一種是
Redis系列--6、Redis Java連接操作
redis java連接操作安裝要在Java程序中使用使用操作Redis,需要確保有Redis的Java驅動程序和Java設置在機器上。可以檢查看Java教程-學習如何在機器上安裝Java。現在,讓我們來看看如何設置Redis的Java驅動程序。需要下載jedis.jar。請一定要下載它的最新版本。需要包括j
PHP獲取服務器環境信息
虛擬主機 6.2 執行 主機名 true 字符 fastcgi port 服務器環境 PHP獲取服務器環境信息 PHP的php_uname() 函數 和$_SERVER(服務器和執行環境信息) echo ‘服務器版本和虛擬主機名的字符串‘.$_SERVER[‘SERVER
獲取服務器時間ajax
min ava ise 獲取 years subst log esp hour $.ajax({ type:"OPTIONS", url:"/", complete:function(x){ // alert(x.getResponseHea
php獲取服務器信息常用方法(零碎知識記憶)
附件 sof 常用方法 exe 上傳 系統 文檔 file 信息 突然整理下零碎小知識.......加深下印象: $info = array( ‘操作系統‘=>PHP_OS, ‘運行環境‘=>$_SERVER["
獲取服務器IP,客戶端IP
進程 獲取 () base accep roc eight 可能 php_sapi 客戶端IP相關的變量 1. $_SERVER[‘REMOTE_ADDR‘]; 客戶端IP,有可能是用戶的IP,也有可能是代理的IP。 2. $_SERVER[‘HTTP_CLIENT_IP
20170825L08-05老男孩linux實戰運維培訓-Lamp系列之-Apache服務生產實戰應用指南02
apache這一節說Apache的安裝目錄文件具體介紹了一些重要文件的配置tree -L 1 /usr/local/apache[[email protected] extra]# tree -L 1 /usr/local/apache/usr/local/apache├── apache ->
20170830L08-06老男孩linux實戰運維培訓-Lamp系列之-Apache服務生產實戰應用指南03
apache還是說的apache的設置這一次說的是虛擬主機主要配置文件httpd.confhttpd-vhhsots.confhttpd.conf主要控制目錄的訪問httpd-vhosts.conf控制域名的轉換,要別名,日誌的路徑對於實驗中的訪問主機中要設置 hosts文件<Directory "/v
6.FTP服務
ftp1、概述(1)FTP(File transfer protocol)文件傳輸協議常見的FTPLinux:VSFTP(Very Secure FTP)、ProFTPD(Daemon 守護進程)Windows:Serv-U(2)特點:安全、高速、穩定(3)端口21:傳指令20:傳數據2、服務安裝(1)服務端
SpringCloud系列研究---Eureka服務發現
url next register default spring one static demo auto :創建項目工程 新建project 這裏選擇gradle 直接next 繼續next 最後點擊finish 二:創建Eureka服務中心 選擇第一步中創
Centos 6.5 服務器下面配置郵件客戶端 發送報警郵件
postfix crontab 郵件報警 監控以Centos 系統為例,確保服務器可以正常連接外網Centos 6.5 下面默認 安裝 postfix查看rpm -qa | grep postfixpostfix-2.6.6-2.2.el6_1.x86_64配置郵件客戶端set [email pr
獲取服務端https證書
certificate最近開發一個需求,涉及獲取服務端https證書。一般進行https調用我們都不太關心底層細節,直接使用WebClient或者HttpWebRequest來發送請求,這兩種方法都無法獲取證書信息,需要用到ServicePoint,這個類用於提供HTTP連接的管理。寫個Demo,拿新浪首頁試
Openstack 安裝部署指南翻譯系列 之 Horizon服務安裝(Dashboad)
openstack 翻譯 horizon安裝1.1.1.1. Horizon服務安裝(Dashboad)本節介紹如何在控制器節點上安裝和配置儀表板。儀表板所需的唯一核心服務是身份服務。您可以使用儀表板與其他服務(如鏡像服務,計算和網絡)結合使用。您還可以在具有獨立服務(如對象存儲)的環境中使用儀表板。註意:本