binder的介紹:
由于不同的進程不可以直接互相訪問,所以需要一些機制來確保進程間能夠通信,在linxu中,有以下幾種:
1.管道(Pipe)及有名管道(named pipe):管道可用于具有親緣關系進程間的通信,有名管道克服了管道沒有名字的限制,因此,除具有管道所具有的功能外,它還允許無親緣關系進程間的通信。
2.信號(Signal):信號是比較復雜的通信方式,用于通知接受進程有某種事件發生,除了用于進程間通信外,進程還可以發送信號給進程本身;linux除了支持Unix早期信號語義函數sigal外,還支持語義符合Posix.1標準的信號函數sigaction(實際上,該函數是基于BSD的,BSD為了實現可靠信號機制,又能夠統一對外接口,用sigaction函數重新實現了signal函數);
3.報文(Message)隊列(消息隊列):消息隊列是消息的鏈接表,包括Posix消息隊列system V消息隊列。有足夠權限的進程可以向隊列中添加消息,被賦予讀權限的進程則可以讀走隊列中的消息。消息隊列克服了信號承載信息量少,管道只能承載無格式字節流以及緩沖區大小受限等缺點。
4.共享內存:使得多個進程可以訪問同一塊內存空間,是最快的可用IPC形式。是針對其他通信機制運行效率較低而設計的。往往與其它通信機制,如信號量結合使用,來達到進程間的同步及互斥。
5.信號量(semaphore):主要作為進程間以及同一進程不同線程之間的同步手段。
6.套接口(Socket):更為一般的進程間通信機制,可用于不同機器之間的進程間通信。起初是由Unix系統的BSD分支開發出來的,但現在一般可以移植到其它類Unix系統上:Linux和System V的變種都支持套接字。
上面幾點來源于:https://www.ibm.com/developerworks/cn/linux/l-ipc/
基于上面的各種機制及手機的應用場景,安卓采用binder機制來實現進程間的共享機制。
根據(Aleksandar Gargenta)描述,安卓出于安全性、穩定性和內存管理的目的,android的應用和系統服務需要運行在分離的進程中,但是它們之間需要通訊和共享數據,但同時也要滿足以下幾點:
- 安全性:每一個進程就是一個沙盒,運行在一個不同的系統標識中。
- 穩定性:如果一個進程失常(例如:崩潰),它不影響其它的進程。
- 內存管理:“不需要”的進程會被移除,為新的釋放資源(主要是內存)。
- 事實上,一個單獨的Android應用可以讓它的組件運行在不同的進程中。
那安卓為什么會采用binder機制,而不用其他的機制呢?那就是基于binder機制以下幾點: - 線程遷移:遠程對象可以像本地的一樣調用自動管理線程池方法;“跳轉”到其它的進程中;同步和異步(單向)的調用模式。
- 分辨發送者和接受者(通過UID/PID)- 對于安全很重要。
- 獨特的跨進程邊界對象映射。
- 一個遠程對象的引用可以傳遞到的另外的進程中,并且可以用作一個標志令牌。
- 各個進程之間發送文件描述符的能力。
- 簡單的Android接口定義語言(AIDL)。
- 內置支持很多編組的常見數據類型。
- 通過自動生成的代理和存根簡化事務調用模型(只有Java)。
- 跨進程遞歸 – 例如:當調用本地對象上的方法時就跟遞歸的語義一樣。
- 如果客戶端和服務器運行在同樣的進程中,就會是本地執行模式(不是IPC數據信號編集)。
不過binder也存在以下的缺陷: - 不支持RPC(只有本地).
- 客戶端與服務之間是基于消息的通信 – 不適合流.
- 沒有被POSIX或任何其他標準定義.
binder的初步分析:
采用binder驅動協議,其客戶端(binder client)和服務器端(binder sever)進程間通信的過程,大概如圖:
其術語如下:
Binder (Framework):所有的IPC架構。
Binder Driver:內核級別的驅動,處理各個進程之間的通信。
Binder Protocol:底層協議(基于ioctl),用于與Binder驅動通信。
IBinder Interface:定義良好的行為(例如:方法),Binder對象必須實現。
AIDL:Android接口定義語言,用于描述IBinder接口的業務操作。
Binder (Object):通用IBinder接口的實現。
Binder Token:一個抽象的32位數值,在系統的所有進程中唯一的標識一個Binder對象.
Binder Service:真正實現Binder(對象)的業務操作.
Binder Client:一個對象,使用Binder服務提供的行為.
Binder Transaction:遠程Binder對象調用一個行為(例如:一個方法),基于Binder協議,可能涉及發送、接受的數據.
Parcel:"可以在IBinder中發送消息的容器(數據和對象的引用)",事務處理的數據單元——一個用作流出請求,另一個用作流入.
Marshalling:將高級的應用程序數據結構(例如:請求、響應參數)轉化成parcel對象的過程,目的是將它們嵌套進Binder的事務中.
Unmarshalling:將Binder事務中獲取到的parcel對象重構成高級應用的數據結構的過程(例如:請求、響應參數).
Proxy:一個AIDL接口的實現,編組、解組數據,映射調用事務的方法,將一個封裝的IBinder引用指向Binder對象.
Stub:一個AIDL接口局部的實現,當編組/解組數據時,映射事務到Binder Service調用.
Context Manager:一個特殊的已知處理的Binder對象,被用作為其它Binder注冊、查詢.
從上次寫的內存管理了解到,用內存映射可以提高文件傳輸效率,具體介紹可以參考下面的博客:
http://blog.csdn.net/xuguoli_beyondboy/article/details/50153145
android系統中,我們常常需要獲得由servicemanager管理的系統服務如:WindowService,WiFiService等等,但從上面我們了解到應用程序和服務是獨立在各個進程運行,因此應用程序肯定要頻繁發送數據給服務或從服務得到數據,由于是不同進程的通訊,其應用程序不能直接訪問服務數據,因此android用基于binder驅動的內存映射方式來實現這效果并且提高效率,其數據通信如圖:
拷貝過程:
Client將數據從用戶空間傳輸到Binder驅動;Binder驅動將第1步得到的數據拷貝到Service通過mmap申請得到的那塊物理空間;Binder驅動將第2步得到的物理空間對應的虛擬地址傳遞給Service的用戶空間;Service的用戶空間通過Binder驅動傳遞過來的虛擬地址來訪問Client傳輸過來的數據。
注:整個過程只有第2步是需要拷貝數據的,這也是Binder進程間通信機制的精華所在。這樣只對數據進行一次拷貝就完成了進程間的數據交換,從而大大了提高了客戶端和服務端的數據交互效率。
Linux中的字符設備通常要經過alloc_chrdev_region(),cdev_init()等一系列操作才能在內核中注冊自己,而misc類型驅動則相對簡單,只需要調用misc_register()就可以輕松解決。
binder.c(commondriversandroidbinder.c)與驅動相關的源碼:
static struct miscdevice binder_miscdev = {//動態分配次設備號.minor = MISC_DYNAMIC_MINOR,.name = "binder",//驅動名稱.fops = &binder_fops//Binder驅動支持的文件操作
};
//binder提供給上層應用操作文件的接口
static const struct file_operations binder_fops = {.owner = THIS_MODULE,.poll = binder_poll,.unlocked_ioctl = binder_ioctl,//IO設備操作管理.compat_ioctl = binder_ioctl,.mmap = binder_mmap,.open = binder_open,.flush = binder_flush,.release = binder_release,
};
當上層進層在訪問Binder驅動時,首先就需要打開/dev/binder節點,這個操作最終的實現是在binder_open()中。
源碼:
//Binder驅動為用戶創建一個它自己的binder_proc實體,之后用戶對Binder設備的操作將以這個對象為基礎
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);//binder_stats是binder中的統計載體數據載體hlist_add_head(&proc->proc_node, &binder_procs);//將proc加入到binder_procs的隊列頭部proc->pid = current->group_leader->pid;//進程IDINIT_LIST_HEAD(&proc->delivered_death);filp->private_data = proc;//將這個proc與filp關聯起來,這樣下次通過filp就能找到這個procbinder_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;
}
初始化完成之后,就需要把一塊拿來共享的內存塊映射到用戶進程中,其函數是mmap(),它會返回指向該塊內存的虛擬地址,關于虛擬地址和物理地址轉換,可以參考我之前寫的一篇博客:
http://blog.csdn.net/xuguoli_beyondboy/article/details/50153145
源碼:
//內存映射
static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{int ret;struct vm_struct *area;//內核的虛擬地址,而vm_area_struct *vma表示用戶的虛擬地址struct binder_proc *proc = filp->private_data;//進程結構體const char *failure_string;//失敗字符記錄struct binder_buffer *buffer//它表示要映射的物理內存在內核空間的起始位置if (proc->tsk != current)return -EINVAL;//最大虛擬內存為4Mif ((vma->vm_end - vma->vm_start) > SZ_4M)vma->vm_end = vma->vm_start + SZ_4M;binder_debug(BINDER_DEBUG_OPEN_CLOSE,"binder_mmap: %d %lx-%lx (%ld K) vma %lx pagep %lx\n",proc->pid, vma->vm_start, vma->vm_end,(vma->vm_end - vma->vm_start) / SZ_1K, vma->vm_flags,(unsigned long)pgprot_val(vma->vm_page_prot));//是否禁止了mmapif (vma->vm_flags & FORBIDDEN_MMAP_FLAGS) {ret = -EPERM;failure_string = "bad vm_flags";goto err_bad_arg;}vma->vm_flags = (vma->vm_flags | VM_DONTCOPY) & ~VM_MAYWRITE;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;//映射后的虛擬地址//它表示的是內核使用的虛擬地址與進程使用的虛擬地址之間的差值,即如果某個物理頁面在內核空間中對應的虛擬地址是addr的話,//那么這個物理頁面在進程空間對應的虛擬地址就為addr + user_buffer_offset。proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer;mutex_unlock(&binder_mmap_lock);
#ifdef CONFIG_CPU_CACHE_VIPTif (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//僅分配頁page數組的空間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;}//分配物理內存,映射到內核和用戶進程的虛擬地址中
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;binder_debug(BINDER_DEBUG_BUFFER_ALLOC,"%d: %s pages %p-%p\n", proc->pid,allocate ? "allocate" : "free", start, end);if (end <= start)return 0;trace_binder_update_page_range(proc, allocate, start, end);if (vma)mm = NULL;elsemm = get_task_mm(proc->tsk);if (mm) {down_write(&mm->mmap_sem);vma = proc->vma;if (vma && mm != proc->vma_vm_mm) {pr_err("%d: vma mm and task mm mismatch\n",proc->pid);vma = NULL;}}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;struct page **page_array_ptr;page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE];BUG_ON(*page);//分配頁的物理空間*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? */;page_array_ptr = page;ret = map_vm_area(&tmp_area, PAGE_KERNEL, &page_array_ptr);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;free_range:for (page_addr = end - PAGE_SIZE; page_addr >= start;page_addr -= PAGE_SIZE) {page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE];if (vma)zap_page_range(vma, (uintptr_t)page_addr +proc->user_buffer_offset, PAGE_SIZE, NULL);
err_vm_insert_page_failed:unmap_kernel_range((unsigned long)page_addr, PAGE_SIZE);
err_map_kernel_failed:__free_page(*page);*page = NULL;
err_alloc_page_failed:;}
err_no_vma:if (mm) {up_write(&mm->mmap_sem);mmput(mm);}return -ENOMEM;
}
這節初步介紹用戶進程和內核是如何共享一塊物理內存,這也大概了解用戶進程如何和數據內核交互數據。
參考資料:
http://blog.csdn.net/Luoshengyang/article/details/6621566
http://events.linuxfoundation.org/images/stories/slides/abs2013_gargentas.pdf
https://www.ibm.com/developerworks/cn/linux/l-ipc/
總結
以上是生活随笔為你收集整理的Binder机制(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。