Linux内核链表交换节点,[笔记]Linux内核链表:结点的插入、删除以及链表的遍历...

Linux内核链表:结点的插入、删除以及链表的遍历

1.

Linux内核链表的核心思想是:在用户自定义的结构A中声明list_head类型的成员p,这样每个结构类型为A的变量a中,都拥有同样的成员p,如下:

struct

A{

int

property;

struct

list_head p;

}

其中,list_head结构类型定义如下:

struct

list_head {

struct

list_head *next,*prev;

};

2. 下面这段程序,完成结点的插入、删除以及链表的遍历:

a4c26d1e5885305701be709a3d33442f.png

MODULE_LICENSE("GPL");

MODULE_AUTHOR("Leonard");

MODULE_DESCRIPTION("List Module");

MODULE_ALIAS("A list test module");

struct mylist{

char data[100];

int num;

struct list_head list;

};

struct list_head *entry;

struct list_head mylist_head;

struct mylist *list_k;

struct mylist *list_k_tmp;

int mylist_init()

{

int i;

INIT_LIST_HEAD(&mylist_head);

list_k = kmalloc((sizeof(struct

mylist))*8,GFP_KERNEL);

memset(list_k,0,sizeof(struct mylist)*8);

for(i=0;i<8;i++)

{

sprintf(list_k[i].data,"Leonard%d",i+1);

list_k[i].num = i+1;

if(i<4)

list_add(&(list_k[i].list),&mylist_head);

}

list_add(&(list_k[4].list),&(list_k[0].list));

list_add(&(list_k[5].list),&(list_k[1].list));

list_add(&(list_k[6].list),&(list_k[2].list));

list_add_tail(&(list_k[7].list),&mylist_head);

//list_add(&(list_k[7].list),&mylist_head);

list_for_each(entry,&mylist_head) {

list_k_tmp =

list_entry(entry,struct mylist,list);

printk("<0>Num:%d Data:%s

\n",list_k_tmp->num,list_k_tmp->data);

}

return 0;

}

void mylist_exit()

{

int i;

for(i=0;i<8;i++)

list_del(&(list_k[i].list));

kfree(list_k);

printk("<0>Module Exit!List Delete

OK!\n");

}

module_init(mylist_init);

module_exit(mylist_exit);

分析:

语句 list_k = kmalloc((sizeof(struct

mylist))*8,GFP_KERNEL);为链表分配合适的内存空间,

然后开始插入链表节点:

for(i=0;i<8;i++)

{

sprintf(list_k[i].data,"Leonard%d",i+1);

list_k[i].num = i+1;

if(i<4)

list_add(&(list_k[i].list),&mylist_head);

}

list_add(&(list_k[4].list),&(list_k[0].list));

list_add(&(list_k[5].list),&(list_k[1].list));

list_add(&(list_k[6].list),&(list_k[2].list));

list_add_tail(&(list_k[7].list),&mylist_head);

对上面稍加分析可知,链表节点内容为:4,3,7,2,6,1,5,8。运行结果的确如此:

a4c26d1e5885305701be709a3d33442f.png

注意在mylist_exit()中,删除节点用的是for循环,没有问题。

但是如果用list_for_each(),使用不当容易导致系统死机,具体分析见下一小节。

3.遍历时节点删除

在[include/linux/list.h]中,list_for_each()宏是这么定义的:

#define list_for_each(pos, head) \

for (pos = (head)->next,

prefetch(pos->next); pos != (head); \

pos = pos->next, prefetch(pos->next))

它实际上是一个for循环,利用传入的pos作为循环变量,从表头head开始,逐项向后(next方向)移动pos,直至又回到head(prefetch()可以不考虑,用于预取以提高遍历速度)。

这个实验需要思考的问题是,在链表遍历删除的时候,稍不小心就容易引起系统死机。

前面介绍了用于链表遍历的宏,是通过移动pos指针来达到遍历的目的。但如果遍历的操作中包含删除pos指针所指向的节点,pos指针的移动就会被中断,因为list_del(pos)将把pos的next、prev置成LIST_POSITION2和LIST_POSITION1的特殊值。也就是将该节点的prev指向next,next指向prev,将本节点跨过去然后将本节点的next和prev都赋值为一个无意义的值。因此,调用者可以自己缓存prev指针使遍历操作能够连贯起来,这里如果想让循环继续下去的话,需要先将本节点的prev节点暂存起来,然后执行list_del,然后再将prev节点恢复成当前节点。正确的方法应该改为如下:

void mylist_exit()

{

//int i;

//for(i=0;i<8;i++)

//list_del(&(list_k[i].list));

struct list_head *tmp1;

list_for_each(entry,&mylist_head)

{

list_k_tmp =

list_entry(entry,struct mylist,list); tmp1 =

entry->prev;

list_del(entry);

entry =

tmp1; printk("<0>Delete:

Num:%d Data:%s \n",list_k_tmp->num,list_k_tmp->data);

}

kfree(list_k);

printk("<0>Module Exit!List Delete

OK!\n");

}

这样就不会出现死循环的问题了。

a4c26d1e5885305701be709a3d33442f.png

此外,为了编程的一致性,Linux链表仍然提供了两个对应于基本遍历操作的"_safe"接口:list_for_each_safe(pos,

n, head)、list_for_each_entry_safe(pos, n, head,

member),它们要求调用者另外提供一个与pos同类型的指针n,在for循环中暂存pos下一个节点的地址,避免因pos节点被释放而造成的断链。

参考文章:

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值