1.前情概覽
我們在前片部落格中講述了 proxy - stub 架構的一般程式設計範式,這篇文章關注驅動自身的資料傳輸,做一次完整的資料分析。由于IPC通信流程比較複雜,我們先開啟上帝視角,将一些資料結構和資料流向直接闡述出來,然後再結合源碼調用流檢視是否具體是這樣的。
2.資料結構簡述
2.1messageParcel
messageParcel 使用者态資料,可以寫入一般類型,也可以寫入iremoteObject。為什麼要區分這兩種類型,這裡先賣一個關子。
class MessageParcel : public Parcel {
public:
MessageParcel();
~MessageParcel();
explicit MessageParcel(Allocator *allocator);
bool WriteRemoteObject(const sptr<IRemoteObject> &object);
sptr<IRemoteObject> ReadRemoteObject();
bool WriteFileDescriptor(int fd);
int ReadFileDescriptor();
bool ContainFileDescriptors() const;
bool WriteInterfaceToken(std::u16string name);
std::u16string ReadInterfaceToken();
bool WriteRawData(const void *data, size_t size);
const void *ReadRawData(size_t size);
bool RestoreRawData(std::shared_ptr<char> rawData, size_t size);
const void *GetRawData() const;
size_t GetRawDataSize() const;
size_t GetRawDataCapacity() const;
void WriteNoException();
int32_t ReadException();
bool WriteAshmem(sptr<Ashmem> ashmem);
sptr<Ashmem> ReadAshmem();
void ClearFileDescriptor();
void SetClearFdFlag()
{
needCloseFd_ = true;
};
private:
#ifndef CONFIG_IPC_SINGLE
bool WriteDBinderProxy(const sptr<IRemoteObject> &object, uint32_t handle, uint64_t stubIndex);
#endif
static constexpr size_t MAX_RAWDATA_SIZE = 128 * 1024 * 1024; // 128M
static constexpr size_t MIN_RAWDATA_SIZE = 32 * 1024; // 32k
bool needCloseFd_ = false;
std::vector<sptr<Parcelable>> holders_;
int writeRawDataFd_;
int readRawDataFd_;
void *kernelMappedWrite_;
void *kernelMappedRead_;
std::shared_ptr<char> rawData_;
size_t rawDataSize_;
};
2.2 flat_binder_object
這是核心描述iremoteObject對象資訊的結構體。當調用 object->Marshalling(*this)的時候會将messageParcel 轉化為flat_binder_object。同理還有一個UnMarshalling方法
struct flat_binder_object {
unsigned long type; // binder類型:可以為BINDER_TYPE_BINDER或BINDER_TYPE_HANDLE等類型
unsigned long flags; // 标記
union {
void *binder; // 當type=BINDER_TYPE_BINDER時,它指向Binder對象位于C++層的本地Binder對象的弱引用。
signed long handle; // 當type=BINDER_TYPE_HANDLE時,它等于Binder對象在Binder驅動中對應的Binder實體的Binder引用的描述。
};
void *cookie; // 當type=BINDER_TYPE_BINDER時才有效,它指向Binder對象位于C++層的本地Binder對象。
};
2.3 binder_transaction_data
這個資料不僅包含了flat_binder_object中的純資料,也包含的cmdID以及發送者的其他資訊。
struct binder_transaction_data {
union {
size_t handle; // 當binder_transaction_data是由使用者空間的程序發送給Binder驅動時,
// handle是該事務的發送目标在Binder驅動中的資訊,即該事務會交給handle來處理;
// handle的值是目标在Binder驅動中的Binder引用。
void *ptr; // 當binder_transaction_data是有Binder驅動回報給使用者空間程序時,
// ptr是該事務的發送目标在使用者空間中的資訊,即該事務會交給ptr對應的服務來處理;
// ptr是處理該事務的服務的服務在使用者空間的本地Binder對象。
} target; // 該事務的目标對象(即,該事務資料包是給該target來處理的)
void *cookie; // 隻有當事務是由Binder驅動傳遞給使用者空間時,cookie才有意思,它的值是處理該事務的Server位于C++層的本地Binder對象
unsigned int code; // 事務編碼。如果是請求,則以BC_開頭;如果是回複,則以BR_開頭。
unsigned int flags;
pid_t sender_pid;
uid_t sender_euid;
size_t data_size; // 資料大小
size_t offsets_size; // 資料中包含的對象的個數
union {
struct {
const void *buffer;
const void *offsets;
} ptr;
uint8_t buf[8];
} data; // 資料
};
2.4 binder_write_read
驅動真正讀寫的資料,也就是binder_transaction_data 會轉化為binder_write_read交給驅動真正的讀寫。
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;
};
2.5 binder_ref
binder_ref是描述Binder引用的結構體。
struct binder_ref {
int debug_id;
struct rb_node rb_node_desc; // 關聯到binder_proc->refs_by_desc紅黑樹中
struct rb_node rb_node_node; // 關聯到binder_proc->refs_by_node紅黑樹中
struct hlist_node node_entry; // 關聯到binder_node->refs哈希表中
struct binder_proc *proc; // 該Binder引用所屬的Binder程序
struct binder_node *node; // 該Binder引用對應的Binder實體
uint32_t desc; // 描述
int strong;
int weak;
struct binder_ref_death *death;
};
2.6 binder_proc
struct binder_proc {
struct hlist_node proc_node; // 根據proc_node,可以擷取該程序在"全局哈希表binder_procs(統計了所有的binder proc程序)"中的位置
struct rb_root threads; // binder_proc程序内用于處理使用者請求的線程組成的紅黑樹(關聯binder_thread->rb_node)
struct rb_root nodes; // binder_proc程序内的binder實體組成的紅黑樹(關聯binder_node->rb_node)
struct rb_root refs_by_desc; // binder_proc程序内的binder引用組成的紅黑樹,該引用以句柄來排序(關聯binder_ref->rb_node_desc)
struct rb_root refs_by_node; // binder_proc程序内的binder引用組成的紅黑樹,該引用以它對應的binder實體的位址來排序(關聯binder_ref->rb_node)
int pid; // 程序id
struct vm_area_struct *vma; // 程序的核心虛拟記憶體
struct mm_struct *vma_vm_mm;
struct task_struct *tsk; // 程序控制結構體(每一個程序都由task_struct 資料結構來定義)。
struct files_struct *files; // 儲存了程序打開的所有檔案表資料
struct hlist_node deferred_work_node;
int deferred_work;
void *buffer; // 該程序映射的實體記憶體在核心空間中的起始位置
ptrdiff_t user_buffer_offset; // 核心虛拟位址與程序虛拟位址之間的內插補點
// 記憶體管理的相關變量
struct list_head buffers; // 和binder_buffer->entry關聯到同一連結清單,進而對Binder記憶體進行管理
struct rb_root free_buffers; // 空閑記憶體,和binder_buffer->rb_node關聯。
struct rb_root allocated_buffers; // 已配置設定記憶體,和binder_buffer->rb_node關聯。
size_t free_async_space;
struct page **pages; // 映射記憶體的page頁數組,page是描述實體記憶體的結構體
size_t buffer_size; // 映射記憶體的大小
uint32_t buffer_free;
struct list_head todo; // 該程序的待處理事件隊列。
wait_queue_head_t wait; // 等待隊列。
struct binder_stats stats;
struct list_head delivered_death;
int max_threads; // 最大線程數。定義threads中可包含的最大程序數。
int requested_threads;
int requested_threads_started;
int ready_threads;
long default_priority; // 預設優先級。
struct dentry *debugfs_entry;
};
2.7 binder_node
binder_node是描述Binder實體的結構體。
struct binder_node {
int debug_id;
struct binder_work work;
union {
struct rb_node rb_node; // 如果這個Binder實體還在使用,則将該節點連結到proc->nodes中。
struct hlist_node dead_node; // 如果這個Binder實體所屬的程序已經銷毀,而這個Binder實體又被其它程序所引用,則這個Binder實體通過dead_node進入到一個哈希表中去存放
};
struct binder_proc *proc; // 該binder實體所屬的Binder程序
struct hlist_head refs; // 該Binder實體的所有Binder引用所組成的連結清單
int internal_strong_refs;
int local_weak_refs;
int local_strong_refs;
void __user *ptr; // Binder實體在使用者空間的位址(為Binder實體對應的Server在使用者空間的本地Binder的引用)
void __user *cookie; // Binder實體在使用者空間的其他資料(為Binder實體對應的Server在使用者空間的本地Binder自身)
unsigned has_strong_ref:1;
unsigned pending_strong_ref:1;
unsigned has_weak_ref:1;
unsigned pending_weak_ref:1;
unsigned has_async_transaction:1;
unsigned accept_fds:1;
unsigned min_priority:8;
struct list_head async_todo;
};
3.情景分析
3.1 資料流向
其實結合上文,一次的資料發送。其實就是 messageParcel ->flat_binder_object -> binder_transaction_data -> binder_write_read。上文中我們說到普通資料和iremoteObjiect有差別,那差別是啥,就是發送的資料裡面包含iremoteObJiect,驅動程式就會在全局哈希表binder_procs中注冊一個binder_proc。那普通資料和iremote資料是如何區分呢?答案是他有兩個指針一個指向普通的資料,另一個指向iremoteObject。也就是binder_transaction_data的buffer指針和offsets指針。那binder驅動在這個資料發送的過程中做了什麼呢?
3.2 驅動事件循環
binder_ref-> binder_proc->binder_node
對于發送者來說,要知道發送的資料需要傳輸給誰,他自身持有一個服務程序的binder_ref,通過binder_ref找到對應的binder_proc,然後通過binder_proc找到對應的binder_node(一個程序可以有多個binder_node,即可以有多個服務)。那binder_write_read中擷取的資料也就會添加到宿主程序binder_proc的todo連結清單中。
源碼調用圖解
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsQTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5SOycDN1YDZhFjM1EWMhZDNyYzX5IDN1QTMxAzLcRDMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjL0M3Lc9CX6MHc0RHaiojIsJye.png)
client和stub的互動過程就是 client - > stub ->client 這樣看起來就像自身程序調用自己的方法一樣。上圖中ipcObjectStub -> OnRemoteRequest()就會向binder驅動放松資料,這也是為什麼繼承的子類需要重寫OnRemoteRequest方法。
這裡就是大概ipc調用的過程,對驅動自身的闡述比較淺薄,驅動中的多線程模型還有自身資料結構的複雜性并沒有闡述。如果有機會研究後,再講講dsoftbus驅動和binder驅動的不同和相似之處,講講iremoteObJect是如何适配這兩種不同驅動模型的。
想了解更多關于鴻蒙的内容,請通路:
51CTO和華為官方合作共建的鴻蒙技術社群
https://ost.51cto.com/#bkwz