嵌入式Linux-内核链表完全讲解

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

Baidu Nhomakorabea
小知识:内联函数 inline 在 c 中,为了解决一些频繁调用的小函数而大量消耗栈空间或者是叫栈内存的问题, 特别的引入了 inline 修饰符,表示为内联函数。内联函数使用 inline 关键字定义,并 且函数体和声明必须结合在一起,否则编译器将他作为普通函数对待。 inline void function(int x); //仅仅是声明函数,没有任何效果 inline void function(int x) //正确 { return x; }
对 于 list_add_tail() 中 的 __list_add(new, head->prev, head) 表 示 在 head->prev(双向循环链表的最后一个结点)和 head 之间添加一个新的结点。 获取更多视频资料欢迎加入 397164505 朱老师技术群。 以上就简单分析内核链表 代码,具体问题用到时随时查看即可。
普通链表与内核链表的比较 normal list 展示的是普通链表的结构,kernel list 展示的是内核链表的 结构。head 是链表头,p1,p2,p3 是链表节点。从图中可以看出普通链表的 p1 的 next 指针是指向的结构体 p2 的地址,p2 的 pre 指针指向 p1 结构体的地址。 而内核链表的 p1 的 next 指向的是 p2 结构体中包含 pre 和 next 部分的地址, 的 p2 的 pre 指向的是 p1 结构体中包含 pre 和 next 部分的地址。依此类推,这就 是区别。 内核数据区域的结构不与特定类型结构相关,任何结构体都可通过内核 的添加成为链表中的节点。 1.4、list.h 文件简介 内核中核心纯链表的实现在 include/linux/list.h 文件中 list.h 中就是一个纯链表的完整封装,包含节点定义和各种链表操作方法。 (1)首先看到的是下面这句代码,这句代码是宏定义与用{}对结构体成员赋值 的结合,作用是将 list_head 类型的结构体 name 的两个成员变量初始化为该结 构体的首地址。 #define LIST_HEAD_INIT(name) { &(name), &(name) } #define LIST_HEAD(name) \ struct list_head name = LIST_HEAD_INIT(name) (2)下面代码为结构体初始化函数,初始化结构体 list 指向自己本身。 static inline void INIT_LIST_HEAD(struct list_head *list) { list->next = list; list->prev = list; } 对于(1)结构体赋值和(2)结构体初始化来说,最终的效果是一样的,都 是将一个 struct list_head 类型变量成员指向自己本身。
Linux 内核链表完全讲解
1.linux 内核链表 linux 内核中有很多用的很经典的数据结构,链表就算其中之一。在 Linux 内核中使用了大量的链表来组织其数据, 其采用了双向链表作为其基本的数据结 构。Linux 链表同样具有链表的共同属性:第一,链表都是由节点组成的。第二, 链表的节点和节点之间是由指针进行连接的。 但是与我们传统的数据结构中所学 的双向链表又有着一些不同(其不包含数据域) 。其主要是 Linux 内核链表在设 计时给出了一种抽象的定义。 采用这种定义有以下两种好处:1 是可扩展性,2 是封装。可扩展性指的是 内核是在发展中的,所以代码都不能写成死代码,要方便修改和追加。而将链表 常见的操作都进行封装,使用者可以只关注接口,不需关注实现。 分析内核中的链表我们可以做些什么呢?我们可以将其复用到用户态编程 中,以后在用户态下编程就不需要写一些关于链表的代码了,直接将内核中 list.h 中的代码拷贝过来用。下面我们通过分析传统链表的局限性引出内核链 表的讲解。 1.1、前述链表数据区域的局限性 我们讲解单链表时,发现由于只含有一个后向指针,不能前向移动,于是我 们引出了双向链表。双链表可以实现前向和后向的移动,操作更为便利。然而依 然有它的局限性。前面我们讲链表时,为了简便起见,结构体的数据区域都是以 整型数据 int data 为例的。然而在实际编程中,链接中的节点数据不可能这么 简单。数据区域的大小和类型也是因实际需求不同而多种多样的。 一般实际项目中的链表,为了方便管理,节点中存储的数据其实是一个结构 体, 这个结构体中包含若干的成员, 这些成员加起来构成了我们的节点数据区域。 1.2、解决思路:数据区封装为结构体由用户实现,通用部分通过调用函数实现 由于实际问题对节点的内部数据区域的需求(大小和类型等)各不相同,从 而由节点构造的链表也是多种多样的。 这就导致了不同程序中链表的总体构成是 多种多样的。 这给我们构建底层内核链表的通用操作函数带来了麻烦——我们无 法通过一个泛性的、 普遍适用的操作函数来访问所有的链表。这就意味着我们设 计一个链表就得写一套链表的操作函数(节点创建、插入、删除、遍历· · · · · · ) , 显然这降低了代码的可重用性。 那么我们能否找到一种对链表操作的通用性方法,来满足现实问题中链表的 多样性呢?正如前面所述,Linux 链表同样具有链表的共同属性,虽然不同问题 所需的链表操作代码不能通用, 需要单独编写, 但是内部的思路和方法是相同的, 只是函数中与实际问题相对应的局部数据区域有所不同。 (实际上链表操作是相 同的,而涉及到数据区域的操作就有不同) 鉴于以上 2 点:我们的想能不能找到一种办法把所有链表中操作方法里共同 的部分提取出来用一套标准方法实现, 然后把不同的部分留着让具体链表的实现 者自己去处理,通用的部分则通过调用函数的方式来实现。 1.3、内核链表的设计思路 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 之间加入一个新的节点,是头插法。
了一个纯链表(纯链表就是没有数据区域,只有前后向指针)的封装,以及纯链 表的各种操作函数(节点创建、插入、删除、遍历· · · · · · ) 。这个纯链表本身没 法直接使用, 它类似于一个半成品,作为核心提供给我们调用来实现自己的具体 链表。 内核链表是一个双向链表,但是与普通的双向链表又有所区别。内核链表中 的链表元素不与特定类型相关, 具有通用性。下图为普通链表与内核链表区别的 示意图。
相关文档
最新文档