上线啦 图谱智能网站,wordpress删除漏洞,有创意的设计工作室名字,安阳网站建设推广优化Android binder 内核实现是用红黑树的#xff0c;理解红黑树我觉得是每一个Linux er的重中之重#xff0c;感谢格子森同学的投稿#xff0c;周末愉快。内核版本为 linux4.2.1 本文主要从红黑树的代码实现入手#xff0c;来讨论linux内核中是如何实现红黑树的(主要是插入和删… Android binder 内核实现是用红黑树的理解红黑树我觉得是每一个Linux er的重中之重感谢格子森同学的投稿周末愉快。内核版本为 linux4.2.1 本文主要从红黑树的代码实现入手来讨论linux内核中是如何实现红黑树的(主要是插入和删除操作)其中涉及到的函数有三个__rb_insert __rb_erase_augmented ____rb_erase_color本文将用图示的方式解析这三个函数。1.红黑树性质1.节点是红色或者黑色2.根节点是黑色3.所有叶子节点null都为黑色4.红色节点的子节点都是黑色5.从根节点到叶子节点所有路径上黑色节点数相同2.基础2.1 节点结构struct rb_node { unsigned long __rb_parent_color; struct rb_node *rb_right; struct rb_node *rb_left;
} __attribute__((aligned(sizeof(long))));不难看出 rb_left 和 rb_right 是该节点左右子节点的指针结构体后面的__attribute__((aligned(sizeof(long))))让这个结构体按照4字节对齐64位是8字节。 __rb_parent_color这个参数名字有点奇怪父亲的颜色啥意思不过当我们理解了这个参数的意义之后发现还真就应该叫这个名字。这个名字的意思其实是 父亲和颜色它即代表了父节点的地址也代表了自身的颜色。有人可能要问了就这一个参数怎么代表了两个东西这个其实也简单之前我们不是提到了这个rb_node是按照4字节对齐的嘛那么当我们声明一个rb_node实例的时候它的首地址必然是4的整数倍。首地址是4的整数倍那地址的0bit和1bit肯定都是0嘛不管怎么玩它都是0那不就没啥意义了么不如拿过来放颜色。那我们就让这个__rb_parent_color 最低位也就是第0bit来表示颜色0代表红色1代表黑色。当我们需要颜色的时候我们就看第0bit当我们需要父节点地址的时候我们就把最低两位弄成0就行啦。//颜色宏
#define RB_RED 0
#define RB_BLACK 12.2 左旋和右旋对E进行左旋对S进行右旋当然图片中演示的是一个大致流程因为红黑树节点中还包含父节点地址以及自身颜色等信息所以在实际旋转的时候需要考虑更多。2.3 辅助函数__rb_rotate_set_parents这个函数的作用将old的颜色和父节点给new之后old将new作为新的父节点并且设置自己的颜色为colorstatic inline void
__rb_rotate_set_parents(struct rb_node *old, struct rb_node *new, struct rb_root *root, int color)
{ struct rb_node *parent rb_parent(old); new-__rb_parent_color old-__rb_parent_color;//步骤1 rb_set_parent_color(old, new, color);//步骤2 __rb_change_child(old, new, parent, root);//步骤3
}在图中我们可以注意到一个点就是old指向子节点的指针没有改变在实际程序中是需要将它指向正确位置的不过并不是由这个辅助函数完成。3.插入操作在插入时我们默认插入的节点是红色并且只有插入节点的父节点是红色的时候才需要调整因为违反了性质4并且我们也能保证每次调整过后红黑树只会因为不满足性质4而需要再次调整这点非常重要。在看代码之前我们需要先讨论一下插入总共涉及到几种情况。插入后一共有6种情况本质上3种在逻辑上涉及到这几个节点祖父节点G父节点P叔叔节点U和插入的节点N。其中P是G左孩子有3种情况P是G右孩子有3种情况它们是对称的在逻辑上一致所以我们可以认为插入操作本质上只有3种情况需要讨论这里假设P是G的左孩子。1.U是红色此时我们需要变色2.U是黑色并且G、P、N不在一条直线上折了一下比如 G / \ P U \ N这种情况我们需要对P进行左旋对称情况是右旋让G、P、N处于同一条直线上进入情况33.U是黑色并且G、P、U在同一条直线上比如 G / \ P U / N这种情况我们需要对G进行右旋对称情况是左旋旋转完成后红黑树调整完毕。这里我们可以注意到一个特点在每次调整完成之后node总是指向局部满足的子树的根节点。 由此除了case3调整完直接结束之外我们还能得到另外两个结束条件。1.Parent为空这证明Node已经是根节点了局部满足即为整体满足调整结束另外为了满足根节点为黑色需要将Node根节点设置成黑色。2.Parent为黑色之前我们有说过 每次调整过后红黑树只会因为不满足性质4而需要再次调整既然Parent为黑色那无论Node是什么颜色Node和Parent都不可能为红色也就必然不会违反性质4所以调整结束。static __always_inline void
__rb_insert(struct rb_node *node, struct rb_root *root, void (*augment_rotate)(struct rb_node *old, struct rb_node *new))
{ struct rb_node *parent rb_red_parent(node), *gparent, *tmp; while (true) { /* * Loop invariant: node is red * * If there is a black parent, we are done. * Otherwise, take some corrective action as we dont * want a red root or two consecutive red nodes. */ if (!parent) { rb_set_parent_color(node, NULL, RB_BLACK); break; } else if (rb_is_black(parent)) break; gparent rb_red_parent(parent); tmp gparent-rb_right; if (parent ! tmp) { /* parent gparent-rb_left */ if (tmp rb_is_red(tmp)) { /* * Case 1 - color flips * * G g * / \ / \ * p u -- P U * / / * n n * * However, since gs parent might be red, and * 4) does not allow this, we need to recurse * at g. */ rb_set_parent_color(tmp, gparent, RB_BLACK); rb_set_parent_color(parent, gparent, RB_BLACK); node gparent; parent rb_parent(node); rb_set_parent_color(node, parent, RB_RED); continue; } tmp parent-rb_right; if (node tmp) { /* * Case 2 - left rotate at parent * * G G * / \ / \ * p U -- n U * \ / * n p * * This still leaves us in violation of 4), the * continuation into Case 3 will fix that. */ tmp node-rb_left; WRITE_ONCE(parent-rb_right, tmp); WRITE_ONCE(node-rb_left, parent); if (tmp) rb_set_parent_color(tmp, parent, RB_BLACK); rb_set_parent_color(parent, node, RB_RED); augment_rotate(parent, node); parent node; tmp node-rb_right; } /* * Case 3 - right rotate at gparent * * G P * / \ / \ * p U -- n g * / \ * n U */ WRITE_ONCE(gparent-rb_left, tmp); /* parent-rb_right */ WRITE_ONCE(parent-rb_right, gparent); if (tmp) rb_set_parent_color(tmp, gparent, RB_BLACK); __rb_rotate_set_parents(gparent, parent, root, RB_RED); augment_rotate(gparent, parent); break; } else { tmp gparent-rb_left; if (tmp rb_is_red(tmp)) { /* Case 1 - color flips */ rb_set_parent_color(tmp, gparent, RB_BLACK); rb_set_parent_color(parent, gparent, RB_BLACK); node gparent; parent rb_parent(node); rb_set_parent_color(node, parent, RB_RED); continue; } tmp parent-rb_left; if (node tmp) { /* Case 2 - right rotate at parent */ tmp node-rb_right; WRITE_ONCE(parent-rb_left, tmp); WRITE_ONCE(node-rb_right, parent); if (tmp) rb_set_parent_color(tmp, parent, RB_BLACK); rb_set_parent_color(parent, node, RB_RED); augment_rotate(parent, node); parent node; tmp node-rb_left; } /* Case 3 - left rotate at gparent */ WRITE_ONCE(gparent-rb_right, tmp); /* parent-rb_left */ WRITE_ONCE(parent-rb_left, gparent); if (tmp) rb_set_parent_color(tmp, gparent, RB_BLACK); __rb_rotate_set_parents(gparent, parent, root, RB_RED); augment_rotate(gparent, parent); break; } }
}在这里我还想强调两点其一就是无论是哪种情况在当次调整完成之后Node指针总是指向局部满足的根节点这体现了一种局部到整体的思想在后面更复杂的删除操作中抱着这种思想能简化对整个过程的理解。另一点就是在插入操作中插入节点之后打破是性质4也只有性质4。 我之前在理解case2和case3的时候总是抱有一个疑问为什么case1可以变色case2和case3要旋转我想这个问题现在能够解答了如果我们在case2和case3的时候仅仅变色那么在局部 性质4是满足了但是在整个红黑树上可能同时打破了性质4和性质5这无疑是越调整越复杂了。打破是性质4也只有性质4 就是这个意思。4.删除操作删除操作比较复杂我们将分成两步进行讨论在函数中也是分成了两个函数来处理。这两步分别为 继任 以及 调整 。void rb_erase(struct rb_node *node, struct rb_root *root)
{ struct rb_node *rebalance; rebalance __rb_erase_augmented(node, root, dummy_callbacks); if (rebalance) ____rb_erase_color(rebalance, root, dummy_rotate);
}4.1继任因为红黑树是有序的所以首先我们要保证删除某个节点N之后红黑树还是有序的。为了保证这一点我们需要选择一个节点来继任N我们称这个继任者为S。当然S不能随便找我们肯定要找和N最像的来继任对不对什么叫最像就是稍大一些的那个节点嘛或者稍小一点的。这样继任之后红黑树有序的性质就没问题了有保证了那么这一步我们的核心问题就出来了就是要找到那个继任者是谁然后把它挪过来这时候原先的N就可以安心的去了。再找继任者的时候有两种情况需要讨论。1.N只有一个孩子或者没有孩子 这种情况就让它唯一的那个孩子来继任没有孩子就相当于NULL来继任。2.N有两个孩子 这种情况就比较复杂了需要我们找一找内核代码实现中是找得稍大一些的那个怎么找呢总结下来就一句话右边走一下然后左边走到头。 这么说有点抽象我们来张图。现在找到继任者这个问题解决了还有一个难点就是继任完了之后一定会满足红黑树的5个性质么这当然是否定的不然后面还有一步 调整 那不就成了摆设什么情况下继任完了之后我们需要调整什么情况下不需要呢不急我们慢慢道来。首先我们回到N只有一个孩子或者没有孩子这里这里其实一共三种可能性。如下图所示。这里我们可以得到好几个推论所有的疑问都可以在推论中解答我们先认定它就是三种可能先往下走。第一个图直接删除N之后把C变成黑色继任N没有打破红黑树任何性质。第二个图直接删除N或者说用NULL继任N没有打破红黑树任何性质。第三个图直接删除N我们能够明显发现P的左侧少了一个黑色节点在这种情况下我们需要第二步调整。既然要调整那得知道从哪开始吧我们用rebulance指针指向P标记一下然后把它从继任函数中返回出来并作为参数传到调整函数中意思是喂(#O′)rebulance这家伙左右两边失衡了需要你调整一下。到这里我么就讨论完了。什么你说N两个孩子都有的情况还没讨论呢。嗨呀一样的一样的。在这种情况我们把N这个字母替换成S你看是不是完全一样。现在我们可以腾出地方来好好说一下推论了。推论1红色节点不可能只有一个孩子要么有两个孩子要么没有孩子。这点我们可以用反证法推倒出来首先假设一个红色节点S只有一个孩子根据性质4红色节点的孩子都是黑色那么它的孩子C一定是黑色。如下图所示C下面一定还会存在NULL节点S左右不平衡假设不成立。推论2在推论1的基础上可以得到如果一个节点只有一个孩子那么该节点一定是黑色并且它的孩子一定是红色。推倒方式和推论1类似这里不再赘述。继任的基本思想就如上述所示接下来就是内核中的代码实现以及匹配代码的一张大图了大家可以对照着看。static __always_inline struct rb_node *
__rb_erase_augmented(struct rb_node *node, struct rb_root *root, const struct rb_augment_callbacks *augment)
{ struct rb_node *child node-rb_right; struct rb_node *tmp node-rb_left; struct rb_node *parent, *rebalance; unsigned long pc; if (!tmp) { /* * Case 1: node to erase has no more than 1 child (easy!) * * Note that if there is one child it must be red due to 5) * and node must be black due to 4). We adjust colors locally * so as to bypass __rb_erase_color() later on. */ pc node-__rb_parent_color; parent __rb_parent(pc); __rb_change_child(node, child, parent, root); if (child) { child-__rb_parent_color pc; rebalance NULL; } else rebalance __rb_is_black(pc) ? parent : NULL; tmp parent; } else if (!child) { /* Still case 1, but this time the child is node-rb_left */ tmp-__rb_parent_color pc node-__rb_parent_color; parent __rb_parent(pc); __rb_change_child(node, tmp, parent, root); rebalance NULL; tmp parent; } else { struct rb_node *successor child, *child2; tmp child-rb_left; if (!tmp) { /* * Case 2: nodes successor is its right child * * (n) (s) * / \ / \ * (x) (s) - (x) (c) * \ * (c) */ parent successor; child2 successor-rb_right; augment-copy(node, successor); } else { /* * Case 3: nodes successor is leftmost under * nodes right child subtree * * (n) (s) * / \ / \ * (x) (y) - (x) (y) * / / * (p) (p) * / / * (s) (c) * \ * (c) */ do { parent successor; successor tmp; tmp tmp-rb_left; } while (tmp); child2 successor-rb_right; WRITE_ONCE(parent-rb_left, child2); WRITE_ONCE(successor-rb_right, child); rb_set_parent(child, successor); augment-copy(node, successor); augment-propagate(parent, successor); } tmp node-rb_left; WRITE_ONCE(successor-rb_left, tmp); rb_set_parent(tmp, successor); pc node-__rb_parent_color; tmp __rb_parent(pc); __rb_change_child(node, successor, tmp, root); if (child2) { successor-__rb_parent_color pc; rb_set_parent_color(child2, parent, RB_BLACK); rebalance NULL; } else { unsigned long pc2 successor-__rb_parent_color; successor-__rb_parent_color pc; rebalance __rb_is_black(pc2) ? parent : NULL; } tmp successor; } augment-propagate(tmp, NULL); return rebalance;
} 4.2调整在进行调整步骤之前我们知道继任这一步返回了一个rebulance指针并且作为参数传递给了调整函数。这个rebulance指针告诉了调整函数哪个节点是左右不平衡的、需要调整的。在整个调整过程中我们需要三个指针1.parent指针该指针指向左右不平衡的节点2.node指针该指针指向轻的那一端3.sibling指针该指针指向重的那一端总共的可能有8种本质上是4种另外4种就是对称情况。如果之前的函数流程弄明白了那么这个流程也不是问题了。下面是代码实现和图解。static __always_inline void
____rb_erase_color(struct rb_node *parent, struct rb_root *root, void (*augment_rotate)(struct rb_node *old, struct rb_node *new))
{ struct rb_node *node NULL, *sibling, *tmp1, *tmp2; while (true) { /* * Loop invariants: * - node is black (or NULL on first iteration) * - node is not the root (parent is not NULL) * - All leaf paths going through parent and node have a * black node count that is 1 lower than other leaf paths. */ sibling parent-rb_right; if (node ! sibling) { /* node parent-rb_left */ if (rb_is_red(sibling)) { /* * Case 1 - left rotate at parent * * P S * / \ / \ * N s -- p Sr * / \ / \ * Sl Sr N Sl */ tmp1 sibling-rb_left; WRITE_ONCE(parent-rb_right, tmp1); WRITE_ONCE(sibling-rb_left, parent); rb_set_parent_color(tmp1, parent, RB_BLACK); __rb_rotate_set_parents(parent, sibling, root, RB_RED); augment_rotate(parent, sibling); sibling tmp1; } tmp1 sibling-rb_right; if (!tmp1 || rb_is_black(tmp1)) { tmp2 sibling-rb_left; if (!tmp2 || rb_is_black(tmp2)) { /* * Case 2 - sibling color flip * (p could be either color here) * * (p) (p) * / \ / \ * N S -- N s * / \ / \ * Sl Sr Sl Sr * * This leaves us violating 5) which * can be fixed by flipping p to black * if it was red, or by recursing at p. * p is red when coming from Case 1. */ rb_set_parent_color(sibling, parent, RB_RED); if (rb_is_red(parent)) rb_set_black(parent); else { node parent; parent rb_parent(node); if (parent) continue; } break; } /* * Case 3 - right rotate at sibling * (p could be either color here) * * (p) (p) * / \ / \ * N S -- N Sl * / \ \ * sl Sr s * \ * Sr */ tmp1 tmp2-rb_right; WRITE_ONCE(sibling-rb_left, tmp1); WRITE_ONCE(tmp2-rb_right, sibling); WRITE_ONCE(parent-rb_right, tmp2); if (tmp1) rb_set_parent_color(tmp1, sibling, RB_BLACK); augment_rotate(sibling, tmp2); tmp1 sibling; sibling tmp2; } /* * Case 4 - left rotate at parent color flips * (p and sl could be either color here. * After rotation, p becomes black, s acquires * ps color, and sl keeps its color) * * (p) (s) * / \ / \ * N S -- P Sr * / \ / \ * (sl) sr N (sl) */ tmp2 sibling-rb_left; WRITE_ONCE(parent-rb_right, tmp2); WRITE_ONCE(sibling-rb_left, parent); rb_set_parent_color(tmp1, sibling, RB_BLACK); if (tmp2) rb_set_parent(tmp2, parent); __rb_rotate_set_parents(parent, sibling, root, RB_BLACK); augment_rotate(parent, sibling); break; } else { sibling parent-rb_left; if (rb_is_red(sibling)) { /* Case 1 - right rotate at parent */ tmp1 sibling-rb_right; WRITE_ONCE(parent-rb_left, tmp1); WRITE_ONCE(sibling-rb_right, parent); rb_set_parent_color(tmp1, parent, RB_BLACK); __rb_rotate_set_parents(parent, sibling, root, RB_RED); augment_rotate(parent, sibling); sibling tmp1; } tmp1 sibling-rb_left; if (!tmp1 || rb_is_black(tmp1)) { tmp2 sibling-rb_right; if (!tmp2 || rb_is_black(tmp2)) { /* Case 2 - sibling color flip */ rb_set_parent_color(sibling, parent, RB_RED); if (rb_is_red(parent)) rb_set_black(parent); else { node parent; parent rb_parent(node); if (parent) continue; } break; } /* Case 3 - right rotate at sibling */ tmp1 tmp2-rb_left; WRITE_ONCE(sibling-rb_right, tmp1); WRITE_ONCE(tmp2-rb_left, sibling); WRITE_ONCE(parent-rb_left, tmp2); if (tmp1) rb_set_parent_color(tmp1, sibling, RB_BLACK); augment_rotate(sibling, tmp2); tmp1 sibling; sibling tmp2; } /* Case 4 - left rotate at parent color flips */ tmp2 sibling-rb_right; WRITE_ONCE(parent-rb_left, tmp2); WRITE_ONCE(sibling-rb_right, parent); rb_set_parent_color(tmp1, sibling, RB_BLACK); if (tmp2) rb_set_parent(tmp2, parent); __rb_rotate_set_parents(parent, sibling, root, RB_BLACK); augment_rotate(parent, sibling); break; } }
}扫码或长按关注回复「 加群 」进入技术群聊