生活随笔
收集整理的這篇文章主要介紹了
Linux驱动编程 step-by-step (十一)
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
Linux 內核鏈表(2)
之前描述了如何創建內核鏈表(INIT_LIST_HEAD)向鏈表中添加節點(list_add)刪除一個鏈表節點(list_del)獲取一個鏈表節點對應的結構體(list_entry)等
接下來會介紹幾種操作
替換一個鏈表節點,合并兩個鏈表,將一個鏈表分成兩段,遍歷鏈表。
替換鏈表節點
替換節點很好理解,就是將新的節點替換老節點,將新的節點的對應的prev,next指針指向老節點的prev,next。后將老節點prev->next指向新節點,老節點的next->prev指向新節點
[cpp]?view plaincopyprint?
static?inline?void?list_replace(struct?list_head?*old,?? ????????????????struct?list_head?*new)??????????????????????????????????????????????? {?? ????new->next?=?old->next;?????? ????new->next->prev?=?new;?????? ????new->prev?=?old->prev;?????? ????new->prev->next?=?new;?????? }??
此時old節點的next 與prev指針還是指向 原來的節點,可以使用以下函數重新初始化old節點使其指向 他自身
[cpp]?view plaincopyprint?
static?inline?void?list_replace_init(struct?list_head?*old,?? ????????????????????struct?list_head?*new)?? {?? ????list_replace(old,?new);?? ????INIT_LIST_HEAD(old);?? }??
...
更為一般的情況:我們替換鏈表節點大多都在鏈表頭或者鏈表尾,所以就有了以下函數
[cpp]?view plaincopyprint?
static?inline?void?list_move(struct?list_head?*list,?struct?list_head?*head)??
替換鏈表頭
[cpp]?view plaincopyprint?
static?inline?void?list_move_tail(struct?list_head?*list,?? ??????????????????struct?list_head?*head)??
替換鏈表尾
鏈表的判斷
有時候我們需要判斷鏈表是否為空,或者是否已經到了鏈表的末尾
內核鏈表已經為我們實現了這些判斷函數。
檢查鏈表是否為空
[cpp]?view plaincopyprint?
static?inline?int?list_empty(const?struct?list_head?*head)?? {?? ????return?head->next?==?head;?? }??
類似的檢查是否已經到了鏈表尾部函數:
[cpp]?view plaincopyprint?
static?inline?int?list_is_last(const?struct?list_head?*list,?? ????????????????const?struct?list_head?*head)??
此外還有一個檢測鏈表是否為空的函數
[cpp]?view plaincopyprint?
static?inline?int?list_empty_careful(const?struct?list_head?*head)??
檢查鏈表為空,并且沒有另外的處理器回去操作它
合并兩個鏈表
合并鏈表跟添加一個鏈表節點差不多,在鏈表頭或鏈表尾添加新鏈表均會調用到__list_splice
[cpp]?view plaincopyprint?
static?inline?void?__list_splice(const?struct?list_head?*list,?? ?????????????????struct?list_head?*prev,?? ?????????????????struct?list_head?*next)?? {?? ????struct?list_head?*first?=?list->next;?? ????struct?list_head?*last?=?list->prev;?? ?? ????first->prev?=?prev;?? ????prev->next?=?first;?? ?? ????last->next?=?next;?? ????next->prev?=?last;?? }??
即將list 鏈表加到 prev 與 next之間
在鏈表頭添加新鏈表:
[cpp]?view plaincopyprint?
static?inline?void?list_splice(const?struct?list_head?*list,?? ????????????????struct?list_head?*head)??
在鏈表尾部添加新鏈表調用:
[cpp]?view plaincopyprint?
static?inline?void?list_splice_tail(struct?list_head?*list,?? ????????????????struct?list_head?*head)??
拆分一個鏈表
[cpp]?view plaincopyprint?
static?inline?void?list_cut_position(struct?list_head?*list,?? ????????struct?list_head?*head,?struct?list_head?*entry)??
以entry為節點拆分以head為頭的鏈表,拆分后list保存從head到entry的鏈表。head報鏈表是從entry->next 到鏈表尾。
遍歷鏈表
創建的鏈表的目的是為了能夠遍歷鏈表得到鏈表結構中的有效數據。
上一篇文中提到 list_entry他只能獲得一個鏈表節點對應的結構體。
我們可以自己使用for 循環來遍歷鏈表:
[cpp]?view plaincopyprint?
for?(pos?=?head->next;?pos?!=?head;?pos?=?pos->next){?? ?????struct?data_struct?*data?=?list_entry(pos,?struct?data_struct,?list);?? ?????...?? }??
當然內核已經為我們提供了一套接口(本質就是上邊的 for循環)
[cpp]?view plaincopyprint?
list_for_each(pos,?head)??
到這里 我們想要一個更簡單的: 在循環的同時 就用list_entry為我們拿到鏈表節點對應的數據結構體。所以內核工程師給我們一個接口:
[cpp]?view plaincopyprint?
list_for_each_entry(pos,?head,?member)??
pos:是數據結構體指針, head是鏈表頭,member指在數據結構中鏈表成員的名字
例如現在定義?
[cpp]?view plaincopyprint?
struct?data_struct{?? ????struct?list_head?list;?? ????Data?data;?? };??
我們要遍歷鏈表獲得 此結構
[cpp]?view plaincopyprint?
{?? ????...?? ????struct?data_struct?*pdata;?? ????list_for_each_entry(pdata,?head,?list){?? ?????????Data?tmp?=?pdata->data;?? ?????????....?? ????}?? }??
在這些版本的遍歷中我們不能在循環內刪除節點,如果有刪除操作怎會導致內核崩潰(因為刪除節點時候 node->next被置為了空,如果進行操作.....),但有時候需要在遍歷過程中刪除節點,所以內核NB工程師幫我們做了一個 for_safe的版本,他保存一個節點的副本,在節點被刪除后,副本仍有效。
[cpp]?view plaincopyprint?
list_for_each_safe(pos,?n,?head)??
[cpp]?view plaincopyprint?
list_for_each_entry_safe(pos,?n,?head,?member)??
此處 n用來保存副本
此外還有逆序遍歷等再次不再贅述……
請自行查看include/linux/list.h
總結
以上是生活随笔為你收集整理的Linux驱动编程 step-by-step (十一)的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。