1.Android 图解IPC机制. binder机制 (2024精华版)

目录

1. 一次拷贝流程, mmap内存映射

2. AIDL角度讲解4者的关系和binder机制

3. 从native层完全讲解binder机制

4. 代理模式和AIDL原理

5. 跨进程方式优缺点比较

1. 一次拷贝流程, mmap内存映射

1.1 普通跨进程二次拷贝的过程:

2次拷贝.jpg

数据从用户空间拷贝到内核空间:第一次拷贝:copy_from_user()

数据从内核空间拷贝到用户空间:第二拷贝:copy_to_user()

这种传统的 IPC 通信方式有两个问题:

  1. 性能低下,一次数据传递需要经历:内存缓存区 --> 内核缓存区 --> 内存缓存区,需要 2 次数据拷贝;
  2. 接收数据的缓存区由数据接收进程提供,但是接收进程并不知道需要多大的空间来存放将要传递过来的数据,

因此只能开辟尽可能大的内存空间或者先调用 API 接收消息头来获取消息体的大小,这两种做法不是浪费空间就是浪费时间。

1.2 一次拷贝的过程:

Binder IPC正是基于内存映射(mmap)来实现的

内存映射:Binder IPC 机制中涉及到的内存映射通过mmap() 来实现,mmap() 是操作系统中一种内存映射的方法。内存映射简单的讲就是将用户空间的一块内存区域映

射到内核空间。映射关系建立后,用户对这块内存区域的修改可以直接反应到内核空间;反之内核空间对这段区域的修改也能直接反应到用户空间。

一次拷贝.jpg

1). 首先Binder 驱动在内核空间创建一个数据接收缓存区;

2). 接着在内核空间开辟一块内核缓存区,建立内核缓存区和内核中数据接收

缓存区之间的映射关系,以及内核中数据接收缓存区和接收进程用户空间地址的映射关系;(数据缓存区-------->数据接收缓存区)

3). 发送方进程通过系统调用copyfromuser() 将数据copy 到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也

就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信

2. AIDL角度讲解4者的关系和binder机制

4者的关系.jpg

详细解读:ServiceManager、Binder Client、Binder Server 处于不同的进程,他们三个都在用户空间,而Binder 驱动在内核空间。

总结4者的关系 :

1). 注册到serverManager : server会把自己注册到serverManager(通过binder驱动):

2). 获取Server实体: Client 进程向ServiceManager 查询Server 进程(通过binder驱动)Binder 实体的引用

3). 服务端代理: Server进程中的真实对象转换成代理对象Proxy,返回这个代理对象给Client 进程

4). Client进程拿到了这个代理对象Proxy,然后调用这个代理对象的方法(通过binder驱动)

3. 从native层完全讲解binder机制

完整完全的图.jpg

1). servermanager注册, 成为 Binder 驱动管理者

2). servermanager进入循环等待其他service注册

3). 找到serverManager, 通过binder驱动 (在binder驱动中),通过handler值=0找到serverManager

4). server通过binder驱动注册到servermanager, 通过bp binder, 打开驱动, 得到了servermanager就可以添加 , 同时把handler, name, 写入binder驱动,

5). client请求aidl连接: 通过servermanager查询, 拿到服务代理, 返回给客户端

6). BinderProxy 将我们的请求参数发送给 ServiceManager, 通过共享内存的方式使用内核方法 copy_from_user() 将我们的参数先拷贝到内核空间,这时我们的客户端进入等待状态,

7).内存映射: 服务端拿到client的数据, 执行完之后把执行结果通过 copy_to_user() 将内核的结果拷贝到用户空间(这里只是执行了拷贝命令,并没有拷贝数据,binder只进行一次拷贝),唤醒等待的客户端并把结果响应回来

完整的图binder.jpg

4. 代理模式和AIDL原理

4.1 核心类

1). IInterface , IInterface 代表的就是 Server 进程对象具备什么样的能力

public interface IInterface
{
   public IBinder asBinder();
}

2) .IBinder, IBinder 是一个接口,代表了一种跨进程通信的能力。只要实现了这个借口,这个对象就能跨进程传输。

public class Binder implements IBinder {
在这里,如果链接成功返回回来的。
private val mConnection: ServiceConnection = object : ServiceConnection {
    override fun onServiceConnected(name: ComponentName, service: IBinder) {
        val imageManager: IImageManager = IImageManager.Stub.asInterface(service)

3).Binder Java 层的 Binder 类,代表的其实就是 Binder 本地对象

4).stub: 自动生成的, 而这个类最核心的成员是 Stub类 和 Stub的内部代理类Proxy。

这个类继承了 Binder,它实现了 IInterface 接口!

Stub充当服务端角色,持有Binder实体(本地对象)。远程进程执行onTransact()函数

  • 获取客户端传过来的数据,根据方法 ID 执行相应操作。
  • 将传过来的数据取出来,调用本地写好的对应方法。
  • 将需要回传的数据写入 reply 流,传回客户端。
4.2 具体的案例
public interface BookManager extends IInterface {

    List<Book> getBooks() throws RemoteException;

    void addBook(Book book) throws RemoteException;
}


public abstract class Stub extends Binder implements BookManager {

    private static final String DESCRIPTOR = "com.baronzhang.ipc.server.BookManager";

    public Stub() {
        this.attachInterface(this, DESCRIPTOR);
    }

    public static BookManager asInterface(IBinder binder) {
        if (binder == null)
            return null;
        IInterface iin = binder.queryLocalInterface(DESCRIPTOR);
        if (iin != null && iin instanceof BookManager)
            return (BookManager) iin;
        return new Proxy(binder);
    }

    @Override
    public IBinder asBinder() {
        return this;
    }

    @Override
    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        switch (code) {

            case INTERFACE_TRANSACTION:
                reply.writeString(DESCRIPTOR);
                return true;

            case TRANSAVTION_getBooks:
                data.enforceInterface(DESCRIPTOR);
                List<Book> result = this.getBooks();
                reply.writeNoException();
                reply.writeTypedList(result);
                return true;

            case TRANSAVTION_addBook:
                data.enforceInterface(DESCRIPTOR);
                Book arg0 = null;
                if (data.readInt() != 0) {
                    arg0 = Book.CREATOR.createFromParcel(data);
                }
                this.addBook(arg0);
                reply.writeNoException();
                return true;

        }
        return super.onTransact(code, data, reply, flags);
    }

    
    public class Proxy implements BookManager {

    private static final String DESCRIPTOR = "com.baronzhang.ipc.server.BookManager";

    private IBinder remote;

    public Proxy(IBinder remote) {

        this.remote = remote;
    }

    public String getInterfaceDescriptor() {
        return DESCRIPTOR;
    }

    @Override
    public List<Book> getBooks() throws RemoteException {
        Parcel data = Parcel.obtain();
        Parcel replay = Parcel.obtain();
        List<Book> result;

        try {
            data.writeInterfaceToken(DESCRIPTOR);
            remote.transact(Stub.TRANSAVTION_getBooks, data, replay, 0);
            replay.readException();
            result = replay.createTypedArrayList(Book.CREATOR);
        } finally {
            replay.recycle();
            data.recycle();
        }
        return result;
    }

    @Override
    public void addBook(Book book) throws RemoteException {

        Parcel data = Parcel.obtain();
        Parcel replay = Parcel.obtain();

        try {
            data.writeInterfaceToken(DESCRIPTOR);
            if (book != null) {
                data.writeInt(1);
                book.writeToParcel(data, 0);
            } else {
                data.writeInt(0);
            }
            remote.transact(Stub.TRANSAVTION_addBook, data, replay, 0);
            replay.readException();
        } finally {
            replay.recycle();
            data.recycle();
        }
    }

    @Override
    public IBinder asBinder() {
        return remote;
    }
}

流程总结:

1.在 Proxy 中的 addBook() 方法中首先通过 Parcel 将数据序列化,然后调用 remote.transact()

  1. Client 进程通过系统调用陷入内核态,Client 进程中执行 addBook(), 里面 transact()的线程挂起等待返回;

  2. 驱动完成一系列的操作之后唤醒 Server 进程,调用 Server 进程本地对象的 onTransact()。最终又走到了 Stub 中的 onTransact() 中

  3. onTransact() 根据函数编号调用相关函数(在 Stub 类中为 BookManager 接口中的每个函数中定义了一个编号,只不过上面的源码中我们简化掉了;在跨进程调用的时候,不会传递函数而是传递编号来指明要调用哪个函数

onTransact()代表回调, transact()代表主动!


binder.jpg

5. 跨进程方式优缺点比较

jietu-dffds.jpg
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,240评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,328评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,182评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,121评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,135评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,093评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,013评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,854评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,295评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,513评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,398评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,989评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,636评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,657评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容