Linux 內(nèi)核通用鏈表學(xué)習(xí)小結(jié)
描述
在linux內(nèi)核中封裝了一個(gè)通用的雙向鏈表庫(kù),這個(gè)通用的鏈表庫(kù)有很好的擴(kuò)展性和封裝性,它給我們提供了一個(gè)固定的指針域結(jié)構(gòu)體,我們?cè)谑褂玫臅r(shí)候,只需要在我們定義的數(shù)據(jù)域結(jié)構(gòu)體中包含這個(gè)指針域結(jié)構(gòu)體就可以了,具體的實(shí)現(xiàn)、鏈接并不需要我們關(guān)心,只要調(diào)用提供給我們的相關(guān)接口就可以完成了。
傳統(tǒng)的鏈表結(jié)構(gòu)
struct node{
int key;
int val;
node* prev;
node* next;
}
linux 內(nèi)核通用鏈表庫(kù)結(jié)構(gòu)
提供給我們的指針域結(jié)構(gòu)體:
struct list_head {
struct list_head *next, *prev;
};
我們只需要包含它就可以:
struct node{
int val;
int key;
struct list_head* head;
}
可以看到通過(guò)這個(gè) list_head 結(jié)構(gòu)就把我們的數(shù)據(jù)層跟驅(qū)動(dòng)層分開(kāi)了,而內(nèi)核提供的各種操作方法接口也只關(guān)心 list_head 這個(gè)結(jié)構(gòu),也就是具體鏈接的時(shí)候也只鏈接這個(gè)list_head 結(jié)構(gòu),并不關(guān)心你數(shù)據(jù)層定義了什么類型.
一些接口宏定義
//初始化頭指針
#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) \
struct list_head name = LIST_HEAD_INIT(name)
//遍歷鏈表
#define __list_for_each(pos, head) \
for (pos = (head)->next; pos != (head); pos = pos->next)
//獲取節(jié)點(diǎn)首地址(不是list_head地址,是數(shù)據(jù)層節(jié)點(diǎn)首地址)
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
//container_of在Linux內(nèi)核中是一個(gè)常用的宏,用于從包含在某個(gè)
//結(jié)構(gòu)中的指針獲得結(jié)構(gòu)本身的指針,通俗地講就是通過(guò)結(jié)構(gòu)體變
//量中某個(gè)成員的首地址進(jìn)而獲得整個(gè)結(jié)構(gòu)體變量的首地址
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
#define offsetof(s,m) (size_t)&(((s *)0)->m)
使用方式
typedef struct node{
int val;
int key;
struct list_head* list;
}node;
//初始化頭指針
LIST_HEAD(head);
//創(chuàng)建節(jié)點(diǎn)
node* a = malloc(sizeof(node));
node* b = malloc(sizeof(node));
//插入鏈表 方式一
list_add(&a->list,&head);
list_add(&b->list,&head);
//插入鏈表 方式二
list_add_tail(&a->list,&head);
list_add_tail(&b->list,&head);
//遍歷鏈表
struct list_head* p;
struct node* n;
__list_for_each(p,head){
//返回list_head地址,然后再通過(guò)list_head地址反推
//節(jié)點(diǎn)結(jié)構(gòu)體首地址.
n = list_entry(pos,struct node,list);
}
list_add 接口,先入后出原則,有點(diǎn)類似于棧

list_add-先入后出模式
list_add_tail 接口,先入先出原則,有點(diǎn)類似于fifo

list_add-先入先出模式
我們的鏈表節(jié)點(diǎn),實(shí)際在內(nèi)存中的展示形態(tài)

節(jié)點(diǎn)描述
可以看到最終的形態(tài)是,通過(guò)指向每個(gè)結(jié)構(gòu)體里面的 list_head 類型指針,然后把它們串聯(lián)起來(lái)的
list_entry 接口,通過(guò)結(jié)構(gòu)體變量某個(gè)成員的地址,反推結(jié)構(gòu)體首地址,就像 __list_for_each 接口只返回 list_head 地址,所以我們要通過(guò)這個(gè)成員地址在去獲取它本身的結(jié)構(gòu)體首地址,底層實(shí)現(xiàn)方法 container_of 宏

反推結(jié)構(gòu)體首地址
舉個(gè)例子
這個(gè)例子包括簡(jiǎn)單的增、刪、遍歷
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/list.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("David Xie");
MODULE_DESCRIPTION("List Module");
MODULE_ALIAS("List module");
struct student //代表一個(gè)實(shí)際節(jié)點(diǎn)的結(jié)構(gòu)
{
char name[100];
int num;
struct list_head list; //內(nèi)核鏈表里的節(jié)點(diǎn)結(jié)構(gòu)
};
struct student *pstudent;
struct student *tmp_student;
struct list_head student_list;
struct list_head *pos;
int mylist_init(void)
{
int i = 0;
//初始化一個(gè)鏈表,其實(shí)就是把student_list的prev和next指向自身
INIT_LIST_HEAD(&student_list);
pstudent = kmalloc(sizeof(struct student)*5,GFP_KERNEL);//向內(nèi)核申請(qǐng)5個(gè)student結(jié)構(gòu)空間
memset(pstudent,0,sizeof(struct student)*5); //清空,這兩個(gè)函數(shù)可以由kzalloc單獨(dú)做到
for(i=0;i<5;i++)
{ //為結(jié)構(gòu)體屬性賦值
sprintf(pstudent[i].name,"Student%d",i+1);
pstudent[i].num = i+1;
//加入鏈表節(jié)點(diǎn),list_add的話是在表頭插入,list_add_tail是在表尾插入
list_add( &(pstudent[i].list), &student_list);//參數(shù)1是要插入的節(jié)點(diǎn)地址,參數(shù)2是鏈表頭地址
}
list_for_each(pos,&student_list) //list_for_each用來(lái)遍歷鏈表,這是個(gè)宏定義
//pos在上面有定義
{
//list_entry用來(lái)提取出內(nèi)核鏈表節(jié)點(diǎn)對(duì)應(yīng)的實(shí)際結(jié)構(gòu)節(jié)點(diǎn),即根據(jù)struct list_head來(lái)提取struct student
//第三個(gè)參數(shù)list就是student結(jié)構(gòu)定義里的屬性list
//list_entry的原理有點(diǎn)復(fù)雜,也是linux內(nèi)核的一個(gè)經(jīng)典實(shí)現(xiàn),這個(gè)在上面那篇鏈接文章里也有講解
tmp_student = list_entry(pos,struct student,list);
//打印一些信息,以備驗(yàn)證結(jié)果
printk("<0>student %d name: %s/n",tmp_student->num,tmp_student->name);
}
return 0;
}
void mylist_exit(void)
{
int i ;
/* 實(shí)驗(yàn):將for換成list_for_each來(lái)遍歷刪除結(jié)點(diǎn),觀察要發(fā)生的現(xiàn)象,并考慮解決辦法 */
for(i=0;i<5;i++)
{
//額,刪除節(jié)點(diǎn),只要傳個(gè)內(nèi)核鏈表節(jié)點(diǎn)就行了
list_del(&(pstudent[i].list));
}
//釋放空間
kfree(pstudent);
}
module_init(mylist_init);
module_exit(mylist_exit);
結(jié)束
linux 內(nèi)核提供的這個(gè)通用鏈表庫(kù)里面還有很多其他的接口,這里沒(méi)有詳細(xì)的一一舉例,有興趣的可以自己去看看,在源碼包 include/linux/list.h 文件里面,不過(guò)通過(guò)閱讀一些源代碼確實(shí)對(duì)我們也有很大的提高,可以看看高手是如何去設(shè)計(jì)并實(shí)現(xiàn),還可以學(xué)到一些技巧以及對(duì)代碼細(xì)節(jié)的掌握~~.
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Linux中的內(nèi)核鏈表實(shí)例詳解
- Linux內(nèi)核鏈表實(shí)現(xiàn)過(guò)程
- Linux內(nèi)核設(shè)備驅(qū)動(dòng)之proc文件系統(tǒng)筆記整理
- Linux內(nèi)核設(shè)備驅(qū)動(dòng)之高級(jí)字符設(shè)備驅(qū)動(dòng)筆記整理
- Linux內(nèi)核設(shè)備驅(qū)動(dòng)之Linux內(nèi)核模塊加載機(jī)制筆記整理
- Linux內(nèi)核設(shè)備驅(qū)動(dòng)地址映射筆記整理
- Linux內(nèi)核設(shè)備驅(qū)動(dòng)之Linux內(nèi)核基礎(chǔ)筆記整理
- 增強(qiáng)Linux內(nèi)核中訪問(wèn)控制安全的方法
- Linux 內(nèi)核空間與用戶空間實(shí)現(xiàn)與分析
- 詳解Linux內(nèi)核進(jìn)程調(diào)度函數(shù)schedule()的觸發(fā)和執(zhí)行時(shí)機(jī)
- Linux內(nèi)核設(shè)備驅(qū)動(dòng)之內(nèi)核中鏈表的使用筆記整理
相關(guān)文章
Linux 塊設(shè)備驅(qū)動(dòng)代碼編寫(xiě)
這篇文章主要介紹了Linux 塊設(shè)備驅(qū)動(dòng)代碼編寫(xiě),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-04-04
Linux如何實(shí)現(xiàn)斷點(diǎn)續(xù)傳文件功能
最近在工作中遇到一個(gè)需求,要實(shí)現(xiàn)Linux下的文件傳輸,支持?jǐn)帱c(diǎn)續(xù)傳,所以這篇文章主要給大家介紹了關(guān)于Linux如何實(shí)現(xiàn)斷點(diǎn)續(xù)傳文件功能的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來(lái)一起看看吧。2017-12-12
CentOS下使用LibreOffice實(shí)現(xiàn)文檔格式的轉(zhuǎn)換方式
項(xiàng)目需求,對(duì)上傳的文檔進(jìn)行一些預(yù)處理,如果用戶上傳了doc格式的文檔,需要將其處理為docx或者pdf格式,以便后續(xù)的流程對(duì)文檔內(nèi)容進(jìn)行提取。接下來(lái)通過(guò)本文給大家分享CentOS下使用LibreOffice實(shí)現(xiàn)文檔格式的轉(zhuǎn)換,感興趣的朋友一起看看吧2019-07-07
SpringBoot整合Activiti7的實(shí)現(xiàn)代碼
這篇文章主要介紹了SpringBoot整合Activiti7的實(shí)現(xiàn)代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11
Win10 安裝Linux ubuntu-18.04雙系統(tǒng)(安裝指南)
這篇文章主要介紹了Win10+Linux ubuntu-18.04雙系統(tǒng)安裝教程,本文分步驟給大家記錄下來(lái),需要的朋友可以參考下2019-10-10
把windows下的字體安裝到Linux系統(tǒng)下的方法介紹
Linux(Fedora/Ubuntu/CentOS)的字體實(shí)在不盡如人意,而且在網(wǎng)頁(yè)及文檔顯示時(shí)很多字無(wú)法顯示出來(lái),特別多的空白和亂碼,其實(shí),我們可以把windows下的字體和自己心儀的字體添加到Linux中,本文將介紹如何在Linux下添加字體2018-03-03

