版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/tidyjiang/article/details/52750485

Zephyr OS 所有的学习笔记已托管到 Github,CSDN 博客里的内容只是 Github 里内容的拷贝,因此链接会有错误,请谅解。

最新的学习笔记请移步 GitHub:https://github.com/tidyjiang8/zephyr-inside

本文先简单地介绍了一些内联函数的知识,然后再详细分析 Zephyr OS 内核中的链表的源码。

  • 内联(inline)函数
  • 链表的定义
  • 链表的初始化
  • 判断某节点是否是链表的头节点
  • 判断某节点是否是链表的尾节点
  • 判断链表是否为空
  • 获取链表头节点
  • 获取节点的下一个节点
  • 在链表尾部插入节点
  • 在链表头部插入节点
  • 在某节点后插入节点
  • 在某节点前插入节点
  • 删除某节点
  • 取出第一个节点

内联(inline)函数

在 Zephyr OS 中,实现了一个双链表,其代码位于头文件 inclue/misc/dlist.h 中。

居然将函数实现在头文件中!!再仔细一看,这些函数不是普通的函数,而是内联函数!

为什么?这样有什么好处?在网上搜了一通,总结如下:

  • 什么是内联函数:

    • 简单地讲,内联函数就是用关键字 inline 修饰的函数。编译器在编译含有内联函数的文件时,会在调用内联函数的地方将该函数展开,这一点与处理宏的过程类似。但是,在将内联函数展开的时候,编译器会对该内联函数的类型进行检查,比如检查参数、返回值类型等。
  • 什么时候使用内联函数:
    • 如果一段代码需要重用,且重用的频率很高,且代码很短,那么推荐使用内联函数。
  • 内联函数 vs 宏:
    • 编译器会对内联函数进行类型检查,而对宏只是进行简单的替换。
    • 内联函数在编译时被展开,宏在预编译时被展开
  • 内联函数 vs 普通函数:
    • 前面已经说了,内联函数的调用频率高,代码短小,在这种情况下,如果不使用内联函数而使用普通函数,会降低运行效率,因为函数调用有开销,比如参数、返回值、返回地址等的压栈、出栈操作。
  • 内联函数的缺点:
    • 增加了编译后的代码的大小。

在 Zephyr OS 的源码中,还会看到另外一套链表,位于 net/ip/contiki/os/list.[ch]。这套链表是移植于Contiki,只在ip协议栈中有使用。

链表的定义

struct _dnode {union {struct _dnode *head; struct _dnode *next; };union {struct _dnode *tail; struct _dnode *prev;};
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

第一眼就蒙了,为什么节点的定义里面居然有两个联合体。直到看到后来,恍惚间想明白了。Zephyr OS 中对该结构体使用 tpyedef 定义了两种类型:

typedef struct _dnode sys_dlist_t;
typedef struct _dnode sys_dnode_t;
  • 1
  • 2

sys_dlist_t 定义了一个双链表,sys_dnode_t 定义了一个节点。在使用 sys_dlist_t 时,使用的是结构体中的 head, tail 两个成员;在使用 sys_dnode_t 时,使用的是结构体中的 next, prev 两个成语。

其实我们可以对上面的代码展开成两个结构体:

typedef struct _dnode { // 定义节点struct _dnode *next;  // 后继节点struct _donde *prev;  // 前驱节点
} sys_dnode_t;typedef struct _dlist { //  定义链表struct _dlist *head;  //  链表头struct _dlist *tail;  //  链表尾
} sys_dlist_t;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

有人可能会被链表和节点两个概念搞晕了,比如曾经的我。我们只需要记住一点,在使用链表的时候,我们都是先定义一个链表,然后往链表里进行添加节点、删除节点、查找节点等操作。比如:

sys_dlist_t mylist;               // 定义链表
sys_dlist_init(&mylist);      // 链表初始化sys_dnode_t mynode1, mynode2; // 定义节点
...   // 对节点初始化
sys_dlist_append(&mylist, &mynode1);  // 插入节点1
sys_dlist_append(&mylist, &mynode2);  // 插入节点2
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

链表的初始化

static inline void sys_dlist_init(sys_dlist_t *list)
{list->head = (sys_dnode_t *)list;list->tail = (sys_dnode_t *)list;
}
  • 1
  • 2
  • 3
  • 4
  • 5

理解了前面所说的东西,再看具体的代码实现,so easy.

进行链表的初始化,此时链表是空的,没有任何节点,所以将 head, tail 两个指针都指向 list 自身。

判断某节点是否是链表的头节点

static inline int sys_dlist_is_head(sys_dlist_t *list, sys_dnode_t *node)
{return list->head == node;
}
  • 1
  • 2
  • 3
  • 4

判断某节点是否是链表的尾节点

static inline int sys_dlist_is_tail(sys_dlist_t *list, sys_dnode_t *node)
{return list->tail == node;
}
  • 1
  • 2
  • 3
  • 4

判断链表是否为空

static inline int sys_dlist_is_empty(sys_dlist_t *list)
{return list->head == list;
}
  • 1
  • 2
  • 3
  • 4

获取链表头节点

static inline sys_dnode_t *sys_dlist_peek_head(sys_dlist_t *list)
{return sys_dlist_is_empty(list) ? NULL : list->head;
}
  • 1
  • 2
  • 3
  • 4

先判断链表是否为空,如果为空,则返回 NULL,否则返回头结点。所以在使用才函数时,需要判断返回值是否为 NULL,再做处理。

获取节点的下一个节点

static inline sys_dnode_t *sys_dlist_peek_next(sys_dlist_t *list,sys_dnode_t *node)
{return node == list->tail ? NULL : node->next;
}
  • 1
  • 2
  • 3
  • 4
  • 5

先判断传入的节点是不是链表的尾节点,如果是,则返回 NULL,否则返回下一个节点。所以在使用才函数时,需要判断返回值是否为 NULL,再做处理。

在链表尾部插入节点

static inline void sys_dlist_append(sys_dlist_t *list, sys_dnode_t *node)
{node->next = list;node->prev = list->tail;list->tail->next = node;list->tail = node;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

在链表头部插入节点

static inline void sys_dlist_prepend(sys_dlist_t *list, sys_dnode_t *node)
{node->next = list->head;node->prev = list;list->head->prev = node;list->head = node;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

在某节点后插入节点

static inline void sys_dlist_insert_after(sys_dlist_t *list,sys_dnode_t *insert_point, sys_dnode_t *node)
{if (!insert_point) {sys_dlist_prepend(list, node);} else {node->next = insert_point->next;node->prev = insert_point;insert_point->next->prev = node;insert_point->next = node;}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

在某节点前插入节点

static inline void sys_dlist_insert_before(sys_dlist_t *list,sys_dnode_t *insert_point, sys_dnode_t *node)
{if (!insert_point) {sys_dlist_append(list, node);} else {node->prev = insert_point->prev;node->next = insert_point;insert_point->prev->next = node;insert_point->prev = node;}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

删除某节点

static inline void sys_dlist_remove(sys_dnode_t *node)
{node->prev->next = node->next;node->next->prev = node->prev;
}
  • 1
  • 2
  • 3
  • 4
  • 5

取出第一个节点

static inline sys_dnode_t *sys_dlist_get(sys_dlist_t *list)
{sys_dnode_t *node;if (sys_dlist_is_empty(list)) {return NULL;}node = list->head;sys_dlist_remove(node);return node;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

取出头节点后将其重链表中删除。

Zephyr OS 内核篇: 内核链表相关推荐

  1. 物联网操作系统Zephyr(内核篇)之2.0 内核服务之线程(1)

    Zephyr物联网操作系统专栏汇总 目录 1.生命周期 1.1 线程创建 1.2.线程的正常结束 1.3.线程的异常终止 1.4.线程挂起 2. 线程状态 3.线程堆栈对象 4. 仅内核堆栈 5.线程 ...

  2. [内核编程] 内核环境及其特殊性,驱动编程基础篇

    [内核编程] 内核环境及其特殊性,驱动编程基础篇  在学习汉江独钓一书后,打算总结一下内核编程应该注意的事项,以及有关的一些基础知识.第一次接触内核编程,还真是很生疏,很多东西不能一下马上消化.这里做 ...

  3. 鸿蒙内核代码 行,鸿蒙内核源码分析(CPU篇) | 内核是如何描述CPU的 ? | 祝新的一年牛气冲天 ! | v36.01...

    本篇说清楚CPU 读本篇之前建议先读鸿蒙内核源码分析(总目录)进程/线程篇.指令是稳定的,但指令序列是变化的,只有这样计算机才能够实现用计算来解决一切问题这个目标.计算是稳定的,但计算的数据是多变的, ...

  4. linux内核数据结构之链表

    1.前言 最近写代码需用到链表结构,正好公共库有关于链表的.第一眼看时,觉得有点新鲜,和我之前见到的链表结构不一样,只有前驱和后继指针,而没有数据域.后来看代码注释发现该代码来自linux内核,在li ...

  5. 如何放出Linux内核中的链表大招

    前言 上回,我们说到Linux内核中max()宏的终极奥义,Linux内核链表也不甘示弱,那么接下来,让我们看看Linux内核中的链表大招. 如何放出Linux内核中的链表大招 前言 一.链表简介 ( ...

  6. Linux内核(10) - 内核中的链表

    早上上班坐地铁要排队,到了公司楼下等电梯要排队,中午吃饭要排队,下班了追求一个女孩子也要排队,甚至在网上下载个什么门的短片也要排队,每次看见人群排成一条长龙时,才真正意识到自己是龙的传人.那么下面咱们 ...

  7. 《Linux内核修炼之道》精华分享与讨论(14)——内核中的链表

    早上上班坐地铁要排队,到了公司楼下等电梯要排队,中午吃饭要排队,下班了追求一个女孩子也要排队,甚至在网上下载个什么门的短片也要排队,每次看见人群排成一条长龙时,才真正意识到自己是龙的传人.那么下面咱们 ...

  8. 基于小熊派的HarmonyOS鸿蒙开发教程——内核篇

    复习时间:貌似很遥远呀!(未定期) 基于小熊派的鸿蒙开发内核篇 一.CMSIS-RTOS2接口 二.HarmonyOS内核开发-任务管理 三.HarmonyOS内核开发-定时器管理 定时器基本概念 定 ...

  9. 物联网操作系统Zephyr(蓝牙篇)之6.0 zephyr os中的bt stack概述

    Zephyr物联网操作系统专栏汇总 Zephyr os中的蓝牙协议栈还有完整的BLE和部分 经典蓝牙的host. 1.支持的蓝牙features 1.1.Zephyr 中的蓝牙协议栈兼容蓝牙5.0. ...

最新文章

  1. excel求和为什么是0_Excel教程:小小的N函数竟如此厉害
  2. 使用 CallableStatement 接口调用存储过程
  3. 从0到100——知乎架构变迁史
  4. java map存储对象_JAVA:查找存储在hashMap中的对象的最佳性能方法
  5. C博客作业03--函数
  6. 为什么C4C UI上看不到新建按钮
  7. leetcode 483. 最小好进制
  8. window系统服务器改名,微软:不会将 Windows Server 改名为 Microsoft Server 系统
  9. 善良公社项目总结之如何从前台向后台传输数据
  10. 精悍的Python代码段-转
  11. mysql 键缓冲区_mysql:键缓存
  12. I.MX6 android 禁止低电量自动关机
  13. linux中查看resin进程,resin安装
  14. 双重差分法之平行趋势检验
  15. 喜欢我们不如加入我们:来投稿吧,稿酬靠谱!
  16. 自制月球灯第一期之无线充电篇
  17. 与ChatGPT合作解析《三体》数学
  18. 优秀的LOGO设计都有哪些共同点,是需要我们借鉴的?
  19. STM32内部EE使用问题跟踪
  20. Linux命令 ——ipconfig与ifconfig命令用法及区别

热门文章

  1. python中字符串相关
  2. swoole+redis(websocket聊天室demo)
  3. HDU 2515 Yanghee 的算术【找规律】
  4. 工具箱 - Putty 安装调试3
  5. Nagios+pnp4nagios+rrdtool 安装配置nagios(一)
  6. 一个设计元素很多的网站
  7. webpack-dev-server 不是内部或外部命令,也不是可运行的程序 解决方案
  8. 【数据结构与算法】之深入解析“字符串转换整数 (atoi)”的求解思路和算法示例
  9. 【数据结构与算法】之深入解析“石子游戏VI”的求解思路与算法示例
  10. 电商三巨头交成绩单,这次拼多多输了吗?