新聞中心
Linux 內(nèi)核中自己實(shí)現(xiàn)了雙向鏈表,可以在 include/linux/list.h 找到定義。我們將會(huì)首先從雙向鏈表數(shù)據(jù)結(jié)構(gòu)開始介紹內(nèi)核里的數(shù)據(jù)結(jié)構(gòu)。為什么?因?yàn)樗趦?nèi)核里使用的很廣泛,你只需要在 free-electrons.com 檢索一下就知道了。

創(chuàng)新互聯(lián)公司專注為客戶提供全方位的互聯(lián)網(wǎng)綜合服務(wù),包含不限于成都網(wǎng)站制作、網(wǎng)站設(shè)計(jì)、外貿(mào)網(wǎng)站建設(shè)、內(nèi)丘網(wǎng)絡(luò)推廣、小程序開發(fā)、內(nèi)丘網(wǎng)絡(luò)營(yíng)銷、內(nèi)丘企業(yè)策劃、內(nèi)丘品牌公關(guān)、搜索引擎seo、人物專訪、企業(yè)宣傳片、企業(yè)代運(yùn)營(yíng)等,從售前售中售后,我們都將竭誠(chéng)為您服務(wù),您的肯定,是我們最大的嘉獎(jiǎng);創(chuàng)新互聯(lián)公司為所有大學(xué)生創(chuàng)業(yè)者提供內(nèi)丘建站搭建服務(wù),24小時(shí)服務(wù)熱線:028-86922220,官方網(wǎng)址:www.cdcxhl.com
首先讓我們看一下在 include/linux/types.h 里的主結(jié)構(gòu)體:
struct list_head {
struct list_head *next, *prev;
};
你可能注意到這和你以前見過的雙向鏈表的實(shí)現(xiàn)方法是不同的。
舉個(gè)例子來說,在 glib 庫(kù)里是這樣實(shí)現(xiàn)的:
struct GList {
gpointer data;
GList *next;
GList *prev;
};
通常來說一個(gè)鏈表結(jié)構(gòu)會(huì)包含一個(gè)指向某個(gè)項(xiàng)目的指針。
但是 Linux 內(nèi)核中的鏈表實(shí)現(xiàn)并沒有這樣做。所以問題來了:鏈表在哪里保存數(shù)據(jù)呢?實(shí)際上,內(nèi)核里實(shí)現(xiàn)的鏈表是侵入式鏈表(Intrusive list)。侵入式鏈表并不在節(jié)點(diǎn)內(nèi)保存數(shù)據(jù)-它的節(jié)點(diǎn)僅僅包含指向前后節(jié)點(diǎn)的指針,以及指向鏈表節(jié)點(diǎn)數(shù)據(jù)部分的指針——數(shù)據(jù)就是這樣附加在鏈表上的。這就使得這個(gè)數(shù)據(jù)結(jié)構(gòu)是通用的,使用起來就不需要考慮節(jié)點(diǎn)數(shù)據(jù)的類型了。
比如:
struct nmi_desc {
spinlock_t lock;
struct list_head head;
};
讓我們看幾個(gè)例子來理解一下在內(nèi)核里是如何使用 list_head 的。
如上所述,在內(nèi)核里有很多很多不同的地方都用到了鏈表。我們來看一個(gè)在雜項(xiàng)字符驅(qū)動(dòng)里面的使用的例子。在 drivers/char/misc.c 的雜項(xiàng)字符驅(qū)動(dòng) API 被用來編寫處理小型硬件或虛擬設(shè)備的小驅(qū)動(dòng)。這些驅(qū)動(dòng)共享相同的主設(shè)備號(hào):
#define MISC_MAJOR 10
但是都有各自不同的次設(shè)備號(hào)。
比如:
ls -l /dev | grep 10
crw------- 1 root root 10, 235 Mar 21 12:01 autofs
drwxr-xr-x 10 root root 200 Mar 21 12:01 cpu
crw------- 1 root root 10, 62 Mar 21 12:01 cpu_dma_latency
crw------- 1 root root 10, 203 Mar 21 12:01 cuse
drwxr-xr-x 2 root root 100 Mar 21 12:01 dri
crw-rw-rw- 1 root root 10, 229 Mar 21 12:01 fuse
crw------- 1 root root 10, 228 Mar 21 12:01 hpet
crw------- 1 root root 10, 183 Mar 21 12:01 hwrng
crw-rw----+ 1 root kvm 10, 232 Mar 21 12:01 kvm
crw-rw---- 1 root disk 10, 237 Mar 21 12:01 loop-control
crw------- 1 root root 10, 227 Mar 21 12:01 mcelog
crw------- 1 root root 10, 59 Mar 21 12:01 memory_bandwidth
crw------- 1 root root 10, 61 Mar 21 12:01 network_latency
crw------- 1 root root 10, 60 Mar 21 12:01 network_throughput
crw-r----- 1 root kmem 10, 144 Mar 21 12:01 nvram
brw-rw---- 1 root disk 1, 10 Mar 21 12:01 ram10
crw--w---- 1 root tty 4, 10 Mar 21 12:01 tty10
crw-rw---- 1 root dialout 4, 74 Mar 21 12:01 ttyS10
crw------- 1 root root 10, 63 Mar 21 12:01 vga_arbiter
crw------- 1 root root 10, 137 Mar 21 12:01 vhci
現(xiàn)在讓我們看看它是如何使用鏈表的。首先看一下結(jié)構(gòu)體 miscdevice:
struct miscdevice
{
int minor;
const char *name;
const struct file_operations *fops;
struct list_head list;
struct device *parent;
struct device *this_device;
const char *nodename;
mode_t mode;
};
可以看到結(jié)構(gòu)體miscdevice的第四個(gè)變量list 是所有注冊(cè)過的設(shè)備的鏈表。
在源代碼文件的開始可以看到這個(gè)鏈表的定義:
static LIST_HEAD(misc_list);
它實(shí)際上是對(duì)用list_head 類型定義的變量的擴(kuò)展。
#define LIST_HEAD(name) \
struct list_head name = LIST_HEAD_INIT(name)
然后使用宏 LIST_HEAD_INIT 進(jìn)行初始化,
這會(huì)使用變量name 的地址來填充prev和next 結(jié)構(gòu)體的兩個(gè)變量。
#define LIST_HEAD_INIT(name) { &(name), &(name) }
現(xiàn)在來看看注冊(cè)雜項(xiàng)設(shè)備的函數(shù)misc_register。
它在一開始就用函數(shù) INIT_LIST_HEAD 初始化了miscdevice->list。
INIT_LIST_HEAD(&misc->list);
作用和宏LIST_HEAD_INIT一樣。
static inline void INIT_LIST_HEAD(struct list_head *list){list->next = list;list->prev = list;}
接下來,在函數(shù)device_create 創(chuàng)建了設(shè)備后,
我們就用下面的語句將設(shè)備添加到設(shè)備鏈表:
list_add(&misc->list, &misc_list);
內(nèi)核文件list.h 提供了向鏈表添加新項(xiàng)的 API 接口。
我們來看看它的實(shí)現(xiàn):
static inline void list_add(struct list_head *new, struct list_head *head){__list_add(new, head, head->next);}
實(shí)際上就是使用3個(gè)指定的參數(shù)來調(diào)用了內(nèi)部函數(shù)__list_add:
new – 新項(xiàng)。 head – 新項(xiàng)將會(huì)插在head的后面 head->next – 插入前,head 后面的項(xiàng)。 __list_add的實(shí)現(xiàn)非常簡(jiǎn)單:
static inline void __list_add(struct list_head *new,struct list_head *prev,struct list_head *next){next->prev = new;new->next = next;new->prev = prev;prev->next = new;}
這里,我們?cè)趐rev和next 之間添加了一個(gè)新項(xiàng)。
所以我們開始時(shí)用宏LIST_HEAD_INIT定義的misc 鏈表會(huì)包含指向miscdevice->list 的向前指針和向后指針。 這兒還有一個(gè)問題:如何得到列表的內(nèi)容呢?這里有一個(gè)特殊的宏:
#define list_entry(ptr, type, member) \container_of(ptr, type, member)
使用了三個(gè)參數(shù):
ptr – 指向結(jié)構(gòu) list_head 的指針; type – 結(jié)構(gòu)體類型; member – 在結(jié)構(gòu)體內(nèi)類型為list_head 的變量的名字;
比如:
const struct miscdevice *p = list_entry(v, struct miscdevice, list)
然后我們就可以使用p->minor 或者 p->name來訪問miscdevice。讓我們來看看list_entry 的實(shí)現(xiàn):
#define list_entry(ptr, type, member) \container_of(ptr, type, member)
如我們所見,它僅僅使用相同的參數(shù)調(diào)用了宏container_of。初看這個(gè)宏挺奇怪的:
#define container_of(ptr, type, member) ({ \const typeof( ((type *)0)->member ) *__mptr = (ptr); \(type *)( (char *)__mptr - offsetof(type,member) );})
首先你可以注意到花括號(hào)內(nèi)包含兩個(gè)表達(dá)式。
編譯器會(huì)執(zhí)行花括號(hào)內(nèi)的全部語句,然后返回最后的表達(dá)式的值。
比如:
#include int main() {int i = 0;printf("i = %d\n", ({++i; ++i;}));return 0;}
最終會(huì)打印出2。
下一點(diǎn)就是typeof,它也很簡(jiǎn)單。
就如你從名字所理解的,它僅僅返回了給定變量的類型。當(dāng)我第一次看到宏container_of的實(shí)現(xiàn)時(shí),讓我覺得最奇怪的就是表達(dá)式((type *)0)中的0。實(shí)際上這個(gè)指針巧妙的計(jì)算了從結(jié)構(gòu)體特定變量的偏移,這里的0剛好就是位寬里的零偏移。
比如:
#include
struct s {
int field1;
char field2;
char field3;
};
int main() {
printf("%p\n", &((struct s*)0)->field3);
return 0;
}
結(jié)果顯示0x5。
下一個(gè)宏offsetof會(huì)計(jì)算從結(jié)構(gòu)體起始地址到某個(gè)給定結(jié)構(gòu)字段的偏移。
它的實(shí)現(xiàn)和上面類似: #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) 現(xiàn)在我們來總結(jié)一下宏container_of。只需給定結(jié)構(gòu)體中l(wèi)ist_head類型 字段的地址、名字和結(jié)構(gòu)體容器的類型,它就可以返回結(jié)構(gòu)體的起始地址。在宏定義的第一行,聲明了一個(gè)指向結(jié)構(gòu)體成員變量ptr的指針mptr,并且把ptr 的地址賦給它。現(xiàn)在ptr 和mptr 指向了同一個(gè)地址。從技術(shù)上講我們并不需要這一行,但是它可以方便地進(jìn)行類型檢查。第一行保證了特定的結(jié)構(gòu)體(參數(shù)type)包含成員變量member。第二行代碼會(huì)用宏offsetof計(jì)算成員變量相對(duì)于結(jié)構(gòu)體起始地址的偏移,然后從結(jié)構(gòu)體的地址減去這個(gè)偏移,最后就得到了結(jié)構(gòu)體。
當(dāng)然了list_add 和 list_entry不是
提供的唯一功能。雙向鏈表的實(shí)現(xiàn)還提供了如下API:
list_add
list_add_tail
list_del
list_replace
list_move
list_is_last
list_empty
list_cut_position
list_splice
list_for_each
list_for_each_entry
等等很多其它API。
新聞標(biāo)題:Linux內(nèi)核中雙向鏈表
本文網(wǎng)址:http://fisionsoft.com.cn/article/coiddoo.html


咨詢
建站咨詢
