linux内核数据结构之链表

合集下载

数据结构中linklist的理解

数据结构中linklist的理解

数据结构中linklist的理解LinkList(链表)的理解。

在数据结构中,链表(LinkList)是一种基本的数据结构,它由一系列节点组成,每个节点包含数据和指向下一个节点的指针。

链表是一种线性数据结构,它可以用来表示一系列元素的顺序。

与数组不同,链表中的元素在内存中不是连续存储的,而是通过指针相互连接起来的。

这种特性使得链表具有一些独特的优势和应用场景。

链表的基本结构。

链表由节点组成,每个节点包含两部分,数据和指针。

数据部分用来存储元素的值,指针部分用来指向下一个节点。

链表的第一个节点称为头节点,最后一个节点称为尾节点,尾节点的指针指向空值(NULL)。

链表的分类。

链表可以分为单向链表、双向链表和循环链表三种基本类型。

单向链表,每个节点只包含一个指针,指向下一个节点。

双向链表,每个节点包含两个指针,分别指向前一个节点和后一个节点。

循环链表,尾节点的指针指向头节点,形成一个闭环。

不同类型的链表适用于不同的场景,选择合适的链表类型可以提高数据操作的效率。

链表的优势。

链表相对于数组有一些明显的优势:插入和删除操作高效,由于链表中的元素不是连续存储的,插入和删除操作可以在常数时间内完成,而数组中的插入和删除操作需要移动大量元素,时间复杂度为O(n)。

动态扩展,链表的大小可以动态调整,不需要预先分配固定大小的内存空间。

链表的应用场景。

由于链表的优势,它在一些特定的应用场景中得到了广泛的应用:LRU缓存,链表可以用来实现LRU(Least Recently Used)缓存淘汰算法,当缓存空间不足时,链表可以高效地删除最久未使用的元素。

大整数运算,链表可以用来表示大整数,实现大整数的加减乘除运算。

图论算法,在图论算法中,链表常常用来表示图的邻接表,用于表示图中的顶点和边的关系。

链表的实现。

链表的实现可以使用指针或者引用来表示节点之间的关系。

在C语言中,可以使用指针来表示节点之间的连接关系;在Java等语言中,可以使用引用来表示节点之间的连接关系。

linux内核hash表 使用例程

linux内核hash表 使用例程

linux内核hash表使用例程Linux内核中的hash表是一种常用的数据结构,用于快速查找和插入数据。

它是一种哈希表,通过将关键字映射到一个固定大小的数组中,并在数组中存储对应的值来实现高效的查找和插入操作。

在Linux内核中,hash表广泛应用于各个子系统中,比如网络子系统、文件系统、进程管理等。

这些子系统需要快速地查找和插入数据,而hash表正是为此而设计的。

hash表的实现方式多种多样,但在Linux内核中,一般采用的是拉链法(chaining)来处理冲突。

具体来说,每个数组元素都是一个链表的头指针,当多个关键字映射到同一个数组元素时,它们会被插入到链表中。

这样,当需要查找某个关键字时,只需要根据关键字的哈希值找到对应的数组元素,然后遍历链表即可。

为了提高查找效率,Linux内核中的hash表还采用了一些优化措施。

例如,为了减少冲突,每个数组元素都会被分成多个桶(bucket),每个桶中存放一条链表。

这样,即使多个关键字映射到同一个数组元素,它们也可以分布在不同的桶中,从而提高查找效率。

为了进一步提高查找效率,Linux内核中的hash表还采用了一种叫做“二次哈希”的技术。

具体来说,每个关键字的哈希值会经过一次次的哈希函数计算,得到一个新的哈希值,然后再根据这个新的哈希值找到对应的数组元素。

这样,即使多个关键字的哈希值相同,它们经过二次哈希后得到的新的哈希值也会不同,从而减少冲突,提高查找效率。

除了拉链法和二次哈希,Linux内核中的hash表还可以采用其他的冲突解决方法,比如开放定址法(open addressing)。

在开放定址法中,当发生冲突时,会根据一定的规则来寻找下一个可用的数组元素,直到找到一个空闲的位置或者遍历完整个数组。

虽然开放定址法的实现比较简单,但由于可能出现聚集现象,导致查找效率下降,因此在Linux内核中使用较少。

总的来说,Linux内核中的hash表是一种高效的数据结构,用于快速查找和插入数据。

Linux内核:RCU机制与使用

Linux内核:RCU机制与使用

Linux内核:RCU机制与使⽤Linux 内核:RCU机制与使⽤背景学习Linux源码的时候,发现很多熟悉的数据结构多了__rcu后缀,因此了解了⼀下这些内容。

介绍RCU(Read-Copy Update)是数据同步的⼀种⽅式,在当前的Linux内核中发挥着重要的作⽤。

RCU主要针对的数据对象是链表,⽬的是提⾼遍历读取数据的效率,为了达到⽬的使⽤RCU机制读取数据的时候不对链表进⾏耗时的加锁操作。

这样在同⼀时间可以有多个线程同时读取该链表,并且允许⼀个线程对链表进⾏修改(修改的时候,需要加锁)。

RCU适⽤于需要频繁的读取数据,⽽相应修改数据并不多的情景,例如在⽂件系统中,经常需要查找定位⽬录,⽽对⽬录的修改相对来说并不多,这就是RCU发挥作⽤的最佳场景。

RCU(Read-Copy Update),是 Linux 中⽐较重要的⼀种同步机制。

顾名思义就是“读,拷贝更新”,再直⽩点是“随意读,但更新数据的时候,需要先复制⼀份副本,在副本上完成修改,再⼀次性地替换旧数据”。

这是 Linux 内核实现的⼀种针对“读多写少”的共享数据的同步机制。

RCU机制解决了什么在RCU的实现过程中,我们主要解决以下问题:1、在读取过程中,另外⼀个线程删除了⼀个节点。

删除线程可以把这个节点从链表中移除,但它不能直接销毁这个节点,必须等到所有的读取线程读取完成以后,才进⾏销毁操作。

RCU中把这个过程称为宽限期(Grace period)。

2、在读取过程中,另外⼀个线程插⼊了⼀个新节点,⽽读线程读到了这个节点,那么需要保证读到的这个节点是完整的。

这⾥涉及到了发布-订阅机制(Publish-Subscribe Mechanism)。

3、保证读取链表的完整性。

新增或者删除⼀个节点,不⾄于导致遍历⼀个链表从中间断开。

但是RCU并不保证⼀定能读到新增的节点或者不读到要被删除的节点。

RCU(Read-Copy Update),顾名思义就是读-拷贝修改,它是基于其原理命名的。

list_head 用法

list_head 用法

list_head 用法list_head是Linux内核中用于表示链表节点的数据结构,它提供了一些方便的方法来操作链表。

在Linux操作系统中,list_head通常用于表示一个链表的头部,链表的每个节点包含一些数据,而list_head仅包含对链表中下一个节点的指针引用。

list_head的用法非常简单,它主要有以下几个常用的成员变量和方法:1.list_head的成员变量:成员变量包括list_head的next指针和前一个节点的指针prev。

当list_head指向链表的最后一个节点时,prev通常为NULL。

2.list_add方法:用于将一个节点添加到链表的末尾。

该方法需要传入要添加的节点和链表的头节点。

3.list_del方法:用于从链表中删除一个节点。

该方法需要传入要删除的节点。

4.list_empty方法:用于判断链表是否为空。

如果链表为空,则返回TRUE,否则返回FALSE。

5.list_entry方法:用于获取链表中指定索引的节点。

该方法需要传入索引号和头节点。

使用list_head可以方便地遍历链表中的所有节点,也可以方便地添加、删除和查找节点。

下面是一个简单的示例代码,演示了如何使用list_head:```c#include<linux/list_head.h>#include<linux/module.h>structnode{intdata;structlist_headlist;};intmain(void){structnode*node1=kmalloc(sizeof(structnode),GFP_KERNEL);structnode*node2=kmalloc(sizeof(structnode),GFP_KERNEL);structnode*head=NULL;unsignedinti;/*初始化链表头部*/list_add(&head->list,&node1->list);list_add(&node2->list,&head->list);node2->data=1;node1->data=2;/*遍历链表*/printk("Listhead:%p\n",head);printk("Listelements:\n");for(i=0;i<2;i++){printk("Node%d:%d\n",i,list_entry(head->list.next,structnode,list)->data);list_del(&head->list);/*删除头节点*/head=list_entry(head->list.next,structnode,list)->list;/*移动到下一个节点*/}printk("Afterdeletion:\n");printk("Node%d:%d\n",i,head->data);/*打印最后一个节点*/ kfree(node1);kfree(node2);return0;}```在上面的示例代码中,我们首先创建了一个链表,并使用list_add方法将两个节点添加到链表中。

struct list_head的定义

struct list_head的定义

struct list_head的定义struct list_head是Linux内核中一个非常重要的数据结构,其主要作用是实现双向链表,是Linux内核实现链表的基础。

本文将详细介绍struct list_head的定义、使用方法及其重要性。

一、struct list_head的定义struct list_head是一个重要的数据结构,它定义在include/linux/list.h文件中,其定义如下:struct list_head {struct list_head *prev, *next;};其中,prev和next分别表示前一个节点和后一个节点的指针,因此struct list_head可以实现双向链表。

二、struct list_head的使用struct list_head主要用于实现链表,其使用方法如下:1. 初始化链表初始化链表的方法是通过定义一个struct list_head类型的变量,然后将其prev和next指针都指向自己,代码如下:struct list_head my_list = LIST_HEAD_INIT(my_list);2. 插入节点在链表中插入一个节点的方法是通过list_add函数,该函数将新节点插入到链表头部,代码如下:list_add(&new_node->list, &my_list);其中,new_node为新节点的指针,&new_node->list为新节点的struct list_head成员,&my_list为链表头的struct list_head 成员。

3. 删除节点从链表中删除一个节点的方法是通过list_del函数,该函数将节点从链表中删除,代码如下:list_del(&node->list);其中,node为要删除的节点的指针,&node->list为节点的struct list_head成员。

list_for_each_safe的用法

list_for_each_safe的用法

list_for_each_safe的用法list_for_each_safe是Linux内核中的一个宏,用于在遍历一个双向链表时确保遍历过程中的链表修改不会导致出现问题。

在本文中,将详细讨论list_for_each_safe的使用方法以及相关的背景知识。

1. 引言(介绍链表和链表遍历的重要性)链表是计算机科学中常用的数据结构之一,它由一系列节点组成,每个节点包含数据和指向下一个节点的指针。

链表的遍历是对链表中的每个元素进行操作或访问的过程。

在操作系统中,特别是在Linux内核中,链表的使用非常广泛。

因此,高效且线程安全的链表遍历方法变得至关重要。

2. 理解安全遍历的需求链表是一个动态数据结构,在遍历链表时,可能会有其他线程或处理器修改链表的结构,比如添加、删除或移动节点。

如果不采取措施来处理这种情况,就可能导致指针丢失或访问无效的节点。

这对于操作系统内核来说是一个严重的问题,因为它们需要保证数据的一致性和稳定性。

list_for_each_safe宏就是为了解决这个问题而设计的。

3. list_for_each_safe的定义和参数在Linux内核源代码中,list_for_each_safe的定义如下:c#define list_for_each_safe(pos, n, head) \for (pos = list_first_entry(head, typeof(*pos), member), \ n = list_next_entry(pos, member); \&pos->member != (head); \pos = n, n = list_next_entry(n, member))在这个宏中,pos表示当前遍历节点的指针,n表示pos的下一个节点的指针,head是链表的头节点。

4. list_for_each_safe的实现原理list_for_each_safe的实现原理非常巧妙。

linux 内核list使用

linux 内核list使用

linux 内核list使用
在Linux内核中,list是一种常用的数据结构,用于实现链表。

它在内核中被广泛使用,包括进程管理、文件系统、网络等多个方面。

在内核中使用list时,有几个常见的操作和用法需要注意:
1. 初始化list,在使用list之前,需要先对其进行初始化。

可以使用宏INIT_LIST_HEAD来初始化一个空的list。

2. 添加元素到list,使用list_add、list_add_tail等函数
可以将新元素添加到list中。

这些函数会在指定位置插入新元素,
并更新相关指针。

3. 遍历list,可以使用list_for_each、
list_for_each_entry等宏来遍历list中的元素。

这些宏会帮助我
们遍历整个list,并执行指定的操作。

4. 删除元素,使用list_del和list_del_init函数可以从
list中删除指定的元素。

需要注意的是,在删除元素之后,需要手
动释放相关资源,以避免内存泄漏。

除了上述基本操作外,list还支持一些高级操作,如合并两个list、反转list等。

在编写内核代码时,合理地使用list可以提高代码的可读性和性能。

总的来说,Linux内核中的list是一种非常灵活和强大的数据结构,合理地使用它可以帮助我们更好地管理和组织数据。

在实际编程中,需要根据具体的需求和场景来选择合适的list操作,以达到最佳的效果。

掌握数据结构中的链表和数组的应用场景

掌握数据结构中的链表和数组的应用场景

掌握数据结构中的链表和数组的应用场景链表和数组都是常用的数据结构,它们在不同的场景下有不同的应用。

一、链表的应用场景:1.链表适合动态插入和删除操作:链表的插入和删除操作非常高效,只需修改指针的指向,时间复杂度为O(1)。

因此,当需要频繁进行插入和删除操作时,链表是一个很好的选择。

-操作系统中的进程控制块(PCB):操作系统需要频繁地创建、停止、销毁进程,使用链表存储这些PCB,可以方便地插入、删除和管理进程。

-聊天记录:在聊天应用中,新的消息会动态插入到聊天记录中,使用链表存储聊天记录可以方便地插入新消息。

2.链表节省内存空间:每个节点只需存储当前元素和指向下一个节点的指针,不需要像数组一样预分配一块连续的内存空间,因此链表对内存空间的利用更加灵活。

-操作系统中的内存管理:操作系统使用链表来管理空闲内存块和已分配的内存块,可有效节省内存空间。

3.链表支持动态扩展:链表的长度可以随时变化,可以动态地扩容和缩容。

-缓存淘汰算法:在缓存中,如果链表已满,当有新数据需要加入缓存时,可以通过删除链表头部的节点来腾出空间。

4.链表可以快速合并和拆分:将两个链表合并成一个链表只要调整指针的指向即可,时间复杂度为O(1)。

-链表排序:在排序算法中,链表归并排序利用链表快速合并的特性,使得归并排序在链表上更高效。

二、数组的应用场景:1.随机访问:数组可以根据索引快速访问元素,时间复杂度为O(1)。

-图像处理:图像通常以像素点的形式存储在数组中,可以通过索引快速访问某个特定像素点的颜色信息。

2.内存连续存储:数组的元素在内存中是连续存储的,可以利用硬件缓存机制提高访问效率。

-矩阵运算:矩阵可以通过二维数组来表示,利用矩阵的连续存储特性,可以高效地进行矩阵运算。

3.大数据存储:数组可以预先分配一块连续的内存空间,非常适合存储大量的数据。

-数据库中的数据表:数据库中的数据表通常使用数组来实现,可以快速存取和处理大量的数据。

Linux内核链表list_head扩展---klist

Linux内核链表list_head扩展---klist

142/**143*klist_add_after-Init a klist_node and add it after an existing node 144*@n:node we're adding.145*@pos:node to put@n after146*//*在节点pos后面插入节点n*/147void klist_add_after(struct klist_node*n,struct klist_node*pos)148{149struct klist*k=knode_klist(pos);150151klist_node_init(k,n);152spin_lock(&k->k_lock);153list_add(&n->n_node,&pos->n_node);154spin_unlock(&k->k_lock);155}156EXPORT_SYMBOL_GPL(klist_add_after);157158/**159*klist_add_before-Init a klist_node and add it before an existing node 160*@n:node we're adding.161*@pos:node to put@n after162*//*在节点pos前面插入节点n*/163void klist_add_before(struct klist_node*n,struct klist_node*pos)164{165struct klist*k=knode_klist(pos);166167klist_node_init(k,n);168spin_lock(&k->k_lock);169list_add_tail(&n->n_node,&pos->n_node);170spin_unlock(&k->k_lock);171}172EXPORT_SYMBOL_GPL(klist_add_before);173/*等待者结构体,用于删除节点,删除完成唤醒进程*/174struct klist_waiter{175struct list_head list;176struct klist_node*node;177struct task_struct*process;178int woken;179};180/*定义并初始化klist节点移除自旋锁*/181static DEFINE_SPINLOCK(klist_remove_lock);/*定义一个等待器的链表*/182static LIST_HEAD(klist_remove_waiters);183184static void klist_release(struct kref*kref)185{186struct klist_waiter*waiter,*tmp;187struct klist_node*n=container_of(kref,struct klist_node,n_ref);188189WARN_ON(!knode_dead(n));/*删除链表中的节点入口*/190list_del(&n->n_node);191spin_lock(&klist_remove_lock);/*内核链表操作宏include/linux/list.h,遍历klist节点移除等待链表*/192list_for_each_entry_safe(waiter,tmp,&klist_remove_waiters,list){/*是要删除链表节点的等待器*/193if(waiter->node!=n)194continue;195/*等待者唤醒标志*/196waiter->woken=1;197mb();/*唤醒等待进程*/198wake_up_process(waiter->process);/*删除链表入口*/199list_del(&waiter->list);200}201spin_unlock(&klist_remove_lock);/*设置节点n指向的klist为空*/202knode_set_klist(n,NULL);203}204/*减引用次数并删除节点*/205static int klist_dec_and_del(struct klist_node*n)206{/*n->nref减引用次数,若引用次数减完不为0,调用klist_release清除节点对象,返回1;为0,则返回0*/207return kref_put(&n->n_ref,klist_release);208}209/*带锁操作的节点删除,不判断是否成功,减引用次数*/210static void klist_put(struct klist_node*n,bool kill)211{/*获取节点的put方法*/212struct klist*k=knode_klist(n);213void(*put)(struct klist_node*)=k->put;214215spin_lock(&k->k_lock);/*“需要杀死节点”==*/216if(kill)217knode_kill(n);/*节点对象引用次数为0了,则不需要调用put方法*/218if(!klist_dec_and_del(n))219put=NULL;220spin_unlock(&k->k_lock);/*调用put方法*/221if(put)222put(n);223}224225/**226*klist_del-Decrement the reference count of node and try to remove. 227*@n:node we're deleting.228*//*删除节点“杀死死节点*/229void klist_del(struct klist_node*n)230{231klist_put(n,true);232}233EXPORT_SYMBOL_GPL(klist_del);234235/**236*klist_remove-Decrement the refcount of node and wait for it to go away. 237*@n:node we're removing.238*/239void klist_remove(struct klist_node*n)240{/*定义一个等待者,并加入等待者加入移除等待者链表*/241struct klist_waiter waiter;242243waiter.node=n;244waiter.process=current;245waiter.woken=0;246spin_lock(&klist_remove_lock);247list_add(&waiter.list,&klist_remove_waiters);248spin_unlock(&klist_remove_lock);249/*清除节点,并设置等待者*/330*First grab list lock.Decrement the reference count of the previous 331*node,if there was one.Grab the next node,increment its reference 332*count,drop the lock,and return that next node.333*//*“预下”链表中下一节点*/334struct klist_node*klist_next(struct klist_iter*i)335{336void(*put)(struct klist_node*)=i->i_klist->put;337struct klist_node*last=i->i_cur;338struct klist_node*next;339/*抢占锁*/340spin_lock(&i->i_klist->k_lock);341/*获取下一节点*/342if(last){343next=to_klist_node(last->n_node.next);/*减上一节点引用次数*/344if(!klist_dec_and_del(last))345put=NULL;346}else347next=to_klist_node(i->i_klist->k_list.next);348349i->i_cur=NULL;/*链表中有节点“没死”,增加引用次数*/350while(next!=to_klist_node(&i->i_klist->k_list)){351if(likely(!knode_dead(next))){352kref_get(&next->n_ref);353i->i_cur=next;354break;355}356next=to_klist_node(next->n_node.next);357}358/*丢弃锁*/359spin_unlock(&i->i_klist->k_lock);360361if(put&&last)362put(last);363return i->i_cur;364}365EXPORT_SYMBOL_GPL(klist_next);366----------------------/*使用迭代查找下一链表节点*/1124struct klist_node*n=klist_next(i);1125struct device*dev=NULL;1126struct device_private*p;11271128if(n){/*根据节点入口获取该节点上的设备*/1129p=to_device_private_parent(n);1130dev=p->device;1131}1132return dev;1133}/*-------------------------------------------------------------------------------*//*其中device_private是设备私有数据结构,一下代码不难看出*想要由链表节点迭代查找设备非常容易*/66/**67*struct device_private-structure to hold the private to the driver core portions of the device structure.68*69*@klist_children-klist containing all children of this device70*@knode_parent-node in sibling list71*@knode_driver-node in driver list72*@knode_bus-node in bus list73*@driver_data-private pointer for driver specific info.Will turn into a74*list soon.75*@device-pointer back to the struct class that this structure is76*associated with.77*78*Nothing outside of the driver core should ever touch these fields.79*/80struct device_private{81struct klist klist_children;82struct klist_node knode_parent;83struct klist_node knode_driver;84struct klist_node knode_bus;85void*driver_data;86struct device*device;87};88#define to_device_private_parent(obj)\89container_of(obj,struct device_private,knode_parent)90#define to_device_private_driver(obj)\91container_of(obj,struct device_private,knode_driver)92#define to_device_private_bus(obj)\93container_of(obj,struct device_private,knode_bus) 94driver_attach()函数driver_attach()函数2009-04-2114:39:03|分类:linux kernel|字号订阅最近在看一个mpc8315CPU上的驱动程序发现在使用spi_register注册完成后没有调用到相应的probe函数,分析后发现在driver_attach()函数执行时没有找到匹配的device,在网上狗狗后找到关于这部分的分析,引用如下:个浅析linux2.6.23驱动自动匹配设备driver_attach()函数文章来源:int driver_attach(struct device_driver*drv){return bus_for_each_dev(drv->bus,NULL,drv,__driver_attach);}调用该函数,那么drv驱动程式会和drv所在总线上连接了的物理设备进行一一匹配,再来看看下面:int bus_for_each_dev(struct bus_type*bus,struct device*start,void*data,int(*fn)(struct device*,void*)){struct klist_iter i;//专门用于遍历的链表结构体,其中i_cur是遍历移动的关键struct device*dev;int error=0;if(!bus)return-EINVAL;klist_iter_init_node(&bus->klist_devices,&i,(start?&start->knode_bus:NULL));//i->i_klist=&bus->klist_devices;//i->i_head=&bus->klist_devices.k_list;//i->i_cur=NULL;//表示从最前端开始遍历挂接到bus总线上的整个设备链条.while((dev=next_device(&i))&&!error)//dev为该bus总线链表上的一个设备,[就像一根藤条上的一朵小花gliethttp_20071025] //这些device设备把自己的&device->knode_bus链表单元链接到了bus->klist_devices 上//这也说明名字为knode_bus的list单元将是要被挂接到bus->klist_devices的链表上//同理&device->knode_driver将是这个device设备链接到drivers驱动上的list节点识别单元//见driver_bound()->klist_add_tail(&dev->knode_driver,&dev->driver->klist_devices);error=fn(dev,data);//调用__driver_attach函数,进行匹配运算klist_iter_exit(&i);return error;//成功匹配返回0}struct klist_iter{struct klist*i_klist;struct list_head*i_head;struct klist_node*i_cur;};void klist_iter_init_node(struct klist*k,struct klist_iter*i,struct klist_node*n){i->i_klist=k;//需要被遍历的klisti->i_head=&k->k_list;//开始的链表头i->i_cur=n;//当前位置对应的klist_node节点,next_device()会从当前n 开始一直搜索到//链表的结尾,也就是i_head->prev处停止if(n)kref_get(&n->n_ref);//引用计数加1}static struct device*next_device(struct klist_iter*i){struct klist_node*n=klist_next(i);return n?container_of(n,struct device,knode_bus):NULL;//因为n是device->knode_bus的指针,所以container_of将返回device的指针}struct klist_node*klist_next(struct klist_iter*i){struct list_head*next;struct klist_node*lnode=i->i_cur;struct klist_node*knode=NULL;//赋0,当next==i->i_head时用于退出void(*put)(struct klist_node*)=i->i_klist->put;spin_lock(&i->i_klist->k_lock);if(lnode){next=lnode->n_node.next;if(!klist_dec_and_del(lnode))//释放前一个i_cur对象的引用计数put=NULL;//klist_dec_and_del成功的对引用计数做了减1操作,那么失效用户定义put}elsenext=i->i_head->next;//如果lnode=0,那么从链表头开始,所以head->next指向第1个实际对象if(next!=i->i_head){//head并不链接设备,所以head无效//当next==i->i_head时,说明已遍历到了head牵头的链表的末尾,回环到了head, //所以knode将不会进行赋值,这时knode=0,while((dev=next_device(&i))&&!error)因为0而退出knode=to_klist_node(next);//调用container_of()获取klist_node->n_node中klist_node地址kref_get(&knode->n_ref);//对该node的引用计数加1}i->i_cur=knode;//记住当前遍历到的对象,当next==i->i_head时,knode=0spin_unlock(&i->i_klist->k_lock);if(put&&lnode)put(lnode);return knode;}static int klist_dec_and_del(struct klist_node*n){return kref_put(&n->n_ref,klist_release);//对该node的引用计数减1,如果引用计数到达0,那么调用klist_release}static void klist_release(struct kref*kref){struct klist_node*n=container_of(kref,struct klist_node,n_ref);list_del(&n->n_node);//从节点链表上摘掉该node节点complete(&n->n_removed);//n->n_klist=NULL;}void fastcall complete(struct completion*x){unsigned long flags;spin_lock_irqsave(&x->wait.lock,flags);//关闭中断,防止并发x->done++;//唤醒因为某些原因悬停在klist_node->n_removed等待队列上的task们//这种现象之一是:__device_release_driver()删除挂接在设备上的driver时,会出现//删除task小憩在node的wait上__wake_up_common(&x->wait,TASK_UNINTERRUPTIBLE|TASK_INTERRUPTIBLE,1,0,NULL);spin_unlock_irqrestore(&x->wait.lock,flags);//恢复中断}static void__wake_up_common(wait_queue_head_t*q,unsigned int mode,int nr_exclusive,int sync,void*key){struct list_head*tmp,*next;list_for_each_safe(tmp,next,&q->task_list){//遍历以head牵头的链表上的task们wait_queue_t*curr=list_entry(tmp,wait_queue_t,task_list);unsigned flags=curr->flags;if(curr->func(curr,mode,sync,key)&&//调用wait上准备好了的回调函数func (flags&WQ_FLAG_EXCLUSIVE)&&!--nr_exclusive)break;}}//抛开链表上的head,当最后一个post==head时,说明链表已遍历结束(gliethttp_20071025) #define list_for_each_safe(pos,n,head)\for(pos=(head)->next,n=pos->next;pos!=(head);\pos=n,n=pos->next)void klist_iter_exit(struct klist_iter*i){if(i->i_cur){//对于正常遍历的退出,i->i_cur会等于0,如果找到了匹配对象,提前退出了,那么就会在这里对引用进行释放klist_del(i->i_cur);i->i_cur=NULL;}}static int__driver_attach(struct device*dev,void*data){struct device_driver*drv=data;//data就是打算把自己匹配到bus上挂接的合适设备上的driver驱动if(dev->parent)down(&dev->parent->sem);//使用信号量保护下面的操作down(&dev->sem);if(!dev->driver)//如果当前这个dev设备还没有挂接一个driver驱动driver_probe_device(drv,dev);//那么尝试该dev是否适合被该drv驱动管理up(&dev->sem);if(dev->parent)up(&dev->parent->sem);return0;}int driver_probe_device(struct device_driver*drv,struct device*dev){int ret=0;if(!device_is_registered(dev))//设备是否已被bus总线认可return-ENODEV;if(drv->bus->match&&!drv->bus->match(dev,drv))//调用该driver驱动自定义的match函数,如:usb_device_match(),查看//这个设备是否符合自己,drv->bus->match()返回1,表示本drv认可该设备//否则,goto done,继续检测下一个device设备是否和本drv匹配goto done;pr_debug("%s:Matched Device%s with Driver%s\n",drv->bus->name,dev->bus_id,drv->name);//这下来真的了,ret=really_probe(dev,drv);done:return ret;}static inline int device_is_registered(struct device*dev){return dev->is_registered;//当调用bus_attach_device()之后,is_registered=1}static int really_probe(struct device*dev,struct device_driver*drv){int ret=0;atomic_inc(&probe_count);pr_debug("%s:Probing driver%s with device%s\n",drv->bus->name,drv->name,dev->bus_id);WARN_ON(!list_empty(&dev->devres_head));dev->driver=drv;//管理本dev的驱动指针指向drvif(driver_sysfs_add(dev)){//将driver和dev使用link,链接到一起,使他们真正相关printk(KERN_ERR"%s:driver_sysfs_add(%s)failed\n",__FUNCTION__,dev->bus_id);goto probe_failed;}if(dev->bus->probe){//总线提供了设备探测函数ret=dev->bus->probe(dev);if(ret)goto probe_failed;}else if(drv->probe){//驱动自己提供了设备探测函数//因为drv驱动自己也不想管理那些意外的非法设备//所以一般drv都会提供这个功能,相反//比如:usb_bus_type没有提供probe,而usb驱动提供了usb_probe_interface//来确认我这个driver软件真的能够管理这个device设备ret=drv->probe(dev);if(ret)goto probe_failed;}driver_bound(dev);ret=1;pr_debug("%s:Bound Device%s to Driver%s\n",drv->bus->name,dev->bus_id,drv->name);goto done;probe_failed:devres_release_all(dev);driver_sysfs_remove(dev);dev->driver=NULL;if(ret!=-ENODEV&&ret!=-ENXIO){printk(KERN_WARNING"%s:probe of%s failed with error%d\n",drv->name,dev->bus_id,ret);}ret=0;done:atomic_dec(&probe_count);wake_up(&probe_waitqueue);return ret;}static void driver_bound(struct device*dev){if(klist_node_attached(&dev->knode_driver)){//本dev已挂到了某个driver驱动的klist_devices链条上了//感觉不应该发生printk(KERN_WARNING"%s:device%s already bound\n",__FUNCTION__,kobject_name(&dev->kobj));return;}pr_debug("bound device’%s’to driver’%s’\n",dev->bus_id,dev->driver->name);if(dev->bus)blocking_notifier_call_chain(&dev->bus->bus_notifier,BUS_NOTIFY_BOUND_DRIVER,dev);//将本dev的knode_driver链表结构体节点挂接到该driver->klist_devices上//这样driver所管理的device设备又多了1个,//也能说又多了1个device设备使用本driver驱动管理他自己(gilethttp_20071025).klist_add_tail(&dev->knode_driver,&dev->driver->klist_devices);}Linux内核中的klist分析分析的内核版本照样是2.6.38.5。

需要了解Linux内核通知链机制的原理及实现

需要了解Linux内核通知链机制的原理及实现

需要了解Linux内核通知链机制的原理及实现一、概念:大多数内核子系统都是相互独立的,因此某个子系统可能对其它子系统产生的事件感兴趣。

为了满足这个需求,也即是让某个子系统在发生某个事件时通知其它的子系统,Linux 内核提供了通知链的机制。

通知链表只能够在内核的子系统之间使用,而不能够在内核与用户空间之间进行事件的通知。

通知链表是一个函数链表,链表上的每一个节点都注册了一个函数。

当某个事情发生时,链表上所有节点对应的函数就会被执行。

所以对于通知链表来说有一个通知方与一个接收方。

在通知这个事件时所运行的函数由被通知方决定,实际上也即是被通知方注册了某个函数,在发生某个事件时这些函数就得到执行。

其实和系统调用signal的思想差不多。

二、数据结构:通知链有四种类型:原子通知链( Atomic notifier chains ):通知链元素的回调函数(当事件发生时要执行的函数)只能在中断上下文中运行,不允许阻塞。

对应的链表头结构:struct atomic_noTIfier_head{ spinlock_t lock; struct noTIfier_block *head;};可阻塞通知链( Blocking noTIfier chains ):通知链元素的回调函数在进程上下文中运行,允许阻塞。

对应的链表头:struct blocking_noTIfier_head{ struct rw_semaphore rwsem; struct notifier_block *head;}; 原始通知链( Raw notifier chains ):对通知链元素的回调函数没有任何限制,所有锁和保护机制都由调用者维护。

对应的链表头:struct raw_notifier_head{ struct notifier_block *head;};SRCU 通知链( SRCU notifier chains ):可阻塞通知链的一种变体。

list head 数据结构

list head 数据结构

1. 双向链表(list)linux内核中的双向链表通过结构 structlist_head来将各个节点连接起来,此结构会作为链表元素结构中的一个参数:structlist_head {structlist_head *next, *prev;};链表头的初始化,注意,结构中的指针为NULL并不是初始化,而是指向自身才是初始化,如果只是按普通情况下的置为NULL,而不是指向自身,系统会崩溃,这是一个容易犯的错误:#define LIST_HEAD_INIT(name) { &(name), &(name) }#define LIST_HEAD(name) \structlist_head name = LIST_HEAD_INIT(name)#define INIT_LIST_HEAD(ptr) do { \(ptr)->next = (ptr); (ptr)->prev = (ptr); \} while (0)最常用的链表操作:插入到链表头:voidlist_add(structlist_head *new, structlist_head *head);插入到链表尾:voidlist_add_tail(structlist_head *new, structlist_head *head);删除链表节点:voidlist_del(structlist_head *entry);将节点移动到另一链表:voidlist_move(structlist_head *list, structlist_head *head);将节点移动到链表尾:voidlist_move_tail(structlist_head *list,structlist_head *head);判断链表是否为空,返回1为空,0非空intlist_empty(structlist_head *head);把两个链表拼接起来:voidlist_splice(structlist_head *list, structlist_head *head);取得节点指针:#define list_entry(ptr, type, member) \((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))遍历链表中每个节点:#define list_for_each(pos, head) \for (pos = (head)->next, prefetch(pos->next); pos != (head); \pos = pos->next, prefetch(pos->next))逆向循环链表中每个节点:#define list_for_each_prev(pos, head) \for (pos = (head)->prev, prefetch(pos->prev); pos != (head); \pos = pos->prev, prefetch(pos->prev))举例:LISH_HEAD(mylist);structmy_list{structlist_head list;int data;};staticintini_list(void){structmy_list *p;int i;for(i=0; i<100; i++){p=kmalloc(sizeof(structmy_list), GFP_KERNEL);list_add(&p->list, &mylist);}}在内存中形成如下结构的一个双向链表:+---------------------------------------------------------------+| || mylist 99 98 0 || +----+ +---------+ +---------+ +---------+ |+->|next|--->|list.next|--->|list.next|--->...--->|list.next|---+|----| |---------| |---------| |---------|+--|prev|<---|list.prev|<---|list.prev|<---...<---|list.prev|<--+| +----+ |---------| |---------| |---------| || | data | | data | | data | || +---------+ +---------+ +---------+ || |+---------------------------------------------------------------+知道了链表头就能遍历整个链表,如果是用list_add()插入新节点的话,从链表头的next方向看是一个堆栈型。

数据结构—链表

数据结构—链表

数据结构—链表链表⽬录⼀、概述1.链表是什么链表数⼀种线性数据结构。

它是动态地进⾏储存分配的⼀种结构。

什么是线性结构,什么是⾮线性结构?线性结构是⼀个有序数据元素的集合。

常⽤的线性结构有:线性表,栈,队列,双队列,数组,串。

⾮线性结构,是⼀个结点元素可能有多个直接前趋和多个直接后继。

常见的⾮线性结构有:⼆维数组,多维数组,⼴义表,树(⼆叉树等)。

2.链表的基本结构链表由⼀系列节点组成的集合,节点(Node)由数据域(date)和指针域(next)组成。

date负责储存数据,next储存其直接后续的地址3.链表的分类单链表(特点:连接⽅向都是单向的,对链表的访问要通过顺序读取从头部开始)双链表循环链表单向循环链表双向循环链表4.链表和数组的⽐较数组:优点:查询快(地址是连续的)缺点:1.增删慢,消耗CPU内存链表就是⼀种可以⽤多少空间就申请多少空间,并且提⾼增删速度的线性数据结构,但是它地址不是连续的查询慢。

⼆、单链表[1. 认识单链表](#1. 认识单链表)1. 认识单链表(1)头结点:第0 个节点(虚拟出来的)称为头结点(head),它没有数据,存放着第⼀个节点的⾸地址(2)⾸节点:第⼀个节点称为⾸节点,它存放着第⼀个有效的数据(3)中间节点:⾸节点和接下来的每⼀个节点都是同⼀种结构类型:由数据域(date)和指针域(next)组成数据域(date)存放着实际的数据,如学号(id)、姓名(name)、性别(sex)、年龄(age)、成绩(score)等指针域(next)存放着下⼀个节点的⾸地址(4)尾节点:最后⼀个节点称为尾节点,它存放着最后⼀个有效的数据(5)头指针:指向头结点的指针(6)尾指针:指向尾节点的指针(7)单链表节点的定义public static class Node {//Object类对象可以接收⼀切数据类型解决了数据统⼀问题public Object date; //每个节点的数据Node next; //每个节点指向下⼀结点的连接public Node(Object date) {this.date = date;}}2.引⼈头结点的作⽤1. 概念头结点:虚拟出来的⼀个节点,不保存数据。

linux list.h 用法

linux list.h 用法

在Linux系统中,list.h是一个非常重要的头文件,它提供了许多用于处理链表的数据结构。

链表是一种常见的数据结构,它允许我们以线性方式访问元素,同时还可以添加和删除元素。

list.h头文件提供了许多有用的函数和宏,可以轻松地创建和管理链表。

首先,要使用list.h头文件,需要在代码中包含它。

通常,使用"#include <list>"语句将其包含在源文件中。

接下来,我们可以使用list.h头文件中的函数和宏来创建链表对象。

链表通常由节点组成,每个节点包含数据和指向下一个节点的指针。

我们可以使用list_node结构来定义链表节点,并在代码中使用list_init()函数初始化链表对象。

创建链表后,我们可以使用list_push_back()函数向链表中添加元素。

该函数将一个元素添加到链表的末尾。

同样地,可以使用list_pop_front()函数从链表的开头删除并返回一个元素。

该函数会更新指针以确保链表的完整性。

此外,我们还可以使用list_empty()函数检查链表是否为空。

该函数会返回一个布尔值,指示链表是否包含任何元素。

我们还能够使用list_for_each()宏遍历链表中的所有元素,并使用其他函数来访问每个节点的数据。

总的来说,list.h头文件提供了一种简单而有效的方法来处理链表数据结构。

通过使用这些函数和宏,我们可以轻松地创建和管理链表对象,并在代码中遍历和处理它们。

这是一个非常有用的库,对于开发人员来说是一个非常有用的工具。

总的来说,使用Linux list.h头文件可以方便地处理链表数据结构。

它提供了许多有用的函数和宏,使我们能够轻松地创建和管理链表对象,并在代码中遍历和处理它们。

通过正确使用这些函数和宏,我们可以提高代码的可读性和可维护性,同时提高开发效率。

总的来说,list.h头文件是一个非常有用的库,对于Linux系统开发人员来说是一个非常值得使用的工具。

Linux内核中工作队列(work_queue)的操作

Linux内核中工作队列(work_queue)的操作
// 保存线程指针
cwq->thread = p;
return p;
}
static int worker_thread(void *__cwq)
{
struct cpu_workqueue_struct *cwq = __cwq;
// 声明一个等待队列
DECLARE_WAITQUEUE(wait, current);
// 参数初始化定义, 而该宏用在程式之中对工作结构赋值
#define INIT_WORK(_work, _func, _data) \
do { \
INIT_LIST_HEAD(&(_work)->entry); \
(_work)->pending = 0; \
/*
* The externally visible workqueue abstraction is an array of
* per-CPU workqueues:
*/
// 工作队列结构
struct workqueue_struct {
struct cpu_workqueue_struct *cpu_wq;
int cpu)
{
// 每个CPU的工作队列
struct cpu_workqueue_struct *cwq = per_cpu_ptr(wq->cpu_wq, cpu);
struct task_struct *p;
spin_lock_init(&cwq->lock);
{
...
keventd_wq = create_workqueue("events");

链表基本操作

链表基本操作

链表基本操作链表作为一种重要的数据结构,在计算机程序设计中被广泛应用。

链表是一种元素之间通过指针相连接的线性结构,每个元素包含数据和指向下一个元素的指针。

链表能够灵活地增加和删除元素,适用于许多需要频繁插入和删除数据的场景。

在本文中,我们将介绍链表的基本操作,并按照类别进行介绍。

创建链表链表的创建是链表操作的第一步。

首先需要声明链表节点类型的结构体,并定义链表头指针。

然后通过动态内存分配函数malloc为链表节点动态分配内存,建立链表节点之间的关系,直到最后一个节点。

struct Node{int data;Node* next;};Node* createLinkedList(int n){Node* head = NULL;Node* tail = NULL;for(int i = 0; i < n; i++){Node* node = (Node*)malloc(sizeof(Node));node->data = 0;node->next = NULL;if(head == NULL){head = node;}else{tail->next = node;}tail = node;}return head;}插入数据链表的插入操作包括在链表头插入和在链表尾插入两种情况。

在链表头插入时,新节点的指针指向链表头,链表头指针指向新节点。

在链表尾插入时,先找到链表尾节点,然后将新节点插入在尾节点后面。

void insertAtFront(Node** head, int data){Node* node = (Node*)malloc(sizeof(Node));node->data = data;node->next = *head;*head = node;}void insertAtEnd(Node** head, int data){Node* node = (Node*)malloc(sizeof(Node)); node->data = data;node->next = NULL;if(*head == NULL){*head = node;}else{Node* tail = *head;while(tail->next != NULL){tail = tail->next;}tail->next = node;}}删除数据链表的删除操作包括在链表头删除和在链表尾删除两种情况。

数据结构之链表

数据结构之链表

数据结构之链表链表是一种常见的数据结构,它由一系列节点组成,每个节点包含数据和指向下一个节点的指针。

相比于数组,链表具有更灵活的插入和删除操作,但访问元素的效率较低。

在计算机科学中,链表被广泛应用于各种算法和数据处理任务中。

链表的基本结构可以用以下代码表示:```pythonclass Node:def __init__(self, data):self.data = dataself.next = None```在链表中,每个节点都包含一个数据项和一个指向下一个节点的指针。

链表的头节点是链表的入口,通过头节点可以遍历整个链表。

链表的插入操作是将一个新节点插入到链表的指定位置。

例如,我们可以在链表的头部插入一个新节点:```pythondef insert_at_head(head, data):new_node = Node(data)new_node.next = headhead = new_nodereturn head```链表的删除操作是将链表中的某个节点删除。

例如,我们可以删除链表中的第一个节点:```pythondef delete_at_head(head):if head is None:return Nonehead = head.nextreturn head```链表的遍历操作是按顺序访问链表中的每个节点。

例如,我们可以遍历链表并打印每个节点的数据:```pythondef print_list(head):current = headwhile current is not None:print(current.data)current = current.next```链表的搜索操作是在链表中查找某个特定的节点。

例如,我们可以搜索链表中是否存在某个特定的数据项:```pythondef search_list(head, data):current = headwhile current is not None:if current.data == data:return Truecurrent = current.nextreturn False```链表的反转操作是将链表中的节点顺序颠倒。

linux内核分析之list_head双向链表结构.doc

linux内核分析之list_head双向链表结构.doc

linux 内核分析之list_head 双向链表结构本文详细分析了 2.6.x 内核中链表结构的实现,并通过实例对每个链表操作接口进行了详尽的讲解。

一、链表数据结构简介链表是一种常用的组织有序数据的数据结构,它通过指针将一系列数据节点连接成一条数据链,是线性表的一种重要实现方式。

相对于数组,链表具有更好的动态性,建立链表时无需预先知道数据总量,可以随机分配空间,可以高效地在链表中的任意位置实时插入或删除数据。

链表的开销主要是访问的顺序性和组织链的空间损失。

通常链表数据结构至少应包含两个域:数据域和指针域,数据域用于存储数据,指针域用于建立与下一个节点的联系。

按照指针域的组织以及各个节点之间的联系形式,链表又可以分为单链表、双链表、循环链表等多种类型,下面分别给出这几类常见链表类型的示意图:1.单链表图1 单链表单链表是最简单的一类链表,它的特点是仅有一个指针域指向后继节点(next),因此,对单链表的遍历只能从头至尾(通常是NULL空指针)顺序进行。

2.双链表图2 双链表通过设计前驱和后继两个指针域,双链表可以从两个方向遍历,这是它区别于单链表的地方。

如果打乱前驱、后继的依赖关系,就可以构成"二叉树";如果再让首节点的前驱指向链表尾节点、尾节点的后继指向首节点(如图2中虚线部分),就构成了循环链表;如果设计更多的指针域,就可以构成各种复杂的树状数据结构。

3.循环链表循环链表的特点是尾节点的后继指向首节点。

前面已经给出了双循环链表的示意图,它的特点是从任意一个节点出发,沿两个方向的任何一个,都能找到链表中的任意一个数据。

如果去掉前驱指针,就是单循环链表。

在Linux内核中使用了大量的链表结构来组织数据,包括设备列表以及各种功能模块中的数据组织。

这些链表大多采用在[include/linux/list.h]实现的一个相当精彩的链表数据结构。

本文的后继部分就将通过示例详细介绍这一数据结构的组织和使用。

数据结构中链表及常见操作

数据结构中链表及常见操作

链表1 定义链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。

由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而顺序表相应的时间复杂度分别是O(logn)和O(1)。

使用链表结构可以克服数组链表需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。

但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。

在计算机科学中,链表作为一种基础的数据结构可以用来生成其它类型的数据结构。

链表通常由一连串节点组成,每个节点包含任意的实例数据(data fields)和一或两个用来指向明上一个或下一个节点的位置的链接("links")。

链表最明显的好处就是,常规数组排列关联项目的方式可能不同于这些数据项目在记忆体或磁盘上顺序,数据的访问往往要在不同的排列顺序中转换。

而链表是一种自我指示数据类型,因为它包含指向另一个相同类型的数据的指针(链接)。

链表允许插入和移除表上任意位置上的节点,但是不允许随机存取。

链表有很多种不同的类型:单向链表,双向链表以及循环链表。

2 结构2.1 单向链表链表中最简单的一种是单向链表,它包含两个域,一个信息域和一个指针域。

这个链接指向列表中的下一个节点,而最后一个节点则指向一个空值。

一个单向链表的节点被分成两个部分。

第一个部分保存或者显示关于节点的信息,第二个部分存储下一个节点的地址。

单向链表只可向一个方向遍历。

链表最基本的结构是在每个节点保存数据和到下一个节点的地址,在最后一个节点保存一个特殊的结束标记,另外在一个固定的位置保存指向第一个节点的指针,有的时候也会同时储存指向最后一个节点的指针。

一般查找一个节点的时候需要从第一个节点开始每次访问下一个节点,一直访问到需要的位置。

深入浅出linux内核源代码之双向链表list_head(list.h)

深入浅出linux内核源代码之双向链表list_head(list.h)

struct list_head{ struct list_head *next, *prev; };
那么这个头文件又是有什么样的作用呢,这篇文章就是用来解释它的作用,虽然这是 linux 下的源代码,但对 于学习 C 语言的人来说,这是算法和平台没有什么关系。
一、双向链表
学习计算机的人都会开一门课程《数据结构》,里面都会有讲解双向链表的内容。 什么是双向链表,它看起来是这样的:
struct list_head{ struct list_head *next, *prev; };
表示人的:
struct person {
int age; int weight; struct list_head list; };
表示动物的:
struct animal { int age; int weight; struct list_head list; };
如果是双向循环链表,那么就加上虚线所示。 现在有几个结构体,它们是: 表示人的:
struct person {
int int };
表示动物的:
age; weight;源自struct animal { int age; int weight; };
如果有一组 filename 变量和 filedata 变量,把它们存起来,我们会怎么做,当然就用数组了,但我们想使 用双向链表,让它们链接起来,那该怎么做,唯一可以做的就是给每个结构加如两个成员,如下: 表示人的:
/** * 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. */
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

linux内核数据结构之链表1、前言最近写代码需用到链表结构,正好公共库有关于链表的。

第一眼看时,觉得有点新鲜,和我之前见到的链表结构不一样,只有前驱和后继指针,而没有数据域。

后来看代码注释发现该代码来自linux内核,在linux 源代码下include/Lish.h下。

这个链表具备通用性,使用非常方便。

只需要在结构定义一个链表结构就可以使用。

2、链表介绍链表是非常基本的数据结构,根据链个数分为单链表、双链表,根据是否循环分为单向链表和循环链表。

通常定义定义链表结构如下:typedef struct node{ElemType data; //数据域struct node *next; //指针域}node, *list;链表中包含数据域和指针域。

链表通常包含一个头结点,不存放数据,方便链表操作。

单向循环链表结构如下图所示:双向循环链表结构如下图所示:这样带数据域的链表降低了链表的通用性,不容易扩展。

linux内核定义的链表结构不带数据域,只需要两个指针完成链表的操作。

将链表节点加入数据结构,具备非常高的扩展性,通用性。

链表结构定义如下所示:struct list_head {struct list_head *next, *prev;};链表结构如下所示:需要用链表结构时,只需要在结构体中定义一个链表类型的数据即可。

例如定义一个app_info链表,1 typedef struct application_info3uint32_t app_id;4uint32_t up_flow;5uint32_t down_flow;6struct list_head app_info_head; //链表节点7 }app_info;定义一个app_info链表,app_info app_info_list;通过app_info_head进行链表操作。

根据C语言指针操作,通过container_of和offsetof,可以根据app_info_head的地址找出app_info的起始地址,即一个完整ap_info结构的起始地址。

可以参考:/Anker/p/3472271.html。

3、linux内核链表实现内核实现的是双向循环链表,提供了链表操作的基本功能。

(1)初始化链表头结点#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_HEAD宏创建一个链表头结点,并用LIST_HEAD_INIT宏对头结点进行赋值,使得头结点的前驱和后继指向自己。

INIT_LIST_HEAD函数对链表进行初始化,使得前驱和后继指针指针指向头结点。

(2)插入节点1static inline void __list_add(struct list_head *new,2struct list_head *prev,3struct list_head *next)4 {5next->prev = new;6new->next = next;7new->prev = prev;8prev->next = new;9 }1011static inline void list_add(struct list_head *new, struct list_head *head)12 {13__list_add(new, head, head->next);14 }1516static inline void list_add_tail(struct list_head *new, struct list_head *head)17 {18__list_add(new, head->prev, head);插入节点分为从链表头部插入list_add和链表尾部插入list_add_tail,通过调用__list_add函数进行实现,head->next指向之一个节点,head->prev指向尾部节点。

(3)删除节点1static inline void __list_del(struct list_head * prev, struct list_head * next)2 {3next->prev = prev;4prev->next = next;5 }67static inline void list_del(struct list_head *entry)8 {9__list_del(entry->prev, entry->next);10entry->next = LIST_POISON1;11entry->prev = LIST_POISON2;12 }从链表中删除一个节点,需要改变该节点前驱节点的后继结点和后继结点的前驱节点。

最后设置该节点的前驱节点和后继结点指向LIST_POSITION1和LIST_POSITION2两个特殊值,这样设置是为了保证不在链表中的节点项不可访问,对LIST_POSITION1和LIST_POSITION2的访问都将引起页故障/** These are non-NULL pointers that will result in page faults* under normal circumstances, used to verify that nobody uses* non-initialized list entries.*/#define LIST_POISON1 ((void *) 0x00100100 + POISON_POINTER_DELTA)#define LIST_POISON2 ((void *) 0x00200200 + POISON_POINTER_DELTA)(4)移动节点1/**2* list_move - delete from one list and add as another's head3* @list: the entry to move4* @head: the head that will precede our entry5*/6static inline void list_move(struct list_head *list, struct list_head *head)7 {8__list_del(list->prev, list->next);9list_add(list, head);10 }1112/**13* list_move_tail - delete from one list and add as another's tail14* @list: the entry to move15* @head: the head that will follow our entry16*/17static inline void list_move_tail(struct list_head *list,18struct list_head *head)20__list_del(list->prev, list->next);21list_add_tail(list, head);22 }move将一个节点移动到头部或者尾部。

(5)判断链表1/**2* list_is_last - tests whether @list is the last entry in list @head3* @list: the entry to test4* @head: the head of the list5*/6static inline int list_is_last(const struct list_head *list,7const struct list_head *head)8 {9return list->next == head;10 }1112/**13* list_empty - tests whether a list is empty14* @head: the list to test.15*/16static inline int list_empty(const struct list_head *head)17 {18return head->next == head;19 }list_is_last函数判断节点是否为末尾节点,list_empty判断链表是否为空。

(6)遍历链表1/**2* list_entry - get the struct for this entry3* @ptr: the &struct list_head pointer.4* @type: the type of the struct this is embedded in.5* @member: the name of the list_struct within the struct.6*/7#define list_entry(ptr, type, member) \8container_of(ptr, type, member)910/**11* list_first_entry - get the first element from a list12* @ptr: the list head to take the element from.13* @type: the type of the struct this is embedded in.14* @member: the name of the list_struct within the struct.15*16* Note, that list is expected to be not empty.17*/18#define list_first_entry(ptr, type, member) \19list_entry((ptr)->next, type, member)22* list_for_each - iterate over a list23* @pos: the &struct list_head to use as a loop cursor.24* @head: the head for your list.25*/26#define list_for_each(pos, head) \27for (pos = (head)->next; prefetch(pos->next), pos != (head); \28pos = pos->next)宏list_entity获取链表的结构,包括数据域。

相关文档
最新文档