rokevin
移动
前端
语言
  • 基础

    • Linux
    • 实施
    • 版本构建
  • 应用

    • WEB服务器
    • 数据库
  • 资讯

    • 工具
    • 部署
开放平台
产品设计
  • 人工智能
  • 云计算
计算机
其它
GitHub
移动
前端
语言
  • 基础

    • Linux
    • 实施
    • 版本构建
  • 应用

    • WEB服务器
    • 数据库
  • 资讯

    • 工具
    • 部署
开放平台
产品设计
  • 人工智能
  • 云计算
计算机
其它
GitHub
  • Binder面试

  • 说说你对 binder 驱动的了解
  • Binder 相比其他 IPC 机制有什么优势?
  • Binder 的工作原理是什么?
  • AIDL 的作用是什么?如何使用?
  • Binder 如何传递对象?
  • 如何处理 Binder 调用中的线程问题?
  • 什么是 Binder 死亡通知?如何实现?
  • Binder 权限验证机制是怎样的?
  • Binder 驱动是如何实现的?
  • 为什么 Android 要设计 Binder 这种 IPC 机制?
  • Binder 通信中的数据是如何传递的?
  • Binder 传值为什么有大小限制?
  • 如何解决 Binder 传递大数据时的 TransactionTooLargeException?
  • Intent 传递数据的大小限制与 Binder 有什么关系?
  • MMAP 原理讲解
  • 为什么 Intent 不能传递大数据?
  • AIDL 生成的 Java 类细节
  • 四大组件底层的通信机制
  • Binder 机制是如何跨进程的?
  • 说说 Linux 的驱动设备?
  • 看过 binder 驱动的 open、mmap、ioctl 方法的具体实现吗?
  • 仅 binder_ioctl() 一个方法是怎么实现大部分业务的?
  • binder 驱动中要实现这些业务功能,必然要用一些数据结构来存放相关数据,比如你上面说 binder_open() 方法时提到的 binder_proc,你还知道其他的结构体吗?

Binder面试

说说你对 binder 驱动的了解

binder 机制分为四部分,binder 驱动、Service Manager、客户端、服务端。类比网络通信,Service Manager 是 DNS,binder 驱动就是路由器,它运行在内核空间,不同进程间通过 binder 驱动才能通信。

Binder 相比其他 IPC 机制有什么优势?

Binder 相比传统的 IPC 机制(如管道、Socket、共享内存)有以下优势:

  • 高效性:通过内存映射实现,数据只需拷贝一次
  • 安全性:自带 UID/PID 身份标识,可进行权限验证
  • 易用性:通过 AIDL 自动生成模板代码,简化开发
  • 面向对象:支持远程对象调用,更符合面向对象思想
  • 低资源消耗:相比 Socket 等方式,资源消耗更低

Binder 的工作原理是什么?

Binder 基于客户端 - 服务端模型,通过 Binder 驱动实现跨进程通信:

  1. 服务端向 ServiceManager 注册服务
  2. 客户端从 ServiceManager 获取服务的 Binder 引用
  3. 客户端通过 Binder 引用调用远程方法
  4. Binder 驱动负责将请求转发给服务端并返回结果

底层通过内存映射(mmap)实现高效的数据传递,避免了传统 IPC 机制中数据多次拷贝的问题。

AIDL 的作用是什么?如何使用?

AIDL(Android 接口定义语言)用于定义跨进程通信的接口,编译器会根据 AIDL 文件自动生成 Binder 通信的模板代码。

使用步骤:

  1. 创建 .aidl 文件,定义接口方法
  2. 编译生成 Java 类(包含 Binder 实现)
  3. 服务端实现 AIDL 接口
  4. 客户端绑定服务并获取 Binder 引用
  5. 通过 Binder 引用调用远程方法

Binder 如何传递对象?

Binder 传递对象有两种方式:

  1. 基本数据类型和 String 可以直接传递

  2. 自定义对象需要实现Parcelable

    接口:

    • 实现 writeToParcel() 方法,将对象数据写入 Parcel
    • 实现 CREATOR 接口,用于从 Parcel 中恢复对象
    • 创建对应的 .aidl 文件声明该类型

传递方式分为两种:

  • in:客户端到服务端(默认)
  • out:服务端到客户端
  • inout:双向传递

如何处理 Binder 调用中的线程问题?

  • Binder 方法在服务端的 Binder 线程池中执行,不是主线程
  • 若需要更新 UI,需通过 Handler 切换到主线程
  • 多个客户端同时调用会导致并发,需使用同步机制(如 synchronized)保证线程安全
  • 避免在 Binder 方法中执行耗时操作,以免阻塞 Binder 线程池

什么是 Binder 死亡通知?如何实现?

当服务端进程意外终止时,客户端可以通过死亡通知机制得到通知。

实现方式:

  1. 实现 IBinder.DeathRecipient 接口,重写 binderDied() 方法
  2. 通过 binder.linkToDeath(recipient, flags) 注册死亡通知
  3. 在 binderDied() 方法中处理服务端死亡的情况(如重新绑定服务)
  4. 不再需要时,通过 binder.unlinkToDeath(recipient, flags) 取消注册

Binder 权限验证机制是怎样的?

Binder 提供了多种权限验证方式:

  1. 在 AndroidManifest.xml 中声明权限

  2. 服务端在onBind()方法中验证权限:

    @Override
    public IBinder onBind(Intent intent) {
        int check = checkCallingOrSelfPermission("com.example.MY_PERMISSION");
        if (check != PackageManager.PERMISSION_GRANTED) {
            return null;
        }
        return mBinder;
    }
    
  3. 在 Binder 方法中验证:

    public void sensitiveOperation() {
        mContext.enforceCallingPermission("com.example.MY_PERMISSION", 
            "需要权限才能执行此操作");
        // 执行敏感操作
    }
    

Binder 驱动是如何实现的?

Binder 驱动是一个内核模块,主要功能包括:

  1. 维护 Binder 实体和引用的映射关系
  2. 通过内存映射(mmap)实现高效的数据传递
  3. 管理进程间的通信队列
  4. 实现进程的身份验证(UID/PID)
  5. 处理 Binder 引用的计数管理

当客户端调用远程方法时,Binder 驱动会:

  • 接收客户端的请求数据
  • 查找目标 Binder 实体所在的进程
  • 将请求转发给相应的服务端进程
  • 将服务端的返回结果传递回客户端

为什么 Android 要设计 Binder 这种 IPC 机制?

Android 设计 Binder 主要基于以下考虑:

  1. 性能需求:移动设备资源有限,需要高效的 IPC 机制
  2. 安全性:传统 IPC 没有严格的身份验证机制,Binder 内置了 UID/PID 验证
  3. 易用性:提供面向对象的调用方式,简化跨进程通信开发
  4. 资源限制:相比其他 IPC 机制,Binder 更节省系统资源
  5. 系统架构:Android 大量使用跨进程服务,需要可靠高效的 IPC 支撑

Binder 通信中的数据是如何传递的?

Binder 采用了一种高效的内存共享机制:

  1. 当服务端注册服务时,Binder 驱动会为其创建一个内核缓冲区
  2. 客户端发送数据时,数据会被拷贝到内核缓冲区(一次拷贝)
  3. 服务端通过内存映射可以直接访问该内核缓冲区,无需再次拷贝
  4. 这种方式比传统的 IPC 机制(如 Socket 需要两次拷贝)更高效

对于大内存数据,Binder 有大小限制(通常是 1MB-8KB),超过限制会抛出异常,需要使用其他方式(如文件共享)传递。

Binder 传值为什么有大小限制?

因为 Binder 驱动使用固定大小(约 1MB)的内核缓冲区进行数据中转,此限制用于保护内核资源,同时 Binder 设计初衷是传递轻量数据而非大数据。

如何解决 Binder 传递大数据时的 TransactionTooLargeException?

可通过拆分数据、文件共享、内存映射(SharedMemory)、使用 ContentProvider 等方式绕过限制,避免直接传递大数据。

Intent 传递数据的大小限制与 Binder 有什么关系?

Intent 内部通过 Binder 机制跨进程传递,因此其数据大小受限于 Binder 的 1MB 缓冲区限制,过大的数据会导致 TransactionTooLargeException。

MMAP 原理讲解

MMAP(Memory-Mapped File)是一种将文件或设备内存映射到进程地址空间的机制,实现用户空间与内核空间的高效数据交互,核心原理如下:

  • 映射过程:

    通过 mmap() 系统调用,将内核空间的一块内存区域(如文件缓存或设备缓冲区)与用户进程的虚拟地址空间建立映射关系。此后,进程对该虚拟地址的读写操作会直接反映到内核空间,无需通过 read()/write() 等系统调用来回拷贝数据。

  • 零拷贝特性:

    传统文件读写需经历 “用户态→内核态→用户态” 的两次数据拷贝,而 MMAP 只需一次映射,进程可直接操作映射区域,减少拷贝开销。例如,Binder 利用 MMAP 实现跨进程数据的 “一次拷贝”:客户端将数据写入用户空间映射区,驱动通过映射直接将数据传递到服务端的内核缓冲区。

  • 适用场景:

    大文件读写、进程间共享内存、设备驱动交互(如 Binder 驱动)等,尤其适合需要频繁读写的场景。

为什么 Intent 不能传递大数据?

Intent 传递数据的限制源于底层 Binder 机制的设计,具体原因如下:

  • Binder 缓冲区大小限制:

    Binder 驱动为每个进程分配的缓冲区大小有限(默认约 1MB),而 Intent 数据通过 Binder 传输时,会占用该缓冲区。若数据超过缓冲区上限(通常建议不超过 100KB),会触发 TransactionTooLargeException。

  • 内存管理与性能考虑:

    大数据传输会导致 Binder 线程池阻塞,影响其他进程通信;同时,跨进程传递大数据可能引发内存抖动,尤其在移动设备上会加剧内存压力。

  • 替代方案:

    传递大数据应使用文件、ContentProvider 或 AIDL 配合 ParcelFileDescriptor 实现。

AIDL 生成的 Java 类细节

AIDL(Android Interface Definition Language)会自动生成一个包含 Proxy(客户端代理)和 Stub(服务端骨架)的 Java 类,核心结构如下:

// 生成的类名以 "AIDL接口名+Stub" 命名
public interface IMyAidlInterface extends android.os.IInterface {
    // 1. Stub 类(服务端实现)
    public static abstract class Stub extends android.os.Binder implements IMyAidlInterface {
        // 唯一标识,用于验证接口一致性
        private static final java.lang.String DESCRIPTOR = "com.example.IMyAidlInterface";
        
        public Stub() { this.attachInterface(this, DESCRIPTOR); }
        
        // 将 Binder 转换为接口实例(服务端用)
        public static IMyAidlInterface asInterface(android.os.IBinder obj) { ... }
        
        // 实现 Binder 的 onTransact 方法,处理客户端请求
        @Override
        public boolean onTransact(int code, android.os.Parcel data, 
                                 android.os.Parcel reply, int flags) {
            // 根据 code 分发不同方法调用,反序列化参数,调用服务端实现
            switch (code) {
                case TRANSACTION_add: // 方法标识(自动生成)
                    data.enforceInterface(DESCRIPTOR);
                    int a = data.readInt();
                    int b = data.readInt();
                    int result = this.add(a, b); // 调用服务端实现的 add 方法
                    reply.writeNoException();
                    reply.writeInt(result);
                    return true;
            }
            return super.onTransact(code, data, reply, flags);
        }
    }
    
    // 2. Proxy 类(客户端代理,隐藏在 Stub 内部)
    private static class Proxy implements IMyAidlInterface {
        private android.os.IBinder mRemote; // 指向服务端的 Binder 引用
        
        @Override
        public int add(int a, int b) throws android.os.RemoteException {
            // 序列化参数,通过 transact 发送请求
            android.os.Parcel _data = android.os.Parcel.obtain();
            android.os.Parcel _reply = android.os.Parcel.obtain();
            try {
                _data.writeInterfaceToken(DESCRIPTOR);
                _data.writeInt(a);
                _data.writeInt(b);
                mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
                _reply.readException();
                return _reply.readInt();
            } finally {
                _reply.recycle();
                _data.recycle();
            }
        }
    }
    
    // 3. 接口方法(与 AIDL 定义一致)
    public int add(int a, int b) throws android.os.RemoteException;
}
  • 核心逻辑

    :

    • 客户端通过 Proxy 调用方法,将参数序列化后通过 transact() 发送给驱动;
    • 服务端 Stub 重写 onTransact(),接收请求并反序列化参数,调用实际实现,最后将结果返回。

四大组件底层的通信机制

四大组件(Activity、Service、BroadcastReceiver、ContentProvider)的跨进程通信均基于 Binder 机制,具体实现如下:

  • Activity:

    启动跨进程 Activity 时,通过 Instrumentation 调用 ActivityManagerService(AMS)的 startActivity(),AMS 与应用进程通过 Binder 交互,最终由应用进程的 ActivityThread 创建 Activity。

  • Service:

    绑定跨进程 Service 时,通过 bindService() 触发 AMS 查找服务,服务端通过 onBind() 返回 Binder 实例,客户端通过 AIDL 生成的 Proxy 调用服务方法。

  • BroadcastReceiver:

    发送广播时,通过 AMS 的 broadcastIntent() 分发,AMS 将广播发送到目标进程的 LoadedApk.ReceiverDispatcher,底层通过 Binder 传递 Intent 数据。

  • ContentProvider:

    跨进程访问 ContentProvider 时,通过 ContentResolver 调用远程 ContentProvider 的方法(如 query()),底层通过 IContentProvider 接口的 Binder 通信实现。

Binder 机制是如何跨进程的?

Binder 跨进程通信基于 “客户端 - 服务端 - 驱动 - ServiceManager” 模型,核心流程如下:

  1. 服务注册:

    服务端通过 ServiceManager.addService() 向 ServiceManager 注册 Binder 实体(binder_node),驱动为实体分配唯一标识,ServiceManager 记录 “服务名→实体” 映射。

  2. 服务发现:

    客户端通过 ServiceManager.getService() 查询服务,ServiceManager 返回 Binder 引用(binder_ref,即句柄),客户端通过该引用间接访问服务端。

  3. 通信过程:

    • 客户端调用 Proxy 的方法,将参数序列化到 Parcel,通过 transact() 发送事务(包含目标句柄、方法标识、数据)。
    • Binder 驱动接收事务,解析目标句柄找到对应的服务端进程和 Binder 实体,通过 MMAP 将数据映射到服务端内核缓冲区(一次拷贝)。
    • 服务端 Binder 线程池从驱动读取事务,反序列化参数后调用 onTransact(),执行服务端逻辑并返回结果。
    • 驱动将结果通过映射回传客户端,客户端解析结果完成调用。

通过内存映射(一次拷贝)、身份验证和 ServiceManager 中介,Binder 实现了高效、安全的跨进程通信。

说说 Linux 的驱动设备?

Linux 把所有的硬件访问都抽象为对文件的读写、设置,这一"抽象"的具体实现就是驱动程序。驱动程序充当硬件和软件之间的枢纽,提供了一套标准化的调用,并将这些调用映射为实际硬件设备相关的操作,对应用程序来说隐藏了设备工作的细节。

Linux 驱动设备分为三类,分别是字符设备、块设备和网络设备。字符设备就是能够像字节流文件一样被访问的设备。对字符设备进行读/写操作时,实际硬件的 I/O 操作一般也紧接着发生。字符设备驱动程序通常都会实现 open、close、read 和 write 系统调用,比如显示屏、键盘、串口、LCD、LED 等。

块设备指通过传输数据块(一般为 512 或 1k)来访问的设备,比如硬盘、SD卡、U盘、光盘等。网络设备是能够和其他主机交换数据的设备,比如网卡、蓝牙等设备。

字符设备中有一个比较特殊的 misc 杂项设备,设备号为 10,可以自动生成设备节点。Android 的 Ashmem、Binder 都属于 misc 杂项设备。

看过 binder 驱动的 open、mmap、ioctl 方法的具体实现吗?

它们分别对应于驱动源码 binder.c 中的 binder_open()、binder_mmap()、binder_ioctl() 方法,binder_open() 中主要是创建及初始化 binder_proc ,binder_proc 是用来存放 binder 相关数据的结构体,每个进程独有一份。

binder_mmap() 的主要工作是建立应用进程虚拟内存在内核中的一块映射,这样应用程序和内核就拥有了共享的内存空间,为后面的一次拷贝做准备。

binder 驱动并不提供常规的 read()、write() 等文件操作,全部通过 binder_ioctl() 实现,所以 binder_ioctl() 是 binder 驱动中工作量最大的一个,它承担了 binder 驱动的大部分业务。

仅 binder_ioctl() 一个方法是怎么实现大部分业务的?

binder 机制将业务细分为不同的命令,调用 binder_ioctl() 时传入具体的命令来区分业务,比如有读写数据的 BINDER_WRITE_READ 命令、 Service Manager 专用的注册为 DNS 的命令等等。

BINDER_WRITE_READ 命令最为关键,其细分了一些子命令,比如 BC_TRANSACTION、BC_REPLY 等。BC_TRANSACTION 就是上层最常用的 IPC 调用命令了,AIDL 接口的 transact 方法就是这个命令。

binder 驱动中要实现这些业务功能,必然要用一些数据结构来存放相关数据,比如你上面说 binder_open() 方法时提到的 binder_proc,你还知道其他的结构体吗?

知道一些,比如:

结构体说明
binder_proc描述使用 binder 的进程,当调用 binder_open 函数时会创建
binder_thread描述使用 binder 的线程,当调用 binder_ioctl 函数时会创建
binder_node描述 binder 实体节点,对应于一个 serve,即用户态的 BpBinder 对象
binder_ref描述对 binder 实体节点的引用,关联到一个 binder_node
binder_buffer描述 binder 通信过程中存储数据的Buffer
binder_work描述一个 binder 任务
binder_transaction描述一次 binder 任务相关的数据信息
binder_ref_death描述 binder_node 即 binder server 的死亡信息

其中主要结构体引用关系如下:

最近更新:: 2025/10/22 15:36
Contributors: luokaiwen