linux内核数据基本结构1-内核链表

Linux内核链表

Linux内核代码大量使用了链表这种数据结构。链表是在解决数组不能动态扩展这个缺陷而产生的一种数据结构。链表所包含的元素可以动态创建并插入和删除。链表的每个元素都是离散存放的,因此不需要占用连续的内存。链表通常由若干节点组成,每个节点的结构都是一样的,由有效数据区和指针区两部分组成。有效数据区用来存储有效数据信息,而指针区用来指向链表的前继节点或者后继节点。因此,链表就是利用指针将各个节点串联起来的一种存储结构;

linux中的链表巧妙的解决了这个问题,linux的链表不是将用户数据保存在链表节点中,而是将链表节点保存在用户数据中

 

链表和静态数组的区别:

1. 链表包含的元素都是动态创建并插入链表的,在编译时不知道需要创建多少个元素。

2. 因为链表中每个节点的创建时间各不相同,所以各节点在内存中无需占用连续内存区

 

 

(1)单向链表

单向链表的指针区只包含一个指向下一个节点的指针,因此会形成一个单一方向的链表,如下代码所示。

struct list {

    int data;   /*有效数据可以是单一数据也可以是结构体数据指针等等*/

    struct list *next; /*指向下一个元素的指针*/

};

 

如图所示,单向链表具有单向移动性,也就是只能访问当前的节点的后继节点,而无法访问当前节点的前继节点,因此在实际项目中运用得比较少。

 

 

(2)双向链表

如图所示,双向链表和单向链表的区别是指针区包含了两个指针,一个指向前继节点,另一个指向后继节点,如下代码所示。

struct list {

    int data;   /*有效数据*/

    struct list *next; /*指向下一个元素的指针*/

    struct list *prev; /*指向上一个元素的指针*/

};

 

(3)Linux内核链表实现

单向链表和双向链表在实际使用中有一些局限性,如数据区必须是固定数据,而实际需求是多种多样的。这种方法无法构建一套通用的链表,因为每个不同的数据区需要一套链表。为此,Linux内核把所有链表操作方法的共同部分提取出来,把不同的部分留给代码编程者自己去处理。Linux内核实现了一套纯链表的封装,链表节点数据结构只有指针区而没有数据区,另外还封装了各种操作函数,如创建节点函数、插入节点函数、删除节点函数、遍历节点函数等。

可以看到通过list_head结构就把我们的数据层跟驱动层分开了,而内核提供的各种操作方法接口也只关心list_head这个结构,也就是具体链接的时候也只链接这个list_head结构,并不关心你数据层定义了什么类型。

Linux内核链表使用struct list_head数据结构来描述。

<include/linux/types.h>

 

struct list_head {

    struct list_head *next, *prev;

};

链表头的初始化有两种方法,一种是静态初始化,另一种动态初始化。把next和prev指针都初始化并指向自己,这样便初始化了一个带头节点的空链表。

<include/linux/list.h>

/*静态初始化*/

#define LIST_HEAD_INIT(name) { &(name), &(name) }

#define LIST_HEAD(name) \

    struct list_head name = LIST_HEAD_INIT(name)

/*动态初始化*/

static inline void INIT_LIST_HEAD(struct list_head *list)

{

    list->next = list;

    list->prev = list;

}

添加节点到一个链表中,内核提供了几个接口函数,如list_add()是把一个节点添加到表头,list_add_tail()是插入表尾。

<include/linux/list.h>

void list_add(struct list_head *new, struct list_head *head)

list_add_tail(struct list_head *new, struct list_head *head)

遍历节点的接口函数。

#define list_for_each(pos, head) \

for (pos = (head)->next; pos != (head); pos = pos->next)

那么如何获取节点本身的数据结构呢?这里还需要使用list_entry()宏

#define list_entry(ptr, type, member) \

    container_of(ptr, type, member)

container_of()宏的定义在kernel.h头文件中。

#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)

 

补充:

1: container_of

// 步骤1:将数字0强制转型为type*,然后取得其中的member元素((type *)0)->member  // 相当于((struct student *)0)->list

// 步骤2:定义一个临时变量__mptr,并将其也指向ptr所指向的链表节点const typeof(((type *)0)->member)*__mptr = (ptr);

// 步骤3:计算member字段距离type中第一个字段的距离,也就是type地址和member地址之间的差// offset(type, member)也是一个宏,定义如下:#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

// 步骤4:将__mptr的地址 - type地址和member地址之间的差// 其实也就是获取type的地址

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值