Android跨進程通信IPC之8——Binder驅動

Android跨進程通信IPC整體內容如下

原本是沒有這篇文章的,因為原來寫Binder的時候沒打算寫Binder驅動,不過我發現后面大量的代碼都涉及到了Binder驅動,如果不講解Binder驅動,可能會對大家理解Binder造成一些折扣,我后面還是加上了這篇文章。主要內容如下:

  • 1、Binder驅動簡述
  • 2、Binder驅動的核心函數
  • 3、Binder驅動的結構體
  • 4、Binder驅動通信協議
  • 5、Binder驅動內存
  • 6、附錄:關于misc

驅動層的原路徑(這部分代碼不在AOSP中,而是位于Linux內核代碼中)

/kernel/drivers/android/binder.c
/kernel/include/uapi/linux/android/binder.h

PS:我主要上面的源代碼來分析。

或者

/kernel/drivers/staging/android/binder.c
/kernel/drivers/staging/android/uapi/binder.h

一、Binder驅動簡述

(一)、 簡述

Binder驅動是Android專用的,但底層的驅動架構與Linux驅動一樣。Binder驅動在misc設備上進行注冊,作為虛擬字符設備,沒有直接操作硬件,只對設備內存做處理。主要工作是:

  • 1、驅動設備的初始化(binder_init)
  • 2、打開(binder_open)
  • 3、映射(binder_mmap)
  • 4、數據操作(binder_ioctl)。

如下圖:

Binder驅動簡述.png

(二)、系統調用

用戶態的程序調用Kernel層驅動是需要陷入內核態,進行系統調用(system call,后面簡寫syscall),比如打開Binder驅動方法的調用鏈為:open——> _open()——> binder_open() 。open() 為用戶態的函數,_open()便是系統調用(syscall)中的響應的處理函數,通過查找,調用內核態中對應的驅動binder_open()函數,至于其他的從用戶態陷入內核態的流程也基本一致。

image.png

簡單的說,當用戶空間調用open()函數,最終會調用binder驅動的binder_open()函數;mmap()/ioctl()函數也是同理,Binder的系統中的用戶態進入內核態都依賴系統調用過程。

(三) Binder驅動的四個核心方法

3、binder_init()函數

代碼在/kernel/drivers/android/binder.c
代碼如下:

//kernel/drivers/android/binder.c      4216行
static int __init binder_init(void)
{
    int ret;
    //創建名為binder的工作隊列
    binder_deferred_workqueue = create_singlethread_workqueue("binder");
    //   ****  省略部分代碼  ****
    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);

    if (binder_debugfs_dir_entry_root) {
        //在debugfs文件系統中創建一系列的問題件
        //   ****  省略部分代碼  ****
    }
     //   ****  省略部分代碼  ****
    while ((device_name = strsep(&device_names, ","))) {
       //binder設備初始化
        ret = init_binder_device(device_name);
        if (ret)
            //binder設備初始化失敗
            goto err_init_binder_device_failed;
    }
    return ret;
}

debugfs_create_dir是指在debugfs文件系統中創建一個目錄,返回的是指向dentry的指針。當kernel中禁用debugfs的話,返回值是 -%ENODEV。默認是禁用的。如果需要打開,在目錄/kernel/arch/arm64/configs/下找到目標defconfig文件中添加一行CONFIG_DEBUG_FS=y,再重新編譯版本,即可打開debug_fs。

3.1 init_binder_device()函數解析
//kernel/drivers/android/binder.c      4189行
static int __init init_binder_device(const char *name)
{
    int ret;
    struct binder_device *binder_device;

    binder_device = kzalloc(sizeof(*binder_device), GFP_KERNEL);
    if (!binder_device)
        return -ENOMEM;

    binder_device->miscdev.fops = &binder_fops;
    binder_device->miscdev.minor = MISC_DYNAMIC_MINOR;
    binder_device->miscdev.name = name;

    binder_device->context.binder_context_mgr_uid = INVALID_UID;
    binder_device->context.name = name;
        //注冊 misc設備
    ret = misc_register(&binder_device->miscdev);
    if (ret < 0) {
        kfree(binder_device);
        return ret;
    }
    hlist_add_head(&binder_device->hlist, &binder_devices);
    return ret;
}

這里面主要就是通過調用misc_register()函數來注冊misc設備,miscdevice結構體,便是前面注冊misc設備時傳遞進去的參數

3.1.1 binder_device的結構體

這里要說一下binder_device的結構體

//kernel/drivers/android/binder.c      234行
struct binder_device {
    struct hlist_node hlist;
    struct miscdevice miscdev;
    struct binder_context context;
};

在binder_device里面有一個miscdevice

然后看下

//設備文件操作結構,這是file_operation結構
binder_device->miscdev.fops = &binder_fops;   
// 次設備號 動態分配
binder_device->miscdev.minor = MISC_DYNAMIC_MINOR;
// 設備名
binder_device->miscdev.name = name;
// uid
binder_device->context.binder_context_mgr_uid = INVALID_UID;
//上下文的名字
binder_device->context.name = name;

如果有人對miscdevice結構體有興趣 ,可以自行研究。

3.1.2 file_operations的結構體

file_operations 結構體,指定相應文件操作的方法

/kernel/drivers/android/binder.c       4173行
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,
};
3.2 binder_open()函數解析

開打binder驅動設備

/kernel/drivers/android/binder.c     3456行
static int binder_open(struct inode *nodp, struct file *filp)
{
        // binder進程
        struct binder_proc *proc;
        struct binder_device *binder_dev;
        binder_debug(BINDER_DEBUG_OPEN_CLOSE, "binder_open: %d:%d\n",
             current->group_leader->pid, current->pid);
        //為binder_proc結構體在再分配kernel內存空間
        proc = kzalloc(sizeof(*proc), GFP_KERNEL);
        if (proc == NULL)
             return -ENOMEM;
        get_task_struct(current);
        //當前線程的task保存在binder進程的tsk
        proc->tsk = current;
        proc->vma_vm_mm = current->mm;
        //初始化todo列表
        INIT_LIST_HEAD(&proc->todo);
         //初始化wait隊列
        init_waitqueue_head(&proc->wait);
        // 當前進程的nice值轉化為進程優先級
        proc->default_priority = task_nice(current);
        binder_dev = container_of(filp->private_data, struct binder_device,miscdev);
        proc->context = &binder_dev->context;
        //同步鎖,因為binder支持多線程訪問
        binder_lock(__func__);
        //BINDER_PROC對象創建+1
        binder_stats_created(BINDER_STAT_PROC);
        //將proc_node節點添加到binder_procs為表頭的隊列
        hlist_add_head(&proc->proc_node, &binder_procs);
        proc->pid = current->group_leader->pid;
        INIT_LIST_HEAD(&proc->delivered_death);
        //file文件指針的private_data變量指向binder_proc數據
        filp->private_data = proc;
        //釋放同步鎖
        binder_unlock(__func__);
        if (binder_debugfs_dir_entry_proc) {
        char strbuf[11];
        snprintf(strbuf, sizeof(strbuf), "%u", proc->pid);
/*
* proc debug entries are shared between contexts, so
* this will fail if the process tries to open the driver
* again with a different context. The priting code will
* anyway print all contexts that a given PID has, so this
* is not a problem.
*/
        proc->debugfs_entry =debugfs_create_file(strbuf,S_IRUGO,binder_debugfs_dir_entry_proc,(void *)(unsigned long)proc->pid,&binder_proc_fops);
    }
    return 0;
}

創建binder_proc對象,并把當前進程等信息保存到binder_proc對象,該對象管理IPC所需的各種新并有用其他結構體的跟結構體;再把binder_proc對象保存到文件指針filp,以及binder_proc加入到全局鏈表 binder_proc。如下圖:

binder_proc.png

Binder驅動通過static HIST_HEAD(binder_procs);,創建了全局的哈希鏈表binder_procs,用于保存所有的binder_procs隊列,每次創建的binder_proc對象都會加入binder_procs鏈表中。

3.3 binder_mmap()函數解析

binder_mmap(文件描述符,用戶虛擬內存空間)
主要功能:首先在內核虛擬地址空間,申請一塊與用戶虛擬內存相同的大小的內存;然后申請1個page大小的物理內存,再講同一塊物理內存分別映射到內核虛擬內存空間和用戶虛擬內存空間,從而實現了用戶空間的buffer與內核空間的buffer同步操作的功能。

/kernel/drivers/android/binder.c      3357行
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;
    //保證映射內存大小不超過4M
    if ((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));

    if (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;
      //地址便宜量=用戶空間地址-內核空間地址
    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
        //分配物理頁的指針數組,大小等于用戶虛擬內存/4K
    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;
    // 分配物理頁面,同時映射到內核空間和進程空間,目前只分配1個page的物理頁
    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對象,指向proc的buffer地址
    buffer = proc->buffer;
     //創建進程的buffers鏈表頭
    INIT_LIST_HEAD(&proc->buffers);
     //將binder_buffer地址  加入到所屬進程的buffer隊列
    list_add(&buffer->entry, &proc->buffers);
    buffer->free = 1;
     //將空閑的buffer放入proc->free_buffer中
    binder_insert_free_buffer(proc, buffer);
      // 異步可用空間大小為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通過加鎖,保證一次只有一個進程分享內存,保證多進程間的并發訪問。其中user_buffer_offset是虛擬進程地址與虛擬內核地址的差值,也就是說同一物理地址,當內核地址為kernel_addr,則進程地址為proc_addr=kernel_addr+user_buffer_offset。

這里面重點說下binder_update_page_range()函數

3.3.1 binder_update_page_range()函數解析
/kernel/drivers/android/binder.c      567行
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;
    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;
    else
        mm = 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;

        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;
        }
        // 物理空間映射到內核空間
        ret = map_kernel_range_noflush((unsigned long)page_addr,
                    PAGE_SIZE, PAGE_KERNEL, page);
        flush_cache_vmap((unsigned long)page_addr,
                (unsigned long)page_addr + PAGE_SIZE);
        if (ret != 1) {
            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);
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;
}

主要工作可用下面的圖來表達:

binder_update_page_range.png

binder_update_page_range 主要完成工作:分配物理內存空間,將物理空間映射到內核空間,將物理空間映射到進程空間。當然binder_update_page_range 既可以分配物理頁面,也可以釋放物理頁面

3.4 binder_ioctl()函數解析

在分析binder_ioctl()函數之前,建議大家看下我的上篇文章Android跨進程通信IPC之7——Binder相關結構體簡介了解相關的結構體,為了便于查找,這些結構體之間都留有字段的存儲關聯的結構,下面的這幅圖描述了這里說到的內容

相關結構體.png

現在讓我們詳細分析下binder_ioctl()函數

  • binder_ioctl()函數負責在兩個進程間收發IPC數據和IPC reply數據
  • ioctl(文件描述符,ioctl命令,數據類型)
  • ioctl文件描述符,是通過open()方法打開Binder Driver后返回值。
  • ioctl命令和數據類型是一體,不同的命令對應不同的數據類型

下面這些命令中BINDER_WRITE_READ使用最為頻繁,也是ioctl的最為核心的命令。


ioctl.png

代碼如下:

//kernel/drivers/android/binder.c       3239行
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    int ret;
    struct binder_proc *proc = filp->private_data;
        //binder線程
    struct binder_thread *thread;
    unsigned int size = _IOC_SIZE(cmd);
    void __user *ubuf = (void __user *)arg;

    /*pr_info("binder_ioctl: %d:%d %x %lx\n",
            proc->pid, current->pid, cmd, arg);*/

    if (unlikely(current->mm != proc->vma_vm_mm)) {
        pr_err("current mm mismatch proc mm\n");
        return -EINVAL;
    }
    trace_binder_ioctl(cmd, arg);
        //進入休眠狀態,直到中斷喚醒
    ret = wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);
    if (ret)
        goto err_unlocked;

    binder_lock(__func__);
        //獲取binder_thread
    thread = binder_get_thread(proc);
    if (thread == NULL) {
        ret = -ENOMEM;
        goto err;
    }

    switch (cmd) {
        //進行binder的讀寫操作
    case BINDER_WRITE_READ:
        ret = binder_ioctl_write_read(filp, cmd, arg, thread);
        if (ret)
            goto err;
        break;
        //設置binder的最大支持線程數
    case BINDER_SET_MAX_THREADS:
        if (copy_from_user(&proc->max_threads, ubuf, sizeof(proc->max_threads))) {
            ret = -EINVAL;
            goto err;
        }
        break;
        //設置binder的context管理者,也就是servicemanager稱為守護進程
    case BINDER_SET_CONTEXT_MGR:
        ret = binder_ioctl_set_ctx_mgr(filp);
        if (ret)
            goto err;
        break;
        //當binder線程退出,釋放binder線程
    case BINDER_THREAD_EXIT:
        binder_debug(BINDER_DEBUG_THREADS, "%d:%d exit\n",
                 proc->pid, thread->pid);
        binder_free_thread(proc, thread);
        thread = NULL;
        break;
         //獲取binder的版本號
    case BINDER_VERSION: {
        struct binder_version __user *ver = ubuf;

        if (size != sizeof(struct binder_version)) {
            ret = -EINVAL;
            goto err;
        }
        if (put_user(BINDER_CURRENT_PROTOCOL_VERSION,
                 &ver->protocol_version)) {
            ret = -EINVAL;
            goto err;
        }
        break;
    }
    default:
        ret = -EINVAL;
        goto err;
    }
    ret = 0;
err:
    if (thread)
        thread->looper &= ~BINDER_LOOPER_STATE_NEED_RETURN;
    binder_unlock(__func__);
    wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);
    if (ret && ret != -ERESTARTSYS)
        pr_info("%d:%d ioctl %x %lx returned %d\n", proc->pid, current->pid, cmd, arg, ret);
err_unlocked:
    trace_binder_ioctl_done(ret);
    return ret;
}

這里面重點說兩個函數binder_get_thread()函數和binder_ioctl_write_read()函數

3.4.1 binder_get_thread()函數解析

從binder_proc中查找binder_thread,如果當前線程已經加入到proc的線程隊列則直接返回,如果不存在則創建binder_thread,并將當前線程添加到當前的proc

//kernel/drivers/android/binder.c      3026行
static struct binder_thread *binder_get_thread(struct binder_proc *proc)
{
    struct binder_thread *thread = NULL;
    struct rb_node *parent = NULL;
    struct rb_node **p = &proc->threads.rb_node;
        //根據當前進程的pid,從binder_proc中查找相應的binder_thread
    while (*p) {
        parent = *p;
        thread = rb_entry(parent, struct binder_thread, rb_node);

        if (current->pid < thread->pid)
            p = &(*p)->rb_left;
        else if (current->pid > thread->pid)
            p = &(*p)->rb_right;
        else
            break;
    }
    if (*p == NULL) {
                // 新建binder_thread結構體
        thread = kzalloc(sizeof(*thread), GFP_KERNEL);
        if (thread == NULL)
            return NULL;
        binder_stats_created(BINDER_STAT_THREAD);
        thread->proc = proc;
                //保持當前進程(線程)的pid
        thread->pid = current->pid;
        init_waitqueue_head(&thread->wait);
        INIT_LIST_HEAD(&thread->todo);
        rb_link_node(&thread->rb_node, parent, p);
        rb_insert_color(&thread->rb_node, &proc->threads);
        thread->looper |= BINDER_LOOPER_STATE_NEED_RETURN;
        thread->return_error = BR_OK;
        thread->return_error2 = BR_OK;
    }
      return thread;
}
3.4.2 binder_ioctl_write_read()函數解析

對于ioctl()方法中,傳遞進來的命令是cmd = BINDER_WRITE_READ時執行該方法,arg是一個binder_write_read結構體

/kernel/drivers/android/binder.c      3134行
static int binder_ioctl_write_read(struct file *filp,
                unsigned int cmd, unsigned long arg,
                struct binder_thread *thread)
{
    int ret = 0;
    struct binder_proc *proc = filp->private_data;
    unsigned int size = _IOC_SIZE(cmd);
    void __user *ubuf = (void __user *)arg;
    struct binder_write_read bwr;

    if (size != sizeof(struct binder_write_read)) {
        ret = -EINVAL;
        goto out;
    }
         //把用戶控件數據ubuf拷貝到bwr
    if (copy_from_user(&bwr, ubuf, sizeof(bwr))) {
        ret = -EFAULT;
        goto out;
    }
    binder_debug(BINDER_DEBUG_READ_WRITE,
             "%d:%d write %lld at %016llx, read %lld at %016llx\n",
             proc->pid, thread->pid,
             (u64)bwr.write_size, (u64)bwr.write_buffer,
             (u64)bwr.read_size, (u64)bwr.read_buffer);
        //如果寫緩存中有數據
    if (bwr.write_size > 0) {
                //執行binder寫操作
        ret = binder_thread_write(proc, thread,
                      bwr.write_buffer,
                      bwr.write_size,
                      &bwr.write_consumed);
        trace_binder_write_done(ret);
                //如果寫失敗,再將bwr數據寫回用戶空間,并返回
        if (ret < 0) {
            bwr.read_consumed = 0;
            if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
                ret = -EFAULT;
            goto out;
        }
    }
        //當讀緩存有數據
    if (bwr.read_size > 0) {
                 //執行binder讀操作
        ret = binder_thread_read(proc, thread, bwr.read_buffer,
                     bwr.read_size,
                     &bwr.read_consumed,
                     filp->f_flags & O_NONBLOCK);
        trace_binder_read_done(ret);
        if (!list_empty(&proc->todo))
            wake_up_interruptible(&proc->wait);
                 //如果讀失敗
        if (ret < 0) {
                        //當讀失敗,再將bwr數據寫回用戶空間,并返回
            if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
                ret = -EFAULT;
            goto out;
        }
    }
    binder_debug(BINDER_DEBUG_READ_WRITE,
             "%d:%d wrote %lld of %lld, read return %lld of %lld\n",
             proc->pid, thread->pid,
             (u64)bwr.write_consumed, (u64)bwr.write_size,
             (u64)bwr.read_consumed, (u64)bwr.read_size);
         //將內核數據bwr拷貝到用戶空間ubuf
    if (copy_to_user(ubuf, &bwr, sizeof(bwr))) {
        ret = -EFAULT;
        goto out;
    }
out:
    return ret;
}

對于binder_ioctl_write_read流程圖如下圖:

binder_ioctl_write_read流程圖.png

流程:

  • 1 首先把用戶空間的數據拷貝到內核空間bwr
  • 2 其次當bwr寫緩存中有數據,則執行binder寫操作。如果寫失敗,則再將bwr數據寫回用戶控件,并退出。
  • 3 再次當bwr讀緩存中有數據,則執行binder讀緩存;當讀失敗,再將bwr數據寫會用戶空間,并退出。
  • 4 最后把內核數據拷貝到用戶空間。

3.5 總結

本章主要講解了binder驅動的的四大功能點

  • 1 binder_init :初始化字符設備
  • 2 binder_open:打開驅動設備,過程需要持有binder_main_lock同步鎖
  • 3 binder_mmap: 申請內存空間,該過程需要持有binder_mmap_lock同步鎖;
  • 4 binder_ioctl:執行相應的io操作,該過程需要持有binder_main_lock同步鎖;當處于binder_thread_read過程,卻讀緩存無數據則會先釋放該同步鎖,并處于wait_event_freezable過程,等有數據到來則喚醒并嘗試持有同步鎖。

下面我們重點介紹下binder_thread_write()函數和binder_thread_read()函數

四 Binder驅動通信

(一) Binder驅動通信簡述

Client進程通過RPC(Remote Procedure Call Protocol) 與Server通信,可以簡單地劃分為三層: 1、驅動層 2、IPC層 3、業務層。下圖的doAction()便是Client與Server共同協商好的統一方法;其中handle、PRC數據、代碼、協議、這4項組成了IPC層的數據,通過IPC層進行數據傳輸;而真正在Client和Server兩端建立通信的基礎是Binder Driver

模型如下圖:

binder通信模型.png

(二) Binder驅動通信協議

先來一發完整的Binder通信過程,如下圖:

binder流程.png

Binder協議包含在IPC數據中,分為兩類:

  • BINDER_COMMAND_PROTOCOL:binder請求碼,以"BC_" 開頭,簡稱"BC碼",從IPC層傳遞到 Binder Driver層;
  • BINDER_RETURN_PROTOCOL: binder響應碼,以"BR_"開頭,簡稱"BR碼",用于從BinderDirver層傳遞到IPC層

Binder IPC 通信至少是兩個進程的交互:

  • client進程執行binder_thread_write,根據BC_XXX 命令,生成相應的binder_work;
  • server進程執行binder_thread_read,根據binder_work.type類型,生成BR_XXX,發送用戶處理。

如下圖:

進程.png

其中binder_work.type共有6種類型

//kernel/drivers/android/binder.c      240行
struct binder_work {
    struct list_head entry;
    enum {
        BINDER_WORK_TRANSACTION = 1
        BINDER_WORK_TRANSACTION_COMPLETE,
        BINDER_WORK_NODE,
        BINDER_WORK_DEAD_BINDER,
        BINDER_WORK_DEAD_BINDER_AND_CLEAR,
        BINDER_WORK_CLEAR_DEATH_NOTIFICATION,
    } type;
};

這里用到了上面提到兩個函數一個是binder_thread_write()函數,另一個是binder_thread_read函數罵我們就來詳細研究下:

(三)、通信函數

1、binder_thread_write() 函數詳解

請求處理過程是通過binder_thread_write()函數,該方法用于處理Binder協議的請求碼。當binder_buffer存在數據,binder線程的寫操作循環執行
代碼在 kernel/drivers/android/binder.c 2248行。

代碼太多了就不全部粘貼了,只粘貼重點部分,代碼如下:

static int binder_thread_write(){
    while (ptr < end && thread->return_error == BR_OK) {
        //獲取IPC數據中的Binder協議的BC碼
        if (get_user(cmd, (uint32_t __user *)ptr))
            return -EFAULT;
        switch (cmd) {
            case BC_INCREFS: ...
            case BC_ACQUIRE: ...
            case BC_RELEASE: ...
            case BC_DECREFS: ...
            case BC_INCREFS_DONE: ...
            case BC_ACQUIRE_DONE: ...
            case BC_FREE_BUFFER: ...
            
            case BC_TRANSACTION:
            case BC_REPLY: {
                struct binder_transaction_data tr;
                //拷貝用戶控件tr到內核
                copy_from_user(&tr, ptr, sizeof(tr));
                binder_transaction(proc, thread, &tr, cmd == BC_REPLY);
                break;
            case BC_REGISTER_LOOPER: ...
            case BC_ENTER_LOOPER: ...
            case BC_EXIT_LOOPER: ...
            case BC_REQUEST_DEATH_NOTIFICATION: ...
            case BC_CLEAR_DEATH_NOTIFICATION:  ...
            case BC_DEAD_BINDER_DONE: ...
            }
        }
    }
}

對于 請求碼為BC_TRANSCATION或BC_REPLY時,會執行binder_transaction()方法,這是最頻繁的操作。那我們一起來看下binder_transaction()函數。

1.1、binder_transaction() 函數詳解

這塊的代碼依舊很多,我就只粘貼重點了,全部代碼在/kernel/drivers/android/binder.c 1827行。

static void binder_transaction(struct binder_proc *proc,
               struct binder_thread *thread,
               struct binder_transaction_data *tr, int reply){
    //根絕各種判斷,獲取如下信息:
    //目標進程
    struct binder_proc *target_proc;  
    // 目標線程
    struct binder_thread *target_thread; 
     // 目標binder節點
    struct binder_node *target_node;    
    //目標TODO隊列
    struct list_head *target_list;
    // 目標等待隊列     
    wait_queue_head_t *target_wait;    
   //*** 省略部分代碼 ***
   
    //分配兩個結構體內存
    t = kzalloc(sizeof(*t), GFP_KERNEL);
    if (t == NULL) {
        return_error = BR_FAILED_REPLY;
        goto err_alloc_t_failed;
    }
    binder_stats_created(BINDER_STAT_TRANSACTION);
    *tcomplete = kzalloc(sizeof(*tcomplete), GFP_KERNEL);
    //*** 省略部分代碼 ***
    //從target_proc分配一塊buffer
    t->buffer = binder_alloc_buf(target_proc, tr->data_size,
     //*** 省略部分代碼 ***
    for (; offp < off_end; offp++) {
     //*** 省略部分代碼 ***
        switch (fp->type) {
        case BINDER_TYPE_BINDER: ...
        case BINDER_TYPE_WEAK_BINDER: ...
        case BINDER_TYPE_HANDLE: ...
        case BINDER_TYPE_WEAK_HANDLE: ...
        case BINDER_TYPE_FD: ...
        }
    }
    //分別給target_list和當前線程TODO隊列插入事務
    t->work.type = BINDER_WORK_TRANSACTION;
    list_add_tail(&t->work.entry, target_list);
    tcomplete->type = BINDER_WORK_TRANSACTION_COMPLETE;
    list_add_tail(&tcomplete->entry, &thread->todo);
    if (target_wait)
        wake_up_interruptible(target_wait);
    return;
}

replay的過程會找到 target_thread,非reply則一般找到target_proc,對于特殊的嵌套binder call會根據transaction_stack來決定是否插入事物到目標進程。

1.2、BC_PROTOCOL 請求碼

Binder請求碼,在binder.h里面的366行。是用binder_driver_command_protocol 來定義的,是用于應用程序向Binder驅動設備發送請求消息,應用程序包含Client端和Server端,以BC_開頭,供給17條 ( - 表示目前不支持請求碼)

BC請求碼.png

重點說幾個:

  • BC_FREE_BUFFER:通過mmap()映射內存,其中ServiceMananger映射的空間大小為128K,其他Binder應用的進程映射的內存大小為8K-1M,Binder驅動基于這塊映射的內存采用最佳匹配算法來動態分配和釋放,通過binder_buffer結構體中free字段來表示相應的buffer是空閑還是已分配狀態。對于已分配的buffer加入binder_proc中的allocated_buffers紅黑樹;對于空閑的buffers加入到binder_proch中的free_buffer紅黑樹。當應用程序需要內存時,根據所需內存大小從free_buffers中找到最合適的內存,并放入allocated_buffers樹;當應用程序處理完后,必須盡快用BC_FREE_BUFFER命令來釋放該buffer,從而添加回free_buffers樹中。
  • BC_INCREFS、BC_ACQUIRE、BC_RELEASE、BC_DECREFS等請求碼的作用是對binder的 強弱引用的技術操作,用于實現強/弱 指針的功能。
  • 對于參數類型 binder_ptr_cookie是由binder指針和cookie組成
  • Binder線程創建與退出:
  • BC_ENTER_LOOPER: binder主線程(由應用層發起)的創建會向驅動發送該消息;joinThreadPool()過程創建binder主線程;
  • BC_REGISTER_LOOPER: Binder用于驅動層決策而創建新的binder線程;joinThreadPool()過程,創建非binder主線程;
  • BC_EXIT_LOOPER:退出Binder線程,對于binder主線程是不能退出的;joinThreadPool的過程出現timeout,并且非binder主線程,則會退出該binder線程。
1.3、binder_thread_read()函數詳解

響應處理過程是通過binder_thread_read()函數,該方法根據不同的binder_work->type以及不同的狀態生成不同的響應碼
代碼太多了,我只截取了部分代碼,具體代碼在/kernel/drivers/android/binder.c 2650行

static int binder_thread_read(){
     //*** 省略部分代碼 ***
    // 根據 wait_for_proc_work 來決定wait在當前線程還是進程的等待隊列
    if (wait_for_proc_work) {
         //*** 省略部分代碼 ***
        ret = wait_event_freezable_exclusive(proc->wait, binder_has_proc_work(proc, thread));
          //*** 省略部分代碼 ***
    } else {
        //*** 省略部分代碼 ***
        ret = wait_event_freezable(thread->wait, binder_has_thread_work(thread));
       //*** 省略部分代碼 ***
    }
   //*** 省略部分代碼 ***
    while (1) {
         //*** 省略部分代碼 ***
         //當&thread->todo和&proc->todo都為空時,goto到retry標志處,否則往下執行:
    if (!list_empty(&thread->todo)) {
        w = list_first_entry(&thread->todo, struct binder_work,entry);
    } else if (!list_empty(&proc->todo) && wait_for_proc_work) {
        w = list_first_entry(&proc->todo, struct binder_work,entry);
    } else {
        /* no data added */
        if (ptr - buffer == 4 &&
             !(thread->looper & BINDER_LOOPER_STATE_NEED_RETURN))
                goto retry;
        break;
    }
        struct binder_transaction_data tr;
        struct binder_transaction *t = NULL;
        switch (w->type) {
          case BINDER_WORK_TRANSACTION: ...
          case BINDER_WORK_TRANSACTION_COMPLETE: ...
          case BINDER_WORK_NODE: ...
          case BINDER_WORK_DEAD_BINDER: ...
          case BINDER_WORK_DEAD_BINDER_AND_CLEAR: ...
          case BINDER_WORK_CLEAR_DEATH_NOTIFICATION: ...
        }
        ...
    }
done:
    *consumed = ptr - buffer;
        //當滿足請求線程  加上 已準備線程數 等于0  并且 其已啟動線程小于最大線程數15,并且looper狀態為已注冊或已進入時創建新的線程。
    if (proc->requested_threads + proc->ready_threads == 0 &&
        proc->requested_threads_started < proc->max_threads &&
        (thread->looper & (BINDER_LOOPER_STATE_REGISTERED |
         BINDER_LOOPER_STATE_ENTERED)) /* the user-space code fails to */
         /*spawn a new thread if we leave this out */) {
        proc->requested_threads++;
        binder_debug(BINDER_DEBUG_THREADS,
                 "%d:%d BR_SPAWN_LOOPER\n",
                 proc->pid, thread->pid);
                //生成BR_SPAWN_LOOPER命令,用于創建新的線程
        if (put_user(BR_SPAWN_LOOPER, (uint32_t __user *)buffer))
            return -EFAULT;
        binder_stat_br(proc, thread, BR_SPAWN_LOOPER);
    }
    return 0;
}

當transaction堆棧為空,且線程todo鏈表為空,且non_block=false時,意味著沒有任務事物需要處理,會進入等待客戶端請求的狀態。當有事物需要處理時便會進入循環處理過程,并生成相應的響應碼。

在Binder驅動層面,只有進入binder_thread_read()方法時,同時滿足以下條件,才會生成BR_SPAWN_LOOPER命令,當用戶態進程收到該命令則會創新新線程:

  • binder_proc的requested_threads線程數為0
  • binder_proc的ready_threads線程數為0
  • binder_proc的requested_threads_started個數小于15(即最大線程個數)
  • binder_thread的looper狀態為BINDER_LOOPER_STATE_REGISTERED或者BINDER_LOOPER_STATE_ENTERED

那么問題來了,什么時候處理的響應碼?通過上面的Binder通信協議圖,可以知道處理響應碼的過程是在用戶態處理,下一篇文章會講解到用戶控件IPCThreadState類中IPCThreadState::waitForResponse()和IPCThreadState::executeCommand()兩個方法共同處理Binder協議的18個響應碼

1.3、BR_PROTOCOL 響應碼

binder響應碼,在binder.h里面的278行是用enum binder_driver_return_protocol來定義的,是binder設備向應用程序回復的消息,應用程序包含Client端和Serve端,以BR_開頭,合計18條。

BR響應碼.png

說幾個難點:

  • BR_SPAWN_LOOPER:binder驅動已經檢測到進程中沒有線程等待即將到來的事物。那么當一個進程接收到這條命令時,該進程必須創建一條新的服務線程并注冊該線程,在接下來的響應過程會看到合何時生成該響應嘛
  • BR_TRANSACTION_COMPLETE:當Client端向Binder驅動發送BC_TRANSACTION命令后,Client會受到BR_TRANSACTION_COMPLETE命令,告知Client端請求命令發送成功能;對于Server向Binder驅動發送BC_REPLY命令后,Server端會受到BR_TRANSACTION_COMPLETE命令,告知Server端請求回應命令發送成功。
  • BR_READ_REPLY: 當應用層向Binder驅動發送Binder調用時,若Binder應用層的另一個端已經死亡,則驅動回應BR_DEAD_BINDER命令。
  • BR_FAILED_REPLY:當應用層向Binder驅動發送Binder調用是,若transcation出錯,比如調用的函數號不存在,則驅動回應BR_FAILED_REPLY。

五、Binder內存

為了讓大家更好的理解Binder機制,特意插播一條"廣告",主要是講解Binder內存,Binder內存我主要分3個內容來依次講解,分別為:

  • 1、mmap機制
  • 2、內存分配
  • 3、內存釋放

(一) mmap機制

上面從代碼的角度闡釋了binder_mmap,也是Binder進程通信效率高的核心機制所在,如下圖:

mmap機制1.png
mmap機制2.png

虛擬進程地址空間(vm_area_struct)和虛擬內核地址空間(vm_struct)都映射到同一塊物理內存空間。當Client端與Server端發送數據時,Client(作為數據發送端)先從自己的進程空間把IPC通信數據copy_from_user拷貝到內核空間,而Server端(作為數據接收端)與內核共享數據,不再需要拷貝數據,而是通過內存地址空間的偏移量,即可獲悉內存地址,整個過程只要發生一次內存拷貝。一般地做法,需要Client端進程空間拷貝到內核空間,再由內核空間拷貝到Server進程,會發生兩次拷貝。

對于進程和內核虛擬地址映射到同一個物理內存的操作是發生在數據接收端,而數據發送端還是需要將用戶態的數據復制到內核態。到此,可能會有同學會問,為什么不直接讓發送端和接收端直接映射到同一個物理空間,這樣不就是連一次復制操作都不需要了,0次復制操作就是與Linux標準內核的共享內存你的IPC機制沒有區別了,對于共享內存雖然效率高,但是對于多進程的同步問題比較復雜,而管道/消息隊列等IPC需要復制2次,效率低。關于Linux效率的對比,請看前面的文章。總之Android選擇Binder是基于速度和安全性的考慮。

下面這圖是從Binder在進程間數據通信的流程圖,從圖中更能明白Binder的內存轉移關系。

binder內存轉移.png

(二) 內存分配

Binder內存分配方法通過binder_alloc_buf()方法,內存管理單元為binder_buffer結構體,只有在binder_transaction過程中才需要分配buffer。具體代碼在/kernel/drivers/android/binder.c 678行,我選取了重點部分

static struct binder_buffer *binder_alloc_buf(struct binder_proc *proc,
                          size_t data_size, size_t offsets_size, int is_async)
{ 
     //*** 省略部分代碼 ***
      //如果不存在虛擬地址空間為,則直接返回
    if (proc->vma == NULL) {
        pr_err("%d: binder_alloc_buf, no vma\n",
               proc->pid);
        return NULL;
    }
        
    data_offsets_size = ALIGN(data_size, sizeof(void *)) +
        ALIGN(offsets_size, sizeof(void *));
        //非法的size,直接返回
    if (data_offsets_size < data_size || data_offsets_size < offsets_size) {
        binder_user_error("%d: got transaction with invalid size %zd-%zd\n",
                proc->pid, data_size, offsets_size);
        return NULL;
    }
    size = data_offsets_size + ALIGN(extra_buffers_size, sizeof(void *));
        //非法的size,直接返回
    if (size < data_offsets_size || size < extra_buffers_size) {
        binder_user_error("%d: got transaction with invalid extra_buffers_size %zd\n",
                  proc->pid, extra_buffers_size);
        return NULL;
    }
        //如果 剩余的異步空間太少,以至于滿足需求,也直接返回
    if (is_async &&
        proc->free_async_space < size + sizeof(struct binder_buffer)) {
        binder_debug(BINDER_DEBUG_BUFFER_ALLOC,
                 "%d: binder_alloc_buf size %zd failed, no async space left\n",
                  proc->pid, size);
        return NULL;
    }
    //從binder_buffer的紅黑樹叢中,查找大小相等的buffer塊
        while (n) {
        buffer = rb_entry(n, struct binder_buffer, rb_node);
        BUG_ON(!buffer->free);
        buffer_size = binder_buffer_size(proc, buffer);

        if (size < buffer_size) {
            best_fit = n;
            n = n->rb_left;
        } else if (size > buffer_size)
            n = n->rb_right;
        else {
            best_fit = n;
            break;
        }
    }
   //如果內存分配失敗,地址為空
    if (best_fit == NULL) {
        pr_err("%d: binder_alloc_buf size %zd failed, no address space\n",
            proc->pid, size);
        return NULL;
    }
    if (n == NULL) {
        buffer = rb_entry(best_fit, struct binder_buffer, rb_node);
        buffer_size = binder_buffer_size(proc, buffer);
    }
    binder_debug(BINDER_DEBUG_BUFFER_ALLOC,
             "%d: binder_alloc_buf size %zd got buffer %p size %zd\n",
              proc->pid, size, buffer, buffer_size);

    has_page_addr =
        (void *)(((uintptr_t)buffer->data + buffer_size) & PAGE_MASK);
    if (n == NULL) {
        if (size + sizeof(struct binder_buffer) + 4 >= buffer_size)
            buffer_size = size; /* no room for other buffers */
        else
            buffer_size = size + sizeof(struct binder_buffer);
    }
    end_page_addr =
        (void *)PAGE_ALIGN((uintptr_t)buffer->data + buffer_size);
    if (end_page_addr > has_page_addr)
        end_page_addr = has_page_addr;
    if (binder_update_page_range(proc, 1,
        (void *)PAGE_ALIGN((uintptr_t)buffer->data), end_page_addr, NULL))
        return NULL;

    rb_erase(best_fit, &proc->free_buffers);
    buffer->free = 0;
    binder_insert_allocated_buffer(proc, buffer);
    if (buffer_size != size) {
        struct binder_buffer *new_buffer = (void *)buffer->data + size;

        list_add(&new_buffer->entry, &buffer->entry);
        new_buffer->free = 1;
        binder_insert_free_buffer(proc, new_buffer);
    }
 binder_debug(BINDER_DEBUG_BUFFER_ALLOC,
             "%d: binder_alloc_buf size %zd got %p\n",
              proc->pid, size, buffer);
    buffer->data_size = data_size;
    buffer->offsets_size = offsets_size;
    buffer->extra_buffers_size = extra_buffers_size;
    buffer->async_transaction = is_async;
    if (is_async) {
        proc->free_async_space -= size + sizeof(struct binder_buffer);
        binder_debug(BINDER_DEBUG_BUFFER_ALLOC_ASYNC,
                 "%d: binder_alloc_buf size %zd async free %zd\n",
                  proc->pid, size, proc->free_async_space);
    }
    return buffer;
}

(三)、內存釋放

內存釋放相關函數:

  • binder_free_buf() : 在/kernel/drivers/android/binder.c 847行
  • binder_delete_free_buffer():在/kernel/drivers/android/binder.c 802行
  • binder_transaction_buffer_release():在/kernel/drivers/android/binder.c 1430行

大家有興趣可以自己去研究下,這里就不詳細解說了。

六、附錄:關于misc

(一)、linux子系統-miscdevice

雜項設備(miscdevice) 是嵌入式系統中用的比較多的一種設備類型。在Linux驅動中把無法歸類的五花八門的設備定義為混雜設備(用miscdevice結構體表述)。Linux內核所提供的miscdevice有很強的包容性,各種無法歸結為標準字符設備的類型都可以定義為miscdevice,譬如NVRAM,看門狗,實時時鐘,字符LCD等,就像一組大雜燴。

在Linux內核里把所有misc設備組織在一起,構成一個子系統(subsys),統一進行管理。在這個子系統里的所有miscdevice類型的設備共享一個主設備號MISC_MAJOR(即10),這次設備號不同。

在內核中用struct miscdevice表示miscdevice設備,具體的定義在內核頭文件"include/linux/miscdevice.h"中

//https://github.com/torvalds/linux/blob/master/include/linux/miscdevice.h    63行
struct miscdevice  {
    int minor;
    const char *name;
    const struct file_operations *fops;
    struct list_head list;
    struct device *parent;
    struct device *this_device;
    const char *nodename;
    mode_t mode;
};

miscdevice的API的實現在drivers/char/misc.c中,misc子系統的初始化,以及misc設備的注冊,注銷等接口都實現在這個文件中。通過閱讀這個文件,miscdevice類型的設備實際上就是對字符設備的簡單封裝,最直接的證據可以看misdevice子系統的初始化函數misc_init(),在這個函數里,撇開其他代碼不看,其中有如下兩行關鍵代碼:

//drivers/char/misc.c  279行
...
misc_class = class_create(THIS_MODULE, "misc");
...
if (register_chrdev(MISC_MAJOR,"misc",&misc_fops))

代碼解析:

  • 第一行,創建了一個名字叫misc的類,具體表現是在/sys/class目錄下創建一個名為misc的目錄。以后美注冊一個自己的miscdevice都會在該目錄下新建一項。
  • 第二行,調用register_chrdev為給定的主設備號MISC_MAJOR(10)注冊0~255供256個次設備號,并為每個設備建立一個對應的默認的cdev結構,該函數是2.6內核之前的老函數,現在已經不建議使用了。由此可見misc設備其實也就是主設備號是MISC_MAJOR(10)的字符設備。從面相對象的角度來看,字符設備類是misc設備類的父類。同時我們也主要注意到采用這個函數注冊后實際上系統最多支持有255個驅動自定義的雜項設備,因為雜項設備子系統模塊自己占用了一個次設備號0。查看源文件可知,目前內核里已經被預先使用的子設備號定義在include/linux/miscdevice.h的開頭

在這個子系統中所有的miscdevice設備形成了一個鏈表,他們共享一個主設備號10,但它們有不同的次設備號。對設備訪問時內核根據次設備號查找對應的miscdevice設備。這一點我們可以通過閱讀misc子系統提供的注冊接口函數misc_register()和注銷接口函數misc_deregister()來理解。這就不深入講解了,有興趣的可以自己去研究。

在這個子系統中所有的miscdevice設備不僅共享了主設備號,還共享了一個misc_open()的文件操作方法。

//drivers/char/misc.c       161行
static const struct file_operations misc_fops = {
    .owner        = THIS_MODULE,
    .open        = misc_open,
};

該方法結構在注冊設備時通過register_chrdev(MISC_MAJOR,"misc",&misc_fops)傳遞給內核。但不要以為所有的miscdevice都使用相同的文件open方法,仔細閱讀misc_open()我們發現該函數內部會檢查驅動模塊自已定義的miscdevice結構體對象的fops成員,如果不為空將其替換掉缺省的,參考函數中的new_fops = fops_get(c->fops);以及file->f_op = new_fops;語句。如此這般,以后內核再調用設備文件的操作時就會調用該misc設備自己定義的文件操作函數了。這種實現方式有點類似java里面函數重載的概念。

(二)采用miscdevice開發設備驅動的方法

大致的步驟如下:(大家聯想下binder驅動)

  • 第一步,定義自己misc設備的文件操作函數以及file_operations結構體對象。如下:
static const struct file_operations my_misc_drv_fops = {
    .owner    = THIS_MODULE,
    .open    = my_misc_drv_open,
    .release = my_misc_drv_release,
    //根據實際情況擴充 ...
};
  • 第二步,定義自己的misc設備對象,如下:
static struct miscdevice my_misc_drv_dev = {
    .minor    = MISC_DYNAMIC_MINOR,
    .name    = KBUILD_MODNAME,
    .fops    = &my_misc_drv_fops,
};
  • .minor 如果填充MISC_DYNAMIC_MINOR,則由內核動態分配次設備號,否則根據你自己定義的指定。
  • .name 給定設備的名字,也可以直接利用內核編譯系統的環境變量KBUILD_MODNAME。
  • .fops設置為第一步定義的文件操作結構體的地址
  • 第三步,注銷和銷毀

驅動模塊一般在模塊初始化函數中調用misc_register()注冊自己的misc設備。實例代碼如下:

ret = misc_register(&my_misc_drv_dev);
if (ret < 0) {
    //失敗處理
}

注意在misc_register()函數中有如下語句

misc->this_device = device_create(misc_class, misc->parent, dev,
                  misc, "%s", misc->name);

這句話配合前面在misc_init()函數中的misc_class = class_create(THIS_MODULE, "misc");

這樣,class_create()創建了一個類,而device_create()就創建了該類的一個設備,這些都涉及linux內核的設備模型和sys文件系統額概念,暫不展開,我們只需要知道,如此這般,當該驅動模塊被加載(insmod)時,和內核態的設備模型配套運行的用戶態有個udev的后臺服務會自動在/dev下創建一個驅動模塊中注冊的misc設備對應的設備文件節點,其名字就是misc->name。這樣就省去了自己創建設備文件的麻煩。這樣也有助于動態設備的管理。

驅動模塊可以在模塊卸載函數中調用misc_deregister()注銷自己的misc設備。實例代碼如下:

misc_deregister(&my_misc_drv_dev);

在這個函數中會自動刪除`/dev下的同名設備文件。

(三)總結

雜項設備作為字符設備的封裝,為字符設備提供簡單的編程接口,如果編寫新的字符驅動,可以考慮使用雜項設備接口,簡單粗暴,只需要初始化一個miscdevice的結構體設備對象,然后調用misc_register注冊就可以了,不用的時候,調用misc_deregister進行卸載。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,119評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,382評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,038評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,853評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,616評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,112評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,192評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,355評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,869評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,727評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,928評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,467評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,165評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,570評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,813評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,585評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,892評論 2 372

推薦閱讀更多精彩內容