日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > linux >内容正文

linux

Linux 内核红黑树分析

發(fā)布時(shí)間:2023/12/20 linux 20 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux 内核红黑树分析 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

Android binder 內(nèi)核實(shí)現(xiàn)是用紅黑樹的,理解紅黑樹我覺得是每一個(gè)Linux er的重中之重,感謝格子森同學(xué)的投稿,周末愉快。


內(nèi)核版本為 linux4.2.1 本文主要從紅黑樹的代碼實(shí)現(xiàn)入手,來討論linux內(nèi)核中是如何實(shí)現(xiàn)紅黑樹的(主要是插入和刪除操作),其中涉及到的函數(shù)有三個(gè)__rb_insert?__rb_erase_augmented?____rb_erase_color,本文將用圖示的方式解析這三個(gè)函數(shù)。

1.紅黑樹性質(zhì)

1.節(jié)點(diǎn)是紅色或者黑色2.根節(jié)點(diǎn)是黑色3.所有葉子節(jié)點(diǎn)(null)都為黑色4.紅色節(jié)點(diǎn)的子節(jié)點(diǎn)都是黑色5.從根節(jié)點(diǎn)到葉子節(jié)點(diǎn)所有路徑上黑色節(jié)點(diǎn)數(shù)相同

2.基礎(chǔ)

2.1 節(jié)點(diǎn)結(jié)構(gòu)

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?是該節(jié)點(diǎn)左右子節(jié)點(diǎn)的指針,結(jié)構(gòu)體后面的__attribute__((aligned(sizeof(long))))讓這個(gè)結(jié)構(gòu)體按照4字節(jié)對齊(64位是8字節(jié))。?__rb_parent_color這個(gè)參數(shù)名字有點(diǎn)奇怪,父親的顏色?啥意思?不過當(dāng)我們理解了這個(gè)參數(shù)的意義之后發(fā)現(xiàn)還真就應(yīng)該叫這個(gè)名字。這個(gè)名字的意思其實(shí)是?"父親和顏色",它即代表了父節(jié)點(diǎn)的地址,也代表了自身的顏色。有人可能要問了,就這一個(gè)參數(shù),怎么代表了兩個(gè)東西?這個(gè)其實(shí)也簡單,之前我們不是提到了這個(gè)rb_node是按照4字節(jié)對齊的嘛,那么當(dāng)我們聲明一個(gè)rb_node實(shí)例的時(shí)候,它的首地址必然是4的整數(shù)倍。首地址是4的整數(shù)倍,那地址的0bit和1bit肯定都是0嘛,不管怎么玩它都是0,那不就沒啥"意義"了么,不如拿過來放顏色。那我們就讓這個(gè)__rb_parent_color?最低位,也就是第0bit來表示顏色,0代表紅色,1代表黑色。當(dāng)我們需要顏色的時(shí)候我們就看第0bit,當(dāng)我們需要父節(jié)點(diǎn)地址的時(shí)候我們就把最低兩位弄成0就行啦。

//顏色宏 #define RB_RED 0 #define RB_BLACK 1

2.2 左旋和右旋

對E進(jìn)行左旋對S進(jìn)行右旋當(dāng)然圖片中演示的是一個(gè)大致流程,因?yàn)榧t黑樹節(jié)點(diǎn)中還包含父節(jié)點(diǎn)地址以及自身顏色等信息,所以在實(shí)際旋轉(zhuǎn)的時(shí)候需要考慮更多。

2.3 輔助函數(shù)__rb_rotate_set_parents

這個(gè)函數(shù)的作用:將old的顏色和父節(jié)點(diǎn)給new,之后old將new作為新的父節(jié)點(diǎn)并且設(shè)置自己的顏色為color

static 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 }

在圖中我們可以注意到一個(gè)點(diǎn),就是old指向子節(jié)點(diǎn)的指針沒有改變,在實(shí)際程序中是需要將它指向正確位置的,不過并不是由這個(gè)輔助函數(shù)完成。

3.插入操作

在插入時(shí),我們默認(rèn)插入的節(jié)點(diǎn)是紅色,并且只有插入節(jié)點(diǎn)的父節(jié)點(diǎn)是紅色的時(shí)候才需要調(diào)整(因?yàn)檫`反了性質(zhì)4),并且我們也能保證每次調(diào)整過后,紅黑樹只會因?yàn)椴粷M足性質(zhì)4而需要再次調(diào)整,這點(diǎn)非常重要。在看代碼之前,我們需要先討論一下插入總共涉及到幾種情況。插入后一共有6種情況(本質(zhì)上3種),在邏輯上涉及到這幾個(gè)節(jié)點(diǎn),祖父節(jié)點(diǎn)G,父節(jié)點(diǎn)P,叔叔節(jié)點(diǎn)U和插入的節(jié)點(diǎn)N。其中P是G左孩子有3種情況,P是G右孩子有3種情況,它們是對稱的,在邏輯上一致,所以我們可以認(rèn)為插入操作本質(zhì)上只有3種情況需要討論,這里假設(shè)P是G的左孩子。

1.

U是紅色,此時(shí)我們需要變色

2.

U是黑色,并且G、P、N不在一條直線上,折了一下,比如

G / \ P U \ N

這種情況,我們需要對P進(jìn)行左旋(對稱情況是右旋),讓G、P、N處于同一條直線上,進(jìn)入情況3

3.

U是黑色,并且G、P、U在同一條直線上,比如

G / \ P U / N

這種情況,我們需要對G進(jìn)行右旋(對稱情況是左旋),旋轉(zhuǎn)完成后紅黑樹調(diào)整完畢。

這里我們可以注意到一個(gè)特點(diǎn),在每次調(diào)整完成之后,node總是指向局部滿足的子樹的根節(jié)點(diǎn)。?由此除了case3調(diào)整完直接結(jié)束之外,我們還能得到另外兩個(gè)結(jié)束條件。

1.

Parent為空,這證明Node已經(jīng)是根節(jié)點(diǎn)了,局部滿足即為整體滿足,調(diào)整結(jié)束,另外為了滿足根節(jié)點(diǎn)為黑色,需要將Node(根節(jié)點(diǎn))設(shè)置成黑色。

2.

Parent為黑色,之前我們有說過?"每次調(diào)整過后,紅黑樹只會因?yàn)椴粷M足性質(zhì)4而需要再次調(diào)整",既然Parent為黑色,那無論Node是什么顏色,Node和Parent都不可能為紅色,也就必然不會違反性質(zhì)4,所以調(diào)整結(jié)束。

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 don't * 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 g's 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; } } }

在這里我還想強(qiáng)調(diào)兩點(diǎn),其一就是無論是哪種情況,在當(dāng)次調(diào)整完成之后,Node指針總是指向"局部滿足的根節(jié)點(diǎn)",這體現(xiàn)了一種局部到整體的思想,在后面更復(fù)雜的刪除操作中,抱著這種思想能簡化對整個(gè)過程的理解。

另一點(diǎn)就是,在插入操作中,插入節(jié)點(diǎn)之后打破是性質(zhì)4,也只有性質(zhì)4。?我之前在理解case2和case3的時(shí)候總是抱有一個(gè)疑問,為什么case1可以變色,case2和case3要旋轉(zhuǎn)?我想這個(gè)問題現(xiàn)在能夠解答了,如果我們在case2和case3的時(shí)候僅僅變色,那么在局部 性質(zhì)4是滿足了,但是在整個(gè)紅黑樹上可能同時(shí)打破了性質(zhì)4和性質(zhì)5,這無疑是越調(diào)整越復(fù)雜了。"打破是性質(zhì)4,也只有性質(zhì)4"?就是這個(gè)意思。

4.刪除操作

刪除操作比較復(fù)雜,我們將分成兩步進(jìn)行討論,在函數(shù)中也是分成了兩個(gè)函數(shù)來處理。這兩步分別為?"繼任"?以及?"調(diào)整"?。

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繼任

因?yàn)榧t黑樹是有序的,所以首先我們要保證刪除某個(gè)節(jié)點(diǎn)N之后紅黑樹還是有序的。為了保證這一點(diǎn),我們需要選擇一個(gè)節(jié)點(diǎn)來繼任N,我們稱這個(gè)繼任者為S。當(dāng)然S不能隨便找,我們肯定要找和N"最像"的來繼任對不對,什么叫"最像",就是稍大一些的那個(gè)節(jié)點(diǎn)嘛(或者稍小一點(diǎn)的)。這樣繼任之后,紅黑樹有序的性質(zhì)就沒問題了,有保證了!那么這一步我們的核心問題就出來了,就是要找到那個(gè)繼任者是誰,然后把它挪過來,這時(shí)候原先的N就可以安心的去了。再找繼任者的時(shí)候有兩種情況需要討論。

1.N只有一個(gè)孩子或者沒有孩子 這種情況就讓它唯一的那個(gè)孩子來繼任(沒有孩子就相當(dāng)于NULL來繼任)。2.N有兩個(gè)孩子 這種情況就比較復(fù)雜了,需要我們找一找,內(nèi)核代碼實(shí)現(xiàn)中是找得稍大一些的那個(gè),怎么找呢?總結(jié)下來就一句話,右邊走一下,然后左邊走到頭。?這么說有點(diǎn)抽象,我們來張圖。

現(xiàn)在找到繼任者這個(gè)問題解決了,還有一個(gè)難點(diǎn)就是繼任完了之后一定會滿足紅黑樹的5個(gè)性質(zhì)么?這當(dāng)然是否定的,不然后面還有一步?"調(diào)整"?那不就成了擺設(shè)?什么情況下繼任完了之后我們需要調(diào)整,什么情況下不需要呢?不急,我們慢慢道來。首先我們回到N只有一個(gè)孩子或者沒有孩子這里,這里其實(shí)一共三種可能性。如下圖所示。這里我們可以得到好幾個(gè)推論,所有的疑問都可以在推論中解答,我們先認(rèn)定它就是三種可能先往下走。第一個(gè)圖,直接刪除N之后,把C變成黑色繼任N,沒有打破紅黑樹任何性質(zhì)。第二個(gè)圖,直接刪除N(或者說用NULL繼任N),沒有打破紅黑樹任何性質(zhì)。第三個(gè)圖,直接刪除N,我們能夠明顯發(fā)現(xiàn)P的左側(cè)少了一個(gè)黑色節(jié)點(diǎn),在這種情況下我們需要第二步"調(diào)整"。既然要調(diào)整,那得知道從哪開始吧,我們用rebulance指針指向P標(biāo)記一下,然后把它從"繼任"函數(shù)中返回出來并作為參數(shù)傳到"調(diào)整"函數(shù)中,意思是"喂(#`O′),rebulance這家伙左右兩邊失衡了,需要你調(diào)整一下"。到這里我么就討論完了。什么?你說N兩個(gè)孩子都有的情況還沒討論呢。嗨呀,一樣的一樣的。在這種情況,我們把N這個(gè)字母替換成S,你看是不是完全一樣?,F(xiàn)在我們可以騰出地方來好好說一下推論了。推論1:紅色節(jié)點(diǎn)不可能只有一個(gè)孩子,要么有兩個(gè)孩子,要么沒有孩子。這點(diǎn)我們可以用反證法推倒出來,首先假設(shè)一個(gè)紅色節(jié)點(diǎn)S只有一個(gè)孩子,根據(jù)性質(zhì)4(紅色節(jié)點(diǎn)的孩子都是黑色),那么它的孩子C一定是黑色。如下圖所示:C下面一定還會存在NULL節(jié)點(diǎn),S左右不平衡,假設(shè)不成立。

推論2:在推論1的基礎(chǔ)上可以得到,如果一個(gè)節(jié)點(diǎn)只有一個(gè)孩子,那么該節(jié)點(diǎn)一定是黑色,并且它的孩子一定是紅色。推倒方式和推論1類似,這里不再贅述。繼任的基本思想就如上述所示,接下來就是內(nèi)核中的代碼實(shí)現(xiàn)以及匹配代碼的一張大圖了,大家可以對照著看。

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: node's 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: node's successor is leftmost under * node's 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調(diào)整

在進(jìn)行調(diào)整步驟之前我們知道,"繼任"這一步返回了一個(gè)rebulance指針,并且作為參數(shù)傳遞給了調(diào)整函數(shù)。這個(gè)rebulance指針告訴了調(diào)整函數(shù)哪個(gè)節(jié)點(diǎn)是左右不平衡的、需要調(diào)整的。在整個(gè)調(diào)整過程中我們需要三個(gè)指針:

1.parent指針,該指針指向左右不平衡的節(jié)點(diǎn)2.node指針,該指針指向輕的那一端3.sibling指針,該指針指向重的那一端

總共的可能有8種,本質(zhì)上是4種,另外4種就是對稱情況。如果之前的函數(shù)流程弄明白了,那么這個(gè)流程也不是問題了。下面是代碼實(shí)現(xiàn)和圖解。

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 * p's 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; } } }

掃碼或長按關(guān)注

回復(fù)「?加群?」進(jìn)入技術(shù)群聊

總結(jié)

以上是生活随笔為你收集整理的Linux 内核红黑树分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。