源码分析 ---------openHarmony IPC数据传输情景分析 原创 精华

拓维信息流沙客
发布于 2022-3-29 15:14
浏览
10收藏

源码分析 ---------openHarmony IPC数据传输情景分析

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)的时候将转化为flat_binder_object,然后写入到messageparcel对象中。同理还有一个UnMarshalling方法将parcel中的flat_binder_object读出来,然后转化为iremoteObject对象

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 
    uid_t       sender_euid;  // pid
    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链表中。

源码调用图解

源码分析 ---------openHarmony IPC数据传输情景分析-鸿蒙开发者社区中划线

client和stub的交互过程就是 client - > stub ->client 这样看起来就像自身进程调用自己的方法一样。上图中ipcObjectStub -> OnRemoteRequest()就会向binder驱动放松数据,这也是为什么继承的子类需要重写OnRemoteRequest方法。

这里就是大概ipc调用的过程,对驱动自身的阐述比较浅薄,驱动中的多线程模型还有自身数据结构的复杂性并没有阐述。如果有机会研究后,再讲讲dsoftbus驱动和binder驱动的不同和相似之处,讲讲iremoteObJect是如何适配这两种不同驱动模型的。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2023-4-7 16:34:14修改
12
收藏 10
回复
举报
4条回复
按时间正序
/
按时间倒序
红叶亦知秋
红叶亦知秋

赞,情景中各个环节讲的很清楚,结构体的注释也很详细,感谢分享

4
回复
2022-3-29 15:30:44
民之码农
民之码农

666

3
回复
2022-3-30 08:12:48
科技维度
科技维度

十分清晰,感谢老师分享

1
回复
2022-4-2 23:03:27
beyond阿亮
beyond阿亮

6666

回复
2022-11-3 13:59:00
回复
    相关推荐