文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Android Framework原理Binder驱动源码是什么

2023-07-05 01:54

关注

今天小编给大家分享一下Android Framework原理Binder驱动源码是什么的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。

1 system_server和service_manager的关系

我们知道,Android系统起始于init进程,我们通过adb shell ps -ef命令可以查看当前系统运行的全部进程,init进程它的进程号是1

Android Framework原理Binder驱动源码是什么

我们接着去找system_server进程和service_manager进程

Android Framework原理Binder驱动源码是什么

Android Framework原理Binder驱动源码是什么

Android Framework原理Binder驱动源码是什么

我们通过上图可以看到,service_manager进程的父进程是init进程,而system_server进程的父进程是zygote进程,那么我们可以看下图

Android Framework原理Binder驱动源码是什么

也就是说,当init进程fork出zygote进程之后,通过zygote进程创建了system_server进程

我们看下system_server的源码

//------SystemServer的main函数-------//// The main entry point from zygote.public static void main(String[] args) {    new SystemServer().run();}

在SystemServer源码的main函数注释中,已经提示了这个是zygote进程调用main方法,并启动了SystemServer进程

我们知道,在SystemServer中,持有了像AMS、PMS、WMS等系统服务,但是我们在使用的时候能直接使用这些服务吗?不是的,SystemServer只是持有了这些服务,并不对外暴露

ServiceManager.addService("package", m);final PackageManagerNative pmn = m.new PackageManagerNative();ServiceManager.addService("package_native", pmn);

service_manager则是管理这些服务类,例如PMS,在创建了Service之后还是将Service放到了service_manager中,而且只负责运行Binder,也就是说当service_manager要调用某个服务的时候,是通过进程间通信的方式来获取的。

2 传统IPC与Binder之间的区别

我们看下FileOutputStream的write方法是如何把数据写入磁盘的:

public void write(byte b[], int off, int len) throws IOException {    // Android-added: close() check before I/O.    if (closed && len > 0) {        throw new IOException("Stream Closed");    }    // Android-added: Tracking of unbuffered I/O.    tracker.trackIo(len);    // Android-changed: Use IoBridge instead of calling native method.    IoBridge.write(fd, b, off, len);}

在write方法中,核心方法就是调用了IoBridge的write方法,看注释就是说IoBridge代替了之前调用native方法,但最终还是调用了native的方法。

Android Framework原理Binder驱动源码是什么

像传统的IPC,在用户空间发送写入数据的指令,真正的数据写入是发生在内核空间,通过ioctl的读写操作,写入数据缓冲区,另一个进程如果需要获取这个数据,在通过ioctl将数据拷贝到进程2的内存空间中,所以传统的IPC进程间通信需要2次拷贝;

而Binder的优势在哪呢?Binder只需要一次拷贝,这里就是用了mmap的方式,那么mmap是如何工作的呢?我们知道所有的读写操作都是在内核空间完成的,那么mmap就是开辟一块物理内存,与内核空间完成映射,并且所有的进程内存空间与这块物理内存也存在映射关系。

Android Framework原理Binder驱动源码是什么

当进程1拿到这块物理内存的地址之后,便可以将数据拷贝到这块物理内存,因为进程2和这块内存存在映射关系,因此进程2便可以拿到进程1的数据,腾讯的MMKV便是基于mmap实现的。

所以相较于传统的IPC,Binder进程间通信只需要一次拷贝,因此Binder的性能更优。

3 物理内存和虚拟内存

对于物理内存和虚拟内存,可能很多小伙伴对于这个概念比较模糊;这个概念是源自于Linux,其中物理内存是系统硬件提供的内存,这才是真正的内存,例如系统有32M的物理内存,运行33M内存的应用肯定不能work的,这个时候虚拟内存就出现了,目的就是为了解决物理内存不足的情况,因此当一个系统物理内存用尽之后,意味着离崩溃就不远了。

因此现在大多数的程序就是运行在虚拟内存,而且在应用层是绝对不可能取到物理内存的,例如:

val a:Int = 10
int a = 10int *addr = &a

那么我们的代码是存在虚拟内存还是物理内存呢?首先,因为我们的代码在某一时间并不是全部执行的,在一个类中有1000个方法,可能只有1个方法被执行,这就是程序的局部性原则; 所以只有当部分代码被CPU执行的时候,才会将代码加载到物理内存,剩下的大部分代码会存储在磁盘中,因此128M的物理内存,可以加载10G的程序代码。

Android Framework原理Binder驱动源码是什么

4 Binder驱动源码分析

因为service_manager主要负责Binder运行,那么Binder驱动的初始化必然也是在其中,所以我们先去看一下service_manager的源码;我这边看的是Android 9.0的源码,因为底层源码很少会有改动,所以每个版本基本一致

https://www.androidos.net.cn/android/9.0.0_r8/xref/frameworks/native/cmds/servicemanager ==> service_manager.cint main(int argc, char** argv){    struct binder_state *bs;    union selinux_callback cb;    char *driver;    if (argc > 1) {        driver = argv[1];    } else {        driver = "/dev/binder";    }    //开启binder驱动 ==> /dev/binder    bs = binder_open(driver, 128*1024);    if (!bs) {        #ifdef VENDORSERVICEMANAGER                ALOGW("failed to open binder driver %s\n", driver);        while (true) {            sleep(UINT_MAX);        }        #else        ALOGE("failed to open binder driver %s\n", driver);        #endif        return -1;    }    if (binder_become_context_manager(bs)) {        ALOGE("cannot become context manager (%s)\n", strerror(errno));        return -1;    }    cb.func_audit = audit_callback;    selinux_set_callback(SELINUX_CB_AUDIT, cb);    cb.func_log = selinux_log_callback;    selinux_set_callback(SELINUX_CB_LOG, cb);    #ifdef VENDORSERVICEMANAGER        sehandle = selinux_android_vendor_service_context_handle();    #else    sehandle = selinux_android_service_context_handle();    #endif    selinux_status_open(true);    if (sehandle == NULL) {        ALOGE("SELinux: Failed to acquire sehandle. Aborting.\n");        abort();    }    if (getcon(&service_manager_context) != 0) {    ALOGE("SELinux: Failed to acquire service_manager context. Aborting.\n");    abort();    }    //开启循环    binder_loop(bs, svcmgr_handler);    return 0;}

首先,我们先看service_manager的源码,一般C/C++的源码首先找main函数,这个是程序的入口,首先调用了binder_open,打开了/dev/binder路径下的驱动driver,我们看下binder_open的实现。

// https://www.androidos.net.cn/android/9.0.0_r8/xref/frameworks/native/cmds/servicemanager ==> binder.cstruct binder_state *binder_open(const char* driver, size_t mapsize){    struct binder_state *bs;    struct binder_version vers;    bs = malloc(sizeof(*bs));    if (!bs) {        errno = ENOMEM;        return NULL;    }    //① 打开binder驱动文件,类似于打开一个apk,驱动文件是由代码生成的    bs->fd = open(driver, O_RDWR | O_CLOEXEC);    if (bs->fd < 0) {    fprintf(stderr,"binder: cannot open %s (%s)\n",        driver, strerror(errno));    goto fail_open;    }    if ((ioctl(bs->fd, BINDER_VERSION, &vers) == -1) ||    (vers.protocol_version != BINDER_CURRENT_PROTOCOL_VERSION)) {    fprintf(stderr,        "binder: kernel driver version (%d) differs from user space version (%d)\n",        vers.protocol_version, BINDER_CURRENT_PROTOCOL_VERSION);    goto fail_open;    }    bs->mapsize = mapsize;    //② 内存映射    bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);    if (bs->mapped == MAP_FAILED) {    fprintf(stderr,"binder: cannot map device (%s)\n",        strerror(errno));    goto fail_map;    }    return bs;    fail_map:    close(bs->fd);    fail_open:    free(bs);    return NULL;}

在binder_open方法中,首先初始化一个binder_state对象,这个会作为binder_open的返回值,并在一开始为其分配内存空间

①:调用open方法,这里是把/dev/binder传进来,相当于将驱动打开,那么接下来移步至4.1小节,看Binder驱动在内核空间做了什么事?
②:打开驱动之后,调用了mmap方法,通过4.1小节我们知道,这个其实是调用了binder_mmap,那么移步至4.2小节,看下binder_mmap的源码

4.1 binder_init

接下来,我们看下Binder驱动的源码,在Binder驱动中也有一个binder.c文件,看下它的初始化方法,在device_initcall中传入一个方法binder_init,这个方法就是Binder驱动初始化的开始

//http://androidxref.com/kernel_3.18/xref/drivers/staging/android/binder.cstatic int __init binder_init(void){   int ret;   binder_deferred_workqueue = create_singlethread_workqueue("binder");   if (!binder_deferred_workqueue)      return -ENOMEM;   binder_debugfs_dir_entry_root = debugfs_create_dir("binder", NULL);   if (binder_debugfs_dir_entry_root)      binder_debugfs_dir_entry_proc = debugfs_create_dir("proc",                   binder_debugfs_dir_entry_root);   //注册Binder设备   ret = misc_register(&binder_miscdev);   if (binder_debugfs_dir_entry_root) {      debugfs_create_file("state",                S_IRUGO,                binder_debugfs_dir_entry_root,                NULL,                &binder_state_fops);      debugfs_create_file("stats",                S_IRUGO,                binder_debugfs_dir_entry_root,                NULL,                &binder_stats_fops);      debugfs_create_file("transactions",                S_IRUGO,                binder_debugfs_dir_entry_root,                NULL,                &binder_transactions_fops);      debugfs_create_file("transaction_log",                S_IRUGO,                binder_debugfs_dir_entry_root,                &binder_transaction_log,                &binder_transaction_log_fops);      debugfs_create_file("failed_transaction_log",                S_IRUGO,                binder_debugfs_dir_entry_root,                &binder_transaction_log_failed,                &binder_transaction_log_fops);   }   return ret;}//初始化的位置device_initcall(binder_init);

在binder_init方法中,调用了misc_register,传入了一个对象binder_miscdev

static struct miscdevice binder_miscdev = {   .minor = MISC_DYNAMIC_MINOR,   .name = "binder",   .fops = &binder_fops};static const struct file_operations binder_fops = {   .owner = THIS_MODULE,   .poll = binder_poll,   .unlocked_ioctl = binder_ioctl,   .compat_ioctl = binder_ioctl,   .mmap = binder_mmap,   .open = binder_open,   .flush = binder_flush,   .release = binder_release,};

其实这里主要就是干了一件事,对外暴露对驱动的操作,并与驱动内部的方法做映射;这句话可能比较绕,但是看本节开头的①部分,这里调用了open方法,其实在驱动中就是调用了binder_open方法,只不过外部是无法直接调用binder_open方法

我们看这里注册了几个方法,都比较重要:binder_open、binder_mmap、binder_ioctl,我们一个一个来看

4.2 binder_open

这个方法,才是用户空间真正地打开驱动的位置

static int binder_open(struct inode *nodp, struct file *filp){   struct binder_proc *proc;   binder_debug(BINDER_DEBUG_OPEN_CLOSE, "binder_open: %d:%d\n",           current->group_leader->pid, current->pid);   //① 分配内存   proc = kzalloc(sizeof(*proc), GFP_KERNEL);   if (proc == NULL)      return -ENOMEM;   //②   get_task_struct(current);   proc->tsk = current;   INIT_LIST_HEAD(&proc->todo);   init_waitqueue_head(&proc->wait);   proc->default_priority = task_nice(current);   binder_lock(__func__);   binder_stats_created(BINDER_STAT_PROC);   hlist_add_head(&proc->proc_node, &binder_procs);   proc->pid = current->group_leader->pid;   INIT_LIST_HEAD(&proc->delivered_death);   filp->private_data = proc;   binder_unlock(__func__);   if (binder_debugfs_dir_entry_proc) {      char strbuf[11];      snprintf(strbuf, sizeof(strbuf), "%u", proc->pid);      proc->debugfs_entry = debugfs_create_file(strbuf, S_IRUGO,         binder_debugfs_dir_entry_proc, proc, &binder_proc_fops);   }   return 0;}

在这个方法中,首先定义了一个binder_proc引用,这个binder_proc是什么?它是Binder中维护的一个双向链表,用于记录每个进程的信息,我们看下图:

Android Framework原理Binder驱动源码是什么

因为我们知道,每个进程只要调用服务,那么service_manager都会调用binder_open方法,将这个进程信息存储在binder_proc链表中。

①:所以在调用binder_open之后,调用kzalloc在内核空间为这个进程分配一块内存
②:然后获取当前进程信息,并将其放置在binder_proc链表的头部

打开了驱动,就有了进程间通信的能力。

4.2 binder_mmap

binder_mmap,我们之前简单介绍过mmap的原理,那么这里我们看下,Binder驱动内部是如何做的

static int binder_mmap(struct file *filp, struct vm_area_struct *vma){   int ret;   //内核空间   struct vm_struct *area;   //当前进程信息   struct binder_proc *proc = filp->private_data;   const char *failure_string;   struct binder_buffer *buffer;   if (proc->tsk != current)      return -EINVAL;   //①   if ((vma->vm_end - vma->vm_start) > SZ_4M)      vma->vm_end = vma->vm_start + SZ_4M;   mutex_lock(&binder_mmap_lock);   if (proc->buffer) {      ret = -EBUSY;      failure_string = "already mapped";      goto err_already_mapped;   }   ......   //②   area = get_vm_area(vma->vm_end - vma->vm_start, VM_IOREMAP);   if (area == NULL) {      ret = -ENOMEM;      failure_string = "get_vm_area";      goto err_get_vm_area_failed;   }   proc->buffer = area->addr;   proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer;   mutex_unlock(&binder_mmap_lock);#ifdef CONFIG_CPU_CACHE_VIPT   if (cache_is_vipt_aliasing()) {      while (CACHE_COLOUR((vma->vm_start ^ (uint32_t)proc->buffer))) {         pr_info("binder_mmap: %d %lx-%lx maps %p bad alignment\n", proc->pid, vma->vm_start, vma->vm_end, proc->buffer);         vma->vm_start += PAGE_SIZE;      }   }#endif   proc->pages = kzalloc(sizeof(proc->pages[0]) * ((vma->vm_end - vma->vm_start) / PAGE_SIZE), GFP_KERNEL);   if (proc->pages == NULL) {      ret = -ENOMEM;      failure_string = "alloc page array";      goto err_alloc_pages_failed;   }   proc->buffer_size = vma->vm_end - vma->vm_start;   vma->vm_ops = &binder_vm_ops;   vma->vm_private_data = proc;   //③   if (binder_update_page_range(proc, 1, proc->buffer, proc->buffer + PAGE_SIZE, vma)) {      ret = -ENOMEM;      failure_string = "alloc small buf";      goto err_alloc_small_buf_failed;   }   buffer = proc->buffer;   INIT_LIST_HEAD(&proc->buffers);   list_add(&buffer->entry, &proc->buffers);   buffer->free = 1;   binder_insert_free_buffer(proc, buffer);   proc->free_async_space = proc->buffer_size / 2;   barrier();   proc->files = get_files_struct(current);   proc->vma = vma;   proc->vma_vm_mm = vma->vm_mm;      return 0;err_alloc_small_buf_failed:   kfree(proc->pages);   proc->pages = NULL;err_alloc_pages_failed:   mutex_lock(&binder_mmap_lock);   vfree(proc->buffer);   proc->buffer = NULL;err_get_vm_area_failed:err_already_mapped:   mutex_unlock(&binder_mmap_lock);err_bad_arg:   pr_err("binder_mmap: %d %lx-%lx %s failed %d\n",          proc->pid, vma->vm_start, vma->vm_end, failure_string, ret);   return ret;}

我们先看下binder_mmap的两个入参,它是从service_manager那边传过来的,我们重点关注第二个参数:vma,我们可以把它看做是用户空间,然后在binder_mmap中创建了一个area,就是内核空间

①:首先,会判断用户空间大小是否超过4M,我们可以往前看,当service_manager调用open方法时,传入的mapsize大小为128 * 1024,也就是128K,也就是说在内核空间开辟了一块128K的用户空间内存

②:get_vm_area(vma->vm_end - vma->vm_start, VM_IOREMAP);调用get_vm_area方法,就是在内核空间寻找一块连续的内存,多大呢?就是传进来的用户空间的大小;然后将内核空间的虚拟地址赋值给用户进程

③:调用binder_update_page_range方法,这个方法中主要工作就是创建物理内存并做映射关系,看下源码

static int binder_update_page_range(struct binder_proc *proc, int allocate,                void *start, void *end,                struct vm_area_struct *vma){   void *page_addr;   unsigned long user_page_addr;   struct vm_struct tmp_area;   struct page **page;   struct mm_struct *mm;   //......   if (allocate == 0)      goto free_range;   if (vma == NULL) {      pr_err("%d: binder_alloc_buf failed to map pages in userspace, no vma\n",         proc->pid);      goto err_no_vma;   }   for (page_addr = start; page_addr < end; page_addr += PAGE_SIZE) {      int ret;      page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE];      BUG_ON(*page);      //分配一页的物理内存 4K      *page = alloc_page(GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO);      if (*page == NULL) {         pr_err("%d: binder_alloc_buf failed for page at %p\n",            proc->pid, page_addr);         goto err_alloc_page_failed;      }      tmp_area.addr = page_addr;      tmp_area.size = PAGE_SIZE + PAGE_SIZE ;      //将内核空间与其建立映射关系      ret = map_vm_area(&tmp_area, PAGE_KERNEL, page);      if (ret) {         pr_err("%d: binder_alloc_buf failed to map page at %p in kernel\n",                proc->pid, page_addr);         goto err_map_kernel_failed;      }      user_page_addr =         (uintptr_t)page_addr + proc->user_buffer_offset;      //将用户空间与其建立映射关系      ret = vm_insert_page(vma, user_page_addr, page[0]);      if (ret) {         pr_err("%d: binder_alloc_buf failed to map page at %lx in userspace\n",                proc->pid, user_page_addr);         goto err_vm_insert_page_failed;      }         }   if (mm) {      up_write(&mm->mmap_sem);      mmput(mm);   }   return 0;

这里我们看到就是,首先会分配一页的物理内存4K,然后调用map_vm_area将内核空间虚拟地址与物理内存映射;调用vm_insert_page方法,将用户空间与物理内存映射,见下图:

Android Framework原理Binder驱动源码是什么

就这样,完成了物理内存与用户空间和内核空间的映射,binder_mmap完成了自己的工作。

接着再回到service_manager的main方法中,我们看到调用了binder_open之后,会调用binder_loop方法,这个有点儿类似Android的Handler,也是开启循环,接收命令去执行任务。

以上就是“Android Framework原理Binder驱动源码是什么”这篇文章的所有内容,感谢各位的阅读!相信大家阅读完这篇文章都有很大的收获,小编每天都会为大家更新不同的知识,如果还想学习更多的知识,请关注编程网行业资讯频道。

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯