亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Android?Framework原理Binder驅動源碼解析

 更新時間:2023年01月30日 16:29:06   作者:layz4android  
這篇文章主要為大家介紹了Android?Framework原理Binder驅動源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

引言

相信大家對于Binder這個概念是非常熟悉了,這是Android系統(tǒng) 獨有的進程間通信框架,而對于Binder底層是如何實現(xiàn)進程間通信,大家熟悉嗎,包括Proxy和Stub機制,那么從本章開始就開始介紹Binder進程間通信機制。

1 system_server和service_manager的關系

我們知道,Android系統(tǒng)起始于init進程,我們通過adb shell ps -ef命令可以查看當前系統(tǒng)運行的全部進程,init進程它的進程號是1

我們接著去找system_server進程和service_manager進程

我們通過上圖可以看到,service_manager進程的父進程是init進程,而system_server進程的父進程是zygote進程,那么我們可以看下圖

也就是說,當init進程fork出zygote進程之后,通過zygote進程創(chuàng)建了system_server進程

我們看下system_server的源碼

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

在SystemServer源碼的main函數(shù)注釋中,已經提示了這個是zygote進程調用main方法,并啟動了SystemServer進程

我們知道,在SystemServer中,持有了像AMS、PMS、WMS等系統(tǒng)服務,但是我們在使用的時候能直接使用這些服務嗎?不是的,SystemServer只是持有了這些服務,并不對外暴露;

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

service_manager則是管理這些服務類,例如PMS,在創(chuàng)建了Service之后還是將Service放到了service_manager中,而且只負責運行Binder,也就是說當service_manager要調用某個服務的時候,是通過進程間通信的方式來獲取的。

2 傳統(tǒng)IPC與Binder之間的區(qū)別

我們看下FileOutputStream的write方法是如何把數(shù)據(jù)寫入磁盤的:

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的方法。

像傳統(tǒng)的IPC,在用戶空間發(fā)送寫入數(shù)據(jù)的指令,真正的數(shù)據(jù)寫入是發(fā)生在內核空間,通過ioctl的讀寫操作,寫入數(shù)據(jù)緩沖區(qū),另一個進程如果需要獲取這個數(shù)據(jù),在通過ioctl將數(shù)據(jù)拷貝到進程2的內存空間中,所以傳統(tǒng)的IPC進程間通信需要2次拷貝;

而Binder的優(yōu)勢在哪呢?Binder只需要一次拷貝,這里就是用了mmap的方式,那么mmap是如何工作的呢?我們知道所有的讀寫操作都是在內核空間完成的,那么mmap就是開辟一塊物理內存,與內核空間完成映射,并且所有的進程內存空間與這塊物理內存也存在映射關系。

當進程1拿到這塊物理內存的地址之后,便可以將數(shù)據(jù)拷貝到這塊物理內存,因為進程2和這塊內存存在映射關系,因此進程2便可以拿到進程1的數(shù)據(jù),騰訊的MMKV便是基于mmap實現(xiàn)的。

所以相較于傳統(tǒng)的IPC,Binder進程間通信只需要一次拷貝,因此Binder的性能更優(yōu)。

3 物理內存和虛擬內存

對于物理內存和虛擬內存,可能很多小伙伴對于這個概念比較模糊;這個概念是源自于Linux,其中物理內存是系統(tǒng)硬件提供的內存,這才是真正的內存,例如系統(tǒng)有32M的物理內存,運行33M內存的應用肯定不能work的,這個時候虛擬內存就出現(xiàn)了,目的就是為了解決物理內存不足的情況,因此當一個系統(tǒng)物理內存用盡之后,意味著離崩潰就不遠了。

因此現(xiàn)在大多數(shù)的程序就是運行在虛擬內存,而且在應用層是絕對不可能取到物理內存的,例如:

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

那么我們的代碼是存在虛擬內存還是物理內存呢?首先,因為我們的代碼在某一時間并不是全部執(zhí)行的,在一個類中有1000個方法,可能只有1個方法被執(zhí)行,這就是程序的局部性原則; 所以只有當部分代碼被CPU執(zhí)行的時候,才會將代碼加載到物理內存,剩下的大部分代碼會存儲在磁盤中,因此128M的物理內存,可以加載10G的程序代碼。

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.c
int 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();
    }
    //開啟循環(huán)
    binder_loop(bs, svcmgr_handler);
    return 0;
}

首先,我們先看service_manager的源碼,一般C/C++的源碼首先找main函數(shù),這個是程序的入口,首先調用了binder_open,打開了/dev/binder路徑下的驅動driver,我們看下binder_open的實現(xiàn)。

// https://www.androidos.net.cn/android/9.0.0_r8/xref/frameworks/native/cmds/servicemanager ==> binder.c
struct 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小節(jié),看Binder驅動在內核空間做了什么事?
②:打開驅動之后,調用了mmap方法,通過4.1小節(jié)我們知道,這個其實是調用了binder_mmap,那么移步至4.2小節(jié),看下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.c
static 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,
};

其實這里主要就是干了一件事,對外暴露對驅動的操作,并與驅動內部的方法做映射;這句話可能比較繞,但是看本節(jié)開頭的①部分,這里調用了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中維護的一個雙向鏈表,用于記錄每個進程的信息,我們看下圖:

因為我們知道,每個進程只要調用服務,那么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;
   /*pr_info("binder_mmap: %d %lx-%lx maps %p\n",
       proc->pid, vma->vm_start, vma->vm_end, proc->buffer);*/
   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那邊傳過來的,我們重點關注第二個參數(shù):vma,我們可以把它看做是用戶空間,然后在binder_mmap中創(chuàng)建了一個area,就是內核空間

①:首先,會判斷用戶空間大小是否超過4M,我們可以往前看,當service_manager調用open方法時,傳入的mapsize大小為128 * 1024,也就是128K,也就是說在內核空間開辟了一塊128K的用戶空間內存

②:get_vm_area(vma->vm_end - vma->vm_start, VM_IOREMAP);調用get_vm_area方法,就是在內核空間尋找一塊連續(xù)的內存,多大呢?就是傳進來的用戶空間的大??;然后將內核空間的虛擬地址賦值給用戶進程

③:調用binder_update_page_range方法,這個方法中主要工作就是創(chuàng)建物理內存并做映射關系,看下源碼

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 /* guard page? */;
      //將內核空間與其建立映射關系
      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;
      }
      /* vm_insert_page does not seem to increment the refcount */
   }
   if (mm) {
      up_write(&mm->mmap_sem);
      mmput(mm);
   }
   return 0;

這里我們看到就是,首先會分配一頁的物理內存4K,然后調用map_vm_area將內核空間虛擬地址與物理內存映射;調用vm_insert_page方法,將用戶空間與物理內存映射,見下圖:

就這樣,完成了物理內存與用戶空間和內核空間的映射,binder_mmap完成了自己的工作。

接著再回到service_manager的main方法中,我們看到調用了binder_open之后,會調用binder_loop方法,這個有點兒類似Android的Handler,也是開啟循環(huán),接收命令去執(zhí)行任務。

以上就是Android Framework原理Binder驅動源碼解析的詳細內容,更多關于Android Framework Binder驅動的資料請關注腳本之家其它相關文章!

相關文章

  • Android 解決TextView排版參差不齊的問題

    Android 解決TextView排版參差不齊的問題

    這篇文章主要介紹了Android 解決TextView排版參差不齊的問題的相關資料,需要的朋友可以參考下
    2017-01-01
  • Android實現(xiàn)邊錄邊播功能

    Android實現(xiàn)邊錄邊播功能

    這篇文章主要為大家詳細介紹了Android實現(xiàn)邊錄邊播功能,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-11-11
  • 如何設置Android studio 3.0顯示光標返回上一次瀏覽位置的箭頭圖標

    如何設置Android studio 3.0顯示光標返回上一次瀏覽位置的箭頭圖標

    這篇文章主要介紹了如何設置Android studio 3.0顯示光標返回上一次瀏覽位置的箭頭圖標 很多朋友反映剛升級了Android studio 3.0,發(fā)現(xiàn)光標返回上一次瀏覽位置的箭頭圖標沒有了,下文給大家介紹的非常詳細,需要的朋友可以參考下
    2017-11-11
  • Android應用中使用TabHost組件繼承TabActivity的布局方法

    Android應用中使用TabHost組件繼承TabActivity的布局方法

    這篇文章主要介紹了Android應用中使用TabHost組件繼承TabActivity的布局方法,文中分別介紹了以Activity和以布局文件進行布局的方式,需要的朋友可以參考下
    2016-04-04
  • Android 文件存儲及常見問題解決

    Android 文件存儲及常見問題解決

    這篇文章主要介紹了Android 文件存儲及常見問題解決的相關資料,需要的朋友可以參考下
    2017-02-02
  • Android WebView交互傳遞json字符串并解析的方法

    Android WebView交互傳遞json字符串并解析的方法

    這篇文章主要給大家介紹了關于Android中WebView交互傳遞json字符串并解析的相關資料,文中通過示例代碼介紹的非常詳細,對各位Android開發(fā)者具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧。
    2018-05-05
  • 代碼分析Android消息機制

    代碼分析Android消息機制

    本文通過代碼實例詳細分析了Android消息機制的相關知識點,對此有需要的朋友可以參考學習下。
    2018-03-03
  • Android實現(xiàn)圖片點擊放大

    Android實現(xiàn)圖片點擊放大

    這篇文章主要為大家詳細介紹了Android實現(xiàn)圖片點擊放大,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-10-10
  • java  深入理解內存映射文件原理

    java 深入理解內存映射文件原理

    這篇文章主要介紹了java 深入理解內存映射文件原理的相關資料,虛擬內存與內存映射文件的區(qū)別與聯(lián)系,內存映射文件的原理和效率,需要的朋友可以參考下
    2016-11-11
  • Android實現(xiàn)拍照、選擇相冊圖片并裁剪功能

    Android實現(xiàn)拍照、選擇相冊圖片并裁剪功能

    這篇文章主要為大家詳細介紹了Android實現(xiàn)拍照、選擇相冊圖片并裁剪功能的相關資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-12-12

最新評論