传统链表的特点:
1.传统链表的指针域跟节点的数据类型一样
2.传统链表的指针域指向下一个节点的首地址
3.传统链表中拿第一个节点作为头结点
linux内核链表的特点:
对于指针域内核进行抽象,定义一个数据结构struct list_head作为节点的指针域
1.节点的指针域不再和节点的数据类型保持一致,大家都是struct list_head
2.节点的指针域不再指向下一个节点的首地址,而是指向下一个节点的指针域,需要配合container_of来获取这个节点的首地址,通过首地址再去访问数据域的信息
3.一般来说内核用structlist_head作为链表头,即链表头没有数据域。
好处:如果有多种链表,如果使用struct list_head作为指针域,那么这些链表的操作都是跟list_head相关,所以可以定义一套操作函数。原因:内核链表的指针域跟节点的数据类型无关!
4.关于内核链表structlist_head的所有操作方法 include/linux/list.h
内核链表使用
1. 首先定义节点的数据结构
2. 然后将structlist_head嵌入其中
struct fox {
intdata;
chardata1;
structlist_head list; //指针域
};
3.定义一个链表头 //不包含数据域
struct list_head head;
INIT_LIST_HEAD(&head); //初始化链表头
4. 分配节点
struct fox *fox1 = kzalloc(sizeof(struct fox),
GFP_KERNEL);
struct fox *fox2 = kzalloc(sizeof(struct fox),
GFP_KERNEL);
struct fox *fox3 = kzalloc(sizeof(struct fox),
GFP_KERNEL);
4. 调用list_add/list_add_tail添加节点
list_add(&head, &fox1->list);
list_add(&head, &fox2->list);
list_add(&head, &fox3->list);
顺序:3->2->1
list_add_tail(&head, &fox1->list);
list_add_tail(&head, &fox2->list);
list_add_tail(&head, &fox3->list);
顺序:1->2->3
5. 调用list_del删除节点
list_del(&fox1->list);
6. 遍历链表list_for_each/list_for_each_safe
struct list_head *pos; //存放每一个节点指针域的首地址
list_for_each (pos, &head) {
//获取节点信息
}
struct list_head *pos; //存放下一个节点指针域的首地址
struct list_head *n; //存放下下一个节点指针域的首地址
list_for_each_safe(pos, n, &head) {
//获取节点信息
}
区别在于:如果仅仅是遍历链表,它们都一样,没什么区别。
如果在遍历链表的时候,进行了删除节点的操作,必须使用后者,否则造成断链,内存非法访问!
6. 遍历链表时,每次都是取出的是节点的指针域,如果要访问这个节点的数据域,还是使用list_entry来获取节点的首地址。
#define list_entry(ptr, type, member) \
container_of(ptr,type, member)
ptr:已知成员的首地址
type:结构体名
member:已知成员的变量名
返回值:结构体的首地址
container_of:
#define container_of (ptr, type, member) ({ \
const typeof ( ((type *)0)->member ) *_mptr = (ptr); \
(type *)( (char *)_mptr - offsetof(type,member) );})
#define offsetof (type, member) (size_t)(&( ((type *)0)->member ))