LVGL源码分析(1):lv_ll链表的实现
在LVGL中难免需要用到链表:group中的对象需要用链表来存储,这样可以切换对象的焦点;再比如LVGL内部的定时器,多个定时器也是用链表进行存储的。这篇文章就来分析一下LVGL中链表的源码。
文章目录
- 1 链表结构体
- 2 插入元素源码分析
- 2.1 初始化函数
- 2.2 插入元素
- 2.3 插入元素的用法
- 3 总结
1 链表结构体
对于链表来说,肯定有一个头指针和一个尾指针,在LVGL中,链表的数据结构如下:
/** Dummy type to make handling easier*/
typedef uint8_t lv_ll_node_t;/** Description of a linked list*/
typedef struct {uint32_t n_size;lv_ll_node_t * head;lv_ll_node_t * tail;
} lv_ll_t;
可以看出头尾指针实际上是用一个uint8_t *的指针来保存某个数据的地址。
2 插入元素源码分析
下面以向链表的尾部插入元素为例,来分析一下源码:
2.1 初始化函数
void _lv_ll_init(lv_ll_t * ll_p, uint32_t node_size)
{ll_p->head = NULL;ll_p->tail = NULL;/*Round the size up to 4*/node_size = (node_size + 3) & (~0x3);ll_p->n_size = node_size;
}
初始化函数就是初始化一下链表中单个node的大小,这里还将长度四字节对齐了。
2.2 插入元素
_lv_ll_ins_head用于在链表的最前面插入节点,而_lv_ll_ins_tail用于在链表的最后插入节点。它们的实现基本上一样,这里以_lv_ll_ins_tail为例进行分析。
#define LL_NODE_META_SIZE (sizeof(lv_ll_node_t *) + sizeof(lv_ll_node_t *))void * _lv_ll_ins_tail(lv_ll_t * ll_p)
{lv_ll_node_t * n_new;n_new = lv_mem_alloc(ll_p->n_size + LL_NODE_META_SIZE);if(n_new != NULL) {node_set_next(ll_p, n_new, NULL); /*No next after the new tail*/node_set_prev(ll_p, n_new, ll_p->tail); /*The prev. before new is the old tail*/if(ll_p->tail != NULL) { /*If there is old tail then the new comes after it*/node_set_next(ll_p, ll_p->tail, n_new);}ll_p->tail = n_new; /*Set the new tail in the dsc.*/if(ll_p->head == NULL) { /*If there is no head (1. node) set the head too*/ll_p->head = n_new;}}return n_new;
}
首先就是分配一个大小为ll_p->n_size + LL_NODE_META_SIZE大小的内存,也就是刚刚我们设置的每个节点的大小,然后再加上两个用于保存前一个元素和后一个元素的指针。
然后以node_set_prev函数为例,看下代码做了什么事:
#define LL_PREV_P_OFFSET(ll_p) (ll_p->n_size)static void node_set_prev(lv_ll_t * ll_p, lv_ll_node_t * act, lv_ll_node_t * prev)
{if(act == NULL) return; /*Can't set the prev node of `NULL`*/uint8_t * act8 = (uint8_t *)act;act8 += LL_PREV_P_OFFSET(ll_p);lv_ll_node_t ** act_node_p = (lv_ll_node_t **) act8;lv_ll_node_t ** prev_node_p = (lv_ll_node_t **) &prev;*act_node_p = *prev_node_p;
}
首先 act8 += LL_PREV_P_OFFSET(ll_p)实际上就是act中prev指针的位置,然后将这个指针指向的值赋值为参数中的prev指针。对于node_set_next来说,完成的操作类似,就是更改act中next指针的值。
对于这边使用二维指针,把指针的地址取出来然后再给地址里指向的指针赋值,我觉得完全是多此一举,只需要强制转化act8的类型大小为指针的大小(prev元素的类型)即可,这样不会覆盖掉下一个元素的值。这里可能是为了处理更一般化的情况,比如prev不只是一个指针,可能是一个结构体,结构体里有更多信息,但也不保存结构体的地址,而是保存结构体数据,但这种想法似乎也没有什么意义。
所以对于下面这两行的代码来说,就是把新创建节点的prev指向当前链表的最后一个元素,将next指向NULL,这样就在链表的最后插入了一个元素。
node_set_next(ll_p, n_new, NULL);
node_set_prev(ll_p, n_new, ll_p->tail);
继续分析代码:
if(ll_p->tail != NULL) { /*If there is old tail then the new comes after it*/node_set_next(ll_p, ll_p->tail, n_new);
}ll_p->tail = n_new; /*Set the new tail in the dsc.*/
if(ll_p->head == NULL) { /*If there is no head (1. node) set the head too*/ll_p->head = n_new;
}
这表示对于ll_p结构来说,我们知道前面只保存了单个元素的大小,还有头尾指针。所以最开始先判断当前的链表的尾指针是否有值,若有,则将其next指向我们新创建的节点。然后将链表中的尾指针赋值为新节点的地址。如果链表的头也为空的话,表示链表刚刚创建,该节点不仅是头节点也是尾结点。
2.3 插入元素的用法
有了上面插入元素到链表尾部源码的分析,我们来看看实际上是怎么使用_lv_ll_ins_tail函数的。
lv_obj_t ** next = _lv_ll_ins_tail(&ll);
LV_ASSERT_MALLOC(next);
*next = next_node;
前面源码中我们知道,插入的新元素的内存是在_lv_ll_ins_tail中分配的,所以我们先插入,然后判断如果这个内存分配成功的话,我们就可以把插入到末尾的指针的值赋值为我们的节点next_node。
3 总结
实际上LVGL中链表的实现和我们预期的链表数据结构差不多,唯一的不同是这里允许自定义每个节点的大小,然后直接在节点中保存数据,而不是保存指针,这也是一种思路吧。当然,链表的操作不止在尾部插入元素,在lv_ll.c文件中还有获取链表长度、删除节点等函数,如果全部都分析一遍,篇幅就太长了,也是大家熟知的链表,故没有多大的意义。这篇文章的目的就是了解一下LVGL中链表的数据结构,然后以往尾部插入元素为例加深对LVGL中实现的链表的理解。
相关文章:
LVGL源码分析(1):lv_ll链表的实现
在LVGL中难免需要用到链表:group中的对象需要用链表来存储,这样可以切换对象的焦点;再比如LVGL内部的定时器,多个定时器也是用链表进行存储的。这篇文章就来分析一下LVGL中链表的源码。 文章目录 1 链表结构体2 插入元素源码分析…...
js判断数据类型的几种方法及其局限性(typeof, instanceof, Object.prototype.toString.call())
js中判断了类型的方法有很多, 这篇文章主要来说一下常用的几种判断类型的方法,以及使用: 每个方法都各有优缺点,在日常使用的时候请结合这些优缺点进行斟酌: 1. 使用typeof判断数据类型 javaScript中typeof可以判断以下类型: undefined: 未定义的变量或者值 boolean: 布…...
【MySQL】一文带你掌握聚合查询和联合查询
文章目录 1. 聚合函数1.1 COUNT1.2 SUM1.3 AVG1.4 MAX,MIN 2. GROUP BY3. HAVING4. 联合查询4.1 内连接4.2 外连接4.3 自连接4.4 子连接 5.合并查询5.1 UNION5.2 UNION ALL 1. 聚合函数 概念: 聚合函数是一种用于处理数据集合的函数,它将多个…...
初步了解JVM
JVM 整体组成部分 类加载器 类加载过程 加载:使用IO读取字节码文件,转换并存储,为每个类创建一个Class对象,存储在方法区中 链接(验证,准备,解析) 验证:对字节码文件格式进…...
嘀嗒陪诊小程序v1.0.8+小程序前端
嘀嗒陪诊小程序功能相对简单,后台也简捷,如果只是做个陪诊服务的小程序也基本能满足了,整体测试了下海参崴发现BUG,小程序端也能正常为使用,唯一用户授权接口是老的。 应用背景:人口老龄化少子化ÿ…...
Java中线程的生命周期
Java中线程的生命周期 Java中线程的声明周期与os中线程的生命周期不太一样,java中线程有6个状态,见下: NEW: 初始状态,线程被创建出来但没有被调用 start() 。RUNNABLE: 运行状态,线程被调用了 start()等待运行的状态…...
光线追踪RayTracing,基本原理,判断物体与光线相交
光线的三点假设: 光线按直线传播光线之间不会发生碰撞光线会经过一系列折射反射进入摄像机 可以从摄像机发出光线,推出可逆的光路 上图中,透明球在与相机直连的线条处,需要将折射和反射的着色点结果相加,如果有光源直…...
三十六、数学知识——组合数(递推法 + 预处理法 + 卢卡斯定理 + 分解质因数求解组合数 + 卡特兰数)
组合数算法主要内容 一、基本思路1、组合数基本概念2、递推法——询问次数多 a b 值较小 模处理(%mod)3、预处理阶乘方法——询问次数较多 a b 值很大 模处理(%mod)4、卢卡斯定理——询问次数较少 (a b 值很大&am…...
LinuxC编程——高级文件操作
目录 一、查询文件信息1、stat2、stat fstat lstat区别 二、目录操作2.1 opendir2.2 readdir2.3 closedir例练习:实现ls操作 三、库3.1 库的定义3.2 库的分类3.2.1 静态库3.2.2 动态库 3.3 创建库3.3.1 静态库制作3.3.2 动态库制作 一、查询文件信息 1、stat int …...
【基础知识整理】图的基本概念 邻接矩阵 邻接表
一、图概述 定义: 图(graph)是由一些点(vertex)和这些点之间的连线(edge)所组成的; 其中,点通常被成为"顶点(vertex)“,而点与点之间的连线则被成为"边或弧”(edege)。 通常记为,G(V,E)。 图是一种重要的…...
5.程序控制结构|Java学习笔记
文章目录 程序流程控制介绍顺序控制分支控制分支控制if elseswitch分支结构 循环控制for循环控制while循环控制do...while循环控制跳转控制语句breakcontinuereturn 程序流程控制介绍 顺序控制分支控制循环控制 顺序控制 程序从上到下逐行地执行,中间没有任何判断…...
【最优PID 整定】PID性能指标(ISE,IAE,ITSE和ITAE)优化、稳定性裕量(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...
Linux内核中断和Linux内核定时器
目录 Linux内核中断 Linux内核定时器 Linux内核中断 int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev) 功能:注册中断 参数: irq : 软中断号 gpio的软中断号 软中断号 gpio_to_i…...
OMG--IDL(Interface Definition Language)
OMG--IDL(Interface Definition Language) 1 概述2 内容缩写IDL 语法和语义概述词法约定ISO Latin-1的字母字符如下表十进制数字字符图形字符格式化字符Tokens注释标识符冲突规则转义标识符关键字IDL识别的其他字符字面量 预处理IDL 语法构建块核心数据类…...
英语学习:M开头
machine 机器 mad 发疯的,生气的 madam 女士,夫人 madame 夫人 magazine 杂志 magic 有魔力的 maid 女仆,侍女 mail 邮递 mailbox 邮箱 mainland 大陆 major 较大的,主要的 majority 大多数 male 雄的 man 人类 man…...
【计算机组成原理与体系结构】控制器
目录 一、CPU的功能与基本结构 二、指令周期的数据流 三、数据通路 四、硬布线控制器 五、微程序控制器 六、微指令 一、CPU的功能与基本结构 运算器基本结构 控制器基本结构 CPU的基本结构 二、指令周期的数据流 取址周期 间址周期 中断周期 指令周期流程 三、数据通路 …...
结构化命令
章节目录: 一、使用 if-then 语句二、if-then-else 语句三、嵌套 if 语句四、test 命令4.1 数值比较4.2 字符串比较4.3 文件比较 五、复合条件测试六、if-then 的高级特性6.1 使用单括号6.2 使用双括号6.3 使用双方括号 七、case 命令八、结束语 本章内容࿱…...
Java Web实训项目:西蒙购物网
文章目录 一、创建数据库和表1、创建数据库2、创建用户表3、创建类别表4、创建商品表5、创建订单表 二、创建Simonshop项目1、创建web项目2、修改Artifacts名称:simonshop3、重新部署项目4、编辑首页5、启动应用,查看效果 三、创建实体类1、用户实体类2、…...
ChatGPT Prompt 提示词设计技巧必知必会
本文内容整理自图灵社区直播《朱立成:ChatGPT Prompt提示词技巧必知必会》。 朱立成,图灵社区《ChatGPT即学即用》视频课程作者,软件工程师,对新事物充满好奇,关注ChatGPT应用。2001年毕业于浙江大学,从事软…...
尚硅谷-云尚办公-项目复盘
尚硅谷-云尚办公-项目复盘 资料地址本文介绍问题汇总问题1.knife4j无法下载 视频4问题2.dev等含义 视频5问题3.wrapper继承/实现图 视频8问题4.修改统一返回结果 视频11问题5.修改后新增也变修改 视频29问题6.redis中key值乱码 视频55-60问题7.RangeError: Maximum call stack …...
华为云AI开发平台ModelArts
华为云ModelArts:重塑AI开发流程的“智能引擎”与“创新加速器”! 在人工智能浪潮席卷全球的2025年,企业拥抱AI的意愿空前高涨,但技术门槛高、流程复杂、资源投入巨大的现实,却让许多创新构想止步于实验室。数据科学家…...
python打卡day49
知识点回顾: 通道注意力模块复习空间注意力模块CBAM的定义 作业:尝试对今天的模型检查参数数目,并用tensorboard查看训练过程 import torch import torch.nn as nn# 定义通道注意力 class ChannelAttention(nn.Module):def __init__(self,…...
三维GIS开发cesium智慧地铁教程(5)Cesium相机控制
一、环境搭建 <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"> 关键配置点: 路径验证:确保相对路径.…...
MongoDB学习和应用(高效的非关系型数据库)
一丶 MongoDB简介 对于社交类软件的功能,我们需要对它的功能特点进行分析: 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具: mysql:关系型数据库&am…...
从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路
进入2025年以来,尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断,但全球市场热度依然高涨,入局者持续增加。 以国内市场为例,天眼查专业版数据显示,截至5月底,我国现存在业、存续状态的机器人相关企…...
JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作
一、上下文切换 即使单核CPU也可以进行多线程执行代码,CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短,所以CPU会不断地切换线程执行,从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...
是否存在路径(FIFOBB算法)
题目描述 一个具有 n 个顶点e条边的无向图,该图顶点的编号依次为0到n-1且不存在顶点与自身相连的边。请使用FIFOBB算法编写程序,确定是否存在从顶点 source到顶点 destination的路径。 输入 第一行两个整数,分别表示n 和 e 的值(1…...
Device Mapper 机制
Device Mapper 机制详解 Device Mapper(简称 DM)是 Linux 内核中的一套通用块设备映射框架,为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程,并配以详细的…...
初探Service服务发现机制
1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能:服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源…...
音视频——I2S 协议详解
I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议,专门用于在数字音频设备之间传输数字音频数据。它由飞利浦(Philips)公司开发,以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...
