《Linux內核設計與實現》讀書筆記——內核數據結構

鏈表

簡單鏈表:

雙向鏈表:

環形鏈表:

如果對數據集合的主要操作是遍歷數據,就使用鏈表。

 

雙向鏈表

Linux不是將數據結構塞入鏈表,而是將鏈表節點塞入數據結構。

鏈表節點(include\linux\list.h):

/*
 * Simple doubly linked list implementation.
 *
 * Some of the internal functions ("__xxx") are useful when
 * manipulating whole lists rather than single entries, as
 * sometimes we already know the next/prev entries and we can
 * generate better code by using them directly rather than
 * using the generic single-entry routines.
 */
struct list_head {
    struct list_head *next, *prev;
};

list_head本身沒有意義,它需要嵌入到數據結構中才能生效:

struct clk {
    struct list_head node;
    const char *name;
    int id;
    struct module *owner;
    struct clk *parent;
    struct clk_ops *ops;
    struct kref kref;
    unsigned long rate;
    unsigned long flags;
};

使用宏container_of()可以方便地從鏈表節點找到父結構中的成員。

/**
 * container_of - cast a member of a structure out to the containing structure
 * @ptr:    the pointer to the member.
 * @type:   the type of the container struct this is embedded in.
 * @member: the name of the member within the struct.
 *
 */
#define container_of(ptr, type, member) ({          \
    const typeof(((type *)0)->member) * __mptr = (ptr); \
    (type *)((char *)__mptr - offsetof(type, member)); })

對應的有一個鏈表的宏:

/**
 * list_entry - get the struct for this entry
 * @ptr:    the &struct list_head pointer.
 * @type:   the type of the struct this is embedded in.
 * @member: the name of the list_struct within the struct.
 */
#define list_entry(ptr, type, member) \
    container_of(ptr, type, member)

用於返回包含list_head的父類型結構體相關成員。

依靠list_entry(),內核提供了創建、操作以及其它鏈表管理的各種例程。

 

鏈表操作

鏈表節點初始化:

#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) \
    struct list_head name = LIST_HEAD_INIT(name)
static inline void INIT_LIST_HEAD(struct list_head *list)
{
    list->next = list;
    list->prev = list;
}

數據結構靜態初始化中的鏈表節點初始化:

static struct ts_ops bm_ops = {
    .name         = "bm",
    .find         = bm_find,
    .init         = bm_init,
    .get_pattern      = bm_get_pattern,
    .get_pattern_len  = bm_get_pattern_len,
    .owner        = THIS_MODULE,
    .list         = LIST_HEAD_INIT(bm_ops.list)
};

雙向鏈表通常沒有所謂的頭部,但是很多時候會需要指定:

static LIST_HEAD(cpufreq_governor_list);

然後使用:

    list_for_each_entry(t, &cpufreq_governor_list, governor_list)
        if (!strnicmp(str_governor, t->name, CPUFREQ_NAME_LEN))
            return t;

鏈表相關的操作都在include\linux\list.h中,注意操作函數都是內聯函數,所以在頭文件中。

這裏不一一介紹。

 

隊列

隊列先進後出:

如果代碼符合生產者/消費者模式,則使用隊列。

 

隊列操作

Linux內核通用隊列實現稱爲kfifo,位於kernel\kfifo.c,聲明在include\linux\kfifo.h。

隊列:

struct kfifo {
    unsigned char *buffer;  /* the buffer holding the data */
    unsigned int size;  /* the size of the allocated buffer */
    unsigned int in;    /* data is added at offset (in % size) */
    unsigned int out;   /* data is extracted from off. (out % size) */
};

隊列動態分配:

extern void kfifo_init(struct kfifo *fifo, void *buffer,
            unsigned int size);
extern __must_check int kfifo_alloc(struct kfifo *fifo, unsigned int size,
            gfp_t gfp_mask);

隊列靜態分配:

#define DECLARE_KFIFO(name, size) \
union { \
    struct kfifo name; \
    unsigned char name##kfifo_buffer[size + sizeof(struct kfifo)]; \
}
#define INIT_KFIFO(name) \
    name = __kfifo_initializer(sizeof(name##kfifo_buffer) - \
                sizeof(struct kfifo), \
                name##kfifo_buffer + sizeof(struct kfifo))

推入:

extern unsigned int kfifo_in(struct kfifo *fifo,
                const void *from, unsigned int len);

送出:

extern __must_check unsigned int kfifo_out(struct kfifo *fifo,
                void *to, unsigned int len);

 

映射

映射也稱關聯數組。

映射指的是鍵值的對應關係。

鍵是唯一的。

Linux中實現的映射是一個唯一的標識數(UID,其實是一個int類型的數值)到指針的映射。

Linux提供了idr數據結構用於映射UID(include\linux\idr.h)。

struct idr {
    struct idr_layer *top;
    struct idr_layer *id_free;
    int       layers; /* only valid without concurrent changes */
    int       id_free_cnt;
    spinlock_t    lock;
};

如果需要映射一個UID到一個對象,就使用映射。

 

映射操作

初始化一個idr:

void idr_init(struct idr *idp);

獲取UID,這裏分爲兩步:

int idr_pre_get(struct idr *idp, gfp_t gfp_mask);
int idr_get_new(struct idr *idp, void *ptr, int *id);

UID存放在id中,與ptr指針關聯。

查找UID:

void *idr_find(struct idr *idp, int id);

刪除UID:

void idr_remove(struct idr *idp, int id);

銷燬idr:

void idr_destroy(struct idr *idp);

但是不會清除已分配給UID使用的內存,除非使用:

void idr_remove_all(struct idr *idp);

 

結構是一個能夠提供分層的樹形數據結構。

二叉樹每個節點最多隻有兩個出邊的樹。

二叉搜索樹是一個節點有序的二叉樹:

  • 根的左分支節點值都小於根節點值;
  • 右分支節點值都大於根節點值;
  • 所有子樹也都是二叉搜索樹;

自平衡二叉搜索樹所有葉子節點深度差不超過1。

紅黑樹是一種自平衡二叉搜索樹,有如下特定:

  • 所有節點要麼着紅色,要麼着黑色;
  • 葉子節點都是黑色的;
  • 葉子節點不包含數據;
  • 所有非葉子節點都有兩個子節點;
  • 如果一個節點是紅色,則它的子節點都是黑色;
  • 在一個節點到其葉子節點的路徑中,如果總是包含相同數目的黑色節點,則該路徑相比其它路徑是最短的;

如果需要存儲大量的數據,並且檢索迅速,就使用紅黑樹。

 

紅黑樹實現

Linux主要的平衡二叉樹數據結構就是紅黑樹。

Linux實現的紅黑樹稱爲rbtree。

根節點用rb_root表示(include\linux\rbtree.h):

struct rb_root
{
    struct rb_node *rb_node;
};

其它節點用rb_node表示:

struct rb_node
{
    unsigned long  rb_parent_color;
#define RB_RED      0
#define RB_BLACK    1
    struct rb_node *rb_right;
    struct rb_node *rb_left;
} __attribute__((aligned(sizeof(long))));

rbtree的實現並沒有提供搜索和插入例程。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章