linux内核链表


#define container_of(ptr, type, member) ({             /  
         const typeof( ((type *)0)->member ) *__mptr = (ptr);     /  
         (type *)( (char *)__mptr - offsetof(type,member) );})  

container_of :
实现了根据一个结构体变量中的一个域成员变量的指针来获取指向整个结构体变量的指针的功能。

0地址指针】

(type *)0 : 好比:假设type结构存放于0其实的内存地址中(在编译的时候换算,不会执行。)

((type *)0)->member : 0指针指向的type结构中的member成员,访问结构中的数据成员; 

&((type *)0)->member : 0指针指向的type结构中的member成员的地址相对于0地址,即得到member成员地址相对type结构体的相对偏移

typeof (((type *)0)->member) 0指针指向的type结构中的member成员的类型

分析:
(TYPE *)0,将 0 强制转换为 TYPE 型指针,记 p = (TYPE *)0,p是指向TYPE的指针,它的值是0。那么 p->MEMBER 就是 MEMBER 这个元素了,而&(p->MEMBER)就是MENBER的地址,而基地址为0,这样就巧妙的转化为了TYPE中的偏移量。再把结果强制转换为size_t型的就OK了,size_t其实也就是int。



&操作如果是对一个表达式,而不是一个标识符,会取消操作,而不是添加。
比如&*a,会直接把a的地址求出来,不会访问*a。
&a->member,会把访问a->member的操作取消,只会计算出a->member的地址

【offsetof()】

#define OFFSET(struct_type, member)   ((size_t) &((struct_type *) 0)->member) 

typeof ()

typeof关键字是C语言中的一个新扩展,其的参数可以是两种形式表达式类型

比如: 

extern int foo();
typeof(foo()) var; 等价于 int var;


例子2
   typeof(int *) a,b;      等价于:  int *a,*b;

例子3

   typeof(int *) p1,p2; /* Declares two int pointers p1, p2 */
   int *p1, *p2;

   typeof(int) *p3,p4;/* Declares int pointer p3 and int p4 */
   int *p3, p4;


   typeof(int [10]) a1, a2;/* Declares two arrays of integers */


   int a1[10], a2[10];


遍历链表最简单的方法是使用list_for_each()宏。

**
 * list_for_each    -   iterate over a list
 * @pos:    the &struct list_head to use as a loop cursor.
 * @head:   the head for your list.
 */
#define list_for_each(pos, head) \
    for (pos = (head)->next; pos != (head); pos = pos->next)

可以看出,使用了辅助指针pos,pos是从第一节点开始的,并没有访问头节点,直到pos到达头节点指针head的时候结束。

而且 这种遍历仅仅是找到一个个结点的当前位置,那如何通过pos获得起始结点的地址,从而可以引用结点的域?
list.h 中定义了 list_entry 宏:
           #define   list_entry( ptr, type, member )  \
              ( (type *) ( (char *) (ptr)  - (unsigned long) ( &( (type *)0 )  ->  member ) ) )
          分析:(unsigned long) ( &( (type *)0 )  ->  member ) 把 0 地址转化为 type 结构的指针,然后获取该
          结构中 member 域的指针,也就是获得了 member 在type 结构中的偏移量。其中  (char *) (ptr) 求
         出的是 ptr 的绝对地址 ,二者相减,于是得到 type 类型结构体的起始地址,即起始结点的地址 。使用方法非常的巧妙!
(该宏使用了两个list_head类型参数,第一个参数用来指向当前项,这是一个你必须提供的临时变量,第二个参数是需要遍历的链表的头节点。
每次遍历中,第一个参数在链表中不断移动指向下一个元素,直到链表中的所有元素都被访问为止.

遍历链表的两个宏:
list_for_each: 
//遍历整个链表,每次遍历将数据打印出来  
    list_for_each(pos, &score_head)//这里的pos会自动被赋新值  
    {  
        tmp = list_entry(pos, struct score, list);  
        printk(KERN_WARNING"num: %d, English: %d, math: %d\n", tmp->num, tmp->English, tmp->math);  
    }  

list_for_each_safe: 

/**  
 * list_for_each_safe - iterate over a list safe against removal of list entry  
 * @pos:the &struct list_head to use as a loop cursor.  
 * @n:another &struct list_head to use as temporary storage  
 * @head:</span>the head for your list.  
 */  
#define list_for_each_safe(pos, n, head) \  
    for (pos = (head)->next, n = pos->next; pos != (head); \  
        pos = n, n = pos->next)  

两个遍历链表的区别
由上面两个对比来看,list_for_each_safe()函数比list_for_each()多了一个中间变量n(struct list_head *n)

当在遍历的过程中需要删除结点时,来看一下会出现什么情况:

list_for_each():list_del(pos)将pos的前后指针指向undefined state,导致kernel panic,list_del_init(pos)将pos前后指针指向自身,导                             致死循环。

list_for_each_safe():首先将pos的后指针缓存到n,处理一个流程后再赋回pos,避免了这种情况发生。


因此之遍历链表不删除结点时,可以使用list_for_each(),而当由删除结点操作时,则要使用list_for_each_safe()

其他带safe的处理也是基于这个原因。


关于kernel panic:

它表示Linux kernel走到了一个不知道怎么走下一步的状况,一旦出现这个情况,kernel就尽可能把它此时能获取的全部信息打印出来,至于能打印出多少信息,那就看哪种情况导致它panic了。

有两种主要类型的kernel panic:hard panic(也就是Aieee信息输出),soft panic(也就是Oops信息输出)

什么能导致kernel panic:

只有加载到内核空间的驱动模块才能直接导致kernel panic,可以在系统正常的情况下,使用lsmod查看当前系统加载了哪些模块。除此之外,内建在内核里的组建(比如memory map等)也能导致panic。

详细的就不说了。




但是,这里一个指向链表结构的指针通常是无用的,我们所需要的是一个指向包含list_head的结构体的指针。这时,我们就可以用之前
讨论的container_of方法,来获取list_head所在的结构体的指针。

/**
 * list_entry - get the struct for this entry
 * @ptr:    the &struct list_head pointer.
 * @type:   the type of the struct this is embedded in.
 * @member: the name of the list_struct within the struct.
 */
#define list_entry(ptr, type, member) \
    container_of(ptr, type, member)

/**
 * 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))


常用的Linux内核双向链表API介绍

linux link list结构图如下:



内核双向链表的在linux内核中的位置:/include/linux/list.h

使用双向链表的过程,主要过程包括创建包含struct link_head结构的结构体(item),建立链表头,向链表中添加item(自定义数据结构,双向链表数据单元),删除链表节点,遍历链表,判空等。

1、建立自定义链表数据结构

  1. struct kool_list{  
  2.     int to;  
  3.     struct list_head list;  //包含链表头
  4.     int from;  
  5.     };//自定义欲链接的数据额结构,并包含双向链表结构  
2、建立链表头

  1.     struct kool_list mylist;  
  2.     INIT_LIST_HEAD(&mylist.list);//初始化一个链表表头  
  3.   
或者

static LIST_HEAD(adc_host_head);//初始化一个链表头adc_host_head

第二种创建链表头和第一种的区别在于,第二种在编译的时候才会被初始化。还有一点就是链表头是独立的还是位于自定义链表数据结构中的。就好比在《例说Linux内核链表(二)》中给出的链表结构图和本文给出的结构图的区别。

3、向链表添加item

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

例如:

  1.  struct kool_list *tmp;  

  2.  tmp= (struct kool_list *)malloc(sizeof(struct kool_list));  
  3.           
  4.  printf("enter to and from:");  
  5.  scanf("%d %d", &tmp->to, &tmp->from);  //初始化数据结构
  6.         /* add the new item 'tmp' to the list of items in mylist */  
  7.  list_add(&(tmp->list), &(mylist.list));//项链表中添加新的元素节点,tmp中的list  

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

在链表的尾部添加一个item,和list_add的区别在于,list_add把新的item加到了链表头的后面,list_add_tail把item加到了链表头的前面。

4、遍历链表

list_for_each_entry(type *cursor, struct list_head *list, member)

这并不是一个函数,它是一个for循环,依次列出要遍历的链表,三个元素代表的意义:type *cursor代表item的指针,struct list_head *list是链表头,member是item中包含的list_head数据项。通过这三个数据可以定位到链表的每一个数据元素。其中定位原理就是结构体偏移。

例如:

  1.  list_for_each_entry(tmp, &mylist.list, list)  
  2.          printf("to= %d from= %d\n", tmp->to, tmp->from);  

这个宏可以分为两步,第一步是遍历链表,pos依次指向链表中每个item的struct list_head 结构,第二步是获取pos指向的struct list_head所在的item。这里是tmp 。

list_for_each(pos, &mylist.list){//遍历链表,pos依次指向链表的元素

tmp= list_entry(pos, struct kool_list, list);//获得包含pos节点的数据结构指针    

5、删除

删除链表中的某节点,首先要使用安全遍历,然后再删除。

例如:

  

  1. list_for_each_safe(pos, q, &mylist.list){  
  2.          tmp= list_entry(pos, struct kool_list, list);  
  3.          printf("freeing item to= %d from= %d\n", tmp->to, tmp->from);  
  4.          list_del(pos);  

6、链表空

int list_empty(struct list_head *head);

Returns a nonzero value if the given list is empty.



http://blog.csdn.net/sjmping/article/details/7639126













  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值