最近学习Binder,必然少不了Binder相关的数据结构,其中最重要的就是binder_proc结构体:
struct binder_proc {
struct hlist_node proc_node; // 根据proc_node,可以获取该进程在"全局哈希表binder_procs(统计了所有的binder proc进程)"中的位置
struct rb_root threads; // binder_proc进程内用于处理用户请求的线程组成的红黑树(关联binder_thread->rb_node)
struct rb_root nodes; // binder_proc进程内的binder实体组成的红黑树(关联binder_node->rb_node)
struct rb_root refs_by_desc; // binder_proc进程内的binder引用组成的红黑树,该引用以句柄来排序(关联binder_ref->rb_node_desc)
struct rb_root refs_by_node; // binder_proc进程内的binder引用组成的红黑树,该引用以它对应的binder实体的地址来排序(关联binder_ref->rb_node)
int pid; // 进程id
struct vm_area_struct *vma; // 进程的内核虚拟内存
struct mm_struct *vma_vm_mm;
struct task_struct *tsk; // 进程控制结构体(每一个进程都由task_struct 数据结构来定义)。
struct files_struct *files; // 保存了进程打开的所有文件表数据
struct hlist_node deferred_work_node;
int deferred_work;
void *buffer; // 该进程映射的物理内存在内核空间中的起始位置
ptrdiff_t user_buffer_offset; // 内核虚拟地址与进程虚拟地址之间的差值
// 内存管理的相关变量
struct list_head buffers; // 和binder_buffer->entry关联到同一链表,从而对Binder内存进行管理
struct rb_root free_buffers; // 空闲内存,和binder_buffer->rb_node关联。
struct rb_root allocated_buffers; // 已分配内存,和binder_buffer->rb_node关联。
size_t free_async_space;
struct page **pages; // 映射内存的page页数组,page是描述物理内存的结构体
size_t buffer_size; // 映射内存的大小
uint32_t buffer_free;
struct list_head todo; // 该进程的待处理事件队列。
wait_queue_head_t wait; // 等待队列。
struct binder_stats stats;
struct list_head delivered_death;
int max_threads; // 最大线程数。定义threads中可包含的最大进程数。
int requested_threads;
int requested_threads_started;
int ready_threads;
long default_priority; // 默认优先级。
struct dentry *debugfs_entry;
};
这里的threads、nodes这些其实是binder_thread、binder_node结构体的成员变量rb_node,而proc里面描述binder_thread等的数据类型是rb_root,也就是红黑树的节点,C是没有模板的,那么这个rb_root也就是实实在在的红黑树节点,因此我的疑问就是:
怎么通过rb_node也就是红黑树的节点反向找到对应数据结构的实体?
翻代码,在binder.c里面,以获得binder_thread为例:
static struct binder_thread *binder_get_thread_ilocked(
struct binder_proc *proc, struct binder_thread *new_thread)
{
struct binder_thread *thread = NULL;
struct rb_node *parent = NULL;
struct rb_node **p = &proc->threads.rb_node;
while (*p) {
parent = *p;
// 通过parent找到binder_thread*的地址,rb_entry = container_of
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
return thread;
}
if (!new_thread)
... 没有就new 一个
这里最关键的就是 rb_entry 函数,实际的实现是 container_of :
// container_of的作用就是 通过一个结构变量中一个成员的地址找到这个结构体变量的首地址。
// ptr,type,member分别代表指针、类型、成员。
#define container_of(ptr, type, member) ({ \
// (sturct test *)0 表示数据段基址
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
// member在结构体中的偏移量
// 将type类型的member成员的地址取出。这里用法很妙,由于type指针地址是0,
// 故其成员地址都是基地址为0加上偏移地址
#define offsetof(st, m) ((size_t)&(((st *)0)->m))
总结来说就是:
①先拿到这个结构体member的地址;(此部分为什么要重新定义__mptr呢?这就是写linux内核工程师的牛逼,严谨之处。如果开发者使用时输入的参数有问题:ptr与member类型不匹配,编译时便会有warnning,但是如果去掉改行,那个就没有了,而这个警告恰恰是必须的(防止出错有不知道错误在哪里))
②拿到member的地址后减去member在结构体中的偏移量,就是目标结构体的首地址了。
不得不说这几行代码真的是把指针用得炉火纯青~而且像这样优秀的代码在linux内核里面还有很多,感兴趣可以再深入探索一下。
相关链接:
https://www.linuxidc.com/Linux/2016-08/134481.htm
https://zhuanlan.zhihu.com/p/54932270
https://www.cnblogs.com/linux-37ge/p/10219359.html