linux kernel内存泄漏检测工具之slub debug

一、背景

slub debug 是一个debug集,聚焦于kmem_cache 分配机制的slub内存(比如kmalloc),这部分内存在内核中使用最频繁,slub debug其中有相当部分是用来处理内存踩踏,内存use after free 等异常的,由于这部分的检测效果不如kasan(调试时slub前后填充不同的flag,在分配和释放时做检查,存在发现问题不及时的问题), 本文就不介绍了,本文关注slub debug当中的内存泄漏定位方法。

注意:本文中slub和slab名称有些混用,目前linux版本中实际默认都是使用slub,由于内核代码复用的缘故,有很多的函数名,结构体等还是slab命名,是slub还是slab还是以内核config是打开的CONFIG_SLUB还是CONFIG_SLAB来区分,本文所有实验和分析都是基于CONFIG_SLUB=y;

二、SLUB_DEBUG配置及调试工具

2.1 内核中相关配置

CONFIG_SLUB=y
CONFIG_SLUB_DEBUG=y
CONFIG_SLUB_DEBUG_ON=y
CONFIG_SLUB_STATS=y
#save the stack
CONFIG_STACKDEPOT=y

2.2 slub_debug命令行参数控制

这个参考 linux-6.6.1/Documentation/mm/slub.rst 描述即可
slub debug相关控制可以通过命令行方式进行:
slub_debug=<Debug-Options>打开debug选项,应用到所有的slub类型(具体类型可以参考/proc/slabinfo)slub_debug=<Debug-Options>,<slab name1>,<slab name2>,...打开debug选项,可以选择指定的 slub name, 通过逗号隔开debug option 当前可以选择调试类型,及开关控制字符F		Sanity checks on (SLAB_DEBUG_CONSISTENCY_CHECKS)Z		Red zoning(SLAB_RED_ZONE)P		Poisoning (object and padding) SLAB_POISONU		User tracking (free and alloc) SLAB_STORE_USERT		Trace (please only use on single slabs) SLAB_TRACEA		Enable failslab filter mark for the cacheO		Switch debugging off for caches that would havecaused higher minimum slab orders-		关闭所有slub 调试 (useful if the kernel isconfigured with CONFIG_SLUB_DEBUG_ON)例子: slub 调试只开 sanity checks and red zoning:slub_debug=FZ例子:开启调试,仅针对dentry cacheslub_debug=,dentry例子:调试 kmalloc-XXXX和dentry相关slub调试:slub_debug=P,kmalloc-*,dentry例子: 关闭slub debug相关调试(开了CONFIG_SLUB_DEBUG_ON=y才需要)slub_debug=-The state of each debug option for a slab can be found in the respective files
under::/sys/kernel/slab/<slab name>/If the file contains 1, the option is enabled, 0 means disabled. 

slabinfo小工具,内核自带工具,能够方便快速的确认泄漏类型

aarch64-none-linux-gnu-gcc -o slabinfo tools/mm/slabinfo.cslabinfo 有两个参数指令比较重要:
slabinfo -S      //按slub size占用大小排序slub类型及object个数信息等
slabinfo -T      //打印slub的统计信息

2.3 调试节点

查看slab 使用状态
/proc/slabinfo 
查看slab debug信息,统计状态等
/sys/kernel/slab/*
调试内存泄漏,踩踏等信息
/sys/kernel/debug/slab/*

三、slubdebug原理

3.1 slub 分配基本流程

创建kmem_cache
create_cache    //mm/slab-common.c-->struct kmem_cache* s = kmem_cache_zalloc(kmem_cache, GFP_KERNEL);-->__kmem_cache_create(s, flags);-->set_cpu_partial(s);-->init_kmem_cache_nodes(s)-->alloc_kmem_cache_cpus(s)-->list_add(&s->list, &slab_caches); //所有的kmem_cache都会加到slab_caches列表中分配slub
kmem_cache_alloc  mm/slub.c-->__kmem_cache_alloc_lru  mm/slub.c-->slab_alloc   mm/slub.c-->slab_alloc_node mm/slub.c-->__slab_alloc_node-->object = c->freelist      //(1)如果freelist上有空闲,直接使用本cpu上cpu_slab->slab的-->object = __slab_alloc     //否则重新寻找-->slub_percpu_partial   //(2)从cpu_slab->partial上取-->freelist = get_partial(s, node, &pc);  //(3)从node上取         -->slab = new_slab(s, gfpflags, node);    //(4)如果还获取不到,则从buddy中申请内存-->set_track //开启slub debug时启动存储调用栈
  • 获取指定kemem_cache
  • 从percpu变量(struct kmem_cache_cpu*)cpu_slab上获取,从当前cpu cache(cpu_slab->slab)上freelist取
  • 如果cache上用完,则从cpu_slab的partial 列表上提取,并且将该slab 从partial列表删除,并添加到cpu cache上
  • 如果percpu上的cpu_slab上partial列表中用完,则从kmem_cache_node的partial上提取
  • 从page(buddy system)中分配一个struct slab (之前老版本是struct page,当前已经通过filo机制转换)

3.2 slub debug memleak 检测方法

  • 分配slub object时记录trace信息,会根据调用栈生成hash
  • 在每个slub object对应的tack区域存储hash值,记录pid,分配时间等信息
  • 提取分配信息时,扫描所有已分配object的track区域,根据hash值出现次数排序,并利用hash寻找对应的完整调用栈信息,最终排序打印出来

四、测试验证

4.1 测试代码:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/workqueue.h>
#include <linux/jiffies.h>
#include <asm/page.h>
#include <linux/vmalloc.h>
#include <linux/mm.h>enum sample_kmemleak_test_case{SLAB_LEAK = 0,PAGE_LEAK = 1,VMALLOC_LEAK = 2,PCPU_LEAK = 3,SLAB_ALLOC_FREE = 4,
};static noinline void kmalloc_leak(size_t size, int cnt, bool bfree)
{char *ptr;int i = 0;for (; i < cnt; i++){ptr = kmalloc(size, GFP_KERNEL);if(bfree)kfree(ptr);}
}static noinline void pagealloc_leak(size_t order)
{struct page *pages;char *ptr;pages = alloc_pages(GFP_KERNEL, order);ptr = page_address(pages);pr_info("%s page addr %llx, page_to_virt %llx\n", __func__, (unsigned long long)pages, (unsigned long long)ptr);
}static noinline void vmalloc_leak(size_t size)
{char *v_ptr;v_ptr = vmalloc(size);OPTIMIZER_HIDE_VAR(v_ptr);pr_info("%s %llx", __func__, (unsigned long long)v_ptr);v_ptr[0] = 0;}static noinline void sample_kmemleak_test_case(int type, int param)
{switch(type) {case SLAB_LEAK:kmalloc_leak(128, param, false); //alloc 128 byte and repeat param times break;case PAGE_LEAK: pagealloc_leak(param);break;case VMALLOC_LEAK:vmalloc_leak(2048);break;case PCPU_LEAK:break;case SLAB_ALLOC_FREE:kmalloc_leak(128, param, true); //alloc 128 byte and free repeat param times break;default :pr_info("undef error type %d\n", type);break;}pr_info("%s type %d\n", __func__, type);
}static noinline ssize_t sample_kmemleak_testcase_write(struct file *filp, const char __user *buf,size_t len, loff_t *off)
{char kbuf[64] = {0};int ntcase;int nparam;int ret = 0;if(len > 64) {len = 64;}if (copy_from_user(kbuf, buf, len) != 0) {pr_info("copy the buff failed \n");goto done;}ret = sscanf(kbuf, "%d %d", &ntcase, &nparam);if (ret <= 0) {pr_err("should enter 2 param, first is test case type, second is param\n");goto done;}sample_kmemleak_test_case(ntcase, nparam);
done:return len;
}static struct file_operations sample_kmemleak_fops = {.owner  =   THIS_MODULE,.write  =   sample_kmemleak_testcase_write,.llseek =   noop_llseek,
};static struct miscdevice sample_kmemleak_misc = {.minor  = MISC_DYNAMIC_MINOR,.name   = "sample_kmemleak_test",.fops   = &sample_kmemleak_fops,
};static int __init sample_kmemleak_start(void) 
{int ret;ret = misc_register(&sample_kmemleak_misc);if (ret < 0) {printk(KERN_EMERG " sample_kmemleak test register failed %d\n", ret);return ret;}printk(KERN_INFO "sample_kmemleak test register\n");return 0;
}static void __exit sample_kmemleak_end(void) 
{ misc_deregister(&sample_kmemleak_misc);
} MODULE_LICENSE("GPL");
MODULE_AUTHOR("geek");
MODULE_DESCRIPTION("A simple kmemleak test driver!");
MODULE_VERSION("0.1");module_init(sample_kmemleak_start);
module_exit(sample_kmemleak_end);

4.2 定位泄漏

加载内存泄漏测试程序
/test # insmod kmemleak_driver.ko 触发kmalloc 128 泄漏 100000次
/test # echo 0 100000 > /dev/sample_kmemleak_test 利用工具slabinfo按的size排序查看 slub object,可以查看 space 总占用大小,及objects个数判断泄漏点 为kmalloc-128;
/test # ./slabinfo -S
Name                   Objects Objsize           Space Slabs/Part/Cpu  O/S O %Fr %Ef Flg
kmalloc-128             100331     128           25.6M       3136/1/0   32 1   0  49 U
inode_cache               6649     616            4.7M        290/1/0   23 2   0  86 aU
kernfs_node_cache        23966     128            4.6M       1142/1/0   21 0   0  65 U
dentry                    4041     192            1.0M        127/1/0   32 1   0  74 aU
kmalloc-1k                 384    1024          786.4K         24/0/0   16 3   0  50 U
kmalloc-512                417     512          458.7K         14/2/0   32 3  14  46 U
task_struct                102    3776          425.9K         13/2/0    8 3  15  90 U
kmalloc-2k                  93    2048          393.2K         12/1/0    8 3   8  48 U
kmalloc-4k                  46    4096          393.2K         12/1/0    4 3   8  47 U
radix_tree_node            596     576          393.2K         24/1/0   25 2   4  87 aU
kmalloc-192               1398     192          385.0K         47/1/0   30 1   2  69 U
kmalloc-256                472     256          245.7K         15/1/0   32 2   6  49 U
pool_workqueue             282     512          229.3K         14/1/0   21 2   7  62 U
sighand_cache              102    2080          229.3K          7/2/0   15 3  28  92 AU
biovec-max                  40    4096          196.6K          6/1/0    7 3  16  83 AU......如果没有工具,直接通过/proc/slabinfo节点来确认
/test # cat /proc/slabinfo 
slabinfo - version: 2.1
# name            <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail>
......
kmalloc-4k            49     50  12288    2    8 : tunables    0    0    0 : slabdata     25     25      0
kmalloc-2k            94     95   6144    5    8 : tunables    0    0    0 : slabdata     19     19      0
kmalloc-1k           383    390   3072   10    8 : tunables    0    0    0 : slabdata     39     39      0
kmalloc-512          417    441   1536   21    8 : tunables    0    0    0 : slabdata     21     21      0
kmalloc-256          472    483    768   21    4 : tunables    0    0    0 : slabdata     23     23      0
kmalloc-192         1400   1404    296   27    2 : tunables    0    0    0 : slabdata     52     52      0
kmalloc-128       100331 100352    256   32    2 : tunables    0    0    0 : slabdata   3136   3136      0
//这里也可以通过num_objs * objsize的大小排序来确认是kmalloc-128出现泄漏
kmalloc-96           496    540    200   20    1 : tunables    0    0    0 : slabdata     27     27      0
kmalloc-64           877    928    256   32    2 : tunables    0    0    0 : slabdata     29     29      0
kmalloc-32           725    750    160   25    1 : tunables    0    0    0 : slabdata     30     30      0
kmalloc-16           927    928    128   32    1 : tunables    0    0    0 : slabdata     29     29      0
kmalloc-8           1614   1620    112   36    1 : tunables    0    0    0 : slabdata     45     45      0
kmem_cache_node      211    224    256   32    2 : tunables    0    0    0 : slabdata      7      7      0
kmem_cache           211    231    384   21    2 : tunables    0    0    0 : slabdata     11     11      0
......查看具体的泄漏点
/test # cat /sys/kernel/debug/slab/kmalloc-128/alloc_traces100000 kmalloc_leak.constprop.0+0x54/0x80 [kmemleak_driver] age=12920/12945/12972 pid=95 cpus=3__kmem_cache_alloc_node+0xf4/0x2a4kmalloc_trace+0x20/0x2ckmalloc_leak.constprop.0+0x54/0x80 [kmemleak_driver]sample_kmemleak_test_case+0x9c/0xa8 [kmemleak_driver]sample_kmemleak_testcase_write+0xb0/0x12c [kmemleak_driver]vfs_write+0xc8/0x300ksys_write+0x74/0x10c__arm64_sys_write+0x1c/0x28invoke_syscall+0x48/0x110el0_svc_common.constprop.0+0x40/0xe0do_el0_svc+0x1c/0x28el0_svc+0x40/0xe4el0t_64_sync_handler+0x120/0x12cel0t_64_sync+0x190/0x19488 set_kthread_struct+0x38/0xc4 waste=1408/16 age=13270/17638/17797 pid=2 cpus=0-2__kmem_cache_alloc_node+0xf4/0x2a4kmalloc_trace+0x20/0x2cset_kthread_struct+0x38/0xc4copy_process+0x7a0/0x145ckernel_clone+0x68/0x368kernel_thread+0x80/0xb4kthreadd+0x144/0x1c4ret_from_fork+0x10/0x20
......

五、小结

5.1、性能分析

内存损耗比较大,从slabinfo也可以看到打开slub debug相关的config之后(/proc/slabinfo)下objsize大小变化,未开启时kmalloc-128 objsize就是128,开启debug后变成了384;

5.2 一个标准的slub泄漏定位方法

1、先确认slub存在内存泄漏

启动时记录/proc/meminfo 中SUnreclaim size 大小, 假设此时初始值为A ,然后每半小时或1小时检测SUnreclaim size 大小,假设为B, 如果B - A 增量超过600M 表示存在SUnreclaim 内存泄漏, 这个600M 可以根据实际项目,内存大小来综合设定

2、细化内存泄漏类型

如果版本存在SLUB内存泄漏, 抓取/proc/slabinfo或者使用slabinfo 工具来确认泄漏的slub 类型;

可以使用slabinfo -S 指令,或者直接通过/proc/slabinfo 中<num_objs> <objsize> 这两列信息做一个乘积,在excel 排序一下即可 (注意:这里的size 统计是不区分unreclaim的)

3、开启指定slub 类型泄漏的debug

例子:调试 kmalloc-XXXX和dentry相关slub leak调试:

slub_debug=U,kmalloc-*,dentry

或者全开:slub_debug=U

4、确认泄漏调用栈

等待内存泄漏一段时间后,执行下面即可确认slub泄漏调用栈及泄漏次数(乘上object的size即泄漏大小)

cat /sys/kernel/debug/slab/XXX/alloc_traces

5.3 优化和改进

商用版本上默认可以打开:CONFIG_SLUB_DEBUG=y, 不开CONFIG_SLUB_DEBUG_ON=y(或者参数传递slub_debug开启)对性能是无影响的;

缺陷: 无法开始时默认关闭,出现问题后动态打开,当前只能在发现版本存在slub内存泄漏后可以通过修改启动参数来控制调试开关,再来定位泄漏点;

如果问题复现概率极低,商用版本一直打开slub泄漏检测的话对用户的内存还是有较大影响,一般来说size占用是未开slub debug的2~4倍 。

不能动态开启的原因主要是 kmalloc的object 及size(是否有调试的metadata)在初始化时就确立了,后面无法修改object 和size,或者控制flag开关。

优化方案:当前android厂商针对这种情况也做了一些改进方案;利用vendor hook机制,在一个外部驱动中通过vendor hook机制动态修改kmalloc_caches,及获取 alloc trace即可完成针对kmalloc的动态调试;其核心思想是构建一套开启debug的kmem_cache, 然后替换原始的kmalloc_caches, 后续kmalloc就会走开启debug的kmem_cache,然后就可以定位问题

关键代码(从android kernel 5.15 拷贝,非android的只能仿照下自己加一下注册函数了):
mm/slab_common.c
//kmalloc分配时会根据size大小及flag类型去选择用kmalloc_caches中的哪一个
struct kmem_cache *kmalloc_slab(size_t size, gfp_t flags)
{unsigned int index;struct kmem_cache *s = NULL;if (size <= 192) {if (!size)return ZERO_SIZE_PTR;index = size_index[size_index_elem(size)];} else {if (WARN_ON_ONCE(size > KMALLOC_MAX_CACHE_SIZE))return NULL;index = fls(size - 1);}    trace_android_vh_kmalloc_slab(index, flags, &s); //1.注册这里vendor hook可以替换掉后面的kmalloc_cacheif (s)return s;return kmalloc_caches[kmalloc_type(flags)][index];
}mm/slub.c中
static void set_track(struct kmem_cache *s, void *object,enum track_item alloc, unsigned long addr)
{struct track *p = get_track(s, object, alloc);if (addr) {
#ifdef CONFIG_STACKTRACEunsigned int nr_entries;metadata_access_enable();nr_entries = stack_trace_save(kasan_reset_tag(p->addrs),TRACK_ADDRS_COUNT, 3);metadata_access_disable();if (nr_entries < TRACK_ADDRS_COUNT)p->addrs[nr_entries] = 0; 
#endifp->addr = addr;p->cpu = smp_processor_id();p->pid = current->pid;p->when = jiffies;trace_android_vh_save_track_hash(alloc == TRACK_ALLOC, p);  //2.注册这里外部的ko即可获取到调用栈} else {memset(p, 0, sizeof(struct track));}    
}

ko中的实现基本就是将slub.c,slab_common.c中的依赖的结构体,函数等重写一下,注册好上面的两个hook函数,一个替换掉kmalloc_caches, 一个存储slub分配的调用栈,剩下的就是处理好编译报错,将/sys/kernel/debug/slab/XXX/alloc_traces 的实现用在新增的ko中,增加调试节点,即可完成动态开启slubdebug的功能。

参考

极致Linux内核:Linux内存:块分配器slab、slob和slub

SLUB DEBUG原理

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/include/linux/slub_def.h?h=v6.6.23&id=bb192ed9aa7191a5d65548f82c42b6750d65f569

Linux 内存管理新特性 - Memory folios 解读 | 龙蜥技术

五花肉:内存管理特性分析(七):Linux内核复合页(Compound Page)原理分析

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/4266.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

创建electron,解决包清理的问题,解决镜像源卡住下载时间长

我的电脑用户名是Anyphasy,我的node.js安装在D:\developp\nodejss18.18.0 使用npm config get prefix查看node.js安装路径 npm config get prefix 创建electron 创建package.json文件,它里面记载了你的electron版本,项目描述,以及启动命令等信息 npm init -y 先查看你自己的…

禅道项目管理系统身份认证绕过漏洞

禅道项目管理系统身份认证绕过漏洞 1.漏洞描述 禅道项目管理软件是国产的开源项目管理软件&#xff0c;专注研发项目管理&#xff0c;内置需求管理、任务管理、bug管理、缺陷管理、用例管理、计划发布等功能&#xff0c;完整覆盖了研发项目管理的核心流程。 禅道项目管理系统…

手写一个RNN前向传播以及反向传播

前向传播 根据公式 st tanh (Uxt Wst-1 ba) ot softmax(Vst by ) m 3 词的个数 n 5 import numpy as np import tensorflow as tf # 单个cell 的前向传播过程 # 两个输入&#xff0c;x_t&#xff0c;s_prev,parameters def rnn_cell_forward(x_t,s_prev,parameter…

运算符重载(1)

1.加号运算符重载&#xff0c;这里用编译器统一的名称operator代替函数名 #include<iostream> using namespace std; //1.成员函数的加号重载 //2.全局函数的加号重载 class Person { public:Person() {};//1.成员函数的加号重载//Person operator(Person& p)//{// P…

前端HTML5学习2(新增多媒体标签,H5的兼容性处理)

前端HTML5学习2新增多媒体标签&#xff0c;H5的兼容性处理&#xff09; 分清标签和属性新增多媒体标签新增视频标签新增音频标签新增全局属性 H5的兼容性处理 分清标签和属性 标签&#xff08;HTML元素&#xff09;和属性&#xff0c;标签定义了内容的类型或结构&#xff0c;而…

k8s学习(三十七)centos下离线部署kubernetes1.30(高可用)

文章目录 准备工作1、升级操作系统内核1.1、查看操作系统和内核版本1.2、下载内核离线升级包1.3、升级内核1.4、确认内核版本 2、修改主机名/hosts文件2.1、修改主机名2.2、修改hosts文件 3、关闭防火墙4、关闭SELINUX配置5、时间同步5.1、下载NTP5.2、卸载5.3、安装5.4、配置5…

BPE、Wordpiece、Unigram、SpanBERT等Tokenizer细节总结

BPE(Byte Pair Encoding) GPT-2和Roberta用的是这种&#xff0c;不会产生[UNK]这个unknown字符 这部分部分摘录自https://martinlwx.github.io/zh-cn/the-bpe-tokenizer/ 看以下code例子就足够理解了&#xff0c;核心是维护self.merges&#xff08;维护一个pair->str的字…

[蓝桥杯2024]-Reverse:rc4解析(对称密码rc4)

无壳 查看ida 这里应该运行就可以得flag&#xff0c;但是这个程序不能直接点击运行 按照伪代码写exp 完整exp&#xff1a; keylist(gamelab) content[0xB6,0x42,0xB7,0xFC,0xF0,0xA2,0x5E,0xA9,0x3D,0x29,0x36,0x1F,0x54,0x29,0x72,0xA8, 0x63,0x32,0xF2,0x44,0x8B,0x85,0x…

如何在 Visual Studio 中通过 NuGet 添加包

在安装之前要先确定Nuget的包源是否有问题。 Visual Studio中怎样更改Nuget程序包源-CSDN博客 1.图形界面安装 打开您的项目&#xff0c;并在解决方案资源管理器中选择您的项目。单击“项目”菜单&#xff0c;然后选择“管理 NuGet 程序包”选项。在“NuGet 包管理器”窗口中…

详解如何品味品深茶的精髓

在众多的茶品牌中&#xff0c;品深茶以其独特的韵味和深厚的文化底蕴&#xff0c;赢得了众多茶友的喜爱。今天&#xff0c;让我们一同探寻品深茶的精髓&#xff0c;品味其独特的魅力。 品深茶&#xff0c;源自中国传统茶文化的精髓&#xff0c;承载着世代茶人的智慧与匠心。这…

03-MVC执行流程-参数解析与Model

重要组件 准备Model&#xff0c;Controller Configuration public class WebConfig {ControllerAdvicestatic class MyControllerAdvice {ModelAttribute("b")public String bar() {return "bar";}}Controllerstatic class Controller1 {ResponseStatus(H…

windows环境下安装Apache

首先apache官网下载地址&#xff1a;http://www.apachelounge.com/download/按照自己的电脑操作系统来安装 这里我安装的是win64 主版本是2.4的apache。 然后解压压缩包到一个全英文的路径下&#xff01;&#xff01;&#xff01;一定一定不要有中文 中文符号也不要有&#xff…

ansible-copy用法

目录 概述实践不带目录拷贝带目录拷贝 概述 ansible copy 常用用法举例 不带目录拷贝&#xff0c;拷贝的地址要写全 带目录拷贝&#xff0c;拷贝路径不要写在 dest 路径中 实践 不带目录拷贝 # with_fileglob 是 Ansible 中的一个循环关键字&#xff0c;用于处理文件通配符匹…

【Vue3+Tres 三维开发】02-Debug

预览 介绍 Debug 这里主要是讲在三维中的调试,同以前threejs中使用的lil-gui类似,TRESJS也提供了一套可视化参数调试的插件。使用方式和之前的组件相似。 使用 通过导入useTweakPane 即可 import { useTweakPane, OrbitControls } from "@tresjs/cientos"const {…

数字文旅重塑旅游发展新格局:以数字化转型为突破口,提升旅游服务的智能化水平,为游客带来全新的旅游体验

随着信息技术的迅猛发展&#xff0c;数字化已成为推动各行各业创新发展的重要力量。在旅游业领域&#xff0c;数字文旅的兴起正以其强大的驱动力&#xff0c;重塑旅游发展的新格局。数字文旅以数字化转型为突破口&#xff0c;通过提升旅游服务的智能化水平&#xff0c;为游客带…

HarmonyOS Next从入门到精通实战精品课

第一阶段&#xff1a;HarmonyOS Next星河版从入门到精通该阶段由HarmonyOS Next星河版本出发&#xff0c;介绍HarmonyOS Next版本应用开发基础概念&#xff0c;辅助学员快速上手新版本开发范式&#xff0c;共计42课时 第一天鸿蒙NEXT Mac版、Windows版【编辑器】和【模拟器】&a…

BootStrap详解

Bootstrap简介 什么是BootStrap&#xff1f; BootStrap来自Twitter&#xff0c;是目前最受欢迎的响应式前端框Bootstrap是基于HTML、CSS、JavaScript的&#xff0c;它简洁灵活&#xff0c;使得Web开发更加快捷 为什么使用Bootstrap&#xff1f; 移动设备优先&#xff1a;自…

Kafka 3.x.x 入门到精通(07)——Java应用场景——SpringBoot集成

Kafka 3.x.x 入门到精通&#xff08;07&#xff09;——Java应用场景——SpringBoot集成 4. Java应用场景——SpringBoot集成4.1 创建SpringBoot项目4.1.1 创建SpringBoot项目4.1.2 修改pom.xml文件4.1.3 在resources中增加application.yml文件 4.2 编写功能代码4.2.1 创建配置…

机器人-轨迹规划

旋转矩阵 旋转矩阵--R--一个3*3的矩阵&#xff0c;其每列的值时B坐标系在A坐标系上的投影值。 代表B坐标系相对于A坐标系的姿态。 旋转矩阵的转置矩阵 其实A相对于B的旋转矩阵就相当于把B的列放到行上就行。 视频 &#xff08;将矩阵的行列互换得到的新矩阵称为转置矩阵。&…

SQLite尽如此轻量

众所周知&#xff0c;SQLite是个轻量级数据库&#xff0c;适用于中小型服务应用等&#xff0c;在我真正使用的时候才发现&#xff0c;它虽然轻量&#xff0c;但不知道它却如此轻量。 下载 官网&#xff1a; SQLite Download Page 安装 1、将下载好的两个压缩包同时解压到一个…