对binder_proc的一个思考

最近学习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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值