在linux內核中奮鬥的工程師們必定每天都會和鏈表打交道
它定義在include/linux/types.h,它也有很多變種,但都是換湯不換藥
struct list_head { struct list_head *next, *prev; };
奇怪,為啥只有前後兩個指針,沒有其他內容這樣的鏈表有啥用?就好像買了10根汽車牽引繩,但沒有汽車,買了好玩嗎?
別不信,kernel中到處都是,隨便找一個:
按照正常的思維定義鏈表是這樣的,很清晰很方便,一切都很好:
struct pwqs_list_struct { struct pwqs_list_struct *next, *prev; struct mutex mutex; int work_color; int flush_color; atomic_t nr_pwqs_to_flush; struct wq_flusher *first_flusher; ...... } struct pwqs_list_struct pwqs_head = {0};
但上面的workqueue_struct就包含了5種不同的鏈表:pwqs, list, flusher_queue, flusher_overflow, maydays,意味著要定義5個不同的structure,但它們的內容可能都是一樣的
在linux這種體量的工程中這是不能接受的,同質化的實現一定要抽象出來:
struct list_head { struct list_head *next, *prev; }; struct workqueue_struct { | struct list_head *next) struct list_head pwqs; /* WR: all pwqs of this wq */ |{ struct list_head list; /* PR: list of all workqueues */ | return true; |} struct mutex mutex; /* protects this wq */ |static inline bool __list_del_entry_valid(struct list_head *entry) int work_color; /* WQ: current work color */ |{ int flush_color; /* WQ: current flush color */ | return true; atomic_t nr_pwqs_to_flush; /* flush in progress */ |} struct wq_flusher *first_flusher; /* WQ: first flusher */ |#endif struct list_head flusher_queue; /* WQ: flush waiters */ | struct list_head flusher_overflow; /* WQ: flush overflow list */ ...... } struct list_head wq_head; struct workqueue_struct wq; wq_head.next = &wq.list;
這樣linux任何module/lib都可以統一了,wq_head可以把多個wq.list鏈接起來,不過問題來了,怎麼通過wq.list拿到struct workqueue_struct中的其他成員呢?這纔是關鍵啊!
linux提供了一個宏container_of來解決這個問題:
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER) /** * container_of - cast a member of a structure out to the containing structure * @ptr: the pointer to the member. * @type: the type of the container struct this is embedded in. * @member: the name of the member within the struct. * */ #define container_of(ptr, type, member) ({ void *__mptr = (void *)(ptr); BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type *)0)->member) && !__same_type(*(ptr), void), "pointer type mismatch in container_of()"); ((type *)(__mptr - offsetof(type, member))); })
移除類型檢查,並將offsetof展開後:
#define container_of(ptr, type, member) ({ void *__mptr = (void *)(ptr); ((type *)(__mptr - ((size_t)&((type *)0)->member))); })
拿workqueue_struct舉例:
struct workqueue_struct wq; struct list_head *ptr = &wq.list;
struct workqueue_struct *p_wq = container_of(ptr, struct workqueue_struct, list);
p_wq指向的就是wq,我再將container_of展開看的更直觀:
struct workqueue_struct *p_wq = ({ void *__mptr = (void *)ptr; (struct workqueue_struct *)(__mptr - ((size_t)&((struct workqueue_struct *)0)->list)); })
藉助0地址來計算list在struct workqueue_struct中的偏移,再用ptr(struct list_head)減去這個偏移即可得到ptr所在的struct workqueue_struct實例的地址,這樣就把鏈表和數據連接起來了
如果你翻開鏈表定義文件include/linux/list.h,會看到很多遍歷鏈表的api:list_for_each_xxx
它們又分為兩種:
帶entry的參數和container_of相同,即它遍歷的是鏈表的父數據結構
#define list_entry(ptr, type, member) container_of(ptr, type, member)
#define list_first_entry(ptr, type, member) list_entry((ptr)->next, type, member)
#define list_next_entry(pos, member) list_entry((pos)->member.next, typeof(*(pos)), member)
#define list_for_each_entry(pos, head, member) for (pos = list_first_entry(head, typeof(*pos), member); &pos->member != (head); pos = list_next_entry(pos, member))
如此,藉助container_of,struct list_head得以一統天下,它也是linux kernel大廈基石的重要部分: