Kernel list數據結構學習筆記

前言

近日在學習Binder驅動的binder_work時,發現了以下結構:git

struct binder_work{
    struct list_head entry;
    enum {
    ...    
    } type;

發現其中引入了list_head鏈表節點,如此一來binder_work類型也能夠看作是個鏈表了。那麼對binder_work也能夠加入鏈表中了,以binder_enqueue_work_ilocked方法爲例:github

static void
binder_enqueue_work_ilocked(struct binder_work *work,
               struct list_head *target_list)
{
    BUG_ON(target_list == NULL);
    BUG_ON(work->entry.next && !list_empty(&work->entry));
    list_add_tail(&work->entry, target_list);//將binder_work的entry成員加入target_list中
}

由此先熟悉kernel中list的實現以及經常使用方法,以幫助學習Binder內容。多線程

1. 內核鏈表初始化

1.1 建立型初始化

#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) \
    struct list_head name = LIST_HEAD_INIT(name)

這種方式構造方法十分巧妙,當調用LIST_HEAD(name)時,即爲ide

struct list_head name = { &(name), &(name) }

因爲list_head結構體的定義爲:oop

struct list_head {                                                                  
    struct list_head *next, *prev; 
};

即把next,prev都指向自身。學習

1.2 初始化模式型

我的以爲這種方式更容易看懂:測試

static inline void INIT_LIST_HEAD(struct list_head *list)
{
    WRITE_ONCE(list->next, list);//等同於list->net = list;
    list->prev = list;
}

INIT_LIST_HEAD初始化鏈表時,實質將頭部節點的next以及prev都指向本身。this

至於WRITE_ONCE第一次見,stackoverflow的解釋是:google

1.Read/write "tearing" : replacing a single memory access with many smaller ones. GCC may (and does!) in certain situations replace something like p = 0x01020304; with two 16-bit store-immediate instructions -instead of presumably placing the constant in a register and then a memory access, and so forth. WRITE_ONCE would allow us to say to GCC, "don't do that", like so: WRITE_ONCE(p, 0x01020304);
編譯器有可能對讀,寫操做分割成多條指令,經過該 WRITE_ONCE可以將保證這些操做只有一次。
2.C compilers have stopped guaranteeing that a word access is atomic. Any program which is non-race-free can be miscompiled with spectacular results. Not only that, but a compiler may decide to not keep certain values in registers inside a loop, leading to multiple references that can mess up code like this:

C的編譯器不可以保證對一個word的訪問是原子的。全部可致使多線程競爭的程序都有可能由於編譯致使出現不一樣的結果。
詳細的內容能夠參考READ_ONCE and WRITE_ONCEatom

簡單而言,有三點好處:

  1. 使代碼更容易理解
  2. 代碼規範的要求
  3. 可以檢測數據競爭(data race)

2. 內核經常使用操做:

2.1 list_add

  • list_add(struct list_head new, struct list_head head); 將節點插入到鏈表頭部

圖片描述

static inline void __list_add(struct list_head *new,
                  struct list_head *prev,
                  struct list_head *next)
{
    if (!__list_add_valid(new, prev, next))
        return;

    next->prev = new;
    new->next = next;
    new->prev = prev;
    WRITE_ONCE(prev->next, new);
}

static inline void list_add(struct list_head *new, struct list_head *head)
{
    __list_add(new, head, head->next);
}

2.2 list_add_tail

  • list_add_tail(struct list_head new, struct list_head head); 將節點插入到鏈表尾部
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
    __list_add(new, head->prev, head);
}

尾部加入節點與頭部加入節點都是使用了__list_add方法。能夠將__list_add參數理解爲:

  • 第一個爲須要加入的節點,二者都爲new;
  • 第二個爲所須要加入的位置的priv,首部加節點的參數爲head,尾部加節點的參數爲head->priv;
  • 第三個爲所須要加入的位置,首部加節點的參數爲head->next,尾部加節點的位置爲head

2.3 list_del

  • list_del(struct list_head *entry); 刪除一個節點,並將他的指針清除0
static inline void list_del(struct list_head *entry)
{
    __list_del_entry(entry);
    entry->next = LIST_POISON1;//LIST_POISINO1和LIST_POISON2是兩個無效的地址區
    entry->prev = LIST_POISON2;
}

static inline void __list_del_entry(struct list_head *entry)
{
    if (!__list_del_entry_valid(entry))
        return;

    __list_del(entry->prev, entry->next);
}

static inline bool __list_del_entry_valid(struct list_head *entry)
{
    return true;
}

static inline void __list_del(struct list_head * prev, struct list_head * next)
{
    next->prev = prev;
    WRITE_ONCE(prev->next, next);
}

圖片描述

2.4 list_del_init

  • list_del_init(struct list_head *entry); 刪除一個節點,並將他的指針再次指向本身自己
static inline void list_del_init(struct list_head *entry)
{
    __list_del_entry(entry);
    INIT_LIST_HEAD(entry);
}

2.5 list_replace

  • list_replace(struct list_head *old, struct list_head *new); 替換鏈表中的某個節點, old須要被替換的節點, new新的節點
static inline void list_replace(struct list_head *old,
                struct list_head *new)
{
    new->next = old->next;
    new->next->prev = new;
    new->prev = old->prev;
    new->prev->next = new;
}

圖片描述

2.6 list_replace_init

  • list_replace_init(struct list_head *old, struct list_head *new); 和上面的區別在於,把替換以後的老的節點進行初始化
static inline void list_replace_init(struct list_head *old,
                    struct list_head *new)
{
    list_replace(old, new);
    INIT_LIST_HEAD(old);
}

2.7 list_is_last

  • int list_is_last(const struct list_head *list, const struct list_head *head); 測試一個節點是否爲鏈表尾節點,list是要測試的節點,head是一個鏈表頭
static inline int list_is_last(const struct list_head *list,
                const struct list_head *head)
{
    return list->next == head;
}

2.8 list_empty

  • int list_empty(const struct list_head *head); 測試鏈表是否爲空
static inline int list_empty(const struct list_head *head)
{
    return READ_ONCE(head->next) == head;
}
相關文章
相關標籤/搜索