Binder驱动初始化

1. kernel Init

binder kernel位于如下路径:

binder.c    kernel\msm-4.19\drivers\android

其初始化调用开始于:

device_initcall(binder_init);

关于device_initcall可以参考:https://www.cnblogs.com/chaozhu/p/6410271.html

2. binder_init

从device_initcall性质可知内核启动的时候就会调用binder_init:

static int __init binder_init(void)
{
    int ret;
    char *device_name, *device_names, *device_tmp;
    struct binder_device *device;
    struct hlist_node *tmp;

    ret = binder_alloc_shrinker_init();
    if (ret)
        return ret;

    atomic_set(&binder_transaction_log.cur, ~0U);
    atomic_set(&binder_transaction_log_failed.cur, ~0U);
    //省略debug相关部分
    /*
     * Copy the module_parameter string, because we don't want to
     * tokenize it in-place.
     */
        //这里device_names=“binder,hwbinder,vndbinder”
    device_names = kzalloc(strlen(binder_devices_param) + 1, GFP_KERNEL);
    strcpy(device_names, binder_devices_param);

    device_tmp = device_names;
    while ((device_name = strsep(&device_tmp, ","))) {
        ret = init_binder_device(device_name);//依次初始化各个binder设备,查看第3节
        if (ret)
            goto err_init_binder_device_failed;
    }

    return ret;
}

3. init_binder_device

以device_name="binder"为例:

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;

//这里uid置为invalid,后续servicemanager设置上下文的时候才配置,名字就是/dev/binder
    binder_device->context.binder_context_mgr_uid = INVALID_UID;
    binder_device->context.name = name;
    mutex_init(&binder_device->context.context_mgr_node_lock);

    ret = misc_register(&binder_device->miscdev);//注册misc 设备
    if (ret < 0) {
        kfree(binder_device);
        return ret;
    }

    hlist_add_head(&binder_device->hlist, &binder_devices);//把binder设备添加到hlist中

    return ret;
}

3.1. 先介绍几个结构体

  1. binder_device 实际上binder也是被抽象为一种设备
struct binder_device {
    struct hlist_node hlist; //一种列表
    struct miscdevice miscdev; //linux标准设备之一,也就是说binder实际上是一种miscdevice, 节点路径:/dev/binder
    struct binder_context context; //binder上下文环境
};
  1. hlist_node 哈希列表,linux内核常用数据结构,参看:Linux 内核 hlist 详解
struct hlist_node {
    struct hlist_node *next, **pprev;
};
  1. miscdevice linux kernel中的一类设备
    参考Linux misc设备驱动理解
struct miscdevice  {
    int minor;//次设备号 ,通常为MISC_DYNAMIC_MINOR,动态分配
    const char *name;//设备名称
    const struct file_operations *fops; //函数操作集
    struct list_head list;
    struct device *parent;
    struct device *this_device;
    const struct attribute_group **groups;
    const char *nodename;
    umode_t mode;
};
  1. binder_context binder上下文
struct binder_context {
    struct binder_node *binder_context_mgr_node;//需要重点关注的,servicemanager初始化的时候会创建binder_node, 到时候在分析。
    struct mutex context_mgr_node_lock;
    kuid_t binder_context_mgr_uid;
    const char *name;
};

3.2. 接着看初始化

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

    binder_device->miscdev.fops = &binder_fops;//第4节介绍函数操作集
    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;
    mutex_init(&binder_device->context.context_mgr_node_lock);

    ret = misc_register(&binder_device->miscdev);

首先给binder_device分配内存,初始化设备miscdevice和context,并注册设备miscdev。完成注册之后就会生成节点/dev/binder,当用户空间调用open等访问该节点的时候,就会直接调用到binder_fops定义的对应操作函数。

 hlist_add_head(&binder_device->hlist, &binder_devices);

接着把hlist_node添加到hlist头节点,binder_devices初始化为hlist_head, 在这里可以看到会依次把前面提到的binder device添加到binder_devices列表中去。这里需要注意,每创建一个binder_device节点都会把它添加到hlist的头部,然后更新头节点,如下图红色框所示为新添加节点。


hlist.jpg

实际上,在这里创建的binder_devices总共有3个,也就是说会产生3个hlist_node,分别是binder,hwbinder,vndbinder,关于这三个binder的区别,可以拜读这篇文章:
binder,hwbinder,vndbinder之间的关系

4. 函数操作集 binder_fops

static const struct file_operations binder_fops = {
    .owner = THIS_MODULE,
    .poll = binder_poll,
    .unlocked_ioctl = binder_ioctl, //实际binder操作
    .compat_ioctl = binder_ioctl, //这个用来兼容32位用户程序访问64位的kernel
    .mmap = binder_mmap, //内存映射
    .open = binder_open, //打开binder设备
    .flush = binder_flush,
    .release = binder_release,
};

这里有三个函数指针比较重要,分别是binder_open,binder_mmap和binder_ioctl。用户空间操作binder的顺序一般是open->mmap->ioctl。

4.1 binder_open

4.1.1. 需要先了解下面的结构体

  1. binder_proc 维护调用者进程相关的一些信息,每一个进程打开binder文件都会构建一个该结构体
struct binder_proc {
    struct hlist_node proc_node; //list,用来串起所有binder_proc
    struct rb_root threads; //红黑树,用来维护当前proc中的所有binder_threads
    struct rb_root nodes; //维护当前proc中的binder node,按照node->ptr的顺序来维护
    struct rb_root refs_by_desc;//refs,以refs->desc顺序维护
    struct rb_root refs_by_node;//refs,以refs->node顺序维护
    struct list_head waiting_threads;//等待处理的thread列表
    int pid;//group_leader PID, 关于group_leader可以参考linux进程相关知识
    struct task_struct *tsk;//进程信息
    struct files_struct *files;//进程文件结构体,维护当前进程打开的文件相关信息
    struct mutex files_lock;
    struct hlist_node deferred_work_node;
    int deferred_work;
    bool is_dead;

    struct list_head todo;//待处理工作列表
    struct binder_stats stats;
    struct list_head delivered_death;
    int max_threads;
    int requested_threads;
    int requested_threads_started;
    int tmp_ref;
    struct binder_priority default_priority;
    struct dentry *debugfs_entry;
    struct binder_alloc alloc;//记录binder allocator信息的结构体
    struct binder_context *context;
    spinlock_t inner_lock;
    spinlock_t outer_lock;
};
  1. binder_alloc
  2. vm_area_struct 该结构体用来定义一段VMM内存空间,每一个这种内存空间都会有一个该结构体。所谓的VM内存空间实际上就是进程虚拟内存的一部分,拥有特殊的页面错误处理规则。
  3. binder_lru_page page关联结构体,指针page_ptr用来指向对应的page
struct binder_lru_page {
    struct list_head lru;
    struct page *page_ptr;
    struct binder_alloc *alloc;
};
  1. binder_buffer,以entry串连到binder_alloc的buffers,以rb_node分别串连到binder_alloc的free_buffers和allocated_buffers中。包含了binder_transaction 和binder_node结构体。
struct binder_buffer {
    struct list_head entry; /* free and allocated entries by address */
    struct rb_node rb_node; /* free entry by size or allocated entry */
                /* by address */
    unsigned free:1;
    unsigned allow_user_free:1;
    unsigned async_transaction:1;
    unsigned debug_id:29;

    struct binder_transaction *transaction;

    struct binder_node *target_node;
    size_t data_size;
    size_t offsets_size;
    size_t extra_buffers_size;
    void __user *user_data;
};

4.1.2. binder_open()

负责打开binder设备文件,比如"/dev/binder"

static int binder_open(struct inode *nodp, struct file *filp)
{
    struct binder_proc *proc;
    struct binder_device *binder_dev;
//给binder_proc结构体分配空间
    proc = kzalloc(sizeof(*proc), GFP_KERNEL);
//tsk指向当前进程的group_leader
    get_task_struct(current->group_leader);
    proc->tsk = current->group_leader;
//把todo标志为列表头
    INIT_LIST_HEAD(&proc->todo);
//通过miscdev的地址和它在binder_device中的偏移量找到binder_device的起始地址,
//按container_of功能,这里filp->private_data应该是指向miscdev的地址,
//但是没有弄明白啥时候指过去的。
    binder_dev = container_of(filp->private_data, struct binder_device,
                  miscdev);
    proc->context = &binder_dev->context;
    binder_alloc_init(&proc->alloc);

    binder_stats_created(BINDER_STAT_PROC);
//当前进程id,也就是打开当前binder设备的进程对应的id,比如servicemanager。
    proc->pid = current->group_leader->pid;
    INIT_LIST_HEAD(&proc->delivered_death);
    INIT_LIST_HEAD(&proc->waiting_threads);
//把当前binder_proc设置成设备私有数据,这个私有数据在调用mmap、
//ioctl等的时候会用到,关于private_data性质可以自行查找。
    filp->private_data = proc;

    mutex_lock(&binder_procs_lock);
//把binder_proc中的proc_node链接到binder_procs的头部,binder_procs的
//连接方式和前面提到的binder_devices是相似的
    hlist_add_head(&proc->proc_node, &binder_procs);
    mutex_unlock(&binder_procs_lock);

    return 0;
}

这里需要主要:
proc->context = &binder_dev->context;
binder_proc对应的context指向了binder_dev对应的context,这表示所有进程的context实际上都是指向同一个地址,也就是binder_dev的context,也就是说各个binder_proc共享一个context,对于设备/dev/binder,它的context是唯一的,后续在service_manager初始化中会知道,这个context是由service_manager创建的。

4.2 binder_mmap

这个方法的作用是进行虚拟空间地址到物理地址的映射,是的进程用户空间虚拟地址空间和内核空间的虚拟地址空间映射到同一块物理内存。


注意:
内存映射一般是server和内核的虚拟空间对物理内存的映射,也就是server端虚拟空间和内核虚拟空间共同映射到同一块物理内存,所以它们在访问数据的时候最终访问的是同一块内存,避免了一次内核到用户空间的copy过程。
binder是CS结构,一般都是Client负责write数据,Server负责read数据,所以从Client到内核需要进行一次数据copy,而内核到Server因为进行了内存映射,所以就避免了一次copy。
还需要注意的是CS结构中的Server和android的service是两个不同的概念,完全不相干,应用进程与系统service通信过程,应用进程既可以作为Client发送请求给service也会作为Server来接受service传来的数据。在后面介绍service注册的时候会看到,其实每一个进程在启动的时候都会调用mmap与binder内核映射一块内存用来接收Client端的数据。


这里只是完成了初步的映射,实际上的物理内存的分配是在数据传输的时候按需分配的。在binder_update_page_range里面调用alloc_page进行页的分配。
这里主要就是对binder_proc结构体的binder_alloc的构建,

static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
    int ret;
//通过private_data取得binder_proc
    struct binder_proc *proc = filp->private_data;

    if (proc->tsk != current->group_leader)
        return -EINVAL;
//vm_area_struct 代表进程用户空间虚拟地址空间,这里限定不可以大于4M
    if ((vma->vm_end - vma->vm_start) > SZ_4M)
        vma->vm_end = vma->vm_start + SZ_4M;

    vma->vm_flags |= VM_DONTCOPY | VM_MIXEDMAP;
    vma->vm_flags &= ~VM_MAYWRITE; //禁止写

    vma->vm_ops = &binder_vm_ops;
    vma->vm_private_data = proc;
//映射,在这里alloc代表的就是binder进程内核空间虚拟地址
    ret = binder_alloc_mmap_handler(&proc->alloc, vma);
    if (ret)
        return ret;
    mutex_lock(&proc->files_lock);
    proc->files = get_files_struct(current);
    mutex_unlock(&proc->files_lock);
    return 0;
}

4.2.1. binder_alloc_mmap_handler

binder_alloc 当前binder进程对应的alloc structure

int binder_alloc_mmap_handler(struct binder_alloc *alloc,
                  struct vm_area_struct *vma)
{
    int ret;
    struct binder_buffer *buffer;
//检查alloc buffer是否分配过
    if (alloc->buffer) {
        ret = -EBUSY;
        failure_string = "already mapped";
        goto err_already_mapped;
    }

    alloc->buffer = (void __user *)vma->vm_start;
    mutex_unlock(&binder_alloc_mmap_lock);
//这里先计算数据需要的page数n,然后分配n个pages结构体对应的空间,
//结构体binder_lru_page在前面介绍过,它拥有一个指向page的指针page_ptr
    alloc->pages = kcalloc((vma->vm_end - vma->vm_start) / PAGE_SIZE,
                   sizeof(alloc->pages[0]),
                   GFP_KERNEL);

    alloc->buffer_size = vma->vm_end - vma->vm_start;
//构建binder_buffer结构体
    buffer = kzalloc(sizeof(*buffer), GFP_KERNEL);
//user_data指向用户空间虚拟地址起始vma->vm_start
    buffer->user_data = alloc->buffer;
//把binder_buffer串到alloc->buffers list
    list_add(&buffer->entry, &alloc->buffers);
    buffer->free = 1;
把binder_buffer插入到alloc->free_buffers
    binder_insert_free_buffer(alloc, buffer);
//free_async_space 是供异步buffers使用的虚拟地址空间(VA space),
//初始化为整个虚拟地址空间的一半
    alloc->free_async_space = alloc->buffer_size / 2;
//把vma指针交给alloc
    binder_alloc_set_vma(alloc, vma);
    mmgrab(alloc->vma_vm_mm);

    return 0;
}

mmap完成之后各个结构体状态如下图所示:


mmap.jpg

从图中可以看到,这里初始化分别分配了结构体binder_alloc、binder_buffer、以及n个binder_lru_page, 至于binder_proc是在open的时候分配,vm_area_struct则是VFS(virtual File System)进行系统调用mmap的时候自动分配的。
从图中可以看到以下几点:

  1. 指针page_ptr指向null,表示当前物理页尚未分配。
  2. binder_alloc的buffer指针和vm_area_struct的start指向同一个地址。
  3. buffers是循环列表结构,保存binder_buffer结构体,binder_buffer的指针user_data实际指向了binder_alloc的buffer指针,图中未画出。
  4. free_buffers是红黑树的结构,保存了处于free状态的binder_buffer,所谓free是指可以重新分配使用。

4.2.2 内存映射状态

执行完binder_mmap之后,client和service通信过程,binder的内存映射状态如下图所示:


内存映射.jpg
  1. server在初始化的时候通过binder_mmap把自己用户空间虚拟地址buffer1和binder kernel的虚拟地址buffer2映射到相同的物理内存Mem2.
  2. client在传送数据的时候显示创建自己的数据并存储到buffer0,实际内存就是Mem1,然后调用ioctl。
  3. kernel收到之后把Mem1的数据copy到buffer2,实际上就是Mem2,然后通知server去处理。
  4. server收到通知直接读取buffer1,实际上就是读取的Mem2的数据。

注:从4.2.1节可以知道,mmap之后实际上物理内存还没有分配,物理内存的分配实在client调用ioctl的时候按需分配的。

4.3 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);
//__user代表指向用户空间虚拟地址指针,arg是用户端传递的数据对应的虚拟地址指针
//这里需要注意,kernel可以访问用户空间虚拟地址,用户端无法访问kernel虚拟地址
    void __user *ubuf = (void __user *)arg;

//根据binder_proc的rb_node threads获取对应的thread,根据thread对应的current->id来区分thread。
//如果对应id的thread不存在,则新建一个thread并加入到rb_node中。
    thread = binder_get_thread(proc);

//数据操作,这个在分析数据传输的时候再详细分析。
    switch (cmd) {
    case BINDER_WRITE_READ:
        ret = binder_ioctl_write_read(filp, cmd, arg, thread);
        if (ret)
            goto err;
        break;
        ...
        //这里省略了各项cmd,具体cmd在后续用到的时候再分析。
        }
}

关于binder_thread和binder_proc的关系用一张图来表示一下:


binder_thread.jpg
  1. 每个service在注册的时候通过open来打开binder,并创建一个binder_proc结构体(参考4.1节)。
  2. binder_proc包含一个红黑树结构threads来保存与自己相关的binder_thread。
  3. client在通过ioctl访问service的时候会创建一个和自己对应的binder_thread。
  4. binder_thread是根据对应client所对应的进程的id来区分的,通过全局变量current来获取对应进程的id。
  5. binder_thread通过rb_node串连到binder_proc的红黑树threads中,按照pid的大小顺序组织数据。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,463评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,868评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,213评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,666评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,759评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,725评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,716评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,484评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,928评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,233评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,393评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,073评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,718评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,308评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,538评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,338评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,260评论 2 352