關於鏈表咱們更多時候是對其進行遍歷的需求,上一篇博文裏咱們主要認識了一下和鏈表操做比較經常使用的幾個內核API接口,其入參全都是清一色的struct list_head{}類型。至於鏈表的遍歷,內核也有一組基本的接口(其實都是宏定義的)供開發者調用。 css
首先是list_for_each(pos, head),參數pos是須要開發者在外部提供的一個臨時struct list_head{}類型的指針對象,相似於for循環的i、j、k之類的遊標,head是咱們要遍歷的鏈表頭。常見用法:
linux
- LIST_HEAD(student_list);
- struct list_head *stu;
- list_for_each(stu, &student_list){
- //在這個做用域裏,指針stu依次指向student_list裏的每個struct list_head{}成員節點
- }
固然stu指向的是struct list_head{}類型的對象,咱們通常是須要指向struct student{}的纔對,此時list_entry(ptr, type, member)就出場了,它徹底是container_of(ptr, type, member)的一個別名而已。container_of()就是根據type類型結構體中的member成員的指針ptr,反身找到該member所在結構體對象的type首地址。廢話很少說,上圖:
此時的用法就變成下面這樣子:
注意結合上圖,領會一下
list_entry(ptr,type,member)
三個參數之間的關係。這樣若是每次要遍歷鏈表時既要定義臨時的
struct list_head{}
指針變量,又要定義目標結構體對象指針變量,總感受些許不爽。好在
Linux
感知到了你的J
點,因而乎:
- list_for_each_entry(pos, head, member)
橫空出世。參數pos和member意義沒有變,而head則指向咱們要遍歷的鏈表首地址,這樣一來開發者不用再本身定義struct list_head{}類型臨時指針變量,只要須要本身定義一個的目標數據結構的臨時指針變量就能夠了:
- LIST_HEAD(student_list);
- struct student *st;
- list_for_each_entry(st, &student_list, stu_list){
- //Todo here … …
- }
此時指針變量st,就至關於for循環的遊標變量i了。
固然,內核能感知的遠不止於此,還有一個名爲
list_for_each_entry_reverse(pos, head, member)
的宏,用於對雙向鏈表的逆向遍歷,參數的意義和
list_for_each_entry()
徹底同樣,區別在它是對鏈表從尾部到首部進行依次遍歷。該接口主要是爲了提升鏈表的訪問速度,考慮兩種狀況:
第一,若是你明確知道你要訪問的節點會出如今鏈表靠後的位置; ide
第二,若是你須要用雙向鏈表實現一個相似於「棧」的數據結構; 函數
針對以上兩種需求,相比於list_for_each_entry(),list_for_each_entry_reverse()的速度和效率明顯優於前者。爲了追求極致,內核開發者們就是這麼任性,沒辦法。 學習
上述兩個接口在遍歷鏈表時已經徹底能夠勝任,但還沒法知足刪除的需求,緣由是…算了,都懶的說了,把那兩個宏展開,在紙上畫一下,若是要刪除節點,會發生什麼「神奇」的事情就一目瞭然了。那若是遍歷鏈表過程當中要刪除節點,該怎麼辦?咱接着嘮: 測試
- list_for_each_entry_safe(pos, n, head, member)
若是你還沒看過list.h文件,那麼單從list_for_each_entry_safe(pos,n,head,member)的四個入參命名上,應該能夠讀懂它們的意思和用法了吧!若是你已經在紙上畫過了,那麼新增的n很明顯應該是pos指針所指元素的下一個節點的地址,注意,pos和n都是目標結構體的類型,而非struct list_head{}類型,本例中它們都是struct student{}類型的指針,童鞋們可不要犯迷糊了。如今用法就更簡單了:
- LIST_HEAD(student_list);
- struct student *st,*next;
- list_for_each_entry_safe (st, next,&student_list, stu_list){
- //在這裏能夠對st所指向的節點作包括刪除在內的任意操做
- //但千萬別操做next,它是由list_for_each_entry_safe()進行維護的
- }
不用多想,確定也存在一個名爲list_for_each_entry_safe_reverse(pos, n, head, member)的宏。簡單小節一下:
1)、list_for_each_entry()和list_for_each_entry_reverse(),若是隻須要對鏈表進行遍歷,這兩個接口效率要高一些;
2)、list_for_each_entry_safe()和list_for_each_entry_safe_reverse(),若是遍歷過程當中有可能要對鏈表進行刪除操做,用這兩個;
實際項目中,你們能夠根據具體場景而考慮使用哪一種方式。另外,關於鏈表遍歷,內核還有其餘一些列list_for_*相關的宏可供調用,這裏就不一一闡述了,list.h源碼裏面不管是註釋仍是實現都至關明確。
說了老半天,仍是操練幾把感覺感覺,模擬訓練之「內核級精簡版學生管理系統」:
頭文件student.h長相以下:
- /*student.h*/
- #ifndef __STUDENT_H_
- #define __STUDENT_H_
-
- #include <linux/list.h>
- #define MAX_STRING_LEN 32
- typedef struct student
- {
- char m_name[MAX_STRING_LEN];
- char m_sex;
- int m_age;
- struct list_head m_list; /*把咱們的學生對象組織成雙向鏈表,就靠該節點了*/
- }Student;
- #endif
源文件student.c長相也不醜陋:
- #include <linux/module.h>
- #include <linux/kernel.h>
- #include <linux/init.h>
-
- #include "student.h"
-
- MODULE_LICENSE("Dual BSD/GPL");
- MODULE_AUTHOR("Koorey Wung");
-
- static int dbg_flg = 0;
-
- LIST_HEAD(g_student_list);
-
- static int add_stu(char* name,char sex,int age)
- {
- Student *stu,*cur_stu;
-
- list_for_each_entry(cur_stu,&g_student_list,m_list){ //僅遍歷是否有同名學生,因此用該接口
- if(0 == strcmp(cur_stu->m_name,name))
- {
- printk("Error:the name confict!\n");
- return -1;
- }
- }
-
- stu = kmalloc(sizeof(Student), GFP_KERNEL);
- if(!stu)
- {
- printk("kmalloc mem error!\n");
- return -1;
- }
-
- memset(stu,0,sizeof(Student));
- strncpy(stu->m_name,name,strlen(name));
- stu->m_sex = sex;
- stu->m_age = age;
- INIT_LIST_HEAD(&stu->m_list);
-
- if(dbg_flg)
- printk("(Add)name:[%s],\tsex:[%c],\tage:[%d]\n",stu->m_name,stu->m_sex,stu->m_age);
-
- list_add_tail(&stu->m_list,&g_student_list); //將新學生插入到鏈表尾部,很簡單吧
-
- return 0;
- }
- EXPORT_SYMBOL(add_stu); //導出該函數,後面咱們要在其餘模塊裏調用,爲了便於測試,下面其餘借個接口相似
-
- static int del_stu(char *name)
- {
- Student *cur,*next;
- int ret = -1;
- list_for_each_entry_safe(cur,next,&g_student_list,m_list){ //由於要刪除鏈表的節點,因此必須有帶有「safe」的宏接口
- if(0 == strcmp(name,cur->m_name))
- {
- list_del(&cur->m_list);
- printk("(Del)name:[%s],\tsex:[%c],\tage:[%d]\n",cur->m_name,\
- cur->m_sex,cur->m_age);
- kfree(cur);
- cur = NULL;
- ret = 0;
- break;
- }
- }
- return ret;
- }
- EXPORT_SYMBOL(del_stu);
-
- static void dump_students(void)
- {
- Student *stu;
- int i = 1;
- printk("===================Student List================\n");
- list_for_each_entry(stu,&g_student_list,m_list){ //一樣,也僅遍歷鏈表而已
- printk("(%d)name:[%s],\tsex:[%c],\tage:[%d]\n",i++,stu->m_name,\
- stu->m_sex,stu->m_age);
- }
- printk("===============================================\n");
- }
- EXPORT_SYMBOL(dump_students);
-
- static void init_system(void)
- {
- /*初始化時,向鏈表g_student_list裏添加6個節點*/
- add_stu("Tom",'m',18);
- add_stu("Jerry",'f',17);
- add_stu("Alex",'m',18);
- add_stu("Conory",'f',18);
- add_stu("Frank",'m',17);
- add_stu("Marry",'f',17);
- }
-
- /*由於沒有數據庫,因此當咱們的模塊退出時,須要釋放內存*/
- static void clean_up(void)
- {
- Student *stu,*next;
- list_for_each_entry_safe(stu,next,&g_student_list,m_list){
- list_del(&stu->m_list);
- printk("Destroy [%s]\n",stu->m_name);
- kfree(stu);
- }
- }
-
- /*模塊初始化接口*/
- static int student_mgt_init(void)
- {
- printk("Student Managment System,Initializing...\n");
-
- init_system();
- dbg_flg = 1; //今後之後,再調用add_stu()時,都會有有內核打印信息,詳見實例訓練
- dump_students();
-
- return 0;
- }
-
- static void student_mgt_exit(void)
- {
- clean_up();
- printk("System Terminated!\n");
- }
-
- module_init(student_mgt_init);
- module_exit(student_mgt_exit);
Makefile:
- obj-m += student.o tools.o
- CURRENT_PATH:=$(shell pwd)
- LINUX_KERNEL:=$(shell uname -r)
- LINUX_KERNEL_PATH:=/usr/src/kernels/$(LINUX_KERNEL)
- all:
- make -I. -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
- clean:
- make -I. -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean
其中tools.c是一個輔助模塊,用於實現從用戶空間直接調用調用內核空間EXPORT_SYMBOL出來的任意一個API接口,好比add_stu()、del_stu()或者dump_students()等等。OK,萬事俱備,只欠東風,一條make命令下去,而後好戲正式開始:
總的來講,Linux內核鏈表的使用還算比較簡單基礎,是內核學習的入門必修課。固然實際項目中,對鏈表進行插入或者刪除時若是有同步或者互斥需求,則須要採用諸如互斥鎖之類的內核保護手段,防止對鏈表操做時出現競爭冒險現象。
(完)