Linux內(nèi)核代碼中廣泛使用了數(shù)據(jù)結(jié)構(gòu)和算法,其中最常用的兩個(gè)是鏈表和紅黑樹。
鏈表
Linux內(nèi)核代碼大量使用了鏈表這種數(shù)據(jù)結(jié)構(gòu)。鏈表是在解決數(shù)組不能動(dòng)態(tài)擴(kuò)展這個(gè)缺陷而產(chǎn)生的一種數(shù)據(jù)結(jié)構(gòu)。鏈表所包含的元素可以動(dòng)態(tài)創(chuàng)建并插入和刪除。鏈表的每個(gè)元素都是離散存放的,因此不需要占用連續(xù)的內(nèi)存。鏈表通常由若干節(jié)點(diǎn)組成,每個(gè)節(jié)點(diǎn)的結(jié)構(gòu)都是一樣的,由有效數(shù)據(jù)區(qū)和指針區(qū)兩部分組成。有效數(shù)據(jù)區(qū)用來(lái)存儲(chǔ)有效數(shù)據(jù)信息,而指針區(qū)用來(lái)指向鏈表的前繼節(jié)點(diǎn)或者后繼節(jié)點(diǎn)。因此,鏈表就是利用指針將各個(gè)節(jié)點(diǎn)串聯(lián)起來(lái)的一種存儲(chǔ)結(jié)構(gòu)。
(1)單向鏈表
單向鏈表的指針區(qū)只包含一個(gè)指向下一個(gè)節(jié)點(diǎn)的指針,因此會(huì)形成一個(gè)單一方向的鏈表,如下代碼所示。
struct list { int data; /*有效數(shù)據(jù)*/ struct list *next; /*指向下一個(gè)元素的指針*/ };
登錄后復(fù)制
如圖所示,單向鏈表具有單向移動(dòng)性,也就是只能訪問(wèn)當(dāng)前的節(jié)點(diǎn)的后繼節(jié)點(diǎn),而無(wú)法訪問(wèn)當(dāng)前節(jié)點(diǎn)的前繼節(jié)點(diǎn),因此在實(shí)際項(xiàng)目中運(yùn)用得比較少。
單向鏈表示意圖
(2)雙向鏈表
如圖所示,雙向鏈表和單向鏈表的區(qū)別是指針區(qū)包含了兩個(gè)指針,一個(gè)指向前繼節(jié)點(diǎn),另一個(gè)指向后繼節(jié)點(diǎn),如下代碼所示。
struct list { int data; /*有效數(shù)據(jù)*/ struct list *next; /*指向下一個(gè)元素的指針*/ struct list *prev; /*指向上一個(gè)元素的指針*/ };
登錄后復(fù)制
雙向鏈表示意圖
(3)Linux內(nèi)核鏈表實(shí)現(xiàn)
單向鏈表和雙向鏈表在實(shí)際使用中有一些局限性,如數(shù)據(jù)區(qū)必須是固定數(shù)據(jù),而實(shí)際需求是多種多樣的。這種方法無(wú)法構(gòu)建一套通用的鏈表,因?yàn)槊總€(gè)不同的數(shù)據(jù)區(qū)需要一套鏈表。為此,Linux內(nèi)核把所有鏈表操作方法的共同部分提取出來(lái),把不同的部分留給代碼編程者自己去處理。Linux內(nèi)核實(shí)現(xiàn)了一套純鏈表的封裝,鏈表節(jié)點(diǎn)數(shù)據(jù)結(jié)構(gòu)只有指針區(qū)而沒(méi)有數(shù)據(jù)區(qū),另外還封裝了各種操作函數(shù),如創(chuàng)建節(jié)點(diǎn)函數(shù)、插入節(jié)點(diǎn)函數(shù)、刪除節(jié)點(diǎn)函數(shù)、遍歷節(jié)點(diǎn)函數(shù)等。
Linux內(nèi)核鏈表使用struct list_head數(shù)據(jù)結(jié)構(gòu)來(lái)描述。
<include/linux/types.h> struct list_head { struct list_head *next, *prev; };
登錄后復(fù)制
struct list_head數(shù)據(jù)結(jié)構(gòu)不包含鏈表節(jié)點(diǎn)的數(shù)據(jù)區(qū),通常是嵌入其他數(shù)據(jù)結(jié)構(gòu),如struct page數(shù)據(jù)結(jié)構(gòu)中嵌入了一個(gè)lru鏈表節(jié)點(diǎn),通常是把page數(shù)據(jù)結(jié)構(gòu)掛入LRU鏈表。
<include/linux/mm_types.h> struct page { ... struct list_head lru; ... }
登錄后復(fù)制
鏈表頭的初始化有兩種方法,一種是靜態(tài)初始化,另一種動(dòng)態(tài)初始化。
把next和prev指針都初始化并指向自己,這樣便初始化了一個(gè)帶頭節(jié)點(diǎn)的空鏈表。
<include/linux/list.h> /*靜態(tài)初始化*/ #define LIST_HEAD_INIT(name) { &(name), &(name) } #define LIST_HEAD(name) \ struct list_head name = LIST_HEAD_INIT(name) /*動(dòng)態(tài)初始化*/ static inline void INIT_LIST_HEAD(struct list_head *list) { list->next = list; list->prev = list; }
登錄后復(fù)制
添加節(jié)點(diǎn)到一個(gè)鏈表中,內(nèi)核提供了幾個(gè)接口函數(shù),如list_add()
是把一個(gè)節(jié)點(diǎn)添加到表頭,list_add_tail()
是插入表尾。
<include/linux/list.h> void list_add(struct list_head *new, struct list_head *head) list_add_tail(struct list_head *new, struct list_head *head)
登錄后復(fù)制
遍歷節(jié)點(diǎn)的接口函數(shù)。
#define list_for_each(pos, head) \ for (pos = (head)->next; pos != (head); pos = pos->next)
登錄后復(fù)制
這個(gè)宏只是遍歷一個(gè)一個(gè)節(jié)點(diǎn)的當(dāng)前位置,那么如何獲取節(jié)點(diǎn)本身的數(shù)據(jù)結(jié)構(gòu)呢?這里還需要使用list_entry()宏。
#define list_entry(ptr, type, member) \ container_of(ptr, type, member) container_of()宏的定義在kernel.h頭文件中。 #define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );}) #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
登錄后復(fù)制
其中offsetof()
宏是通過(guò)把0地址轉(zhuǎn)換為type
類型的指針,然后去獲取該結(jié)構(gòu)體中member
成員的指針,也就是獲取了member
在type
結(jié)構(gòu)體中的偏移量。最后用指針ptr
減去offset
,就得到type
結(jié)構(gòu)體的真實(shí)地址了。
下面是遍歷鏈表的一個(gè)例子。
<drivers/block/osdblk.c> static ssize_t class_osdblk_list(struct class *c, struct class_attribute *attr, char *data) { int n = 0; struct list_head *tmp; list_for_each(tmp, &osdblkdev_list) { struct osdblk_device *osdev; osdev = list_entry(tmp, struct osdblk_device, node); n += sprintf(data+n, "%d %d %llu %llu %s\n", osdev->id, osdev->major, osdev->obj.partition, osdev->obj.id, osdev->osd_path); } return n; }
登錄后復(fù)制
紅黑樹
紅黑樹(Red Black Tree)被廣泛應(yīng)用在內(nèi)核的內(nèi)存管理和進(jìn)程調(diào)度中,用于將排序的元素組織到樹中。紅黑樹被廣泛應(yīng)用在計(jì)算機(jī)科學(xué)的各個(gè)領(lǐng)域中,它在速度和實(shí)現(xiàn)復(fù)雜度之間提供一個(gè)很好的平衡。
紅黑樹是具有以下特征的二叉樹。
每個(gè)節(jié)點(diǎn)或紅或黑。
紅黑樹的一個(gè)優(yōu)點(diǎn)是,所有重要的操作(例如插入、刪除、搜索)都可以在O(log n)時(shí)間內(nèi)完成,n為樹中元素的數(shù)目。經(jīng)典的算法教科書都會(huì)講解紅黑樹的實(shí)現(xiàn),這里只是列出一個(gè)內(nèi)核中使用紅黑樹的例子,供讀者在實(shí)際的驅(qū)動(dòng)和內(nèi)核編程中參考。這個(gè)例子可以在內(nèi)核代碼的documentation/Rbtree.txt
文件中找到。
#include <linux/init.h> #include <linux/list.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/slab.h> #include <linux/mm.h> #include <linux/rbtree.h> MODULE_AUTHOR("figo.zhang"); MODULE_DESCRIPTION(" "); MODULE_LICENSE("GPL"); struct mytype { struct rb_node node; int key; }; /*紅黑樹根節(jié)點(diǎn)*/ struct rb_root mytree = RB_ROOT; /*根據(jù)key來(lái)查找節(jié)點(diǎn)*/ struct mytype *my_search(struct rb_root *root, int new) { struct rb_node *node = root->rb_node; while (node) { struct mytype *data = container_of(node, struct mytype, node); if (data->key > new) node = node->rb_left; else if (data->key < new) node = node->rb_right; else return data; } return NULL; } /*插入一個(gè)元素到紅黑樹中*/ int my_insert(struct rb_root *root, struct mytype *data) { struct rb_node **new = &(root->rb_node), *parent=NULL; /* 尋找可以添加新節(jié)點(diǎn)的地方 */ while (*new) { struct mytype *this = container_of(*new, struct mytype, node); parent = *new; if (this->key > data->key) new = &((*new)->rb_left); else if (this->key < data->key) { new = &((*new)->rb_right); } else return -1; } /* 添加一個(gè)新節(jié)點(diǎn) */ rb_link_node(&data->node, parent, new); rb_insert_color(&data->node, root); return 0; } static int __init my_init(void) { int i; struct mytype *data; struct rb_node *node; /*插入元素*/ for (i =0; i < 20; i+=2) { data = kmalloc(sizeof(struct mytype), GFP_KERNEL); data->key = i; my_insert(&mytree, data); } /*遍歷紅黑樹,打印所有節(jié)點(diǎn)的key值*/ for (node = rb_first(&mytree); node; node = rb_next(node)) printk("key=%d\n", rb_entry(node, struct mytype, node)->key); return 0; } static void __exit my_exit(void) { struct mytype *data; struct rb_node *node; for (node = rb_first(&mytree); node; node = rb_next(node)) { data = rb_entry(node, struct mytype, node); if (data) { rb_erase(&data->node, &mytree); kfree(data); } } } module_init(my_init); module_exit(my_exit);
登錄后復(fù)制
mytree
是紅黑樹的根節(jié)點(diǎn),my_insert()
實(shí)現(xiàn)插入一個(gè)元素到紅黑樹中,my_search()
根據(jù)key
來(lái)查找節(jié)點(diǎn)。內(nèi)核大量使用紅黑樹,如虛擬地址空間VMA
的管理。
無(wú)鎖環(huán)形緩沖區(qū)
生產(chǎn)者和消費(fèi)者模型是計(jì)算機(jī)編程中最常見(jiàn)的一種模型。生產(chǎn)者產(chǎn)生數(shù)據(jù),而消費(fèi)者消耗數(shù)據(jù),如一個(gè)網(wǎng)絡(luò)設(shè)備,硬件設(shè)備接收網(wǎng)絡(luò)包,然后應(yīng)用程序讀取網(wǎng)絡(luò)包。環(huán)形緩沖區(qū)是實(shí)現(xiàn)生產(chǎn)者和消費(fèi)者模型的經(jīng)典算法。環(huán)形緩沖區(qū)通常有一個(gè)讀指針和一個(gè)寫指針。讀指針指向環(huán)形緩沖區(qū)中可讀的數(shù)據(jù),寫指針指向環(huán)形緩沖區(qū)可寫的數(shù)據(jù)。通過(guò)移動(dòng)讀指針和寫指針實(shí)現(xiàn)緩沖區(qū)數(shù)據(jù)的讀取和寫入。
在Linux內(nèi)核中,KFIFO
是采用無(wú)鎖環(huán)形緩沖區(qū)的實(shí)現(xiàn)。FIFO的全稱是“First In First Out”,即先進(jìn)先出的數(shù)據(jù)結(jié)構(gòu),它采用環(huán)形緩沖區(qū)的方法來(lái)實(shí)現(xiàn),并提供一個(gè)無(wú)邊界的字節(jié)流服務(wù)。采用環(huán)形緩沖區(qū)的好處是,當(dāng)一個(gè)數(shù)據(jù)元素被消耗之后,其余數(shù)據(jù)元素不需要移動(dòng)其存儲(chǔ)位置,從而減少?gòu)?fù)制,提高效率。
(1)創(chuàng)建KFIFO
在使用KFIFO之前需要進(jìn)行初始化,這里有靜態(tài)初始化和動(dòng)態(tài)初始化兩種方式。
<include/linux/kfifo.h> int kfifo_alloc(fifo, size, gfp_mask)
登錄后復(fù)制
該函數(shù)創(chuàng)建并分配一個(gè)大小為size的KFIFO
環(huán)形緩沖區(qū)。第一個(gè)參數(shù)fifo是指向該環(huán)形緩沖區(qū)的struct kfifo
數(shù)據(jù)結(jié)構(gòu);第二個(gè)參數(shù)size是指定緩沖區(qū)元素的數(shù)量;第三個(gè)參數(shù)gfp_mask
表示分配KFIFO
元素使用的分配掩碼。
靜態(tài)分配可以使用如下的宏。
#define DEFINE_KFIFO(fifo, type, size) #define INIT_KFIFO(fifo)
登錄后復(fù)制
(2)入列
把數(shù)據(jù)寫入KFIFO
環(huán)形緩沖區(qū)可以使用kfifo_in()
函數(shù)接口。
int kfifo_in(fifo, buf, n)
登錄后復(fù)制
該函數(shù)把buf指針指向的n個(gè)數(shù)據(jù)復(fù)制到KFIFO環(huán)形緩沖區(qū)中。第一個(gè)參數(shù)fifo指的是KFIFO環(huán)形緩沖區(qū);第二個(gè)參數(shù)buf指向要復(fù)制的數(shù)據(jù)的buffer;第三個(gè)數(shù)據(jù)是要復(fù)制數(shù)據(jù)元素的數(shù)量。
(3)出列
從KFIFO環(huán)形緩沖區(qū)中列出或者摘取數(shù)據(jù)可以使用kfifo_out()
函數(shù)接口。
#define kfifo_out(fifo, buf, n)
登錄后復(fù)制
該函數(shù)是從fifo指向的環(huán)形緩沖區(qū)中復(fù)制n個(gè)數(shù)據(jù)元素到buf指向的緩沖區(qū)中。如果KFIFO環(huán)形緩沖區(qū)的數(shù)據(jù)元素小于n個(gè),那么復(fù)制出去的數(shù)據(jù)元素小于n個(gè)。
(4)獲取緩沖區(qū)大小
KFIFO提供了幾個(gè)接口函數(shù)來(lái)查詢環(huán)形緩沖區(qū)的狀態(tài)。
#define kfifo_size(fifo) #define kfifo_len(fifo) #define kfifo_is_empty(fifo) #define kfifo_is_full(fifo)
登錄后復(fù)制
kfifo_size()
用來(lái)獲取環(huán)形緩沖區(qū)的大小,也就是最大可以容納多少個(gè)數(shù)據(jù)元素。kfifo_len()
用來(lái)獲取當(dāng)前環(huán)形緩沖區(qū)中有多少個(gè)有效數(shù)據(jù)元素。kfifo_is_empty()
判斷環(huán)形緩沖區(qū)是否為空。kfifo_is_full()
判斷環(huán)形緩沖區(qū)是否為滿。
(5)與用戶空間數(shù)據(jù)交互
KFIFO還封裝了兩個(gè)函數(shù)與用戶空間數(shù)據(jù)交互。
#define kfifo_from_user(fifo, from, len, copied) #define kfifo_to_user(fifo, to, len, copied)
登錄后復(fù)制
kfifo_from_user()
是把from指向的用戶空間的len個(gè)數(shù)據(jù)元素復(fù)制到KFIFO中,最后一個(gè)參數(shù)copied表示成功復(fù)制了幾個(gè)數(shù)據(jù)元素。
kfifo_to_user()
則相反,把KFIFO的數(shù)據(jù)元素復(fù)制到用戶空間。這兩個(gè)宏結(jié)合了copy_to_user()
、copy_from_user()
以及KFIFO
的機(jī)制,給驅(qū)動(dòng)開(kāi)發(fā)者提供了方便。
以上就是Linux內(nèi)核中常用的數(shù)據(jù)結(jié)構(gòu)和算法的詳細(xì)內(nèi)容,更多請(qǐng)關(guān)注www.92cms.cn其它相關(guān)文章!