【安卓IPC-Binder通信】第三篇:binder驅(qū)動內(nèi)核解析

本章主要介紹binder驅(qū)動的三個重要函數(shù),binder_open()、binder_mmap()以及binder_ioctl()小腊。在介紹這三個函數(shù)的過程中驰徊,簡單提binder中的數(shù)據(jù)結(jié)構(gòu)。

在上一篇《ServiceManager啟動過程》中序愚,在servicemanager.c的main方法中,servicemanager進(jìn)程調(diào)用了自己的binder_open()方法,申請了128K大小的內(nèi)存空間读恃,

我們從這里開始說起。

在servicemanager自己實現(xiàn)的binder_open()中代态,它分別調(diào)用了open()寺惫,mmap()以及ioctl()三個系統(tǒng)調(diào)用。這三個方法最終會調(diào)用到binder驅(qū)動層的binder_open()蹦疑,binder_mmap()以及binder_ioctl()方法西雀。

之所以會這樣,是因為在binder驅(qū)動的調(diào)用binder_init()->init_binder_device()初始化過程中歉摧,在binder_device結(jié)構(gòu)體中艇肴,已經(jīng)對這些驅(qū)動層的方法做了映射。

kernel/msm-5.4/drivers/android/binder.c以及binder_internal.h
/**
 * struct binder_device - information about a binder device node(binder設(shè)備的節(jié)點信息)
 * @hlist:          list of binder devices (only used for devices requested via
 *                  CONFIG_ANDROID_BINDER_DEVICES)
 * @miscdev:        information about a binder character device node
 * @context:        binder context information
 * @binderfs_inode: This is the inode of the root dentry of the super block
 *                  belonging to a binderfs mount.
 */
struct binder_device {
    struct hlist_node hlist;
    struct miscdevice miscdev;
    struct binder_context context;
    struct inode *binderfs_inode;
    refcount_t ref;
};
 
//映射的結(jié)構(gòu)體
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,
};
 
//init_binder_device方法
static int __init init_binder_device(const char *name)
{
    int ret;
    struct binder_device *binder_device;
    //在內(nèi)核空間為binder_device申請內(nèi)存
    binder_device = kzalloc(sizeof(*binder_device), GFP_KERNEL);
 
    //在這里做的映射
    binder_device->miscdev.fops = &binder_fops;
    binder_device->miscdev.minor = MISC_DYNAMIC_MINOR;
    binder_device->miscdev.name = name;
 
    refcount_set(&binder_device->ref, 1);
    binder_device->context.binder_context_mgr_uid = INVALID_UID;
    binder_device->context.name = name;
    mutex_init(&binder_device->context.context_mgr_node_lock);
     
    //掛載binder驅(qū)動
    ret = misc_register(&binder_device->miscdev);
 
    hlist_add_head(&binder_device->hlist, &binder_devices);
 
    return ret;
}

1: binder_open()

任何一個進(jìn)程想要使用binder驅(qū)動進(jìn)行通信時叁温,都需要執(zhí)行binder_open方法再悼,打開binder驅(qū)動。

為的是膝但,創(chuàng)建初始化binder_proc冲九,并將當(dāng)前進(jìn)程的binder_proc保存在binder驅(qū)動管理的全局鏈表binder_procs中。

在binder驅(qū)動中锰镀,binder_procs鏈表管理著所有進(jìn)程執(zhí)行完binder_open()創(chuàng)建的binder_proc娘侍。


在這里插入圖片描述
binder_open()
static int binder_open(struct inode *nodp, struct file *filp)
{
//binder_proc結(jié)構(gòu)體,每一個進(jìn)程在binder驅(qū)動中都對應(yīng)著一個此結(jié)構(gòu)體泳炉。binder_proc是進(jìn)程在使用binder通信時在內(nèi)核中的數(shù)據(jù)結(jié)構(gòu)憾筏。
    struct binder_proc *proc;
//為binder_proc結(jié)構(gòu)體申請內(nèi)存,并初始化為0花鹅,并返回一個指向分配的內(nèi)存塊起始地址的地址指針氧腰。
    proc = kzalloc(sizeof(*proc), GFP_KERNEL);
 
//獲取一個當(dāng)前線程的task_struct(PCB進(jìn)程控制塊),在linux中,無論是進(jìn)程還是線程都是有PCB進(jìn)程控制塊來表示的古拴。
    get_task_struct(current);
 
//將當(dāng)前線程的task保存到binder進(jìn)程的tsk
    proc->tsk = current;
//初始化todo隊列(也叫做工作隊列)箩帚,用于存放待處理的請求(server端),由于服務(wù)進(jìn)程不會是一個一個處理請求的黄痪,在todo隊列中的請求會多線程處理紧帕。
    INIT_LIST_HEAD(&proc->todo);
//初始化wait隊列,當(dāng)某個服務(wù)進(jìn)程被請多的次數(shù)過多時桅打,多余的任務(wù)會放在wait隊列中是嗜。
    init_waitqueue_head(&proc->wait);
//將當(dāng)前線程線程優(yōu)先級保存在binder進(jìn)程的default_priority中
    proc->default_priority = task_nice(current); /*獲取當(dāng)前進(jìn)程的優(yōu)先級*/
 
//持有鎖
    binder_lock(__func__);
 
//將當(dāng)前的binder_proc對象加入到binder驅(qū)動管理的全局binder_procs鏈表中
    hlist_add_head(&proc->proc_node, &binder_procs);
//將當(dāng)前線程所處進(jìn)程的pid記錄在binder進(jìn)程的pid中
    proc->pid = current->group_leader->pid;
 
    INIT_LIST_HEAD(&proc->delivered_death);
//將proc記錄到文件對象(傳到用戶空間的desc描述符就是描述此文件對象的)的私有數(shù)據(jù)當(dāng)中,為的是在其它函數(shù)中也可以訪問到此binder_proc對象
    filp->private_data = proc;
 
//釋放鎖
    binder_unlock(__func__);
 
    return 0;
}

2: binder_mmap()

以下為binder驅(qū)動一次拷貝原理圖挺尾,binder_mmap主要是做了以下的事情鹅搪。


在這里插入圖片描述
binder_mmap
static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
    int ret;
//這里先申請一個內(nèi)核虛擬內(nèi)存的指針地址
    struct vm_struct *area;
//從filp中拿出私有數(shù)據(jù),在binder_open的時候就把本進(jìn)程的binder_proc保存進(jìn)去了遭铺。
    struct binder_proc *proc = filp->private_data;
    const char *failure_string;
//申請一個binder_buffer的地址丽柿,此地址是用來進(jìn)程間傳遞數(shù)據(jù)的內(nèi)核緩沖區(qū)
    struct binder_buffer *buffer;
 
//從這里可以看出,用戶空間一次申請內(nèi)存的大小不能大于4M魂挂,如果大于4M就再加4M甫题。在用戶空間的時候可以看到serviemanager申請了128*1024(128K)的大小。
    if ((vma->vm_end - vma->vm_start) > SZ_4M)
        vma->vm_end = vma->vm_start + SZ_4M;
 
    mutex_lock(&binder_mmap_lock);
//一個進(jìn)程只能映射一次
    if (proc->buffer) {
        ret = -EBUSY;
        failure_string = "already mapped";
        goto err_already_mapped;
    }
 
//使用IOREMAP的方式锰蓬,在內(nèi)核虛擬內(nèi)存申請大小為vma->vm_end - vma->vm_start的空間幔睬。
    area = get_vm_area(vma->vm_end - vma->vm_start, VM_IOREMAP);
 
//把申請到的area的首地址,保存到pro->buffer芹扭。這樣麻顶,*buffer就指向剛剛申請到的內(nèi)核空間的首地址了。
//其中proc->buffer為當(dāng)前進(jìn)程buffer的首地址舱卡,后面還會新增其他buffer
    proc->buffer = area->addr;
     
//這里是在計算用戶空間vm_area_struct vam的首地址與內(nèi)核空間vm_struct area首地址的差值(偏移量)辅肾,并保存至proc->user_buffer_offset
//由于虛擬內(nèi)存是連續(xù)的(32位應(yīng)用為0~4G),通過計算偏移量轮锥,就可以在用戶空間拿到內(nèi)核空間的地址矫钓。
    proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer;
 
 
    mutex_unlock(&binder_mmap_lock);
 
// 在物理內(nèi)存上申請對應(yīng)頁數(shù)總大小的物理內(nèi)存。這里這樣算一下是為了取整舍杜,因為待會分配物理內(nèi)存時會按照page進(jìn)行分配新娜。“sizeof(proc->pages[0]) * ((vma->vm_end - vma->vm_start) / PAGE_SIZE”
    proc->pages = kzalloc(sizeof(proc->pages[0]) * ((vma->vm_end - vma->vm_start) / PAGE_SIZE), GFP_KERNEL);
//設(shè)置內(nèi)核空間的buffer_size既绩。
    proc->buffer_size = vma->vm_end - vma->vm_start;
 
    vma->vm_ops = &binder_vm_ops;
    vma->vm_private_data = proc;
 
//分配物理頁面概龄,同時映射到內(nèi)核空間和進(jìn)程空間,先分配1個物理頁
    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;
    }
 
//讓binder_buffer 對象指向binder_proc的buffer地址饲握,也就是binder_buffer指向了area的首地址私杜。
    buffer = proc->buffer;
 
// 初始化binder_proc的buffers鏈表頭
    INIT_LIST_HEAD(&proc->buffers);
 
//將當(dāng)前binder_proc擁有的binder_buffer鏈接到binder_proc當(dāng)中蚕键,注意這個時候還只有一個binder_buffer
    list_add(&buffer->entry, &proc->buffers);
 
    buffer->free = 1; /*表明當(dāng)前buffer是空閑buffer*/
    binder_insert_free_buffer(proc, buffer); /*將當(dāng)前的binder_buffer插入到binder_proc空閑鏈表中*/
    proc->free_async_space = proc->buffer_size / 2;
    barrier();
    proc->files = get_files_struct(current);
    proc->vma = vma; /*保留這128K在用戶空間地址空間*/
    proc->vma_vm_mm = vma->vm_mm; /*保留這128在kernel的地址空間*/
 
    return 0;
 }

然后是虛擬地址與物理地址的映射,包括用戶空間與內(nèi)核空間的兩次映射衰粹。(這里主要是linux內(nèi)核的知識锣光,暫不做深究)

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 page **page;
  // mm_struct 是 task_struct 結(jié)構(gòu)體中虛擬地址管理的結(jié)構(gòu)體
  struct mm_struct *mm;
 
  if (vma)
        // binder_mmap 過程 vma 不為空,其他情況都為空
        mm = NULL;
    else
        // 獲取 mm 結(jié)構(gòu)體铝耻,從 tsk 中獲取
        mm = get_task_mm(proc->tsk);
       
  if (mm) {
    // 獲取 mm_struct 的寫信號量
    down_write(&mm->mmap_sem);
    vma = proc->vma;
  }
 
  // 此處 allocate 為 1誊爹,代表分配過程。如果為 0 則代表釋放過程
  if (allocate == 0)
    goto free_range;
 
  for (page_addr = start; page_addr < end; page_addr += PAGE_SIZE) {
    int ret;binder_update_page_range()
    page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE];
    // 分配一個 page 的物理內(nèi)存
    *page = alloc_page(GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO);
    // 物理空間映射到虛擬內(nèi)核空間
    ret = map_kernel_range_noflush((unsigned long)page_addr,
          PAGE_SIZE, PAGE_KERNEL, page);
    //  用戶空間地址 = 內(nèi)核空間地址 + 偏移量
    user_page_addr = (uintptr_t)page_addr + proc->user_buffer_offset;
    //物理空間映射到虛擬進(jìn)程空間
    ret = vm_insert_page(vma, user_page_addr, page[0]);
  }
   
  if (mm) {
    // 釋放內(nèi)存的寫信號量
    up_write(&mm->mmap_sem);
     // 減少 mm->mm_users 計數(shù)
    mmput(mm);
  }
  return 0;
 
  //釋放內(nèi)存的流程
  free_range:
  ...
  return -ENOMEM;
}

3瓢捉、linux內(nèi)核分配內(nèi)存函數(shù)

此部分為賦值了他們的文檔替废,為的是看內(nèi)核內(nèi)存分配函數(shù)時可以查詢。

1.kmalloc()
文件包含:#include
功能描述:kmalloc()用于分配在物理上連續(xù)的內(nèi)存泊柬,虛擬地址也是連續(xù)的,是基于slab分配實際上存在的內(nèi)存诈火。
但是kmalloc最多只能開辟大小為32XPAGE_SIZE的內(nèi)存,一般的PAGE_SIZE=4kB,也就是128kB的大小的內(nèi)存兽赁。
函數(shù)定義:static __always_inline void * kmalloc (size_t size, gfp_t flags);
輸入?yún)?shù):size:分配內(nèi)存的字節(jié)數(shù)
flags:分配標(biāo)志,要分配內(nèi)存的類型
返回參數(shù):返回一個指向分配的內(nèi)存塊起始地址的地址指針
釋放函數(shù):kfree()

2.kcalloc()
文件包含:#include
功能描述:kcalloc()是基于slab分配實際上存在的內(nèi)存冷守,并且在分配后將內(nèi)存中的內(nèi)容都初始化為0刀崖,但kcalloc()主要為一個數(shù)組分配
內(nèi)存空間,數(shù)組中的一個元素對應(yīng)一個內(nèi)存對象
函數(shù)定義:static inline void * kcalloc (size_t n, size_t size, gfp_t flags);
輸入?yún)?shù):n:數(shù)組中的元素個數(shù)
size:分配數(shù)組中每個元素所對應(yīng)的內(nèi)存對象的字節(jié)數(shù)
flags:分配標(biāo)志拍摇,要分配內(nèi)存的類型
返回參數(shù):返回一個對所分配的內(nèi)存對象數(shù)組的引用
釋放函數(shù):kfree()

3.kzalloc()
文件包含:#include
功能描述:kzalloc()是基于slab分配實際上存在的內(nèi)存亮钦,在分配內(nèi)存后將內(nèi)存中的內(nèi)容都初始化為0,和kcalloc()有些相似,
但不針對數(shù)組充活。
函數(shù)定義:static inline void * kzalloc (size_t size, gfp_t flags);
輸入?yún)?shù):size:分配內(nèi)存的字節(jié)數(shù)
flags:分配標(biāo)志蜂莉,要分配內(nèi)存的類型
返回參數(shù):返回一個指向分配的內(nèi)存塊起始地址的地址指針
釋放函數(shù):kfree()

4.krealloc()
文件包含:#include
功能描述:重新分配內(nèi)存,但不改變原地址空間中的內(nèi)容
函數(shù)定義:void * __must_check krealloc (const void *p, size_t new_size, gfp_t flags);
輸入?yún)?shù):p:重新分配的原內(nèi)存空間的起始地址
new_size:重新分配內(nèi)存的字節(jié)數(shù)
flags:分配標(biāo)志混卵,要分配內(nèi)存的類型
返回參數(shù):重新分配的內(nèi)存空間的起始地址
釋放函數(shù):遵從原分配內(nèi)存函數(shù)的分配函數(shù)

5.vmalloc()
文件包含:#include
功能描述:用于分配一塊非連續(xù)的地址空間映穗,物理地址一般是非連續(xù)的,但是虛擬地址是連續(xù)的幕随,分配的內(nèi)存空間
被映射進(jìn)入內(nèi)核數(shù)據(jù)段中蚁滋,從用戶空間是不可見的。vmalloc比kmalloc要慢赘淮。
函數(shù)定義:void *vmalloc(unsigned long size);
輸入?yún)?shù):size:分配內(nèi)存的字節(jié)數(shù)
返回參數(shù):返回創(chuàng)建的地址區(qū)間的虛擬地址辕录,如果分配失敗返回NULL。
釋放函數(shù):vfree()

6.vmalloc_to_page()
文件包含:#include
功能描述:找到vmalloc()所分配內(nèi)存的虛擬地址所映射的物理頁梢卸,并返回該頁的指針描述符
函數(shù)定義:struct page *vmalloc_to_page(const void *addr);
輸入?yún)?shù):addr:一般是vmalloc()的返回地址走诞,也是一個虛擬地址
返回參數(shù):addr映射的物理頁的指針描述符

7.vmalloc_user()
文件包含:#include
功能描述:功能類似于vmalloc(),在vmalloc()的基礎(chǔ)上將地址空間清零,這樣該地址空間被映射到用戶空間不會發(fā)生數(shù)據(jù)泄露
函數(shù)定義:void *vmalloc_user(unsigned long size);
輸入?yún)?shù):size:分配內(nèi)存的字節(jié)數(shù)
返回參數(shù):返回創(chuàng)建的地址區(qū)間的虛擬地址低剔,如果分配失敗返回NULL速梗。
釋放函數(shù):vfree()

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末肮塞,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子姻锁,更是在濱河造成了極大的恐慌枕赵,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件位隶,死亡現(xiàn)場離奇詭異拷窜,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)涧黄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門篮昧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人笋妥,你說我怎么就攤上這事懊昨。” “怎么了春宣?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵酵颁,是天一觀的道長。 經(jīng)常有香客問我月帝,道長躏惋,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任嚷辅,我火速辦了婚禮簿姨,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘簸搞。我一直安慰自己扁位,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布攘乒。 她就那樣靜靜地躺著贤牛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪则酝。 梳的紋絲不亂的頭發(fā)上殉簸,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機(jī)與錄音沽讹,去河邊找鬼般卑。 笑死,一個胖子當(dāng)著我的面吹牛爽雄,可吹牛的內(nèi)容都是我干的蝠检。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼挚瘟,長吁一口氣:“原來是場噩夢啊……” “哼叹谁!你這毒婦竟也來了饲梭?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤焰檩,失蹤者是張志新(化名)和其女友劉穎憔涉,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體析苫,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡兜叨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了衩侥。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片国旷。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖茫死,靈堂內(nèi)的尸體忽然破棺而出跪但,到底是詐尸還是另有隱情,我是刑警寧澤峦萎,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布特漩,位于F島的核電站,受9級特大地震影響骨杂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜雄卷,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一搓蚪、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧丁鹉,春花似錦妒潭、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至冯凹,卻和暖如春谎亩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背宇姚。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工匈庭, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人浑劳。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓阱持,卻偏偏與公主長得像,于是被迫代替她去往敵國和親魔熏。 傳聞我的和親對象是個殘疾皇子衷咽,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容