内核中处理链表数据时,会用list_head数据类型,以及对其的一系列操作,list_head是一种双向链表,它只包含两个指针,分别指向前后两个节点。在实际使用时,你可以定义自己所需的结构类型,然后将list_head包含其中,其他是有用数据,这样的话list_head负责联系前后节点形成双向链表,数据则绑在list_head上,使问题得以分解,见者容易理解,套用更加方便。
第一部分
先看一看list.h中的各部分:
/*list_head 结构类型*/
struct list_head {
struct list_head *next, *prev;
};
包含两个指针,指向前后两个节点,说明是双向链表
/*初始化一个节点,前后指针均指向自己*/
static inline void INIT_LIST_HEAD(struct list_head *list)
{
list->next = list;
list->prev = list;
}
/*向链表add一个节点,在prev和next之间插入一个new*/
static inline void __list_add(struct list_head *new, struct list_head *prev,struct list_head *next)
{
next->prev = new;
new->next = next;
new->prev = prev;
prev->next = new;
}
添加节点时,可以通过传入不同头结点前后指针,可以在尾部和首部添加,假设头节点next方向为正方向,比如下面这个:
static inline void list_add(struct list_head *new, struct list_head *head)
{
__list_add(new, head->prev, head);
}
就是从尾部添加,如果是__list_add(new, head, head->next)
就是在首部添加。
/*删除一个节点*/
static inline void __list_del(struct list_head * prev, struct list_head * next)
{
next->prev = prev;
prev->next = next;
}
static inline void list_del(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
entry->next = NULL;
entry->prev = NULL;
}
/*list_entry,从list成员得到整体结构体地址*/
#define list_entry(ptr, type, member) container_of(ptr, type, member)
#define offsetoflist(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetoflist(type,member) );})
第一个宏太简单,container_of是内核常用的套路,从成员地址获取整体结构体的地址
第二宏offsetoflist,TYPE可以认为是一个结构体类型,MEMBER是其中的成员名,将0强制变成TYPE可认为结构体起始位置为0,然后&((TYPE)0)->MEMBER,取其成员的地址并转为size_t,就是我们的结果值,其实就是成员所在地址相对于结构体起始地址的偏移量。
第三个宏container_of,它的作用是已知结构体中一成员名和其地址,求得整个结构体的地址。ptr为成员地址,type为结构体类型,member为结构体成员名。typeof(((type )0)->member)这表示其实就是成员的类型,所以第一句ptr赋给__mptr的const中间变量,这样做的目的更安全,const修饰表示ptr不被修改,中间变量防止ptr的多次出现,当ptr被赋值成p++时出错。第二句是用成员地址减去它相对于结构体地址的偏移量,自然是结构体的地址。
/*遍历list的for循环*/
#define list_for_each(pos, head) \
for (pos = (head)->next; pos != (head);\
pos = pos->next)
#define list_for_each_safe(pos, n, head) \
for(pos = (head)->next,n=pos->next; pos!=(head);\
pos = n,n = pos->next)
这里有两个宏,都是一个for循环,pos代表位置,head是头结点,两者的唯一区别在于,list_for_each_safe多了一个中间变量n,这么做的原因是,如果你在遍历到特定节点进行删除操作时,那么第一个的pos->next就找不到啦,而第二个刚开始就把pos->next存给了n,无论你对pos如何操作,都可以找到。
/*判断list是否为空*/
static inline int list_empty(const struct list_head *head)
{
return head->next == head;
}
简单不解释
下面是list.h:
#ifndef _LINUX_LIST_H
#define _LINUX_LIST_H
#include <stdio.h>
struct list_head {
struct list_head *next, *prev;
};
static inline void INIT_LIST_HEAD(struct list_head *list)
{
list->next = list;
list->prev = list;
}
static inline void __list_add(struct list_head *new, struct list_head *prev,struct list_head *next)
{
next->prev = new;
new->next = next;
new->prev = prev;
prev->next = new;
}
static inline void list_add(struct list_head *new, struct list_head *head)
{
__list_add(new, head->prev, head);
}
static inline void __list_del(struct list_head * prev, struct list_head * next)
{
next->prev = prev;
prev->next = next;
}
static inline void list_del(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
entry->next = NULL;
entry->prev = NULL;
}
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetoflist(type,member) );})
#define offsetoflist(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define list_for_each(pos, head) \
for (pos = (head)->next;pos != (head);\
pos = pos->next)
#define list_for_each_safe(pos, n, head) \
for(pos = (head)->next,n=pos->next; pos!=(head);\
pos = n,n = pos->next)
static inline int list_empty(const struct list_head *head)
{
return head->next == head;
}
#endif
第二部分
使用简单例子测试list.h
#include "list.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//包含list结构的有用结构体类型
struct person{
struct list_head list;
int age;
char name[50];
};
int main(int argc, char const *argv[])
{
//定义两个结构体变量并赋值
struct person Jack;
struct person Tom;
Jack.age = 11;
strcpy(Jack.name,"Jack");
Tom.age = 15;
strcpy(Tom.name,"Tom");
//定义头结点并初始化
struct list_head head;
INIT_LIST_HEAD(&head);
//把两个结构体变量中list节点加入到链表
list_add(&Jack.list,&head);
list_add(&Tom.list,&head);
//遍历链表,然后通过链表指针获取整体结构体指针,打印
struct list_head * pList;
struct person * pPerson;
printf("------1--------\n");
list_for_each(pList,&head){
pPerson = list_entry(pList,struct person,list);
printf("age:%d\nname:%s\n\n",pPerson->age,pPerson->name);
}
//加入申请内存的新节点
struct person *pMary = (struct person *)malloc(sizeof(struct person));
pMary->age = 16;
strcpy(pMary->name,"Mary");
list_add(&pMary->list,&head);
printf("------2--------\n");
list_for_each(pList,&head){
pPerson = list_entry(pList,struct person,list);
printf("age:%d\nname:%s\n\n",pPerson->age,pPerson->name);
}
//遍历链表,使用带safe的,找到特定节点进行删除,释放申请内存,这里删除后break的话,可以不带safe,如果还要遍历,带上
struct list_head * templist;
list_for_each_safe(pList,templist,&head){
pPerson = list_entry(pList,struct person,list);
if( strcmp(pPerson->name,"Mary") == 0){
list_del(&pPerson->list);
free(pPerson);
}
}
//遍历
printf("------3--------\n");
list_for_each(pList,&head){
pPerson = list_entry(pList,struct person,list);
printf("age:%d\nname:%s\n\n",pPerson->age,pPerson->name);
}
return 0;
}
打印结果:
------1--------
age:11
name:Jack
age:15
name:Tom
------2--------
age:11
name:Jack
age:15
name:Tom
age:16
name:Mary
------3--------
age:11
name:Jack
age:15
name:Tom