在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 wq;
struct list_head *ptr = &wq.list;

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

它們又分為兩種:

  • list_for_each(pos, head)
  • list_for_each_entry(pos, head, member)

帶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大廈基石的重要部分:


推薦閱讀:
相關文章