前言
(一)首先介绍一下内核链表:
1、内核链表常出现在系统内部,是ubuntu内部提供的一种双向循环链表 。
双向循环链表:所有的东西都是自定义的:数据域,prev和next指针
内核链表:只有数据域是我们自定义:其他的操作在内核链表文件list.h中均有定义声明
2、内核链表与普通链表的区别:
1. 普通链表当中数据域和指针域,没有做到区分。数据与指针形成了一个整体,而内核链表数据与指针是完全剥离的没有直接的关系。
2. 在普通链表当中所有节点的数据都是一样的类型,而内核链表中每一个节点的类型都可以是不同的。
3. 普通的链表每一个节点都只能出现在一个链表当中,因为它只有一对前驱/后继指针,而内核链表可以有多对前驱/后继指针。
(二)内核链表的好处:
常见的单向链表和双向链表指针指向的是链表节点起始位置,在Linux内核中实际使用中有一些局限性,如数据区必须是固定的,而实际需求是多种多样的。这种方法无法构建一套通用的链表,因为每个不同的数据区需要一套链表。为此,Linux内核把所有链表操作方法的共同部分提取出来,把不同的部分留给代码编写者自己去处理。
Linux内核实现了一套纯链表的封装,链表节点数据结构只有指针区而没有数据区,另外还封装了各种操作函数,如创建节点函数、插入节点函数、删除节点函数、遍历节点函数等。
(三)链表的初始化
链表的构成:大结构体与小结构体
大结构体: 包含了数据以及指针(内核链表中的小的结构体)小结构体: 内核链表中用来指向前一个节点和后一个节点的指针注意:在大结构体当中小结构体必须是一个普通变量不可以使用指针。
typedef struct kernel{//大结构体类型
int data;//数据域
struct list_head list;//小结构体名称
}kernel_t,*pkernel_t;
list.h文件中list_head介绍如下
struct list_head{
struct list_head *next,*prev;
};
(四)链表增删改移动
1、插入操作
思路:
内核链表中插入数据的思路:
把用户传进来的两个参数: 新节点 + 头节点
演变成很三个参数: 新节点 + 新节点的前驱节点 + 新节点的后继节点
1 /**
2 * 节点头插
3 *
4 * 使用内联函数list_add(struct list_head *new, struct list_head *head)
5 * 将会自动调用__list_add,实现功能:将节点 new 插入到 head 后面;实现头插功能
6 */
7 static inline void list_add(struct list_head *xnew, struct list_head *head)
8 {
9 __list_add(new, head, head->next);
10 }
头插法:
//头插操作
void kernel_insert_head(pkernel_t p,int d)
{
//根据数据域创建节点,并头插
pkernel_t node=(pkernel_t)malloc(sizeof(kernel_t));
if(NULL==node){
perror("malloc");//当创建失败,根据错误码,显示原因
return;//
}
//根据传入的参,对节点数据域赋值
node->data=d;
//将node插在p的后面
list_add(&node->list,&p->list);
}
list_add(&node->list,&p->list);
#ifndef CONFIG_DEBUG_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;
}
#else
extern void __list_add(struct list_head *new,
struct list_head *prev,
struct list_head *next);
#endif
/**
* list_add - add a new entry
* @new: new entry to be added
* @head: list head to add it after
*
* Insert a new entry after the specified head.
* This is good for implementing stacks.
*/
static inline void list_add(struct list_head *new, struct list_head *head)
{
__list_add(new, head, head->next);
}
尾插法:
void kernel_insert_tail(pkernel_t p,int d)
{
//根据数据域创建节点,并尾插
pkernel_t node=(pkernel_t)malloc(sizeof(kernel_t));
if(NULL==node){
return;
}
node->data=d;//根据数据域赋值
list_add_tail(&node->list,&p->list);
}
list.h中:
/**
* list_add_tail - add a new entry
* @new: new entry to be added
* @head: list head to add it before
*
* Insert a new entry before the specified head.
* This is useful for implementing queues.
*/
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
__list_add(new, head->prev, head);
}
2、删除操作
//删除操作
void kernel_del(pkernel_t p,int d)
{
//1>查找数据域d是否存在
pkernel_t pos; //定义的循环变量
list_for_each_entry(pos,&p->list,list)
{
if(pos->data==d){
list_del(&pos->list);
free(pos);
return;
}
}
printf("没有找到该数据\n");
}
for循环函数:
/**
* list_for_each_entry - iterate over list of given type
* @pos: the type * to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the list_struct within the struct.
*/
#define list_for_each_entry(pos, head, member) \
for (pos = list_entry((head)->next, typeof(*pos), member); \
&pos->member != (head); \
pos = list_entry(pos->member.next, typeof(*pos), member))
list_del函数
/*
* Delete a list entry by making the prev/next entries
* point to each other.
*
* This is only for internal list manipulation where we know
* the prev/next entries already!
*/
static inline void __list_del(struct list_head * prev, struct list_head * next)
{
next->prev = prev;
prev->next = next;
}
/**
* list_del - deletes entry from list.
* @entry: the element to delete from the list.
* Note: list_empty() on entry does not return true after this, the entry is
* in an undefined state.
*/
#ifndef CONFIG_DEBUG_LIST
static inline void __list_del_entry(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
}
static inline void list_del(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
entry->next = LIST_POISON1;
entry->prev = LIST_POISON2;
}
#else
extern void __list_del_entry(struct list_head *entry);
extern void list_del(struct list_head *entry);
#endif
3、更新操作
//替换操作
void kernel_update(pkernel_t p,int old,int new)
{
//1>查找一下旧数据是否在链表当中
pkernel_t pos;
pkernel_t temp;//传输替换的数据结构体
list_for_each_entry(pos,&p->list,list)
{
if(pos->data==old){
temp=(pkernel_t)malloc(sizeof(kernel_t));
temp->data=new;
list_replace_init(&pos->list,&temp->list);
free(pos);
return;
}
}
printf("没有找到该数据\n");
}
替换:
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;
}
static inline void list_replace_init(struct list_head *old,
struct list_head *new)
{
list_replace(old, new);
INIT_LIST_HEAD(old);
}
static inline void INIT_LIST_HEAD(struct list_head *list)
{
list->next = list;
list->prev = list;
}
(五)链表的遍历
内核链表由于没有数据域,数据域是我们自己操作的定义的,所以需要先知道数据域的大小,才可以通过小结构体与数据域差值得到大结构体的地址。
分析如下:
宏: #define list_entry(ptr, type, member) \
container_of(ptr, type, member)
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
解释: ptr:小结构体的指针类型:struct list_head *
type: 大结构体的类型:struct kernel ·
member:小结构体在大结构体中的名称:list
1>分析
list_entry(ptr, type, member)= container_of(ptr, type, member)
2>container_of(ptr, type, member)=
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
3>offsetof(TYPE, member) = ((size_t) &((TYPE *)0)->member)
4>综上所述,我们调用:list_entry(ptr, type, member)就会获得下面的内容
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - ((size_t) &((type *)0)->member)
重点:宏分析--->是你掌握内核链表的重要一环
1> const typeof( ((type *)0)->member ) *__mptr = (ptr);
((type *)0)--->将0强转为 大结构体的指针类型 0x0
((type *)0)->member 大结构体下的member= list
typeof--->取类型
const struct list_head * __mptr=ptr;
定义一个小结构体指针类型变量,保存小结构体的地址
2>(type *)( (char *)__mptr - ((size_t) &((type *)0)->member)
((size_t) &((type *)0)->member)
(size_t) &list
- 无符号整型 ,将list的地址转化为无符号整型
(type *)( (char *)__mptr
字符类型 --->一次只能运算一个字符
(type *)大结构体类型 --->mptr转化为 char * 运算后,转化为大结构体类型
减去小结构体地址 就会得到 大结构体地址
移动一个字节:偏移量:1
总结:
list_entry(ptr, type, member);
通过小结构体指针类型ptr 得到 大结构体指针类型
使用实例:
struct list_head *ptr=随便一个小结构体地址;外部传入
pkernel_t node=list_entry(ptr,kernel,list);
printf("%d\n",node->data);
遍历函数:
//遍历
void display(pkernel_t p)
{
if( p->list.next == &p->list )
{
printf("该表为空!!\n");
return -1 ;
}
printf("遍历结果为:");
pkernel_t node;
struct list_head *pos;
list_for_each(pos,&p->list){//循环
node=list_entry(pos,kernel_t,list);
printf("%d ",node->data);
}
printf("\n");
}