Linux内核链表list_head扩展---klist
详解Linux内核之双向循环链表
详解Linux内核之双向循环链表本文详解了内核中面向对象的list结构的原理,以及如何以list为内嵌对象来构造自己的链表结构,如何从内嵌list对象获得自定义的对象指针;探讨了各种宏或者函数的详细使用方法及怎样以通用list结构来操作自定义对象。
【关键字】双向循环链表,list,list_entry,typeof,containerof,list_for_each, list_for_each_entry1、双循环链表传统实现2、Linux内核中双循环链表实现3、定义和初始化4、通用链表操作接口4.1添加节点4.2删除节点4.3移动节点4.4链表判空4.5链表合并5、获取宿主对象指针6、遍历6.1 List-head链表遍历6.2遍历宿主对象7、如何使用Linux中的双循环链表、双循环链表传统实现在传统的双循环链表实现中,如果创建某种数据结构的双循环链表,通常采用的办法是在这个数据结构的类型定义中加入两个(指向该类型对象的)指针next 和prev。
例如:typedef struct foo {…struct foo *prev;struct foo *next;…} foo_t;这里给出了对应的节点结构、空的双循环链表和非空的双循环链表示意图。
、Linux内核中双循环链表实现在linux内核中,有大量的数据结构需要用到双循环链表,例如进程、文件、模块、页面等。
若采用双循环链表的传统实现方式,需要为这些数据结构维护各自的链表,并且为每个链表都要设计插入、删除等操作函数。
因为用来维持链表的next和prev指针指向对应类型的对象,因此一种数据结构的链表操作函数不能用于操作其它数据结构的链表。
在Linux源代码树的include/linux/list.h文件中,采用了一种类型无关的双循环链表实现方式。
其思想是将指针prev和next从具体的数据结构中提取出来构成一种通用的"双链表"数据结构list_head。
linux的bus、device、driver介绍
linux的bus、device、driver介绍 linux 通过device和driver分别管理系统中的设备和驱动,⽤bus将设备和驱动关联起来,bus可以看成是设备和驱动的媒介,可以匹配设备和驱动。
这样设备和驱动可以独⽴加载,互不影响。
sysfs是⼀个基于内存的⽂件系统,它的作⽤是将内核信息以⽂件的⽅式提供给⽤户程序使⽤。
我们都知道设备和对应的驱动都是由内核管理的,这些对于⽤户空间是不可见的。
现在通过sysfs,可以在⽤户空间直观的了解设备驱动的层次结构。
⼀、bus注册过程bus_type结构体代表⼀条总线,如下所⽰:struct bus_type {const char *name; //名称const char *dev_name;struct device *dev_root;struct device_attribute *dev_attrs; /* use dev_groups instead */const struct attribute_group **bus_groups;const struct attribute_group **dev_groups;const struct attribute_group **drv_groups;int (*match)(struct device *dev, struct device_driver *drv); //device和driver的匹配函数int (*uevent)(struct device *dev, struct kobj_uevent_env *env);int (*probe)(struct device *dev);int (*remove)(struct device *dev);void (*shutdown)(struct device *dev);int (*online)(struct device *dev);int (*offline)(struct device *dev);int (*suspend)(struct device *dev, pm_message_t state);int (*resume)(struct device *dev);const struct dev_pm_ops *pm;const struct iommu_ops *iommu_ops;struct subsys_private *p; struct lock_class_key lock_key;};struct subsys_private {struct kset subsys; //对应/sys/bus⽬录struct kset *devices_kset; //对应/sys/bus/devices⽬录struct list_head interfaces;struct mutex mutex;struct kset *drivers_kset; //对应/sys/bus/drivers⽬录struct klist klist_devices; //该bus下的所有devicestruct klist klist_drivers; //该bus下的所有driverstruct blocking_notifier_head bus_notifier;unsigned int drivers_autoprobe:1;struct bus_type *bus;struct kset glue_dirs;struct class *class;};向系统添加⼀条bus_type总线时,改总线会⾃动添加到/sys/bus⽬录下,bus⽬录是系统⾃动创建的,这个bus⽬录为static struct kset *bus_kset,定义在kernel/drivers/base/bus.c中。
Linux内核的等待队列
Linux内核的等待队列Linux内核的等待队列是以双循环链表为基础数据结构,与进程调度机制紧密结合,能够用于实现核心的异步事件通知机制。
在Linux2.4.21中,等待队列在源代码树include/linux/wait.h中,这是一个通过list_head连接的典型双循环链表,如下图所示。
在这个链表中,有两种数据结构:等待队列头(wait_queue_head_t)和等待队列项(wait_queue_t)。
等待队列头和等待队列项中都包含一个list_head类型的域作为"连接件"。
由于我们只需要对队列进行添加和删除操作,并不会修改其中的对象(等待队列项),因此,我们只需要提供一把保护整个基础设施和所有对象的锁,这把锁保存在等待队列头中,为wq_lock_t类型。
在实现中,可以支持读写锁(rwlock)或自旋锁(spinlock)两种类型,通过一个宏定义来切换。
如果使用读写锁,将wq_lock_t定义为rwlock_t类型;如果是自旋锁,将wq_lock_t 定义为spinlock_t类型。
无论哪种情况,分别相应设置wq_read_lock、wq_read_unlock、wq_read_lock_irqsave、wq_read_unlock_irqrestore、wq_write_lock_irq、wq_write_unlock、wq_write_lock_irqsave和wq_write_unlock_irqrestore等宏。
等待队列头struct __wait_queue_head {wq_lock_t lock;struct list_head task_list;};typedef struct __wait_queue_head wait_queue_head_t;前面已经说过,等待队列的主体是进程,这反映在每个等待队列项中,是一个任务结构指针(struct task_struct * task)。
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方法将两个节点添加到链表中。
linux内核数据结构.
linux内核数据结构.本⽂是在以上基础上转载⽽成,内核基础内容,就不必浪费时间重新写了.========================================================内核数据结构贯穿于整个内核代码中,这⾥介绍4个基本的内核数据结构。
利⽤这4个基本的数据结构,可以在编写内核代码时节约⼤量时间。
主要内容:链表hash 链表队列映射红⿊树1. 链表链表是linux内核中最简单,同时也是应⽤最⼴泛的数据结构。
内核中定义的是双向链表。
1.1 头⽂件简介内核中关于链表定义的代码位于: include/linux/list.hlist.h⽂件中对每个函数都有注释,这⾥就不详细说了。
其实刚开始只要先了解⼀个常⽤的链表操作(追加,删除,遍历)的实现⽅法,其他⽅法基本都是基于这些常⽤操作的。
1.2 链表代码的注意点在阅读list.h⽂件之前,有⼀点必须注意:linux内核中的链表使⽤⽅法和⼀般数据结构中定义的链表是有所不同的。
⼀般的双向链表⼀般是如下的结构,有个单独的头结点(head)每个节点(node)除了包含必要的数据之外,还有2个指针(pre,next)pre指针指向前⼀个节点(node),next指针指向后⼀个节点(node)头结点(head)的pre指针指向链表的最后⼀个节点最后⼀个节点的next指针指向头结点(head)具体见下图:传统的链表有个最⼤的缺点就是不好共通化,因为每个node中的data1,data2等等都是不确定的(⽆论是个数还是类型)。
linux中的链表巧妙的解决了这个问题,linux的链表不是将⽤户数据保存在链表节点中,⽽是将链表节点保存在⽤户数据中。
linux的链表节点只有2个指针(pre和next),这样的话,链表的节点将独⽴于⽤户数据之外,便于实现链表的共同操作。
具体见下图:linux链表中的最⼤问题是怎样通过链表的节点来取得⽤户数据?和传统的链表不同,linux的链表节点(node)中没有包含⽤户的⽤户data1,data2等。
内核链表
内核链表在Linux内核中使用了大量的链表结构来组织数据,包括设备列表以及各种功能模块的数据组织。
这些链表大多采用在【include/linux/list.h】实现一个相当精彩的链表数据结构。
Linux2.6多了两种功能,链表的读拷贝更新(rcu)和HASH链表(hlist)。
这两种功能都是基于list结构的。
1链表的定义Struct list_head{Struct list_head *next, *prev;};List_head结构包含两个list_head结构的指针prev和next。
由此可见,实际上,通常他们都组织成双向循环链表。
这里的list_head 没有数据域。
在Linux内和链表中,不是在链表结构中包含数据,而是在数据结构中包含链表节点。
在Linux内核链表中,需要用链表组织起来的数据通常都包含一个struct list_head成员。
例如在【include/linux/netfilter.h】中定义了了一个nf_socket_ops结构来描述netfilter摸一个协议族准备的getsocket/setsocketopt接口。
其中就有一个struct list_head list 成员,各个协议族的nf_sockopt_ops结构都通过这个list成员组织在一个链表中,表头是定义在【net/core/netfilter.c】中的nf_sockopts(struct list_head),从下图可以看到,这种通用的链表结避免了为每个数据项类型定义自己链表的麻烦,Linux的简捷实用,不求完美和标准的风格,在这里体现的相当充分。
2 链表的操作接口2.1声明和初始化实际上Linux只定义了链表节点,并没有专门定义链表头,那么一个链表结构是如何建立起来的呢?让我们来看看LIST_HEAD()这个宏。
#define LIST_HEAD_INIT(name) {&(name), &(name)}#define LIST_HEAD(name)struct list_head name=LIST_HEAD_INIT(name) 当我们用LIST_HEAD(nf_sockopts)声明一个名为nf_sockopts的链表头时,它的next, prev指针都初始化为指向自己。
list_head小解析
[Linux内核] post by 陈俊生/ 2012-1-13 17:17 FridayList_head下面的一些内容是我在学习linux内核中list.h文件中list_head时为了更好的理解而找的一些资料,我把它们整理一下发上来以供今后参考一下。
其中也有部分是我自己在学习中的体会。
下面的代码是我从list.h中复制过来的,是源码来的,没有改变,主要是为了大家能清楚的知道list.h的源码。
List_head这个结构体在list.h中的主要作用不是保存数据而是作为一个链表的一个节点来保存地址,为另一个结构体的数据能够获取作铺垫。
具体的情况看完了下面的一些资料就清晰明白了。
list.h头文件集中定义了双链表(struct list_head结构体)的相关操作。
比如这里的一个头文件中就有大量的struct list_head型的数据。
下面先介绍list_head的具体构成Struct list_head{Struct list_head *next;Struct list_head *prev;};List_head中的两个成员分别为两个指针,这个两个指针分别是双向链表的两个不通指向的指针。
从这个结构体的形式可以看出它是不能存放数据的。
所以也只能用来作别人的链接。
下面介绍一下list.h各个函数的具体实现。
1.链表的初始化其实可以从后往前看,这样更容易理解。
INIT_LIST_HEAD函数形成一个空链表。
这个list变量一般作为头指针(非头结点)。
static inline void INIT_LIST_HEAD(struct list_head *list){list->next = list;list->prev = list;}LIST_HEAD_INIT(name)将name的地址直接分别赋值给next和prev,那么它们事实上都指向自己,也形成一个空链表。
现在再回头看宏LIST_HEAD(name),它其实就是一个定义并初始化作用。
Linux内核链表list.h的使用
Linux内核链表list.h的使⽤Linux 内核链表 list.h 的使⽤C 语⾔本⾝并不⾃带集合(Collection)⼯具,当我们需要把结构体(struct)实例串联起来时,就需要在结构体内声明指向下⼀实例的指针,构成所谓的“链表”。
⽽为了实现对链表的操作,我们需要另外实现⼀系列的函数,例如添加、删除、搜索、复制等等。
⽽利⽤ Kernel 源代码中⾃带的 list.h,则可以⽅便地实现任意类型结构体的串联。
编程需求假设我有⼀个表⽰学⽣资料的结构体:#define MAX_STRING_LENGTH 50typedef struct student {char first_name[MAX_STRING_LENGTH];char last_name[MAX_STRING_LENGTH];unsigned int age;} student_t;传统的做法,当我们需要将⼀系列学⽣的数据串联起来,那我们需要在该结构体内部添加⼀枚指针:typedef struct student {char first_name[MAX_STRING_LENGTH];char last_name[MAX_STRING_LENGTH];unsigned int age;struct student *next; /* Look at dis ;p */} student_t;⼏乎每位 C 语⾔学习者都有在教科书中见过此类实现⽅法。
但是这样做的缺点是:我们需要额外编写⼀系列函数,实现对该类型链表的操作。
然⽽,稍微复杂点的项⽬内,有个⼗⼏个结构体那是很平常的事情。
如果我们想为它们都实现链表功能,那我们就需要为每个类型的结构体编写⼀套函数,然后我们就累 shi 了。
有没有⼀种更为通⽤的办法呢?不需要顾及结构体本⾝的类型,就可以简简单单地把它们串起来。
有点类似 Java 中的 LinkedList,如果我需要把⼀堆 Student 类的对象管理起来,那就很简单:LinkedList<Student> lstStudents = new LinkedList<Student>();完事⼉!接着直接把对象往 lstStudents ⾥⾯放就可以了。
【IT专家】linux内核入门之list介绍(1)
本文由我司收集整编,推荐下载,如有疑问,请与我司联系linux内核入门之list介绍(1)2013/11/01 0 1.介绍linux内核中双向链表的核心数据结构struct list_head如下:linux/list.h struct list_head { struct list_head *next, *prev;};从上面定义可以看出,linux将双向链表关系关系抽离成单独的数据结构,只要包含了list_head类型成员的对象都能成为链表节点,这样就可以将具体的数据类型与数据之间的关系解耦合。
如果把包含list_head的对象称为节点,那么:next指向下个节点的list_head;prev指向上个节点的list_head。
使用list_head时,一般将list_head嵌套到自己的结构体中。
如下:struct my_struct { struct list_head list; void *data};链表使用前需要初始化,有两种初始化方式:a.动态初始化,则运行时初始化struct my_struct *p;p = (struct my_struct *)kmalloc(sizeof(struct my_struct), GFP_KERNEL);INIT_LIST_HEAD( p- list);p- data = NULL;INIT_LIST_HEAD是一个inline函数。
b.静态初始化,即编译时初始化struct my_struct mine = { .list = LIST_HEAD_INIT(mine.list), .data = NULL};LIST_HEAD_INIT是一个宏。
使用LIST_HEAD(list)直接创建一个list节点.2.链表操作如果把head当作头节点,则head- next为first节点,head- prev为last节点。
--------------------------------------------------------------------- | ----------- ----------- ---------- | -----| prev | -----| prev | --- ---| prev | --- ----------- ----------- ---- ---------- --- | next | ----- | next |--- --- | next | ---- | ----------- ----------- ---------- | --------------------------------------------------------------------- head first lasta.添加新节点list_add(struct list_head *new, struct list_head *head);list_add_tail(struct list_head *new, struct list_head *head);list_add将new节点添加在head与head- next之间;而list_add_tail则添加在head- prev与head之间。
Linux 内核里的数据结构——双向链表
双向链表Linux 内核中自己实现了双向链表,可以在 include/linux/list.h 找到定义。
我们将会首先从双向链表数据结构开始介绍内核里的数据结构。
为什么?因为它在内核里使用的很广泛,你只需要在 检索一下就知道了。
首先让我们看一下在 include/linux/types.h 里的主结构体:struct list_head {struct list_head *next,*prev;};你可能注意到这和你以前见过的双向链表的实现方法是不同的。
举个例子来说,在 glib 库里是这样实现的:structGList{gpointer data;GList*next;GList*prev;};通常来说一个链表结构会包含一个指向某个项目的指针。
但是Linux 内核中的链表实现并没有这样做。
所以问题来了:链表在哪里保存数据呢?。
实际上,内核里实现的链表是侵入式链表(Intrusive list)。
侵入式链表并不在节点内保存数据-它的节点仅仅包含指向前后节点的指针,以及指向链表节点数据部分的指针——数据就是这样附加在链表上的。
这就使得这个数据结构是通用的,使用起来就不需要考虑节点数据的类型了。
比如:struct nmi_desc {spinlock_t lock;struct list_head head;};让我们看几个例子来理解一下在内核里是如何使用 list_head 的。
如上所述,在内核里有很多很多不同的地方都用到了链表。
我们来看一个在杂项字符驱动里面的使用的例子。
在 drivers/char/misc.c 的杂项字符驱动API 被用来编写处理小型硬件或虚拟设备的小驱动。
这些驱动共享相同的主设备号:#define MISC_MAJOR 10但是都有各自不同的次设备号。
比如:ls-l /dev |grep10crw-------1 root root 10,235Mar2112:01 autofsdrwxr-xr-x 10 root root 200Mar2112:01 cpucrw-------1 root root 10,62Mar2112:01 cpu_dma_latencycrw-------1 root root 10,203Mar2112:01 cusedrwxr-xr-x 2 root root 100Mar2112:01 dricrw-rw-rw-1 root root 10,229Mar2112:01 fusecrw-------1 root root 10,228Mar2112:01 hpetcrw-------1 root root 10,183Mar2112:01 hwrngcrw-rw----+1 root kvm 10,232Mar2112:01 kvmcrw-rw----1 root disk 10,237Mar2112:01 loop-controlcrw-------1 root root 10,227Mar2112:01 mcelogcrw-------1 root root 10,59Mar2112:01 memory_bandwidthcrw-------1 root root 10,61Mar2112:01 network_latencycrw-------1 root root 10,60Mar2112:01 network_throughputcrw-r-----1 root kmem 10,144Mar2112:01 nvrambrw-rw----1 root disk 1,10Mar2112:01 ram10crw--w----1 root tty4,10Mar2112:01 tty10crw-rw----1 root dialout 4,74Mar2112:01 ttyS10crw-------1 root root 10,63Mar2112:01 vga_arbitercrw-------1 root root 10,137Mar2112:01 vhci现在让我们看看它是如何使用链表的。
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方向看是一个堆栈型。
拒绝造轮子!如何移植并使用Linux内核的通用链表(附完整代码实现)
拒绝造轮子!如何移植并使用Linux内核的通用链表(附完整代码实现)实际的工作中会经常使用链表来存储存储结构,可能是嵌入式开发,经常会使用linux内核最详细的详细介绍的链表_head中的详细介绍。
本篇文章Linux内核的通用链表是实现的,的函数都提供了详细的使用说明和用例,移植了一些Linux内核测试好的链表结构,在任何平台都可以方便的内核已经编写函数。
并且建议,以备不时之需!@目录•链表简介o单链表o双链表o循环链表•Linux内核中的链表o链表的定义o链表的初始化▪内核实现▪说明▪举例o月表增加偏差▪内核实现▪说明▪举例o链表删除节点▪内核实现▪说明▪举例o链表替换偏差▪内核实现▪说明▪举例o链删除表并插入节点▪内核实现▪说明▪举例o链表的合并▪内核实现▪说明▪用例o链表的遍历▪内核实现▪说明▪举例o疑问解答o list.h移植源码链表简介链表是一种常用的序列数据结构,它通过指针将链表数据节点连接成一条数据,是直线表的一种重要实现方式。
建立数据链时可以随时提前了解数据,可以方便地使用链表结构,可以在链中数据的任意位置实时或删除数据。
链域的存储,又可以用于建立下一个节点的联系表。
提出这几类常见的链表类型的说明:单链表它的单链表是最简单的空指针表链表,通常是对一个指针域的遍历(next),因此,单链的遍历通常是从头到尾(指向尾)。
双链表通过设计前驱、后继的关系,就可以再构成“二叉树”;如果让链表从前驱和后继的两叉树,就可以构成“二叉树”;节点的前驱指向链表尾节点、尾节点的后继链首节点,就组成了一个循环表;如果设计更多的各种领域,就可以组成复杂的树状数据结构。
循环链表循环链表的特点是尾节点的后继指向首节点节点表的示意图,它的特点是从任意一个节点出发,沿着两个方向的任何一个节点,都可以找到链表的任意一个数据。
如果去掉前驱,就是单循环链表。
链表的内容可以查看链表的列表列表的最完整的链表操作列表以及最完整的链表操作列表。
Linux内核链表完全讲解
(3)增加节点代码 增加结点有两种方式:头插法和尾插法。 我们可以调用 static inline void list_add(struct list_head *new, struct list_head *head); static inline void list_add_tail(struct list_head *new, struct list_head *head); 这两个接口。 头插法: static inline void list_add(struct list_head *new, struct list_head *head) { __list_add(new, head, head->next); } 尾插法: static inline void list_add_tail(struct list_head *new, struct list_head *head) { __list_add(new, head->prev, head); } 注意:以__开头的函数一般为内核本身调用的函数,非用户调用之用。 真正的实现插入: static inline void __list_add(struct list_head *new,struct list_head *prev,struct list_head *next) { next->prev = new; new->next = next; new->prev = prev; prev->next = new; } __list_add(new, prev, next):表示在 prev 和 next 之间添加一个新的节 点 new,所以对于 list_add()中的__list_add(new, head, head->next)表示的 在 head 和 head->next 之间加入一个新的节点,是头插法。
Linux内核链表list_head扩展---klist
看SPI驱动核心模块时,看到用到很多klist,之前没多深入理解,现在来一步步分析源码。
klist是对list_head的扩展,实现代码很少。
内核代码:include/linux/klist.hlib/klist.c----------------------先要有一点点预备知识——list_head----------------------先看看头文件如何定义klist,以及一些基本操作方法接口。
----------------------klist头文件/* 头文件*/1/*2 * klist.h - Some generic list helpers, extending struct list_head a bit.3 *4 * Implementations are found in lib/klist.c5 *6 *7 * Copyright (C) 2005 Patrick Mochel8 *9 * This file is rleased under the GPL v2.10 */1112#ifndef _LINUX_KLIST_H13#define _LINUX_KLIST_H14/* 自旋锁,这个过几天认真看下实现,具体作用就是多处理器的临界操作锁* 对于嵌入式单处理器比较少用到*/15#include <linux/spinlock.h>/* 这个是内核引用次数的原子变量定义和原子操作* 操作接口就是原子变量的增加get、减少put,以此实现内核对象引用次数操作*/16#include <linux/kref.h>/* 内核链表list_head */17#include <linux/list.h>18/* 先声明klist_node节点,在后面定义*/19struct klist_node;/* klist结构体定义*/20struct klist {/* klist操作自旋锁*/21 spinlock_t k_lock;/* 内核链表*/22 struct list_head k_list;/* 获取klist_node节点方法*/23 void (*get)(struct klist_node *);/* 添加klist_node节点方法*/24 void (*put)(struct klist_node *);25} __attribute__ ((aligned (sizeof(void *))));/* 按指针大小对齐*/26/* 定义klist链表初始化宏* 名字初始化锁、链表、get、put方法*/27#define KLIST_INIT(_name, _get, _put) \/* 初始化为解锁状态*/28 { .k_lock = __SPIN_LOCK_UNLOCKED(_name.k_lock), \/* 初始化链表*/29 .k_list = LIST_HEAD_INIT(_name.k_list), \/* get方法*/30 .get = _get, \/* put方法*/31 .put = _put, }32/* 定义并初始化链表*/33#define DEFINE_KLIST(_name, _get, _put) \34 struct klist _name = KLIST_INIT(_name, _get, _put)35/* klist初始化接口,在klist.c里具体分析*/36extern void klist_init(struct klist *k, void (*get)(struct klist_node *),37 void (*put)(struct klist_node *));38/* 节点结构体*/39struct klist_node {40 void *n_klist; /* never access directly *//* 节点链表入口*/41 struct list_head n_node;/* 引用次数的一个原子变量*/42 struct kref n_ref;43};44/* 下面是链表操作方法声明,在klist.c具体分析*/45extern void klist_add_tail(struct klist_node *n, struct klist *k);46extern void klist_add_head(struct klist_node *n, struct klist *k);47extern void klist_add_after(struct klist_node *n, struct klist_node *pos);48extern void klist_add_before(struct klist_node *n, struct klist_node *pos);4950extern void klist_del(struct klist_node *n);51extern void klist_remove(struct klist_node *n);5253extern int klist_node_attached(struct klist_node *n);5455/* klist迭代器和操作方法,关于迭代器比较难理解,先看klist.c再说*/ 56struct klist_iter {57 struct klist *i_klist;58 struct klist_node *i_cur;59};606162extern void klist_iter_init(struct klist *k, struct klist_iter *i);63extern void klist_iter_init_node(struct klist *k, struct klist_iter *i,64 struct klist_node *n);65extern void klist_iter_exit(struct klist_iter *i);66extern struct klist_node *klist_next(struct klist_iter *i);6768#endif69----------------------klist实现代码1/*2 * klist.c - Routines for manipulating klists.3 *4 * Copyright (C) 2005 Patrick Mochel5 *6 * This file is released under the GPL v2.7 *8 * This klist interface provides a couple of structures that wrap around9 * struct list_head to provide explicit list "head" (struct klist) and list10 * "node" (struct klist_node) objects. For struct klist, a spinlock is11 * included that protects access to the actual list itself. struct12 * klist_node provides a pointer to the klist that owns it and a kref13 * reference count that indicates the number of current users of that node14 * in the list.15 *16 * The entire point is to provide an interface for iterating over a list17 * that is safe and allows for modification of the list during the18 * iteration (e.g. insertion and removal), including modification of the19 * current node on the list.20 *21 * It works using a 3rd object type - struct klist_iter - that is declared22 * and initialized before an iteration. klist_next() is used to acquire the23 * next element in the list. It returns NULL if there are no more items.24 * Internally, that routine takes the klist's lock, decrements the25 * reference count of the previous klist_node and increments the count of26 * the next klist_node. It then drops the lock and returns.27 *28 * There are primitives for adding and removing nodes to/from a klist.29 * When deleting, klist_del() will simply decrement the reference count.30 * Only when the count goes to 0 is the node removed from the list.31 * klist_remove() will try to delete the node from the list and block until32 * it is actually removed. This is useful for objects (like devices) that33 * have been removed from the system and must be freed (but must wait until34 * all accessors have finished).35 */3637#include <linux/klist.h>38#include <linux/module.h>39#include <linux/sched.h>40/* 下面定义一些节点操作方法,先看下去,再来理解这些操作真正作用*/41/*42 * Use the lowest bit of n_klist to mark deleted nodes and exclude43 * dead ones from iteration.44 */45#define KNODE_DEAD 1LU46#define KNODE_KLIST_MASK ~KNODE_DEAD47/* 由节点获取链表头*/48static struct klist *knode_klist(struct klist_node *knode)49{50 return (struct klist *)51 ((unsigned long)knode->n_klist & KNODE_KLIST_MASK); 52}53/* 判断节点―死了‖ */54static bool knode_dead(struct klist_node *knode)55{56 return (unsigned long)knode->n_klist & KNODE_DEAD;57}58/* 设置节点的链表*/59static void knode_set_klist(struct klist_node *knode, struct klist *klist)60{61 knode->n_klist = klist;62 /* no knode deserves to start its life dead *//* 没有节点刚开始就是―死的‖ */63 WARN_ON(knode_dead(knode));64}65/* ―杀死‖节点*/66static void knode_kill(struct klist_node *knode)67{68 /* and no knode should die twice ever either, see we're very humane *//* 没有节点能―死‖两次,瞧我们多人性化*/69 WARN_ON(knode_dead(knode));70 *(unsigned long *)&knode->n_klist |= KNODE_DEAD;71}7273/**74 * klist_init - Initialize a klist structure.75 * @k: The klist we're initializing.76 * @get: The get function for the embedding object (NULL if none)77 * @put: The put function for the embedding object (NULL if none)78 *79 * Initialises the klist structure. If the klist_node structures are80 * going to be embedded in refcounted objects (necessary for safe81 * deletion) then the get/put arguments are used to initialise82 * functions that take and release references on the embedding83 * objects.84 *//* klist初始化接口* get/put方法用来操作klist_node*/85void klist_init(struct klist *k, void (*get)(struct klist_node *),86 void (*put)(struct klist_node *))87{88 INIT_LIST_HEAD(&k->k_list);89 spin_lock_init(&k->k_lock);90 k->get = get;91 k->put = put;92}93EXPORT_SYMBOL_GPL(klist_init);94/* 将节点加入到链表头*/95static void add_head(struct klist *k, struct klist_node *n)96{97 spin_lock(&k->k_lock);98 list_add(&n->n_node, &k->k_list);99 spin_unlock(&k->k_lock);100}101/* 将节点加入到链表尾*/102static void add_tail(struct klist *k, struct klist_node *n)104 spin_lock(&k->k_lock);105 list_add_tail(&n->n_node, &k->k_list);106 spin_unlock(&k->k_lock);107}108/* 节点初始化* 包括初始化链表、引用计数、设置指向klist*/109static void klist_node_init(struct klist *k, struct klist_node *n) 110{111 INIT_LIST_HEAD(&n->n_node);112 kref_init(&n->n_ref);113 knode_set_klist(n, k);/* 如果klist的get方法存在,则调用获取节点*/ 114 if (k->get)115 k->get(n);116}117118/**119 * klist_add_head - Initialize a klist_node and add it to front. 120 * @n: node we're adding.121 * @k: klist it's going on.122 *//* 将节点n初始化并加入到klist的头*/123void klist_add_head(struct klist_node *n, struct klist *k)124{125 klist_node_init(k, n);126 add_head(k, n);127}128EXPORT_SYMBOL_GPL(klist_add_head);129130/**131 * klist_add_tail - Initialize a klist_node and add it to back. 132 * @n: node we're adding.133 * @k: klist it's going on.134 *//* 将节点n初始化并加入到klist的尾*/135void klist_add_tail(struct klist_node *n, struct klist *k)136{137 klist_node_init(k, n);138 add_tail(k, n);139}140EXPORT_SYMBOL_GPL(klist_add_tail);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{149 struct klist *k = knode_klist(pos);150151 klist_node_init(k, n);152 spin_lock(&k->k_lock);153 list_add(&n->n_node, &pos->n_node);154 spin_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{165 struct klist *k = knode_klist(pos);166167 klist_node_init(k, n);168 spin_lock(&k->k_lock);169 list_add_tail(&n->n_node, &pos->n_node);170 spin_unlock(&k->k_lock);171}172EXPORT_SYMBOL_GPL(klist_add_before);173/* 等待者结构体,用于删除节点,删除完成唤醒进程*/174struct klist_waiter {175 struct list_head list;176 struct klist_node *node;177 struct task_struct *process;178 int woken;179};180/* 定义并初始化klist节点移除自旋锁*/181static DEFINE_SPINLOCK(klist_remove_lock);/* 定义一个等待器的链表*/182static LIST_HEAD(klist_remove_waiters);183184static void klist_release(struct kref *kref)185{186 struct klist_waiter *waiter, *tmp;187 struct klist_node *n = container_of(kref, struct klist_node, n_ref);188189 WARN_ON(!knode_dead(n));/* 删除链表中的节点入口*/190 list_del(&n->n_node);191 spin_lock(&klist_remove_lock);/* 内核链表操作宏include/linux/list.h,遍历klist节点移除等待链表*/ 192 list_for_each_entry_safe(waiter, tmp, &klist_remove_waiters, list) {/* 是要删除链表节点的等待器*/193 if (waiter->node != n)194 continue;195/* 等待者唤醒标志*/196 waiter->woken = 1;197 mb();/* 唤醒等待进程*/198 wake_up_process(waiter->process);/* 删除链表入口*/199 list_del(&waiter->list);200 }201 spin_unlock(&klist_remove_lock);/* 设置节点n指向的klist为空*/202 knode_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 */207 return kref_put(&n->n_ref, klist_release);208}209/* 带锁操作的节点删除,不判断是否成功,减引用次数*/210static void klist_put(struct klist_node *n, bool kill)211{/* 获取节点的put方法*/212 struct klist *k = knode_klist(n);213 void (*put)(struct klist_node *) = k->put;214215 spin_lock(&k->k_lock);/* ―需要杀死节点‖ = = */216 if (kill)217 knode_kill(n);/* 节点对象引用次数为0了,则不需要调用put方法*/218 if (!klist_dec_and_del(n))219 put = NULL;220 spin_unlock(&k->k_lock);/* 调用put方法*/221 if (put)222 put(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{231 klist_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{/* 定义一个等待者,并加入等待者加入移除等待者链表*/241 struct klist_waiter waiter;242243 waiter.node = n;244 waiter.process = current;245 waiter.woken = 0;246 spin_lock(&klist_remove_lock);247 list_add(&waiter.list, &klist_remove_waiters);248 spin_unlock(&klist_remove_lock);249/* 清除节点,并设置等待者*/250 klist_del(n);251252 for (;;) {/* 设置进程状态不可中断,等待*/253 set_current_state(TASK_UNINTERRUPTIBLE);/* 节点被删除,进程被唤醒*/254 if (waiter.woken)255 break;/* 内核调度器*/256 schedule();257 }/* 设置当前进程状态为运行*/258 __set_current_state(TASK_RUNNING);259}260EXPORT_SYMBOL_GPL(klist_remove);261262/**263 * klist_node_attached - Say whether a node is bound to a list or not. 264 * @n: Node that we're testing.265 *//* 判断节点是不是链表边界*/266int klist_node_attached(struct klist_node *n)267{268 return (n->n_klist != NULL);269}270EXPORT_SYMBOL_GPL(klist_node_attached);271272/**273 * klist_iter_init_node - Initialize a klist_iter structure.274 * @k: klist we're iterating.275 * @i: klist_iter we're filling.276 * @n: node to start with.277 *278 * Similar to klist_iter_init(), but starts the action off with @n,279 * instead of with the list head.280 *//* 初始化迭代器节点,使用链表节点n */281void klist_iter_init_node(struct klist *k, struct klist_iter *i,282 struct klist_node *n)283{284 i->i_klist = k;285 i->i_cur = n;286 if (n)287 kref_get(&n->n_ref);289EXPORT_SYMBOL_GPL(klist_iter_init_node);290291/**292 * klist_iter_init - Iniitalize a klist_iter structure.293 * @k: klist we're iterating.294 * @i: klist_iter structure we're filling.295 *296 * Similar to klist_iter_init_node(), but start with the list head.297 *//* 初始化迭代器节点,使用链表头*/298void klist_iter_init(struct klist *k, struct klist_iter *i)299{300 klist_iter_init_node(k, i, NULL);301}302EXPORT_SYMBOL_GPL(klist_iter_init);303304/**305 * klist_iter_exit - Finish a list iteration.306 * @i: Iterator structure.307 *308 * Must be called when done iterating over list, as it decrements the 309 * refcount of the current node. Necessary in case iteration exited before 310 * the end of the list was reached, and always good form.311 *//* 结束链表迭代,必须在结束迭代链表时调用*/312void klist_iter_exit(struct klist_iter *i)313{314 if (i->i_cur) {315 klist_put(i->i_cur, false);316 i->i_cur = NULL;317 }318}319EXPORT_SYMBOL_GPL(klist_iter_exit);320/* 由链表入口获取节点*/321static struct klist_node *to_klist_node(struct list_head *n)322{323 return container_of(n, struct klist_node, n_node);324}325326/**327 * klist_next - Ante up next node in list.328 * @i: Iterator structure.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{336 void (*put)(struct klist_node *) = i->i_klist->put;337 struct klist_node *last = i->i_cur;338 struct klist_node *next;339/* 抢占锁*/340 spin_lock(&i->i_klist->k_lock);341/* 获取下一节点*/342 if (last) {343 next = to_klist_node(last->n_node.next);/* 减上一节点引用次数*/344 if (!klist_dec_and_del(last))345 put = NULL;346 } else347 next = to_klist_node(i->i_klist->k_list.next);348349 i->i_cur = NULL;/* 链表中有节点―没死‖,增加引用次数*/350 while (next != to_klist_node(&i->i_klist->k_list)) {351 if (likely(!knode_dead(next))) {352 kref_get(&next->n_ref);353 i->i_cur = next;354 break;355 }356 next = to_klist_node(next->n_node.next);357 }358/* 丢弃锁*/359 spin_unlock(&i->i_klist->k_lock);360361 if (put && last)362 put(last);363 return i->i_cur;364}365EXPORT_SYMBOL_GPL(klist_next);366----------------------按面向对象的思想klist是一个链表操作类,klist->k_list是链表入口,get/put是节点操作的方法。
linux内核数据结构之链表
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结构的起始地址。
linux内核里的字符串转换,链表操作常用函数(转)
linux内核⾥的字符串转换,链表操作常⽤函数(转)1.对双向链表的具体操作如下:1. list_add ———向链表添加⼀个条⽬2. list_add_tail ———添加⼀个条⽬到链表尾部3. __list_del_entry ———从链表中删除相应的条⽬4. list_replace———⽤新条⽬替换旧条⽬5. list_del_init———从链表中删除条⽬后重新初始化6. list_move———从⼀个链表中删除并加⼊为另⼀个链表的头部7. list_move_tail———从⼀个列表中删除并加⼊为另⼀个链表的尾部8. list_is_last———测试是否为链表的最后⼀个条⽬9. list_empty———测试链表是否为空10. list_empty_careful———测试链表是否为空并没有被修改11. list_rotate_left———向左转动链表12. list_is_singular———测试链表是否只有⼀个条⽬13. list_cut_position———将链表⼀分为⼆14. list_splice———将两个链表进⾏合并15. list_splice_tail———将两个链表进⾏合并为⼀个链表16. list_splice_init———将两个链表进⾏合并为⼀个链表并初始化为空表17. list_splice_tail_init———将两个链表进⾏合并为⼀个链表(从尾部合并)并初始化为空表18. list_entry———获取条⽬的结构,实现对container_of 的封装19. list_first_entry———获取链表的第⼀个元素20. list_first_entry_or_null———获取链表的第⼀个元素21. list_for_each———遍历链表22. list_for_each_prev———反向遍历链表23. list_for_each_safe———遍历链表并删除链表中相应的条⽬24. list_for_each_prev_safe———反向遍历链表并删除链表中相应的条⽬25. list_for_each_entry———遍历指定类型的链表26. list_for_each_entry_reverse———反向遍历指定类型的链表27. list_prepare_entry———准备⼀个⽤于list_for_each_entry_continue 的条⽬28. list_for_each_entry_continue———从指定点开始继续遍历指定类型的链表29. list_for_each_entry_continue_reverse———从指定点开始反向遍历链表30. list_for_each_entry_from———从当前点遍历指定类型的链表31. list_for_each_entry_safe———反向遍历指定类型的链表并删除链表中相应的条⽬32. list_for_each_entry_safe_continue———继续遍历链表并删除链表中相应的条⽬33. list_for_each_entry_safe_from———从当前点遍历链表并删除链表中相应的条⽬34. list_for_each_entry_safe_reverse———反向遍历链表并删除链表中相应的条⽬35. list_safe_reset_next———获得下⼀个指定类型的条⽬36. hlist_for_each_entry———遍历指定类型的单指针表头链表37. hlist_for_each_entry_continue———从当前点继续遍历单指针表头链表38. hlist_for_each_entry_from———从当前点继续遍历单指针表头链表39. hlist_for_each_entry_safe———遍历指定类型的单指针表头链表并删除链表中相应的条⽬2.字符串相关内核中经常会有字符串转换的需要, 其接⼝如下:1. simple_strtoull———变换⼀个字符串为⽆符号的long long 型2. simple_strtoul———变换⼀个字符串为⽆符号的long 型3. simple_strtol———变换⼀个字符串为有符号的long 型4. simple_strtoll———变换⼀个字符串为有符号的long long 型5. vsnprintf———格式化⼀个字符串并放⼊缓冲区6. vscnprintf———格式化⼀个字符串并放⼊缓冲区7. snprintf———格式化⼀个字符串并放⼊缓冲区8. scnprintf———格式化⼀个字符串并放⼊缓冲区9. vsprintf———格式化⼀个字符串并放⼊缓冲区10. sprintf———格式化⼀个字符串并放⼊缓冲区11. vbin_printf———解析格式化字符串并将⼆进制值放⼊缓冲区12. bstr_printf———对⼆进制参数进⾏格式化字符串操作并放⼊缓冲区13. bprintf———解析格式化字符串并将⼆进制值放⼊缓冲区14. vsscanf———从格式化字符串中分离出的参数列表15. sscanf———从格式化字符串中分离出的参数列表16. kstrtol———变换⼀个字符串为long 型17. kstrtoul———变换⼀个字符串为⽆符号的long 型18. kstrtoull———变换⼀个字符串为⽆符号的long long 型19. kstrtoll———变换⼀个字符串为long long 型20. kstrtouint———变换⼀个字符串为⽆符号的int 型21. kstrtoint———变换⼀个字符串为int 型⽰例:char buf[]="115200";unsigned int rate;kstrtouint(buf,0,&rate);//buf:输⼊字符串,0:⾃动识别,也可以是10(10进制)或16(16进制),rate:存放转换后的整形值. //当没有错误时返回值是0;3.另外字符串本⾝的操作接⼝如下:1. strnicmp———长度有限的字符串⽐较,这⾥不分⼤⼩写2. strcpy———复制⼀个以NULL 结尾的字符串3. strncpy———复制⼀个以NULL 结尾的有限长度字符串4. strlcpy———复制⼀个以NULL 结尾的有限长度字符串到缓冲区中5. strcat———在字符串后附加以NULL 结尾的字符串6. strncat———在字符串后附加以NULL 结尾的⼀定长度的字符串7. strlcat———在字符串后附加以NULL 结尾的⼀定长度的字符串8. strcmp———⽐较两个字符串9. strncmp———⽐较两个限定长度的字符串10. strchr———在字符串中查找第⼀个出现指定字符的位置11. strrchr———在字符串中查找最后出现指定字符的位置12. strnchr———在字符串中查找出现指定字符串的位置13. skip_spaces———从字符串中移除前置空格14. strim———从字符串中移除前置及后置的空格15. strlen———获得字符串的长度16. strnlen———获得⼀个有限长度字符串的长度17. strspn———计算⼀个仅包含可接受字母集合的字符串的长度18. strcspn———计算⼀个不包含指定字母集合的字符串的长度19. strpbrk———找到字符集合在字符串第⼀次出现的位置20. strsep———分割字符串21. sysfs_streq———字符串⽐较,⽤于sysfs22. strtobool———⽤户输⼊转换成布尔值23. memset———内存填充24. memcpy———内存复制25. memmove———内存复制26. memcmp———内存⽐较27. memscan———在内存中找指定的字符28. strstr———在⼀个以NULL 结尾的字符串中找到第⼀个⼦串29. strnstr———在⼀个限定长度字符串中找到第⼀个⼦串30. memchr———找到内存中的字符31. memchr_inv———找到内存中的不匹配字符。
linux内核中的list详解
linux内核中的list详解原因:file_operation 结构中的open函数定义如下:int (*open)(struct inode *inode, struct file* filp);inode中含有i_cdev属性,它描述的是字符设备。
在自己定义的字符设备中,一般会包含字符设备的指针,而open方法被调用时,通常需要获取特定的设备对象,这里就涉及到一个问题:如何通过结构中的某个变量获取结构本身的指针。
Linux 内核中提供了container_of宏(WDM中也定义了相似功能的宏)。
C99中定义了两个宏,typeof和offsetof,它们返回的是某个变量的类型和结构中某变量在结构中的偏移量。
可以预想的是,没有编译器的支持,container_of的宏是很难实现的(至少我还没有想出能够不用 typeof宏实现container_of的方法)。
优点:值得一提的是,offsetof宏的实现非常巧妙,它把0地址转化为TYPE结构的指针,然后获取该结构中MEMBER成员的指针,并将其强制类型转换为size_t类型。
于是,由于结构从0地址开始定义,因此,cast后的MEMBER成员地址,实际上就是它在结构中的偏移量。
这也显示出了C语言中指针的强大。
因为,在某个体系结构下实现的libc,结构中各个成员的偏移总是可以预见的,这比C#那种以托管的方式管理内存的自由度要大的多。
实现:container_of宏定义在include/linux/kernel.h中:offsetof宏定义在include/linux/stddef.h中:container_of宏,它的功能是得到包含某个结构成员的结构的指针:其实现如下:#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)#define container_of(ptr, type, member) ({ \const typeof( ((type *)0)->member ) *__mptr = (ptr); \(type *)( (char *)__mptr - offsetof(type,member) );})分析可知__mptr指向的是一个type结构里typeof(((type *)0)->member)类型member成员的指针,offsetof(type,member)是这个成员在结构中的偏移,单位是字节,所以为了计算type结构的起始地址,__mptr减去它自己的偏移。
linux内核分析之list_head双向链表结构.doc
linux 内核分析之list_head 双向链表结构本文详细分析了 2.6.x 内核中链表结构的实现,并通过实例对每个链表操作接口进行了详尽的讲解。
一、链表数据结构简介链表是一种常用的组织有序数据的数据结构,它通过指针将一系列数据节点连接成一条数据链,是线性表的一种重要实现方式。
相对于数组,链表具有更好的动态性,建立链表时无需预先知道数据总量,可以随机分配空间,可以高效地在链表中的任意位置实时插入或删除数据。
链表的开销主要是访问的顺序性和组织链的空间损失。
通常链表数据结构至少应包含两个域:数据域和指针域,数据域用于存储数据,指针域用于建立与下一个节点的联系。
按照指针域的组织以及各个节点之间的联系形式,链表又可以分为单链表、双链表、循环链表等多种类型,下面分别给出这几类常见链表类型的示意图:1.单链表图1 单链表单链表是最简单的一类链表,它的特点是仅有一个指针域指向后继节点(next),因此,对单链表的遍历只能从头至尾(通常是NULL空指针)顺序进行。
2.双链表图2 双链表通过设计前驱和后继两个指针域,双链表可以从两个方向遍历,这是它区别于单链表的地方。
如果打乱前驱、后继的依赖关系,就可以构成"二叉树";如果再让首节点的前驱指向链表尾节点、尾节点的后继指向首节点(如图2中虚线部分),就构成了循环链表;如果设计更多的指针域,就可以构成各种复杂的树状数据结构。
3.循环链表循环链表的特点是尾节点的后继指向首节点。
前面已经给出了双循环链表的示意图,它的特点是从任意一个节点出发,沿两个方向的任何一个,都能找到链表中的任意一个数据。
如果去掉前驱指针,就是单循环链表。
在Linux内核中使用了大量的链表结构来组织数据,包括设备列表以及各种功能模块中的数据组织。
这些链表大多采用在[include/linux/list.h]实现的一个相当精彩的链表数据结构。
本文的后继部分就将通过示例详细介绍这一数据结构的组织和使用。
下面我们学习一下linux内核中list模块的使用
下面我们学习一下linux内核中list模块的使用List模块是linux内核提供的循环链表函数集。
头文件是:<linux/list.h>主要数据结构:struct list_head {struct list_head *next, *prev;};可见这是一个双向链表。
使用之前必须初始化一个链表头,可以使用下面的宏直接定义并且初始化一个链表头:LIST_HEAD(name)宏参数name是链表头定义名。
例如:LIST_HEAD(listHead)使用时,你的数据结构中必须包含struct list_head类成员,例如:Struct myNode{Int a;Struct list_head listNode;};Struct myNode nodeVar;调用list_add函数可以将一个数据节点加入到链表中:List_add_tail(&nodeVar->listNode, &listHead);其实它是把nodeVar中的listNode元素接入链表末尾List_add(&nodeVar->listNode, &listHead)将元素加入到链表首位置,即链表头listHead的下一个位置。
,整个链表的结构如下:假如有n个struct myNode变量加入了这个链表,那么怎样查找元素a =100的节点呢?使用list_for_each_entry代码如下:#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))#define list_entry(ptr, type, member) \container_of(ptr, type, member)其中pos是暂存的结构体指针,member是结构体内部的struct listhead结构变量。
深入浅出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、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
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。