<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
相信大家對於Binder這個概念是非常熟悉了,這是Android系統 獨有的程序間通訊框架,而對於Binder底層是如何實現程序間通訊,大家熟悉嗎,包括Proxy和Stub機制,那麼從本章開始就開始介紹Binder程序間通訊機制。
我們知道,Android系統起始於init程序,我們通過adb shell ps -ef命令可以檢視當前系統執行的全部程序,init程序它的程序號是1
我們接著去找system_server程序和service_manager程序
我們通過上圖可以看到,service_manager程序的父程序是init程序,而system_server程序的父程序是zygote程序,那麼我們可以看下圖
也就是說,當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要呼叫某個服務的時候,是通過程序間通訊的方式來獲取的。
我們看下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的方法。
像傳統的IPC,在使用者空間傳送寫入資料的指令,真正的資料寫入是發生在核心空間,通過ioctl的讀寫操作,寫入資料緩衝區,另一個程序如果需要獲取這個資料,在通過ioctl將資料拷貝到程序2的記憶體空間中,所以傳統的IPC程序間通訊需要2次拷貝;
而Binder的優勢在哪呢?Binder只需要一次拷貝,這裡就是用了mmap的方式,那麼mmap是如何工作的呢?我們知道所有的讀寫操作都是在核心空間完成的,那麼mmap就是開闢一塊實體記憶體,與核心空間完成對映,並且所有的程序記憶體空間與這塊實體記憶體也存在對映關係。
當程序1拿到這塊實體記憶體的地址之後,便可以將資料拷貝到這塊實體記憶體,因為程序2和這塊記憶體存在對映關係,因此程序2便可以拿到程序1的資料,騰訊的MMKV便是基於mmap實現的。
所以相較於傳統的IPC,Binder程序間通訊只需要一次拷貝,因此Binder的效能更優。
對於實體記憶體和虛擬記憶體,可能很多小夥伴對於這個概念比較模糊;這個概念是源自於Linux,其中實體記憶體是系統硬體提供的記憶體,這才是真正的記憶體,例如系統有32M的實體記憶體,執行33M記憶體的應用肯定不能work的,這個時候虛擬記憶體就出現了,目的就是為了解決實體記憶體不足的情況,因此當一個系統實體記憶體用盡之後,意味著離崩潰就不遠了。
因此現在大多數的程式就是執行在虛擬記憶體,而且在應用層是絕對不可能取到實體記憶體的,例如:
val a:Int = 10
int a = 10 int *addr = &a
那麼我們的程式碼是存在虛擬記憶體還是實體記憶體呢?首先,因為我們的程式碼在某一時間並不是全部執行的,在一個類中有1000個方法,可能只有1個方法被執行,這就是程式的區域性性原則; 所以只有當部分程式碼被CPU執行的時候,才會將程式碼載入到實體記憶體,剩下的大部分程式碼會儲存在磁碟中,因此128M的實體記憶體,可以載入10G的程式程式碼。
因為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 %sn", driver); while (true) { sleep(UINT_MAX); } #else ALOGE("failed to open binder driver %sn", 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.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小節,看Binder驅動在核心空間做了什麼事?
②:開啟驅動之後,呼叫了mmap方法,通過4.1小節我們知道,這個其實是呼叫了binder_mmap,那麼移步至4.2小節,看下binder_mmap的原始碼
接下來,我們看下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, };
其實這裡主要就是幹了一件事,對外暴露對驅動的操作,並與驅動內部的方法做對映;這句話可能比較繞,但是看本節開頭的①部分,這裡呼叫了open方法,其實在驅動中就是呼叫了binder_open方法,只不過外部是無法直接呼叫binder_open方法
我們看這裡註冊了幾個方法,都比較重要:binder_open、binder_mmap、binder_ioctl,我們一個一個來看
這個方法,才是使用者空間真正地開啟驅動的位置
static int binder_open(struct inode *nodp, struct file *filp) { struct binder_proc *proc; binder_debug(BINDER_DEBUG_OPEN_CLOSE, "binder_open: %d:%dn", 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連結串列的頭部
開啟了驅動,就有了程序間通訊的能力。
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 alignmentn", 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 %pn", 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 %dn", 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 vman", 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 %pn", 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 kerneln", 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 userspacen", 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,也是開啟迴圈,接收命令去執行任務。
以上就是Android Framework原理Binder驅動原始碼解析的詳細內容,更多關於Android Framework Binder驅動的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45