Android系統之Binder通訊機制

weixin_33912246發表於2018-02-26

前言

Linunx程式中使用的通訊方式有:socket(套接字通訊),named(命令管道),pipe(管道),message queque(報文佇列),signal(訊號),share memory(共享記憶體)。

Java程式中使用的通訊方式有:socket,named,pipe等。

Android程式中使用的通訊方式主要是Binder通訊,下面就來看看Binder通訊機制

Binder通訊機制

Binder是由ClientServerServiceManagerBinder驅動程式組成。

ServiceManager

其中ServiceManagerBinder機制的守護程式,同時也是一個特殊的Service。它在init.rc裡面就開始啟動了,其中Android7.0servicemanager的啟動程式碼拆分到servicemanager.rc裡面,程式碼如下:

//frameworks/native/master/./cmds/servicemanager/servicemanager.rc

service servicemanager /system/bin/servicemanager
    class core animation
    user system
    group system readproc
    critical
    onrestart restart healthd
    onrestart restart zygote
    onrestart restart audioserver
    onrestart restart media
    onrestart restart surfaceflinger
    onrestart restart inputflinger
    onrestart restart drm
    onrestart restart cameraserver
    onrestart restart keystore
    onrestart restart gatekeeperd
    writepid /dev/cpuset/system-background/tasks
    shutdown critical

從上面程式碼可知啟動了servicemanager程式,從而執行service_manager.cmain函式,同時重啟了下面幾個模組

  • healthd 監聽電池的狀態和資訊,同時傳遞給BatteryService,從而展示電池相關的資訊。
  • zygotezygotekill的時候,servicemanager會在這裡嘗試重新喚醒它
  • audioservermedia 音視訊相關的服務
  • surfaceflinger 繪製應用程式的使用者介面的服務
  • inputflinger 系統輸入事件服務
  • drm 數字版權管理服務
  • cameraserver 相機服務
  • keystore 應用簽名檔案
  • gatekeeperd 系統的圖案/密碼認證

接下來在init.rc裡面呼叫啟動servicemanager

//init.rc
//....
  class_start core
//....

通過class_start core就啟動了servicemanager

接下里就來看看service_manager.cmain函式

//frameworks/native/master/./cmds/servicemanager/service_manager.c
int main()
{
    struct binder_state *bs;
    bs = binder_open(128*1024);
 //....
    if (binder_become_context_manager(bs)) {
        ALOGE("cannot become context manager (%s)\n", strerror(errno));
        return -1;
    }
    //....
    binder_loop(bs, svcmgr_handler);
    return 0;
}

主要做了下面幾種

  1. binder_open開啟Binder裝置
  2. binder_become_context_manager 通知Binder驅動程式自己是Binder上下文管理者
  3. binder_loop 進入一個無窮迴圈,充當Server的角色,等待Client的請求

Binder驅動程式初始化

Binder驅動程式原始碼位於/drivers/staging/android/binder.c,在原始碼中可以看到這行程式碼

device_initcall(binder_init);

binder_init()

Linux載入完核心的時候,init函式會執行device_initcall。從而執行binder_init函式,初始化binder驅動程式。再來看看binder_init()函式

//android/kernel/msm/android-7.1.2_r0.33/./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);
    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;
}

create_singlethread_workqueue 建立了一個binderworker_thread單一核心程式

debugfs_create_dir debugfs是一種核心除錯的虛擬檔案系統,核心開發者通過debugfs和使用者空間交換資料,它是在Linux執行的時候建立。這裡就是建立debugfs相應的檔案

misc_register 它傳的引數是一個結構體如下

static struct miscdevice binder_miscdev = {
    .minor = MISC_DYNAMIC_MINOR, //次裝置號動態分配
    .name = "binder",//裝置號
    .fops = &binder_fops// 裝置的檔案作業系統
};

通過misc_register函式為binder驅動註冊一個misc裝置

接下來就是建立procstate目錄下的一些檔案

binder_open()

binder_init初始化之後,接下來service_manager.cmain函式會呼叫binder_open。先來看看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 它是儲存開啟/dev/binder裝置的程式的結構體,儲存的資訊如下

struct binder_proc {
    //...
    struct rb_root threads;
    struct rb_root nodes;
    struct rb_root refs_by_desc;
    struct rb_root refs_by_node;
    struct task_struct *tsk;
    struct list_head todo;
    wait_queue_head_t wait;
    int max_threads;
    long default_priority;
      //...
};
  1. threads 儲存binder_proc程式內的用來使用者請求處理的執行緒,執行緒最大數由max_threads決定
  2. nodes 儲存binder_proc程式內的binder的實體
  3. refs_by_descrefs_by_node 儲存binder_proc程式內的binder的引用
  4. tsk 儲存binder_proc程式的地址
  5. todo 待處理的事務連結串列
  6. wait 等待處理的連結串列
  7. default_priority 預設處理的事務優先順序

get_task_struct Linux核心方法,原始碼如下

#define get_task_struct(tsk) do{ atomic_inc&((tsk) ->[usage] } while(0)

最終呼叫atomic_add函式,通過原子加的形式實現當前程式引用的計數

接下來就是對binder_proc連結串列的初始化以及其他程式相關資訊進行初始化賦值。

binder_mmap()

binder_open裡面有一句程式碼是

static int binder_open(struct inode *nodp, struct file *filp)
{
//....
  filp->private_data = proc;
//....
}

前面說到binder_init裡面會把binder驅動註冊一個misc裝置。進去misc.c會看到

//kernel/common/drivers/char/misc.c
/ * The structure passed is linked into the kernel and may not be
 *  destroyed until it has been unregistered. By default, an open()
 *  syscall to the device sets file->private_data to point to the
 *  structure. Drivers don't need open in fops for this.
 */

int misc_register(struct miscdevice * misc)
{
//....
list_add(&misc->list, &misc_list);
 out:
    mutex_unlock(&misc_mtx);
    return err;
}

把裝置儲存在misc_list裡面,在上面的註釋可知,它是在file->private_data具有指標指向的時候系統會自動呼叫misc_open函式,而在misc_open函式,會遍歷file->private_data裡面所有儲存的裝置的fops

static int misc_open(struct inode * inode, struct file * file)
{
//...
list_for_each_entry(c, &misc_list, list) {
        if (c->minor == minor) {
            new_fops = fops_get(c->fops);
            break;
        }
    }
//...
}

這裡fops是傳入的binder_miscdev結構體裡面的fopsfops對應的是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,
};

從這裡可以找出初始化binder_mmap函式。再來看看binder_mmap函式

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;
//....
}

通過filp->private_data得到在開啟裝置檔案 /dev/binder建立的struct binder_proc結構,記憶體對映資訊放在vma引數中。其中vmavm_area_struct結構體,它是給程式使用的一塊連續的虛擬地址空間,而vm_struct是個核心使用的一塊連續的虛擬地址空間。在同一個物理頁面中,一方對映到程式虛擬地址空間,一方面對映到核心虛擬地址空間,這樣,程式和核心之間就可以減少一次記憶體拷貝了。接下來該函式就是處理記憶體對映和管理的詳細步驟。

binder_ioctl()

再來看看binder_ioctl函式

static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    int ret;
    struct binder_proc *proc = filp->private_data;
    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);*/
    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__);
    thread = binder_get_thread(proc);
    if (thread == NULL) {
        ret = -ENOMEM;
        goto err;
    }
    switch (cmd) {
    case BINDER_WRITE_READ:
        ret = binder_ioctl_write_read(filp, cmd, arg, thread);
        if (ret)
            goto err;
        break;
    case BINDER_SET_MAX_THREADS:
        if (copy_from_user_preempt_disabled(&proc->max_threads, ubuf, sizeof(proc->max_threads))) {
            ret = -EINVAL;
            goto err;
        }
        break;
    case BINDER_SET_CONTEXT_MGR:
        ret = binder_ioctl_set_ctx_mgr(filp);
        if (ret)
            goto err;
        break;
    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;
    case BINDER_VERSION: {
        struct binder_version __user *ver = ubuf;
        if (size != sizeof(struct binder_version)) {
            ret = -EINVAL;
            goto err;
        }
        if (put_user_preempt_disabled(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;
}

它主要是負責在兩個程式間進行收發資料

相關文章