鏈表
簡單鏈表:
雙向鏈表:
環形鏈表:
如果對數據集合的主要操作是遍歷數據,就使用鏈表。
雙向鏈表
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的實現並沒有提供搜索和插入例程。