Linux驱动编程 step-by-step (十) Linux 内核链表

时间:2021-12-19 08:10:27

终于可以清闲下来打理一下我的blog了,台资企业真的事情很多很烦……

前几篇文章对字符设备有个简单介绍,并以简单的一个字符设备驱动作结尾,其实linux上大部分驱动程序都是字符设备程序,Linux源码中也可以看到很多的字符设备驱动,所以供学习的代码还是很多的。

这一节本想说一下如何测试设备驱动,但是因为最近看了看内核链标,所以称还记的比较清楚赶紧记录下来。有不到位的地方烦请指正。

Linux 内核链表

链表的简单介绍:

链表是线性表的实现方式之一,是一种常用的数据结构,用来组织一系列有序数据,并通过指针将这些有序数列链成一条数据链。相对于数组来说,它有良好的伸缩性,可以动态的添加或者删除等有点(但是访问速度较数组慢)。

数据链表常有两个域 数据域 与 指针域

内核链标是一个双向循环链表,但是它与我们常用的链表有所不同.

常用的双向链标定义如下:

[cpp] view plaincopy
  1. typedef struct NODE{  
  2.     char data;  
  3.     struct NODE *prev;  
  4.     struct NODE *next;  
  5. }NODE;  

这里的next 指针指向了下一个链标节点,通过node->data获得链标中的数据, 类似下图(但下图有些不准确)。

Linux驱动编程 step-by-step (十) Linux 内核链表

内核链表结构定义:

[cpp] view plaincopy
  1. struct list_head {  
  2.     struct list_head *next, *prev;   
  3. };  
使用内核链表,定义数据结构
[cpp] view plaincopy
  1. struct data_struct{  
  2.     struct  list_head list;  
  3.     char data;  
  4. };  

Linux驱动编程 step-by-step (十) Linux 内核链表

在这里我们可以通过内核链标获取获取整个数据结构,并获得有用数据。

这样的好处是:实现了内核链标的通用性,不需要为每个结构定义一个链表,使用统一的内核链表即可。

那我们怎样使用内核链标呢? 如何通过内核链标得到想要的数据结构呢?

使用内核链表:

定义包含内核链标的数据结构

[cpp] view plaincopy
  1. struct list_sample{  
  2.     struct list_head list;  
  3.     /*The data you need to use*/  
  4.     char ch;  
  5.     int x;  
  6.     /*************************/  
  7. };  

只要在数据结构中包含struct list_head成员即可。

初始化内核链表

使用内核链标需要一个链表头,故需要先行初始化脸表头

[cpp] view plaincopy
  1. static inline void INIT_LIST_HEAD(struct list_head *list)  
使用INIT_LIST_HEAD 传入一个list_head指针,他会将传入的list_head结构的next 于 prev成员都指向他本身。

添加一个链表节点

[cpp] view plaincopy
  1. static inline void list_add(struct list_head *newstruct list_head *head)                                                                                                                                       
  2. {  
  3.     __list_add(new, head, head->next);                       
  4. }  
在head 之后添加一个链表节点。此处会调用一个更底层的函数__list_add。 [cpp] view plaincopy
  1. static inline void __list_add(struct list_head *new,  
  2.                   struct list_head *prev,          
  3.                   struct list_head *next)                                                                                                                                                                        
  4. {  
  5.     next->prev = new;  
  6.     new->next = next;  
  7.     new->prev = prev;  
  8.     prev->next = new;  
  9. }  

它会将新的节点放于prev 与 next之间。

同样可以调用list_add_tail, 在head之前添加一个链表节点。

删除链表节点:

[cpp] view plaincopy
  1. static inline void __list_del_entry(struct list_head *entry)  
  2. {  
  3.     __list_del(entry->prev, entry->next);  
  4. }  
[cpp] view plaincopy
  1. static inline void __list_del(struct list_head * prev, struct list_head * next)  
  2. {  
  3.     next->prev = prev;  
  4.     prev->next = next;  
  5. }  

这里即将 entry节点从链表中删除(其实节点仍存在只是不在链表里边了) [cpp] view plaincopy
  1. static inline void list_del(struct list_head *entry)  
而list_del 函数会将entry节点删除并会将entry的两个成员置为不可用的地址,防止代码重复使用这个节点。

获取数据结构

[cpp] view plaincopy
  1. #define list_entry(ptr, type, member) \  
  2.     container_of(ptr, type, member)  

定义 list_entry宏 来获取数据结构,它会调用container_of 宏来获取这个结构体(container_of 宏我在前边的文章里边已经阐述过)

ptr 填入链表节点指针, type填入数据结构体类型, member示意链标在结构体中的名字。

如果现在我们知道链表的指针为entry,就可以这样获取整个结构体。

[cpp] view plaincopy
  1. struct list_sample sample = list_entry(entry, struct list_sample, list);  

以上是最基本的使用方法。