咱們都知道Linux內核裏的雙向鏈表和學校裏教給咱們的那種數據結構仍是些不同。Linux採用了一種更通用的設計,將鏈表以及其相關操做函數從數據自己進行剝離,這樣咱們在使用鏈表的時候就不用本身去實現諸如節點的插入、刪除、遍歷等操做了。固然,Linux也是從2.1.x內核開始纔對鏈表進行了這樣的統一,和咱們目前看到的樣子幾乎差很少: css
- struct list_head {
- struct list_head *next, *prev;
- };
在2.6.21裏這個數據結構定義在include/liinux/list.h頭文件裏,可是在3.4.1內核裏,以及後面要介紹的哈希鏈表的定義都放在include/linux/types.h頭文件裏。而本文將以3.4.1內核爲例進行介紹,其實對鏈表來講內核的版本號幾乎沒什麼影響,只要掌握了Linux設計鏈表的精髓,萬變不離其宗。
今天咱們首先來聊聊鏈表。從上述定義代碼咱們能夠看出,Linux內核的鏈表是雙向鏈表,若是咱們要將本身的數據結構以鏈表的形式進行組織,那麼只要在咱們本身的數據結構裏,增長一個struct list_head{}類型的結構體成員對象就能夠了,這樣,咱們就能夠很方便地使用內核提供給咱們的一組標準接口來對鏈表進行各類操做。
若是咱們須要定義一個鏈表,內核有LIST_HEAD(name)這樣的函數供咱們使用: 數據結構
- #define LIST_HEAD(name) \
- struct list_head name = LIST_HEAD_INIT(name)
其中#define
LIST_HEAD_INIT(name) { &(name), &(name) },其實這樣的代碼已經太簡單不過了。若是咱們要定義一個名爲
student_list的鏈表,直接LIST_HEAD(
student_list)就能夠了,展開後等價於下面的代碼:
- struct list_head student_list= { &(student_list), &(student_list) };
跟內核通知鏈相似,若是咱們已經有了一個鏈表對象
student_list,
INIT_LIST_HEAD()接口能夠對它初始化。因此,LIST_HEAD(
student_list)代碼和下面的代碼是等價的:
- struct list_head student_list;
- INIT_LIST_HEAD (&student_list);
假如,咱們如今要定義一個學生的結構體,並讓其組織成鏈表的形式,能夠這樣作:
設計
- #define NAME_MAX_SIZE 32
-
- typedef struct student{
- char name[NAME_MAX_SIZE]; /*姓名*/
- unsigned char sex; /*性別:m-男生;f-女生*/
- unsigned char age; /*年齡*/
- struct list_head stu_list; /*全部的學生最終經過這個結構串成鏈表*/
- }Student;
那麼在寫代碼時,若是是經過
kmalloc之類的函數動態建立節點,咱們就能夠用下面代碼對鏈表節點進行初始化:
- Student *stu1;
- stu1 = kmalloc(sizeof(*stu1), GFP_KERNEL);
- strcpy(stu1->name,」xiaoming」);
- stu1->sex = ‘m’;
- stu1->age = 18;
- INIT_LIST_HEAD(&stu1-> stu_list); /*和下面的用法注意區別*/
若是是靜態定義結構體變量的話就更簡單了:
- Student stu2={
- .name={「xiaohong」},
- .sex=’f’,
- .age=18,
- .stu_list = LIST_HEAD_INIT(stu2.stu_list); /*和上面的用法注意區別*/
- };
有了數據節點,接下來就要對其進行操做了,內核提供了一組經常使用接口用於對雙向鏈表操做,以下。
還有關於鏈表的分割list_cut_position(*list,*head,*entry)以及合併list_splice(*list,*head)、list_splice_init (*list,*head)、list_splice_tail (*list,*head)、list_splice_tail_init (*list,*head)這幾個API用法也都很是簡單,對照內核源碼的註釋很輕鬆就能夠上手了。
經過上面的圖咱們能夠看出來,在內核中當咱們說起鏈表頭的時候其實並無牽扯到咱們本身的結構體數據自己,鏈表頭的next所指向的節點纔是真正意義上的「鏈表頭節點」,prev所指向的節點叫作「鏈表尾節點」。注意,不要把鏈表頭和鏈表的頭節點混爲一談。有了這個認識以後,咱們就知道若是鏈表頭的next和prev都指向鏈表頭自己的話,那麼這個鏈表其實就是空的,例如list_empty()或者list_empty_careful()所作的事情就是給定一個鏈表頭,判斷其是否爲空,便是否包含任何有效的數據節點。一樣地,如何判斷鏈表是否只有一個節點呢?看看list_is_singular()的實現就豁然開朗了,真的是so easy。
最後,將前面說起的API總結到下表2.1中,方便你們查閱。
須要注意的是,上述全部鏈表操做函數的入參都是struct list_head{}的指針類型,這一點須要時刻牢記在心。
未完,待續…